func mergeManifests(manifests []schema.ImageManifest) schema.ImageManifest { // FIXME(iaguis) we take app layer's manifest as the final manifest for now manifest := manifests[0] manifest.Dependencies = nil layerIndex := -1 for i, l := range manifest.Labels { if l.Name.String() == "layer" { layerIndex = i } } if layerIndex != -1 { manifest.Labels = append(manifest.Labels[:layerIndex], manifest.Labels[layerIndex+1:]...) } nameWithoutLayerID := appctypes.MustACIdentifier(stripLayerID(manifest.Name.String())) manifest.Name = *nameWithoutLayerID // once the image is squashed, we don't need a pathWhitelist manifest.PathWhitelist = nil return manifest }
// Test running pod manifests that contains just one app. // TODO(yifan): Add more tests for port mappings. and multiple apps. func TestPodManifest(t *testing.T) { ctx := newRktRunCtx() defer ctx.cleanup() tmpdir, err := ioutil.TempDir("", "rkt-tests.") if err != nil { t.Fatalf("Cannot create temporary directory: %v", err) } defer os.RemoveAll(tmpdir) boolFalse, boolTrue := false, true tests := []struct { imageName string imagePatches []string podManifest *schema.PodManifest shouldSuccess bool expectedResult string }{ { // Simple read. "rkt-test-run-pod-manifest-read.aci", []string{}, &schema.PodManifest{ Apps: []schema.RuntimeApp{ { Name: baseAppName, App: &types.App{ Exec: []string{"/inspect", "--read-file"}, User: "******", Group: "0", Environment: []types.EnvironmentVariable{ {"FILE", "/dir1/file"}, }, }, }, }, }, true, "dir1", }, { // Simple read after write with volume mounted. "rkt-test-run-pod-manifest-vol-rw.aci", []string{}, &schema.PodManifest{ Apps: []schema.RuntimeApp{ { Name: baseAppName, App: &types.App{ Exec: []string{"/inspect", "--write-file", "--read-file"}, User: "******", Group: "0", Environment: []types.EnvironmentVariable{ {"FILE", "/dir1/file"}, {"CONTENT", "host:foo"}, }, MountPoints: []types.MountPoint{ {"dir1", "/dir1", false}, }, }, }, }, Volumes: []types.Volume{ {"dir1", "host", tmpdir, nil}, }, }, true, "host:foo", }, { // Simple read after write with read-only mount point, should fail. "rkt-test-run-pod-manifest-vol-ro.aci", []string{}, &schema.PodManifest{ Apps: []schema.RuntimeApp{ { Name: baseAppName, App: &types.App{ Exec: []string{"/inspect", "--write-file", "--read-file"}, User: "******", Group: "0", Environment: []types.EnvironmentVariable{ {"FILE", "/dir1/file"}, {"CONTENT", "bar"}, }, MountPoints: []types.MountPoint{ {"dir1", "/dir1", true}, }, }, }, }, Volumes: []types.Volume{ {"dir1", "host", tmpdir, nil}, }, }, false, `Cannot write to file "/dir1/file": open /dir1/file: read-only file system`, }, { // Simple read after write with volume mounted. // Override the image's mount point spec. This should fail as the volume is // read-only in pod manifest, (which will override the mount point in both image/pod manifest). "rkt-test-run-pod-manifest-vol-rw-override.aci", []string{ "--mounts=dir1,path=/dir1,readOnly=false", }, &schema.PodManifest{ Apps: []schema.RuntimeApp{ { Name: baseAppName, App: &types.App{ Exec: []string{"/inspect", "--write-file", "--read-file"}, User: "******", Group: "0", Environment: []types.EnvironmentVariable{ {"FILE", "/dir1/file"}, {"CONTENT", "bar"}, }, MountPoints: []types.MountPoint{ {"dir1", "/dir1", false}, }, }, }, }, Volumes: []types.Volume{ {"dir1", "host", tmpdir, &boolTrue}, }, }, false, `Cannot write to file "/dir1/file": open /dir1/file: read-only file system`, }, { // Simple read after write with volume mounted. // Override the image's mount point spec. "rkt-test-run-pod-manifest-vol-rw-override.aci", []string{ "--mounts=dir1,path=/dir1,readOnly=true", }, &schema.PodManifest{ Apps: []schema.RuntimeApp{ { Name: baseAppName, App: &types.App{ Exec: []string{"/inspect", "--write-file", "--read-file"}, User: "******", Group: "0", Environment: []types.EnvironmentVariable{ {"FILE", "/dir2/file"}, {"CONTENT", "host:bar"}, }, MountPoints: []types.MountPoint{ {"dir1", "/dir2", false}, }, }, }, }, Volumes: []types.Volume{ {"dir1", "host", tmpdir, nil}, }, }, true, "host:bar", }, { // Simple read after write with volume mounted, no apps in pod manifest. "rkt-test-run-pod-manifest-vol-rw-no-app.aci", []string{ "--exec=/inspect --write-file --read-file --file-name=/dir1/file --content=host:baz", "--mounts=dir1,path=/dir1,readOnly=false", }, &schema.PodManifest{ Apps: []schema.RuntimeApp{ {Name: baseAppName}, }, Volumes: []types.Volume{ {"dir1", "host", tmpdir, nil}, }, }, true, "host:baz", }, { // Simple read after write with volume mounted, no apps in pod manifest. // This should succeed even the mount point in image manifest is readOnly, // because it is overrided by the volume's readOnly. "rkt-test-run-pod-manifest-vol-ro-no-app.aci", []string{ "--exec=/inspect --write-file --read-file --file-name=/dir1/file --content=host:zaz", "--mounts=dir1,path=/dir1,readOnly=true", }, &schema.PodManifest{ Apps: []schema.RuntimeApp{ {Name: baseAppName}, }, Volumes: []types.Volume{ {"dir1", "host", tmpdir, &boolFalse}, }, }, true, "host:zaz", }, { // Simple read after write with read-only volume mounted, no apps in pod manifest. // This should fail as the volume is read-only. "rkt-test-run-pod-manifest-vol-ro-no-app.aci", []string{ "--exec=/inspect --write-file --read-file --file-name=/dir1/file --content=baz", "--mounts=dir1,path=/dir1,readOnly=false", }, &schema.PodManifest{ Apps: []schema.RuntimeApp{ {Name: baseAppName}, }, Volumes: []types.Volume{ {"dir1", "host", tmpdir, &boolTrue}, }, }, false, `Cannot write to file "/dir1/file": open /dir1/file: read-only file system`, }, } for i, tt := range tests { patchTestACI(tt.imageName, tt.imagePatches...) hash := importImageAndFetchHash(t, ctx, tt.imageName) defer os.Remove(tt.imageName) tt.podManifest.ACKind = schema.PodManifestKind tt.podManifest.ACVersion = schema.AppContainerVersion imgName := types.MustACIdentifier(tt.imageName) imgID, err := types.NewHash(hash) if err != nil { t.Fatalf("Cannot generate types.Hash from %v: %v", hash, err) } for i := range tt.podManifest.Apps { ra := &tt.podManifest.Apps[i] ra.Image.Name = imgName ra.Image.ID = *imgID } manifestFile := generatePodManifestFile(t, tt.podManifest) defer os.Remove(manifestFile) // 1. Test 'rkt run'. runCmd := fmt.Sprintf("%s run --pod-manifest=%s", ctx.cmd(), manifestFile) t.Logf("Running 'run' test #%v: %v", i, runCmd) child, err := gexpect.Spawn(runCmd) if err != nil { t.Fatalf("Cannot exec rkt #%v: %v", i, err) } if tt.expectedResult != "" { if err := child.Expect(tt.expectedResult); err != nil { t.Fatalf("Expected %q but not found: %v", tt.expectedResult, err) } } if err := child.Wait(); err != nil { if tt.shouldSuccess { t.Fatalf("rkt didn't terminate correctly: %v", err) } } verifyHostFile(t, tmpdir, "file", i, tt.expectedResult) // 2. Test 'rkt prepare' + 'rkt run-prepared'. cmds := strings.Fields(ctx.cmd()) prepareCmd := exec.Command(cmds[0], cmds[1:]...) prepareArg := fmt.Sprintf("--pod-manifest=%s", manifestFile) prepareCmd.Args = append(prepareCmd.Args, "--insecure-skip-verify", "prepare", prepareArg) output, err := prepareCmd.Output() if err != nil { t.Fatalf("Cannot read the output: %v", err) } podIDStr := strings.TrimSpace(string(output)) podID, err := types.NewUUID(podIDStr) if err != nil { t.Fatalf("%q is not a valid UUID: %v", podIDStr, err) } runPreparedCmd := fmt.Sprintf("%s run-prepared %s", ctx.cmd(), podID.String()) t.Logf("Running 'run' test #%v: %v", i, runPreparedCmd) child, err = gexpect.Spawn(runPreparedCmd) if err != nil { t.Fatalf("Cannot exec rkt #%v: %v", i, err) } if tt.expectedResult != "" { if err := child.Expect(tt.expectedResult); err != nil { t.Fatalf("Expected %q but not found: %v", tt.expectedResult, err) } } if err := child.Wait(); err != nil { if tt.shouldSuccess { t.Fatalf("rkt didn't terminate correctly: %v", err) } } verifyHostFile(t, tmpdir, "file", i, tt.expectedResult) } }
// Test running pod manifests that contains just one app. // TODO(yifan): Figure out a way to test port mapping on single host. func TestPodManifest(t *testing.T) { ctx := testutils.NewRktRunCtx() defer ctx.Cleanup() tmpdir := createTempDirOrPanic("rkt-tests.") defer os.RemoveAll(tmpdir) boolFalse, boolTrue := false, true tests := []struct { // [image name]:[image patches] images []imagePatch podManifest *schema.PodManifest shouldSucceed bool expectedResult string cgroup string }{ { // Simple read. []imagePatch{ {"rkt-test-run-pod-manifest-read.aci", []string{}}, }, &schema.PodManifest{ Apps: []schema.RuntimeApp{ { Name: baseAppName, App: &types.App{ Exec: []string{"/inspect", "--read-file"}, User: "******", Group: "0", Environment: []types.EnvironmentVariable{ {"FILE", "/dir1/file"}, }, }, }, }, }, true, "dir1", "", }, { // Simple read after write with volume mounted. []imagePatch{ {"rkt-test-run-pod-manifest-vol-rw.aci", []string{}}, }, &schema.PodManifest{ Apps: []schema.RuntimeApp{ { Name: baseAppName, App: &types.App{ Exec: []string{"/inspect", "--write-file", "--read-file"}, User: "******", Group: "0", Environment: []types.EnvironmentVariable{ {"FILE", "/dir1/file"}, {"CONTENT", "host:foo"}, }, MountPoints: []types.MountPoint{ {"dir1", "/dir1", false}, }, }, }, }, Volumes: []types.Volume{ {"dir1", "host", tmpdir, nil}, }, }, true, "host:foo", "", }, { // Simple read after write with read-only mount point, should fail. []imagePatch{ {"rkt-test-run-pod-manifest-vol-ro.aci", []string{}}, }, &schema.PodManifest{ Apps: []schema.RuntimeApp{ { Name: baseAppName, App: &types.App{ Exec: []string{"/inspect", "--write-file", "--read-file"}, User: "******", Group: "0", Environment: []types.EnvironmentVariable{ {"FILE", "/dir1/file"}, {"CONTENT", "bar"}, }, MountPoints: []types.MountPoint{ {"dir1", "/dir1", true}, }, }, }, }, Volumes: []types.Volume{ {"dir1", "host", tmpdir, nil}, }, }, false, `Cannot write to file "/dir1/file": open /dir1/file: read-only file system`, "", }, { // Simple read after write with volume mounted. // Override the image's mount point spec. This should fail as the volume is // read-only in pod manifest, (which will override the mount point in both image/pod manifest). []imagePatch{ {"rkt-test-run-pod-manifest-vol-rw-override.aci", []string{"--mounts=dir1,path=/dir1,readOnly=false"}}, }, &schema.PodManifest{ Apps: []schema.RuntimeApp{ { Name: baseAppName, App: &types.App{ Exec: []string{"/inspect", "--write-file", "--read-file"}, User: "******", Group: "0", Environment: []types.EnvironmentVariable{ {"FILE", "/dir1/file"}, {"CONTENT", "bar"}, }, MountPoints: []types.MountPoint{ {"dir1", "/dir1", false}, }, }, }, }, Volumes: []types.Volume{ {"dir1", "host", tmpdir, &boolTrue}, }, }, false, `Cannot write to file "/dir1/file": open /dir1/file: read-only file system`, "", }, { // Simple read after write with volume mounted. // Override the image's mount point spec. []imagePatch{ {"rkt-test-run-pod-manifest-vol-rw-override.aci", []string{"--mounts=dir1,path=/dir1,readOnly=true"}}, }, &schema.PodManifest{ Apps: []schema.RuntimeApp{ { Name: baseAppName, App: &types.App{ Exec: []string{"/inspect", "--write-file", "--read-file"}, User: "******", Group: "0", Environment: []types.EnvironmentVariable{ {"FILE", "/dir2/file"}, {"CONTENT", "host:bar"}, }, MountPoints: []types.MountPoint{ {"dir1", "/dir2", false}, }, }, }, }, Volumes: []types.Volume{ {"dir1", "host", tmpdir, nil}, }, }, true, "host:bar", "", }, { // Simple read after write with volume mounted, no apps in pod manifest. []imagePatch{ { "rkt-test-run-pod-manifest-vol-rw-no-app.aci", []string{ "--exec=/inspect --write-file --read-file --file-name=/dir1/file --content=host:baz", "--mounts=dir1,path=/dir1,readOnly=false", }, }, }, &schema.PodManifest{ Apps: []schema.RuntimeApp{ {Name: baseAppName}, }, Volumes: []types.Volume{ {"dir1", "host", tmpdir, nil}, }, }, true, "host:baz", "", }, { // Simple read after write with volume mounted, no apps in pod manifest. // This should succeed even the mount point in image manifest is readOnly, // because it is overrided by the volume's readOnly. []imagePatch{ { "rkt-test-run-pod-manifest-vol-ro-no-app.aci", []string{ "--exec=/inspect --write-file --read-file --file-name=/dir1/file --content=host:zaz", "--mounts=dir1,path=/dir1,readOnly=true", }, }, }, &schema.PodManifest{ Apps: []schema.RuntimeApp{ {Name: baseAppName}, }, Volumes: []types.Volume{ {"dir1", "host", tmpdir, &boolFalse}, }, }, true, "host:zaz", "", }, { // Simple read after write with read-only volume mounted, no apps in pod manifest. // This should fail as the volume is read-only. []imagePatch{ { "rkt-test-run-pod-manifest-vol-ro-no-app.aci", []string{ "--exec=/inspect --write-file --read-file --file-name=/dir1/file --content=baz", "--mounts=dir1,path=/dir1,readOnly=false", }, }, }, &schema.PodManifest{ Apps: []schema.RuntimeApp{ {Name: baseAppName}, }, Volumes: []types.Volume{ {"dir1", "host", tmpdir, &boolTrue}, }, }, false, `Cannot write to file "/dir1/file": open /dir1/file: read-only file system`, "", }, { // Print CPU quota, which should be overwritten by the pod manifest. []imagePatch{ {"rkt-test-run-pod-manifest-cpu-isolator.aci", []string{}}, }, &schema.PodManifest{ Apps: []schema.RuntimeApp{ { Name: baseAppName, App: &types.App{ Exec: []string{"/inspect", "--print-cpuquota"}, User: "******", Group: "0", Isolators: []types.Isolator{ { Name: "resource/cpu", ValueRaw: rawRequestLimit("100", "100"), }, }, }, }, }, }, true, `CPU Quota: 100`, "cpu", }, { // Print memory limit, which should be overwritten by the pod manifest. []imagePatch{ {"rkt-test-run-pod-manifest-memory-isolator.aci", []string{}}, }, &schema.PodManifest{ Apps: []schema.RuntimeApp{ { Name: baseAppName, App: &types.App{ Exec: []string{"/inspect", "--print-memorylimit"}, User: "******", Group: "0", Isolators: []types.Isolator{ { Name: "resource/memory", // 4MB. ValueRaw: rawRequestLimit("4194304", "4194304"), }, }, }, }, }, }, true, `Memory Limit: 4194304`, "memory", }, { // Multiple apps (with same images) in the pod. The first app will read out the content // written by the second app. []imagePatch{ {"rkt-test-run-pod-manifest-app.aci", []string{"--name=aci1"}}, {"rkt-test-run-pod-manifest-app.aci", []string{"--name=aci2"}}, }, &schema.PodManifest{ Apps: []schema.RuntimeApp{ { Name: "rkt-inspect-readapp", App: &types.App{ Exec: []string{"/inspect", "--pre-sleep=10", "--read-file"}, User: "******", Group: "0", Environment: []types.EnvironmentVariable{ {"FILE", "/dir/file"}, }, MountPoints: []types.MountPoint{ {"dir", "/dir", false}, }, }, }, { Name: "rkt-inspect-writeapp", App: &types.App{ Exec: []string{"/inspect", "--write-file"}, User: "******", Group: "0", Environment: []types.EnvironmentVariable{ {"FILE", "/dir/file"}, {"CONTENT", "host:foo"}, }, MountPoints: []types.MountPoint{ {"dir", "/dir", false}, }, }, }, }, Volumes: []types.Volume{ {"dir", "host", tmpdir, nil}, }, }, true, "host:foo", "", }, { // Pod manifest overwrites the image's capability. []imagePatch{ {"rkt-test-run-pod-manifest-cap.aci", []string{"--capability=CAP_NET_ADMIN"}}, }, &schema.PodManifest{ Apps: []schema.RuntimeApp{ { Name: baseAppName, App: &types.App{ Exec: []string{"/inspect", "--print-caps-pid=0"}, User: "******", Group: "0", Environment: []types.EnvironmentVariable{ {"CAPABILITY", strconv.Itoa(int(capability.CAP_NET_ADMIN))}, }, }, }, }, }, true, fmt.Sprintf("%v=disabled", capability.CAP_NET_ADMIN.String()), "", }, { // Pod manifest overwrites the image's capability. []imagePatch{ {"rkt-test-run-pod-manifest-cap.aci", []string{"--capability=CAP_NET_BIND_SERVICE"}}, }, &schema.PodManifest{ Apps: []schema.RuntimeApp{ { Name: baseAppName, App: &types.App{ Exec: []string{"/inspect", "--print-caps-pid=0"}, User: "******", Group: "0", Environment: []types.EnvironmentVariable{ {"CAPABILITY", strconv.Itoa(int(capability.CAP_NET_ADMIN))}, }, Isolators: []types.Isolator{ { Name: "os/linux/capabilities-retain-set", ValueRaw: rawValue(fmt.Sprintf(`{"set":["CAP_NET_ADMIN"]}`)), }, }, }, }, }, }, true, fmt.Sprintf("%v=enabled", capability.CAP_NET_ADMIN.String()), "", }, } for i, tt := range tests { if tt.cgroup != "" && !cgroup.IsIsolatorSupported(tt.cgroup) { t.Logf("Skip test #%v: cgroup %s not supported", i, tt.cgroup) continue } var hashesToRemove []string for j, v := range tt.images { hash := patchImportAndFetchHash(v.name, v.patches, t, ctx) hashesToRemove = append(hashesToRemove, hash) imgName := types.MustACIdentifier(v.name) imgID, err := types.NewHash(hash) if err != nil { t.Fatalf("Cannot generate types.Hash from %v: %v", hash, err) } ra := &tt.podManifest.Apps[j] ra.Image.Name = imgName ra.Image.ID = *imgID } tt.podManifest.ACKind = schema.PodManifestKind tt.podManifest.ACVersion = schema.AppContainerVersion manifestFile := generatePodManifestFile(t, tt.podManifest) defer os.Remove(manifestFile) // 1. Test 'rkt run'. runCmd := fmt.Sprintf("%s run --mds-register=false --pod-manifest=%s", ctx.Cmd(), manifestFile) t.Logf("Running 'run' test #%v", i) child := spawnOrFail(t, runCmd) if tt.expectedResult != "" { if err := expectWithOutput(child, tt.expectedResult); err != nil { t.Fatalf("Expected %q but not found: %v", tt.expectedResult, err) } } if err := child.Wait(); err != nil { if tt.shouldSucceed { t.Fatalf("rkt didn't terminate correctly: %v", err) } } verifyHostFile(t, tmpdir, "file", i, tt.expectedResult) // 2. Test 'rkt prepare' + 'rkt run-prepared'. rktCmd := fmt.Sprintf("%s --insecure-skip-verify prepare --pod-manifest=%s", ctx.Cmd(), manifestFile) uuid := runRktAndGetUUID(t, rktCmd) runPreparedCmd := fmt.Sprintf("%s run-prepared --mds-register=false %s", ctx.Cmd(), uuid) t.Logf("Running 'run-prepared' test #%v", i) child = spawnOrFail(t, runPreparedCmd) if tt.expectedResult != "" { if err := expectWithOutput(child, tt.expectedResult); err != nil { t.Fatalf("Expected %q but not found: %v", tt.expectedResult, err) } } if err := child.Wait(); err != nil { if tt.shouldSucceed { t.Fatalf("rkt didn't terminate correctly: %v", err) } } verifyHostFile(t, tmpdir, "file", i, tt.expectedResult) // we run the garbage collector and remove the imported images to save // space runGC(t, ctx) for _, h := range hashesToRemove { removeFromCas(t, ctx, h) } } }
func GenerateManifest(layerData types.DockerImageData, dockerURL *types.ParsedDockerURL) (*schema.ImageManifest, error) { dockerConfig := layerData.Config genManifest := &schema.ImageManifest{} appURL := "" // omit docker hub index URL in app name if dockerURL.IndexURL != defaultIndex { appURL = dockerURL.IndexURL + "/" } appURL += dockerURL.ImageName + "-" + layerData.ID appURL, err := appctypes.SanitizeACIdentifier(appURL) if err != nil { return nil, err } name := appctypes.MustACIdentifier(appURL) genManifest.Name = *name acVersion, err := appctypes.NewSemVer(schemaVersion) if err != nil { panic("invalid appc spec version") } genManifest.ACVersion = *acVersion genManifest.ACKind = appctypes.ACKind(schema.ImageManifestKind) var ( labels appctypes.Labels parentLabels appctypes.Labels annotations appctypes.Annotations ) layer := appctypes.MustACIdentifier("layer") labels = append(labels, appctypes.Label{Name: *layer, Value: layerData.ID}) tag := dockerURL.Tag version := appctypes.MustACIdentifier("version") labels = append(labels, appctypes.Label{Name: *version, Value: tag}) if layerData.OS != "" { os := appctypes.MustACIdentifier("os") labels = append(labels, appctypes.Label{Name: *os, Value: layerData.OS}) parentLabels = append(parentLabels, appctypes.Label{Name: *os, Value: layerData.OS}) if layerData.Architecture != "" { arch := appctypes.MustACIdentifier("arch") parentLabels = append(parentLabels, appctypes.Label{Name: *arch, Value: layerData.Architecture}) } } if layerData.Author != "" { authorsKey := appctypes.MustACIdentifier("authors") annotations = append(annotations, appctypes.Annotation{Name: *authorsKey, Value: layerData.Author}) } epoch := time.Unix(0, 0) if !layerData.Created.Equal(epoch) { createdKey := appctypes.MustACIdentifier("created") annotations = append(annotations, appctypes.Annotation{Name: *createdKey, Value: layerData.Created.Format(time.RFC3339)}) } if layerData.Comment != "" { commentKey := appctypes.MustACIdentifier("docker-comment") annotations = append(annotations, appctypes.Annotation{Name: *commentKey, Value: layerData.Comment}) } genManifest.Labels = labels genManifest.Annotations = annotations if dockerConfig != nil { exec := getExecCommand(dockerConfig.Entrypoint, dockerConfig.Cmd) if exec != nil { user, group := parseDockerUser(dockerConfig.User) var env appctypes.Environment for _, v := range dockerConfig.Env { parts := strings.SplitN(v, "=", 2) env.Set(parts[0], parts[1]) } app := &appctypes.App{ Exec: exec, User: user, Group: group, Environment: env, WorkingDirectory: dockerConfig.WorkingDir, } app.MountPoints, err = convertVolumesToMPs(dockerConfig.Volumes) if err != nil { return nil, err } app.Ports, err = convertPorts(dockerConfig.ExposedPorts, dockerConfig.PortSpecs) if err != nil { return nil, err } genManifest.App = app } } if layerData.Parent != "" { parentImageNameString := dockerURL.IndexURL + "/" + dockerURL.ImageName + "-" + layerData.Parent parentImageNameString, err := appctypes.SanitizeACIdentifier(parentImageNameString) if err != nil { return nil, err } parentImageName := appctypes.MustACIdentifier(parentImageNameString) genManifest.Dependencies = append(genManifest.Dependencies, appctypes.Dependency{ImageName: *parentImageName, Labels: parentLabels}) } return genManifest, nil }
func TestAPIServiceListInspectPods(t *testing.T) { ctx := testutils.NewRktRunCtx() defer ctx.Cleanup() svc := startAPIService(t, ctx) defer stopAPIService(t, svc) c, conn := newAPIClientOrFail(t, "localhost:15441") defer conn.Close() resp, err := c.ListPods(context.Background(), &v1alpha.ListPodsRequest{}) if err != nil { t.Fatalf("Unexpected error: %v", err) } if len(resp.Pods) != 0 { t.Errorf("Unexpected result: %v, should see zero pods", resp.Pods) } patches := []string{"--exec=/inspect --print-msg=HELLO_API --exit-code=42"} patchImportAndFetchHash("rkt-inspect-print.aci", patches, t, ctx) // TODO(derekparker) There is a bug where `patchImportAndFetchHash` does not // return the full hash, which causes `InspectPod` to fail later in this test. // So instead we list images to get the full, correct hash. iresp, err := c.ListImages(context.Background(), &v1alpha.ListImagesRequest{}) if err != nil { t.Fatalf("Unexpected error: %v", err) } if len(iresp.Images) == 0 { t.Fatal("Expected non-zero images") } imgID, err := types.NewHash(iresp.Images[0].Id) if err != nil { t.Fatalf("Cannot generate types.Hash from %v: %v", iresp.Images[0].Id, err) } pm := schema.BlankPodManifest() pm.Apps = []schema.RuntimeApp{ schema.RuntimeApp{ Name: types.ACName("rkt-inspect"), Image: schema.RuntimeImage{ Name: types.MustACIdentifier("coreos.com/rkt-inspect"), ID: *imgID, }, Annotations: []types.Annotation{types.Annotation{Name: types.ACIdentifier("app-test"), Value: "app-test"}}, }, } pm.Annotations = []types.Annotation{types.Annotation{Name: types.ACIdentifier("test"), Value: "test"}} manifestFile := generatePodManifestFile(t, pm) defer os.Remove(manifestFile) runCmd := fmt.Sprintf("%s run --pod-manifest=%s", ctx.Cmd(), manifestFile) esp := spawnOrFail(t, runCmd) waitOrFail(t, esp, true) // ListPods(detail=false). resp, err = c.ListPods(context.Background(), &v1alpha.ListPodsRequest{}) if err != nil { t.Fatalf("Unexpected error: %v", err) } if len(resp.Pods) == 0 { t.Errorf("Unexpected result: %v, should see non-zero pods", resp.Pods) } for _, p := range resp.Pods { checkPodBasics(t, ctx, p) // Test InspectPod(). inspectResp, err := c.InspectPod(context.Background(), &v1alpha.InspectPodRequest{Id: p.Id}) if err != nil { t.Fatalf("Unexpected error: %v", err) } checkPodDetails(t, ctx, inspectResp.Pod) // Test Apps. for i, app := range p.Apps { checkAnnotations(t, pm.Apps[i].Annotations, app.Annotations) } } // ListPods(detail=true). resp, err = c.ListPods(context.Background(), &v1alpha.ListPodsRequest{Detail: true}) if err != nil { t.Fatalf("Unexpected error: %v", err) } if len(resp.Pods) == 0 { t.Errorf("Unexpected result: %v, should see non-zero pods", resp.Pods) } for _, p := range resp.Pods { checkPodDetails(t, ctx, p) } }
// Test running pod manifests that contains just one app. // TODO(yifan): Figure out a way to test port mapping on single host. func TestPodManifest(t *testing.T) { ctx := newRktRunCtx() defer ctx.cleanup() tmpdir, err := ioutil.TempDir("", "rkt-tests.") if err != nil { t.Fatalf("Cannot create temporary directory: %v", err) } defer os.RemoveAll(tmpdir) boolFalse, boolTrue := false, true tests := []struct { // [image name]:[image patches] images map[string][]string podManifest *schema.PodManifest shouldSuccess bool expectedResult string cgroup string }{ { // Simple read. map[string][]string{ "rkt-test-run-pod-manifest-read.aci": {}, }, &schema.PodManifest{ Apps: []schema.RuntimeApp{ { Name: baseAppName, App: &types.App{ Exec: []string{"/inspect", "--read-file"}, User: "******", Group: "0", Environment: []types.EnvironmentVariable{ {"FILE", "/dir1/file"}, }, }, }, }, }, true, "dir1", "", }, { // Simple read after write with volume mounted. map[string][]string{ "rkt-test-run-pod-manifest-vol-rw.aci": {}, }, &schema.PodManifest{ Apps: []schema.RuntimeApp{ { Name: baseAppName, App: &types.App{ Exec: []string{"/inspect", "--write-file", "--read-file"}, User: "******", Group: "0", Environment: []types.EnvironmentVariable{ {"FILE", "/dir1/file"}, {"CONTENT", "host:foo"}, }, MountPoints: []types.MountPoint{ {"dir1", "/dir1", false}, }, }, }, }, Volumes: []types.Volume{ {"dir1", "host", tmpdir, nil}, }, }, true, "host:foo", "", }, { // Simple read after write with read-only mount point, should fail. map[string][]string{ "rkt-test-run-pod-manifest-vol-ro.aci": {}, }, &schema.PodManifest{ Apps: []schema.RuntimeApp{ { Name: baseAppName, App: &types.App{ Exec: []string{"/inspect", "--write-file", "--read-file"}, User: "******", Group: "0", Environment: []types.EnvironmentVariable{ {"FILE", "/dir1/file"}, {"CONTENT", "bar"}, }, MountPoints: []types.MountPoint{ {"dir1", "/dir1", true}, }, }, }, }, Volumes: []types.Volume{ {"dir1", "host", tmpdir, nil}, }, }, false, `Cannot write to file "/dir1/file": open /dir1/file: read-only file system`, "", }, { // Simple read after write with volume mounted. // Override the image's mount point spec. This should fail as the volume is // read-only in pod manifest, (which will override the mount point in both image/pod manifest). map[string][]string{ "rkt-test-run-pod-manifest-vol-rw-override.aci": {"--mounts=dir1,path=/dir1,readOnly=false"}, }, &schema.PodManifest{ Apps: []schema.RuntimeApp{ { Name: baseAppName, App: &types.App{ Exec: []string{"/inspect", "--write-file", "--read-file"}, User: "******", Group: "0", Environment: []types.EnvironmentVariable{ {"FILE", "/dir1/file"}, {"CONTENT", "bar"}, }, MountPoints: []types.MountPoint{ {"dir1", "/dir1", false}, }, }, }, }, Volumes: []types.Volume{ {"dir1", "host", tmpdir, &boolTrue}, }, }, false, `Cannot write to file "/dir1/file": open /dir1/file: read-only file system`, "", }, { // Simple read after write with volume mounted. // Override the image's mount point spec. map[string][]string{ "rkt-test-run-pod-manifest-vol-rw-override.aci": {"--mounts=dir1,path=/dir1,readOnly=true"}, }, &schema.PodManifest{ Apps: []schema.RuntimeApp{ { Name: baseAppName, App: &types.App{ Exec: []string{"/inspect", "--write-file", "--read-file"}, User: "******", Group: "0", Environment: []types.EnvironmentVariable{ {"FILE", "/dir2/file"}, {"CONTENT", "host:bar"}, }, MountPoints: []types.MountPoint{ {"dir1", "/dir2", false}, }, }, }, }, Volumes: []types.Volume{ {"dir1", "host", tmpdir, nil}, }, }, true, "host:bar", "", }, { // Simple read after write with volume mounted, no apps in pod manifest. map[string][]string{ "rkt-test-run-pod-manifest-vol-rw-no-app.aci": { "--exec=/inspect --write-file --read-file --file-name=/dir1/file --content=host:baz", "--mounts=dir1,path=/dir1,readOnly=false", }, }, &schema.PodManifest{ Apps: []schema.RuntimeApp{ {Name: baseAppName}, }, Volumes: []types.Volume{ {"dir1", "host", tmpdir, nil}, }, }, true, "host:baz", "", }, { // Simple read after write with volume mounted, no apps in pod manifest. // This should succeed even the mount point in image manifest is readOnly, // because it is overrided by the volume's readOnly. map[string][]string{ "rkt-test-run-pod-manifest-vol-ro-no-app.aci": { "--exec=/inspect --write-file --read-file --file-name=/dir1/file --content=host:zaz", "--mounts=dir1,path=/dir1,readOnly=true", }, }, &schema.PodManifest{ Apps: []schema.RuntimeApp{ {Name: baseAppName}, }, Volumes: []types.Volume{ {"dir1", "host", tmpdir, &boolFalse}, }, }, true, "host:zaz", "", }, { // Simple read after write with read-only volume mounted, no apps in pod manifest. // This should fail as the volume is read-only. map[string][]string{ "rkt-test-run-pod-manifest-vol-ro-no-app.aci": { "--exec=/inspect --write-file --read-file --file-name=/dir1/file --content=baz", "--mounts=dir1,path=/dir1,readOnly=false", }, }, &schema.PodManifest{ Apps: []schema.RuntimeApp{ {Name: baseAppName}, }, Volumes: []types.Volume{ {"dir1", "host", tmpdir, &boolTrue}, }, }, false, `Cannot write to file "/dir1/file": open /dir1/file: read-only file system`, "", }, { // Print CPU quota, which should be overwritten by the pod manifest. map[string][]string{ "rkt-test-run-pod-manifest-cpu-isolator.aci": {}, }, &schema.PodManifest{ Apps: []schema.RuntimeApp{ { Name: baseAppName, App: &types.App{ Exec: []string{"/inspect", "--print-cpuquota"}, User: "******", Group: "0", Isolators: []types.Isolator{ { Name: "resource/cpu", ValueRaw: rawRequestLimit("100", "100"), }, }, }, }, }, }, true, `CPU Quota: 100`, "cpu", }, { // Print memory limit, which should be overwritten by the pod manifest. map[string][]string{ "rkt-test-run-pod-manifest-memory-isolator.aci": {}, }, &schema.PodManifest{ Apps: []schema.RuntimeApp{ { Name: baseAppName, App: &types.App{ Exec: []string{"/inspect", "--print-memorylimit"}, User: "******", Group: "0", Isolators: []types.Isolator{ { Name: "resource/memory", // 4MB. ValueRaw: rawRequestLimit("4194304", "4194304"), }, }, }, }, }, }, true, `Memory Limit: 4194304`, "memory", }, { // Multiple apps in the pod. The first app will read out the content // written by the second app. map[string][]string{ "rkt-test-run-pod-manifest-app1.aci": {"--name=rkt-inspect-readapp"}, "rkt-test-run-pod-manifest-app2.aci": {"--name=rkt-inspect-writeapp"}, }, &schema.PodManifest{ Apps: []schema.RuntimeApp{ { Name: "rkt-inspect-readapp", App: &types.App{ Exec: []string{"/inspect", "--pre-sleep=10", "--read-file"}, User: "******", Group: "0", Environment: []types.EnvironmentVariable{ {"FILE", "/dir/file"}, }, MountPoints: []types.MountPoint{ {"dir", "/dir", false}, }, }, }, { Name: "rkt-inspect-writeapp", App: &types.App{ Exec: []string{"/inspect", "--write-file"}, User: "******", Group: "0", Environment: []types.EnvironmentVariable{ {"FILE", "/dir/file"}, {"CONTENT", "host:foo"}, }, MountPoints: []types.MountPoint{ {"dir", "/dir", false}, }, }, }, }, Volumes: []types.Volume{ {"dir", "host", tmpdir, nil}, }, }, true, "host:foo", "", }, { // Pod manifest overwrites the image's capability. map[string][]string{ "rkt-test-run-pod-manifest-cap.aci": {"--capability=CAP_NET_ADMIN"}, }, &schema.PodManifest{ Apps: []schema.RuntimeApp{ { Name: baseAppName, App: &types.App{ Exec: []string{"/inspect", "--print-caps-pid=0"}, User: "******", Group: "0", Environment: []types.EnvironmentVariable{ {"CAPABILITY", strconv.Itoa(int(capability.CAP_NET_ADMIN))}, }, }, }, }, }, true, fmt.Sprintf("%v=disabled", capability.CAP_NET_ADMIN.String()), "", }, { // Pod manifest overwrites the image's capability. map[string][]string{ "rkt-test-run-pod-manifest-cap.aci": {"--capability=CAP_NET_ADMIN"}, }, &schema.PodManifest{ Apps: []schema.RuntimeApp{ { Name: baseAppName, App: &types.App{ Exec: []string{"/inspect", "--print-caps-pid=0"}, User: "******", Group: "0", Environment: []types.EnvironmentVariable{ {"CAPABILITY", strconv.Itoa(int(capability.CAP_NET_BIND_SERVICE))}, }, Isolators: []types.Isolator{ { Name: "os/linux/capabilities-retain-set", ValueRaw: rawValue(fmt.Sprintf(`{"set":["CAP_NET_BIND_SERVICE"]}`)), }, }, }, }, }, }, true, fmt.Sprintf("%v=enabled", capability.CAP_NET_BIND_SERVICE.String()), "", }, } for i, tt := range tests { if tt.cgroup != "" && !cgroup.IsIsolatorSupported(tt.cgroup) { t.Logf("Skip test #%v: cgroup %s not supported", i, tt.cgroup) continue } j := 0 for name, patches := range tt.images { imageFile := patchTestACI(name, patches...) hash := importImageAndFetchHash(t, ctx, imageFile) defer os.Remove(imageFile) imgName := types.MustACIdentifier(name) imgID, err := types.NewHash(hash) if err != nil { t.Fatalf("Cannot generate types.Hash from %v: %v", hash, err) } ra := &tt.podManifest.Apps[j] ra.Image.Name = imgName ra.Image.ID = *imgID j++ } tt.podManifest.ACKind = schema.PodManifestKind tt.podManifest.ACVersion = schema.AppContainerVersion manifestFile := generatePodManifestFile(t, tt.podManifest) defer os.Remove(manifestFile) // 1. Test 'rkt run'. runCmd := fmt.Sprintf("%s run --mds-register=false --pod-manifest=%s", ctx.cmd(), manifestFile) t.Logf("Running 'run' test #%v: %v", i, runCmd) child, err := gexpect.Spawn(runCmd) if err != nil { t.Fatalf("Cannot exec rkt #%v: %v", i, err) } if tt.expectedResult != "" { if err := expectWithOutput(child, tt.expectedResult); err != nil { t.Fatalf("Expected %q but not found: %v", tt.expectedResult, err) } } if err := child.Wait(); err != nil { if tt.shouldSuccess { t.Fatalf("rkt didn't terminate correctly: %v", err) } } verifyHostFile(t, tmpdir, "file", i, tt.expectedResult) // 2. Test 'rkt prepare' + 'rkt run-prepared'. cmds := strings.Fields(ctx.cmd()) prepareCmd := exec.Command(cmds[0], cmds[1:]...) prepareArg := fmt.Sprintf("--pod-manifest=%s", manifestFile) prepareCmd.Args = append(prepareCmd.Args, "--insecure-skip-verify", "prepare", prepareArg) output, err := prepareCmd.Output() if err != nil { t.Fatalf("Cannot read the output: %v", err) } podIDStr := strings.TrimSpace(string(output)) podID, err := types.NewUUID(podIDStr) if err != nil { t.Fatalf("%q is not a valid UUID: %v", podIDStr, err) } runPreparedCmd := fmt.Sprintf("%s run-prepared --mds-register=false %s", ctx.cmd(), podID.String()) t.Logf("Running 'run' test #%v: %v", i, runPreparedCmd) child, err = gexpect.Spawn(runPreparedCmd) if err != nil { t.Fatalf("Cannot exec rkt #%v: %v", i, err) } if tt.expectedResult != "" { if err := expectWithOutput(child, tt.expectedResult); err != nil { t.Fatalf("Expected %q but not found: %v", tt.expectedResult, err) } } if err := child.Wait(); err != nil { if tt.shouldSuccess { t.Fatalf("rkt didn't terminate correctly: %v", err) } } verifyHostFile(t, tmpdir, "file", i, tt.expectedResult) } }