/* * NewNetDefaultGWTest checks if default gateway is correct if only configured network is one provided by flannel. */ func NewNetDefaultGWTest() testutils.Test { return testutils.TestFunc(func(t *testing.T) { ctx := testutils.NewRktRunCtx() defer ctx.Cleanup() _, ntFlannel, err := mockFlannelNetwork(t, ctx) if err != nil { t.Errorf("Can't mock flannel network: %v", err) } defer os.Remove(ntFlannel.SubnetFile) testImageArgs := []string{"--exec=/inspect --print-defaultgwv4"} testImage := patchTestACI("rkt-inspect-networking.aci", testImageArgs...) defer os.Remove(testImage) cmd := fmt.Sprintf("%s --debug --insecure-options=image run --net=%s --mds-register=false %s", ctx.Cmd(), ntFlannel.Name, testImage) child := spawnOrFail(t, cmd) defer waitOrFail(t, child, 0) expectedRegex := `DefaultGWv4: (\d+\.\d+\.\d+\.\d+)` if _, out, err := expectRegexTimeoutWithOutput(child, expectedRegex, time.Minute); err != nil { t.Fatalf("No default gateway!\nError: %v\nOutput: %v", err, out) } }) }
/* * Pass the IP arg to the default networks, ensure it works */ func NewNetDefaultIPArgTest() testutils.Test { doTest := func(netArg, expectedIP string, t *testing.T) { ctx := testutils.NewRktRunCtx() defer ctx.Cleanup() appCmd := "--exec=/inspect -- --print-ipv4=eth0" cmd := fmt.Sprintf("%s --debug --insecure-options=image run --net=\"%s\" --mds-register=false %s %s", ctx.Cmd(), netArg, getInspectImagePath(), appCmd) child := spawnOrFail(t, cmd) defer waitOrFail(t, child, 0) expectedRegex := `IPv4: (\d+\.\d+\.\d+\.\d+)` result, out, err := expectRegexTimeoutWithOutput(child, expectedRegex, 30*time.Second) if err != nil { t.Fatalf("Error: %v\nOutput: %v", err, out) return } containerIP := result[1] if expectedIP != containerIP { t.Fatalf("--net=%s setting IP failed: Got %q but expected %q", netArg, containerIP, expectedIP) } } return testutils.TestFunc(func(t *testing.T) { doTest("default:IP=172.16.28.123", "172.16.28.123", t) doTest("default-restricted:IP=172.17.42.42", "172.17.42.42", t) }) }
func NewNetCustomBridgeTest() testutils.Test { return testutils.TestFunc(func(t *testing.T) { iface, _, err := testutils.GetNonLoIfaceWithAddrs(netlink.FAMILY_V4) if err != nil { t.Fatalf("Error while getting non-lo host interface: %v\n", err) } if iface.Name == "" { t.Skipf("Cannot run test without non-lo host interface") } nt := networkTemplateT{ Name: "bridge0", Type: "bridge", IpMasq: true, IsGateway: true, Master: iface.Name, Ipam: &ipamTemplateT{ Type: "host-local", Subnet: "11.11.3.0/24", Routes: []map[string]string{ {"dst": "0.0.0.0/0"}, }, }, } testNetCustomNatConnectivity(t, nt) testNetCustomDual(t, nt) }) }
/* * Host network * --- * Container must have the same network namespace as the host */ func NewNetHostTest() testutils.Test { return testutils.TestFunc(func(t *testing.T) { testImageArgs := []string{"--exec=/inspect --print-netns"} testImage := patchTestACI("rkt-inspect-networking.aci", testImageArgs...) defer os.Remove(testImage) ctx := testutils.NewRktRunCtx() defer ctx.Cleanup() cmd := fmt.Sprintf("%s --net=host --debug --insecure-options=image run --mds-register=false %s", ctx.Cmd(), testImage) child := spawnOrFail(t, cmd) ctx.RegisterChild(child) defer waitOrFail(t, child, 0) expectedRegex := `NetNS: (net:\[\d+\])` result, out, err := expectRegexWithOutput(child, expectedRegex) if err != nil { t.Fatalf("Error: %v\nOutput: %v", err, out) } ns, err := os.Readlink("/proc/self/ns/net") if err != nil { t.Fatalf("Cannot evaluate NetNS symlink: %v", err) } if nsChanged := ns != result[1]; nsChanged { t.Fatalf("container left host netns") } }) }
/* * NewNetPreserveNetNameTest checks if netName is set if network is configured via flannel */ func NewNetPreserveNetNameTest() testutils.Test { return testutils.TestFunc(func(t *testing.T) { ctx := testutils.NewRktRunCtx() defer ctx.Cleanup() netdir, ntFlannel, err := mockFlannelNetwork(t, ctx) if err != nil { t.Errorf("Can't mock flannel network: %v", err) } defer os.RemoveAll(netdir) defer os.Remove(ntFlannel.SubnetFile) podUUIDFile := filepath.Join(ctx.DataDir(), "pod_uuid") defer os.Remove(podUUIDFile) // start container with 'flannel' network testImageArgs := []string{"--exec=/inspect"} testImage := patchTestACI("rkt-inspect-networking.aci", testImageArgs...) defer os.Remove(testImage) cmd := fmt.Sprintf("%s --debug --insecure-options=image run --uuid-file-save=%s --net=%s --mds-register=false %s", ctx.Cmd(), podUUIDFile, ntFlannel.Name, testImage) spawnAndWaitOrFail(t, cmd, 0) podUUID, err := ioutil.ReadFile(podUUIDFile) if err != nil { t.Fatalf("Can't read pod UUID: %v", err) } // read net-info.json created for pod podDir := filepath.Join(ctx.DataDir(), "pods", "run", string(podUUID)) podDirfd, err := syscall.Open(podDir, syscall.O_RDONLY|syscall.O_DIRECTORY, 0) if err != nil { t.Fatalf("Can't open pod directory for reading! %v", err) } info, err := netinfo.LoadAt(podDirfd) if err != nil { t.Fatalf("Can't open net-info.json for reading: %v", err) } if len(info) != 2 { t.Fatalf("Incorrect number of networks: %v", len(info)) } found := false for _, net := range info { if net.NetName == ntFlannel.Name { found = true break } } if !found { t.Fatalf("Network '%s' not found!\nnetInfo[0]: %v\nnetInfo[1]: %v", ntFlannel.Name, info[0], info[1]) } }) }
// Test that CNI invocations which return DNS information are carried through to /etc/resolv.conf func NewNetCNIDNSTest() testutils.Test { return testutils.TestFunc(func(t *testing.T) { ctx := testutils.NewRktRunCtx() defer ctx.Cleanup() iface, _, err := testutils.GetNonLoIfaceWithAddrs(netlink.FAMILY_V4) if err != nil { t.Fatalf("Error while getting non-lo host interface: %v\n", err) } if iface.Name == "" { t.Skipf("Cannot run test without non-lo host interface") } nt := networkTemplateT{ Name: "bridge0", Type: "cniproxy", Args: []string{"X_LOG=output.json", "X_REAL_PLUGIN=bridge", "X_ADD_DNS=1"}, IpMasq: true, IsGateway: true, Master: iface.Name, Ipam: &ipamTemplateT{ Type: "host-local", Subnet: "11.11.3.0/24", Routes: []map[string]string{ {"dst": "0.0.0.0/0"}, }, }, } // bring the networking up, copy the proxy netdir := prepareTestNet(t, ctx, nt) defer os.RemoveAll(netdir) ga := testutils.NewGoroutineAssistant(t) ga.Add(1) go func() { defer ga.Done() appCmd := "--exec=/inspect -- --read-file --file-name=/etc/resolv.conf" cmd := fmt.Sprintf("%s --debug --insecure-options=image run --net=%v --mds-register=false %s %s", ctx.Cmd(), nt.NetParameter(), getInspectImagePath(), appCmd) child := ga.SpawnOrFail(cmd) defer ga.WaitOrFail(child) expectedRegex := "nameserver 1.2.3.4" _, out, err := expectRegexTimeoutWithOutput(child, expectedRegex, 30*time.Second) if err != nil { ga.Fatalf("Error: %v\nOutput: %v", err, out) } }() ga.Wait() }) }
/* * Default-restricted net * --- * Container launches http server on all its interfaces * Host must be able to connects to container's http server via container's * eth0's IPv4 * TODO: verify that the container isn't NATed */ func NewTestNetDefaultRestrictedConnectivity() testutils.Test { return testutils.TestFunc(func(t *testing.T) { ctx := testutils.NewRktRunCtx() defer ctx.Cleanup() f := func(argument string) { httpPort, err := testutils.GetNextFreePort4() if err != nil { t.Fatalf("%v", err) } httpServeAddr := fmt.Sprintf("0.0.0.0:%v", httpPort) iface := "eth0" testImageArgs := []string{fmt.Sprintf("--exec=/inspect --print-ipv4=%v --serve-http=%v", iface, httpServeAddr)} testImage := patchTestACI("rkt-inspect-networking.aci", testImageArgs...) defer os.Remove(testImage) cmd := fmt.Sprintf("%s --debug --insecure-options=image run %s --mds-register=false %s", ctx.Cmd(), argument, testImage) child := spawnOrFail(t, cmd) expectedRegex := `IPv4: (\d+\.\d+\.\d+\.\d+)` result, out, err := expectRegexWithOutput(child, expectedRegex) if err != nil { t.Fatalf("Error: %v\nOutput: %v", err, out) } httpGetAddr := fmt.Sprintf("http://%v:%v", result[1], httpPort) ga := testutils.NewGoroutineAssistant(t) ga.Add(2) // Child opens the server go func() { defer ga.Done() ga.WaitOrFail(child) }() // Host connects to the child go func() { defer ga.Done() expectedRegex := `serving on` _, out, err := expectRegexWithOutput(child, expectedRegex) if err != nil { ga.Fatalf("Error: %v\nOutput: %v", err, out) } body, err := testutils.HTTPGet(httpGetAddr) if err != nil { ga.Fatalf("%v\n", err) } t.Logf("HTTP-Get received: %s", body) }() ga.Wait() } f("--net=default-restricted") }) }
/* * Host networking * --- * Container launches http server which must be reachable by the host via the * localhost address */ func NewNetHostConnectivityTest() testutils.Test { return testutils.TestFunc(func(t *testing.T) { logger.SetLogger(t) httpPort, err := testutils.GetNextFreePort4() if err != nil { t.Fatalf("%v", err) } httpServeAddr := fmt.Sprintf("0.0.0.0:%v", httpPort) httpGetAddr := fmt.Sprintf("http://127.0.0.1:%v", httpPort) testImageArgs := []string{"--exec=/inspect --serve-http=" + httpServeAddr} testImage := patchTestACI("rkt-inspect-networking.aci", testImageArgs...) defer os.Remove(testImage) ctx := testutils.NewRktRunCtx() defer ctx.Cleanup() cmd := fmt.Sprintf("%s --net=host --debug --insecure-options=image run --mds-register=false %s", ctx.Cmd(), testImage) child := spawnOrFail(t, cmd) ctx.RegisterChild(child) ga := testutils.NewGoroutineAssistant(t) ga.Add(2) // Child opens the server go func() { defer ga.Done() ga.WaitOrFail(child) }() // Host connects to the child go func() { defer ga.Done() expectedRegex := `serving on` _, out, err := expectRegexWithOutput(child, expectedRegex) if err != nil { ga.Fatalf("Error: %v\nOutput: %v", err, out) } body, err := testutils.HTTPGet(httpGetAddr) if err != nil { ga.Fatalf("%v\n", err) } t.Logf("HTTP-Get received: %s", body) }() ga.Wait() }) }
func NewNetOverrideTest() testutils.Test { return testutils.TestFunc(func(t *testing.T) { ctx := testutils.NewRktRunCtx() defer ctx.Cleanup() iface, _, err := testutils.GetNonLoIfaceWithAddrs(netlink.FAMILY_V4) if err != nil { t.Fatalf("Error while getting non-lo host interface: %v\n", err) } if iface.Name == "" { t.Skipf("Cannot run test without non-lo host interface") } nt := networkTemplateT{ Name: "overridemacvlan", Type: "macvlan", Master: iface.Name, Ipam: &ipamTemplateT{ Type: "host-local", Subnet: "11.11.4.0/24", }, } netdir := prepareTestNet(t, ctx, nt) defer os.RemoveAll(netdir) testImageArgs := []string{"--exec=/inspect --print-ipv4=eth0"} testImage := patchTestACI("rkt-inspect-networking1.aci", testImageArgs...) defer os.Remove(testImage) expectedIP := "11.11.4.244" cmd := fmt.Sprintf("%s --debug --insecure-options=image run --net=all --net=\"%s:IP=%s\" --mds-register=false %s", ctx.Cmd(), nt.Name, expectedIP, testImage) child := spawnOrFail(t, cmd) defer waitOrFail(t, child, 0) expectedRegex := `IPv4: (\d+\.\d+\.\d+\.\d+)` result, out, err := expectRegexTimeoutWithOutput(child, expectedRegex, 30*time.Second) if err != nil { t.Fatalf("Error: %v\nOutput: %v", err, out) return } containerIP := result[1] if expectedIP != containerIP { t.Fatalf("overriding IP did not work: Got %q but expected %q", containerIP, expectedIP) } }) }
func NewCapsTest(hasStage1FullCaps bool) testutils.Test { return testutils.TestFunc(func(t *testing.T) { ctx := testutils.NewRktRunCtx() defer ctx.Cleanup() for i, tt := range capsTests { stage1Args := []string{"--exec=/inspect --print-caps-pid=1 --print-user"} stage2Args := []string{"--exec=/inspect --print-caps-pid=0 --print-user"} if tt.capIsolator != "" { stage1Args = append(stage1Args, "--capability="+tt.capIsolator) stage2Args = append(stage2Args, "--capability="+tt.capIsolator) } stage1FileName := patchTestACI("rkt-inspect-print-caps-stage1.aci", stage1Args...) defer os.Remove(stage1FileName) stage2FileName := patchTestACI("rkt-inspect-print-caps-stage2.aci", stage2Args...) defer os.Remove(stage2FileName) stageFileNames := []string{stage1FileName, stage2FileName} for _, stage := range []int{1, 2} { t.Logf("Running test #%v: %v [stage%v]", i, tt.testName, stage) cmd := fmt.Sprintf("%s --debug --insecure-options=image run --mds-register=false --set-env=CAPABILITY=%d %s", ctx.Cmd(), int(tt.capa), stageFileNames[stage-1]) child := spawnOrFail(t, cmd) expectedLine := tt.capa.String() capInStage1Expected := tt.capInStage1Expected || hasStage1FullCaps if (stage == 1 && capInStage1Expected) || (stage == 2 && tt.capInStage2Expected) { expectedLine += "=enabled" } else { expectedLine += "=disabled" } if err := expectWithOutput(child, expectedLine); err != nil { t.Fatalf("Expected %q but not found: %v", expectedLine, err) } if err := expectWithOutput(child, "User: uid=0 euid=0 gid=0 egid=0"); err != nil { t.Fatalf("Expected user 0 but not found: %v", err) } waitOrFail(t, child, 0) } ctx.Reset() } }) }
// Test that `rkt run --dns=none` means no resolv.conf is created, even when // CNI returns DNS informationparseHostsEntries(flagHosts) func NewNetCNIDNSArgNoneTest() testutils.Test { return testutils.TestFunc(func(t *testing.T) { ctx := testutils.NewRktRunCtx() defer ctx.Cleanup() iface, _, err := testutils.GetNonLoIfaceWithAddrs(netlink.FAMILY_V4) if err != nil { t.Fatalf("Error while getting non-lo host interface: %v\n", err) } if iface.Name == "" { t.Skipf("Cannot run test without non-lo host interface") } nt := networkTemplateT{ Name: "bridge0", Type: "cniproxy", Args: []string{"X_LOG=output.json", "X_REAL_PLUGIN=bridge", "X_ADD_DNS=1"}, IpMasq: true, IsGateway: true, Master: iface.Name, Ipam: &ipamTemplateT{ Type: "host-local", Subnet: "11.11.3.0/24", Routes: []map[string]string{ {"dst": "0.0.0.0/0"}, }, }, } // bring the networking up, copy the proxy prepareTestNet(t, ctx, nt) appCmd := "--exec=/inspect -- --stat-file --file-name=/etc/resolv.conf" cmd := fmt.Sprintf("%s --debug --insecure-options=image run --net=%v --mds-register=false --dns=none %s %s", ctx.Cmd(), nt.NetParameter(), getInspectImagePath(), appCmd) child := spawnOrFail(t, cmd) ctx.RegisterChild(child) defer waitOrFail(t, child, 254) expectedRegex := `Cannot stat file "/etc/resolv.conf": stat /etc/resolv.conf: no such file or directory` _, out, err := expectRegexTimeoutWithOutput(child, expectedRegex, 30*time.Second) if err != nil { t.Fatalf("Error: %v\nOutput: %v", err, out) } }) }
func NewTestNetLongName() testutils.Test { return testutils.TestFunc(func(t *testing.T) { nt := networkTemplateT{ Name: "thisnameiswaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaytoolong", Type: "ptp", IpMasq: true, Ipam: &ipamTemplateT{ Type: "host-local", Subnet: "11.11.6.0/24", Routes: []map[string]string{ {"dst": "0.0.0.0/0"}, }, }, } testNetCustomNatConnectivity(t, nt) }) }
/* * None networking * --- * must be in an empty netns */ func NewNetNoneTest() testutils.Test { return testutils.TestFunc(func(t *testing.T) { testImageArgs := []string{"--exec=/inspect --print-netns --print-iface-count"} testImage := patchTestACI("rkt-inspect-networking.aci", testImageArgs...) defer os.Remove(testImage) ctx := testutils.NewRktRunCtx() defer ctx.Cleanup() cmd := fmt.Sprintf("%s --debug --insecure-options=image run --net=none --mds-register=false %s", ctx.Cmd(), testImage) child := spawnOrFail(t, cmd) defer waitOrFail(t, child, 0) expectedRegex := `NetNS: (net:\[\d+\])` result, out, err := expectRegexWithOutput(child, expectedRegex) if err != nil { t.Fatalf("Error: %v\nOutput: %v", err, out) } ns, err := os.Readlink("/proc/self/ns/net") if err != nil { t.Fatalf("Cannot evaluate NetNS symlink: %v", err) } if nsChanged := ns != result[1]; !nsChanged { t.Fatalf("container did not leave host netns") } expectedRegex = `Interface count: (\d+)` result, out, err = expectRegexWithOutput(child, expectedRegex) if err != nil { t.Fatalf("Error: %v\nOutput: %v", err, out) } ifaceCount, err := strconv.Atoi(result[1]) if err != nil { t.Fatalf("Error parsing interface count: %v\nOutput: %v", err, out) } if ifaceCount != 1 { t.Fatalf("Interface count must be 1 not %q", ifaceCount) } }) }
func NewNetCustomPtpTest(runCustomDual bool) testutils.Test { return testutils.TestFunc(func(t *testing.T) { nt := networkTemplateT{ Name: "ptp0", Type: "ptp", IpMasq: true, Ipam: &ipamTemplateT{ Type: "host-local", Subnet: "11.11.1.0/24", Routes: []map[string]string{ {"dst": "0.0.0.0/0"}, }, }, } testNetCustomNatConnectivity(t, nt) if runCustomDual { testNetCustomDual(t, nt) } }) }
/* * Default-restricted net * --- * Container launches http server on all its interfaces * Host must be able to connects to container's http server via container's * eth0's IPv4 * TODO: verify that the container isn't NATed */ func NewTestNetDefaultRestrictedConnectivity() testutils.Test { return testutils.TestFunc(func(t *testing.T) { ctx := testutils.NewRktRunCtx() defer ctx.Cleanup() f := func(argument string) { httpPort := "8080" iface := "eth0" testImageArgs := []string{fmt.Sprintf("--exec=/inspect --print-ipv4=%v --serve-http=0.0.0.0:%v", iface, httpPort)} testImage := patchTestACI("rkt-inspect-networking.aci", testImageArgs...) defer os.Remove(testImage) cmd := fmt.Sprintf("%s --insecure-options=image run %s --mds-register=false %s", ctx.Cmd(), argument, testImage) child := spawnOrFail(t, cmd) // Wait for the container to print out the IP address expectedRegex := `IPv4: (\d+\.\d+\.\d+\.\d+)` result, out, err := expectRegexWithOutput(child, expectedRegex) if err != nil { t.Fatalf("Error: %v\nOutput: %v", err, out) } httpGetAddr := fmt.Sprintf("http://%v:%v", result[1], httpPort) // Wait for the container to open the port expectedRegex = `serving on` _, out, err = expectRegexWithOutput(child, expectedRegex) if err != nil { t.Fatalf("Error: %v\nOutput: %v", err, out) } body, err := testutils.HTTPGet(httpGetAddr) if err != nil { t.Fatalf("%v\n", err) } t.Logf("HTTP-Get received: %s", body) waitOrFail(t, child, 0) } f("--net=default-restricted") }) }
/* * Try and start two containers with the same IP, ensure * the second invocation fails */ func NewNetIPConflictTest() testutils.Test { return testutils.TestFunc(func(t *testing.T) { ctx := testutils.NewRktRunCtx() defer ctx.Cleanup() // Launch one container and grab the IP it uses -- and have it idle appCmd := "--exec=/inspect -- --print-ipv4=eth0 --serve-http=0.0.0.0:80" cmd1 := fmt.Sprintf("%s --debug --insecure-options=image run --mds-register=false %s %s", ctx.Cmd(), getInspectImagePath(), appCmd) child1 := spawnOrFail(t, cmd1) expectedRegex := `IPv4: (\d+\.\d+\.\d+\.\d+)` result, out, err := expectRegexTimeoutWithOutput(child1, expectedRegex, 30*time.Second) if err != nil { t.Fatalf("Error: %v\nOutput: %v", err, out) return } ip := result[1] // Launch a second container with the same IP cmd2 := fmt.Sprintf("%s --debug --insecure-options=image run --net=\"default:IP=%s\" %s --exec=/inspect -- --print-ipv4=eth0", ctx.Cmd(), ip, getInspectImagePath()) child2 := spawnOrFail(t, cmd2) expectedOutput := fmt.Sprintf(`requested IP address "%s" is not available in network: default`, ip) _, out, err = expectRegexTimeoutWithOutput(child2, expectedOutput, 10*time.Second) if err != nil { t.Fatalf("Error: %v\nOutput: %v", err, out) return } // Clean up waitOrFail(t, child2, 254) syscall.Kill(child1.Cmd.Process.Pid, syscall.SIGTERM) waitOrFail(t, child1, 0) }) }
func NewNetCustomMacvlanTest() testutils.Test { return testutils.TestFunc(func(t *testing.T) { iface, _, err := testutils.GetNonLoIfaceWithAddrs(netlink.FAMILY_V4) if err != nil { t.Fatalf("Error while getting non-lo host interface: %v\n", err) } if iface.Name == "" { t.Skipf("Cannot run test without non-lo host interface") } nt := networkTemplateT{ Name: "macvlan0", Type: "macvlan", Master: iface.Name, Ipam: &ipamTemplateT{ Type: "host-local", Subnet: "11.11.2.0/24", }, } testNetCustomDual(t, nt) }) }
func NewTestVolumeMount(volumeMountTestCases [][]volumeMountTestCase) testutils.Test { return testutils.TestFunc(func(t *testing.T) { ctx := testutils.NewRktRunCtx() defer ctx.Cleanup() deferredFuncs := prepareTmpDirWithRecursiveMountsAndFiles(t) defer executeFuncsReverse(deferredFuncs) for _, testCases := range volumeMountTestCases { for i, tt := range testCases { var hashesToRemove []string for j, v := range tt.images { hash, err := patchImportAndFetchHash(v.name, v.patches, t, ctx) if err != nil { t.Fatalf("error running patchImportAndFetchHash: %v", err) } hashesToRemove = append(hashesToRemove, hash) if tt.podManifest != nil { imgName := types.MustACIdentifier(v.name) imgID, err := types.NewHash(hash) if err != nil { t.Fatalf("Cannot generate types.Hash from %v: %v", hash, err) } tt.podManifest.Apps[j].Image.Name = imgName tt.podManifest.Apps[j].Image.ID = *imgID } } manifestFile := "" if tt.podManifest != nil { 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", ctx.Cmd()) if manifestFile != "" { runCmd += fmt.Sprintf(" --pod-manifest=%s", manifestFile) } else { // TODO: run the tests for more than just the first image runCmd += fmt.Sprintf(" %s %s", tt.cmdArgs, hashesToRemove[0]) } t.Logf("Running 'run' test #%v: %q", i, tt.description) child := spawnOrFail(t, runCmd) ctx.RegisterChild(child) if tt.expectedResult != "" { if _, out, err := expectRegexWithOutput(child, tt.expectedResult); err != nil { t.Fatalf("Expected %q but not found: %v\n%s", tt.expectedResult, err, out) } } child.Wait() verifyHostFile(t, volDir, "file", i, tt.expectedResult) // 2. Test 'rkt prepare' + 'rkt run-prepared'. prepareCmd := fmt.Sprintf("%s prepare", ctx.Cmd()) if manifestFile != "" { prepareCmd += fmt.Sprintf(" --pod-manifest=%s", manifestFile) } else { // TODO: run the tests for more than just the first image prepareCmd += fmt.Sprintf(" %s %s", tt.cmdArgs, hashesToRemove[0]) } uuid := runRktAndGetUUID(t, prepareCmd) runPreparedCmd := fmt.Sprintf("%s run-prepared --mds-register=false %s", ctx.Cmd(), uuid) t.Logf("Running 'run-prepared' test #%v: %q", i, tt.description) child = spawnOrFail(t, runPreparedCmd) if tt.expectedResult != "" { if _, out, err := expectRegexWithOutput(child, tt.expectedResult); err != nil { t.Fatalf("Expected %q but not found: %v\n%s", tt.expectedResult, err, out) } } child.Wait() verifyHostFile(t, volDir, "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 NewVolumesTest() testutils.Test { return testutils.TestFunc(func(t *testing.T) { readFileImage := patchTestACI("rkt-inspect-read-file.aci", "--exec=/inspect --read-file") defer os.Remove(readFileImage) writeFileImage := patchTestACI("rkt-inspect-write-file.aci", "--exec=/inspect --write-file --read-file") defer os.Remove(writeFileImage) volRwReadFileImage := patchTestACI("rkt-inspect-vol-rw-read-file.aci", "--exec=/inspect --read-file", "--mounts=dir1,path=/dir1,readOnly=false") defer os.Remove(volRwReadFileImage) volRwWriteFileImage := patchTestACI("rkt-inspect-vol-rw-write-file.aci", "--exec=/inspect --write-file --read-file", "--mounts=dir1,path=/dir1,readOnly=false") defer os.Remove(volRwWriteFileImage) volRwWriteFileOnlyImage := patchTestACI("rkt-inspect-vol-rw-write-file-only.aci", "--exec=/inspect --write-file", "--mounts=dir1,path=/dir1,readOnly=false") defer os.Remove(volRwWriteFileOnlyImage) volRoReadFileOnlyImage := patchTestACI("rkt-inspect-vol-ro-read-file-only.aci", "--name=coreos.com/rkt-inspect-2", "--exec=/inspect --read-file", "--mounts=dir1,path=/dir1,readOnly=true") defer os.Remove(volRoReadFileOnlyImage) volRoReadFileImage := patchTestACI("rkt-inspect-vol-ro-read-file.aci", "--exec=/inspect --read-file", "--mounts=dir1,path=/dir1,readOnly=true") defer os.Remove(volRoReadFileImage) volRoWriteFileImage := patchTestACI("rkt-inspect-vol-ro-write-file.aci", "--exec=/inspect --write-file --read-file", "--mounts=dir1,path=/dir1,readOnly=true") defer os.Remove(volRoWriteFileImage) volAddMountRwImage := patchTestACI("rkt-inspect-vol-add-mount-rw.aci", "--exec=/inspect --write-file --read-file") defer os.Remove(volAddMountRwImage) volAddMountRoImage := patchTestACI("rkt-inspect-vol-add-mount-ro.aci", "--exec=/inspect --write-file --read-file") defer os.Remove(volAddMountRoImage) statFileImage := patchTestACI("rkt-inspect-stat.aci", "--exec=/inspect --stat-file") defer os.Remove(statFileImage) ctx := testutils.NewRktRunCtx() defer ctx.Cleanup() tmpdir := createTempDirOrPanic("rkt-tests.") defer os.RemoveAll(tmpdir) tmpfile := filepath.Join(tmpdir, "file") if err := ioutil.WriteFile(tmpfile, []byte("host"), 0600); err != nil { t.Fatalf("Cannot create temporary file: %v", err) } for i, tt := range volTests { cmd := strings.Replace(tt.rktCmd, "^TMPDIR^", tmpdir, -1) cmd = strings.Replace(cmd, "^RKT_BIN^", ctx.Cmd(), -1) cmd = strings.Replace(cmd, "^READ_FILE^", readFileImage, -1) cmd = strings.Replace(cmd, "^WRITE_FILE^", writeFileImage, -1) cmd = strings.Replace(cmd, "^VOL_RO_READ_FILE^", volRoReadFileImage, -1) cmd = strings.Replace(cmd, "^VOL_RO_WRITE_FILE^", volRoWriteFileImage, -1) cmd = strings.Replace(cmd, "^VOL_RW_READ_FILE^", volRwReadFileImage, -1) cmd = strings.Replace(cmd, "^VOL_RW_WRITE_FILE^", volRwWriteFileImage, -1) cmd = strings.Replace(cmd, "^VOL_RW_WRITE_FILE_ONLY^", volRwWriteFileOnlyImage, -1) cmd = strings.Replace(cmd, "^VOL_RO_READ_FILE_ONLY^", volRoReadFileOnlyImage, -1) cmd = strings.Replace(cmd, "^VOL_ADD_MOUNT_RW^", volAddMountRwImage, -1) cmd = strings.Replace(cmd, "^VOL_ADD_MOUNT_RO^", volAddMountRoImage, -1) cmd = strings.Replace(cmd, "^STAT_FILE^", statFileImage, -1) t.Logf("Running test #%v", i) child := spawnOrFail(t, cmd) defer waitOrFail(t, child, tt.expectedExit) if err := expectTimeoutWithOutput(child, tt.expect, time.Minute); err != nil { fmt.Printf("Command: %s\n", cmd) t.Fatalf("Expected %q but not found #%v: %v", tt.expect, i, err) } } }) }
//Test that the CNI execution environment matches the spec func NewNetCNIEnvTest() testutils.Test { return testutils.TestFunc(func(t *testing.T) { ctx := testutils.NewRktRunCtx() defer ctx.Cleanup() iface, _, err := testutils.GetNonLoIfaceWithAddrs(netlink.FAMILY_V4) if err != nil { t.Fatalf("Error while getting non-lo host interface: %v\n", err) } if iface.Name == "" { t.Skipf("Cannot run test without non-lo host interface") } // Declares a network of type cniproxy, which will record state and // proxy through to $X_REAL_PLUGIN nt := networkTemplateT{ Name: "bridge0", Type: "cniproxy", Args: []string{"X_LOG=output.json", "X_REAL_PLUGIN=bridge"}, IpMasq: true, IsGateway: true, Master: iface.Name, Ipam: &ipamTemplateT{ Type: "host-local", Subnet: "11.11.3.0/24", Routes: []map[string]string{ {"dst": "0.0.0.0/0"}, }, }, } // bring the networking up, copy the proxy netdir := prepareTestNet(t, ctx, nt) appCmd := "--exec=/inspect -- --print-defaultgwv4 " cmd := fmt.Sprintf("%s --debug --insecure-options=image run --net=%v --mds-register=false %s %s", ctx.Cmd(), nt.NetParameter(), getInspectImagePath(), appCmd) child := spawnOrFail(t, cmd) expectedRegex := "DefaultGWv4: 11.11.3.1" _, out, err := expectRegexTimeoutWithOutput(child, expectedRegex, 30*time.Second) if err != nil { t.Fatalf("Error: %v\nOutput: %v", err, out) } waitOrFail(t, child, 0) // Parse the log file cniLogFilename := filepath.Join(netdir, "output.json") proxyLog, err := parseCNIProxyLog(cniLogFilename) if err != nil { t.Fatal("Failed to read cniproxy ADD log", err) } os.Remove(cniLogFilename) // Check that the stdin matches the network config file expectedConfig, err := ioutil.ReadFile(filepath.Join(netdir, nt.Name+".conf")) if err != nil { t.Fatal("Failed to read network configuration", err) } if string(expectedConfig) != proxyLog.Stdin { t.Fatalf("CNI plugin stdin incorrect, expected <<%v>>, actual <<%v>>", expectedConfig, proxyLog.Stdin) } // compare the CNI env against a set of regexes checkEnv := func(step string, expectedEnv, actualEnv map[string]string) { for k, v := range expectedEnv { actual, exists := actualEnv[k] if !exists { t.Fatalf("Step %s, expected proxy CNI arg %s but not found", step, k) } re, err := regexp.Compile(v) if err != nil { t.Fatalf("Step %s, invalid CNI env regex for key %s %v", step, k, err) } found := re.FindString(actual) if found == "" { t.Fatalf("step %s cni environment %s was %s but expected pattern %s", step, k, actual, v) } } } expectedEnv := map[string]string{ "CNI_VERSION": `^0\.1\.0$`, "CNI_COMMAND": `^ADD$`, "CNI_IFNAME": `^eth\d$`, "CNI_PATH": "^" + netdir + ":/usr/lib/rkt/plugins/net:stage1/rootfs/usr/lib/rkt/plugins/net$", "CNI_NETNS": `^/var/run/netns/cni-`, "CNI_CONTAINERID": `^[a-fA-F0-9-]{36}$`, //UUID, close enough } checkEnv("add", expectedEnv, proxyLog.EnvMap) /* Run rkt GC, ensure the CNI invocation looks sane */ ctx.RunGC() proxyLog, err = parseCNIProxyLog(cniLogFilename) if err != nil { t.Fatal("Failed to read cniproxy DEL log", err) } os.Remove(cniLogFilename) expectedEnv["CNI_COMMAND"] = `^DEL$` checkEnv("del", expectedEnv, proxyLog.EnvMap) }) }
func NewAPIServiceListInspectPodsTest() testutils.Test { return testutils.TestFunc(func(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=0"} imageHash, err := patchImportAndFetchHash("rkt-inspect-print.aci", patches, t, ctx) if err != nil { t.Fatalf("%v", err) } imgID, err := types.NewHash(imageHash) if err != nil { t.Fatalf("Cannot generate types.Hash from %v: %v", imageHash, err) } podManifests := []struct { mfst schema.PodManifest net string expectedExitCode int }{ { // 1, Good pod. schema.PodManifest{ ACKind: schema.PodManifestKind, ACVersion: schema.AppContainerVersion, Apps: []schema.RuntimeApp{ { Name: types.ACName("rkt-inspect"), Image: schema.RuntimeImage{ Name: types.MustACIdentifier("coreos.com/rkt-inspect"), ID: *imgID, }, Annotations: []types.Annotation{{Name: types.ACIdentifier("app-test"), Value: "app-test"}}, }, }, Annotations: []types.Annotation{ {Name: types.ACIdentifier("test"), Value: "test"}, }, }, "default", 0, }, { // 2, Bad pod, won't be launched correctly. schema.PodManifest{ ACKind: schema.PodManifestKind, ACVersion: schema.AppContainerVersion, Apps: []schema.RuntimeApp{ { Name: types.ACName("rkt-inspect"), Image: schema.RuntimeImage{ Name: types.MustACIdentifier("coreos.com/rkt-inspect"), ID: *imgID, }, }, }, }, "non-existent-network", 254, }, } // Launch the pods. for _, entry := range podManifests { manifestFile := generatePodManifestFile(t, &entry.mfst) defer os.Remove(manifestFile) runCmd := fmt.Sprintf("%s run --net=%s --pod-manifest=%s", ctx.Cmd(), entry.net, manifestFile) waitOrFail(t, spawnOrFail(t, runCmd), entry.expectedExitCode) } time.Sleep(delta) gcCmd := fmt.Sprintf("%s gc --mark-only=true", ctx.Cmd()) waitOrFail(t, spawnOrFail(t, gcCmd), 0) gcTime := time.Now() // ListPods(detail=false). resp, err = c.ListPods(context.Background(), &v1alpha.ListPodsRequest{}) if err != nil { t.Fatalf("Unexpected error: %v", err) } if len(resp.Pods) != len(podManifests) { t.Errorf("Unexpected result: %v, should see %v pods", len(resp.Pods), len(podManifests)) } for _, p := range resp.Pods { checkPodBasicsWithGCTime(t, ctx, p, gcTime) // 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) } // 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) != len(podManifests) { t.Errorf("Unexpected result: %v, should see %v pods", len(resp.Pods), len(podManifests)) } for _, p := range resp.Pods { checkPodDetails(t, ctx, p) } // ListPods with corrupt pod directory // Note that we don't checkPodDetails here, the failure this is testing is // the api server panicing, which results in a list call hanging for ages // and then failing. // TODO: do further validation on the partial pods returned for _, p := range resp.Pods { numRemoved := 0 podDir := getPodDir(t, ctx, p.Id) filepath.Walk(filepath.Join(podDir, "appsinfo"), filepath.WalkFunc(func(path string, info os.FileInfo, err error) error { if err != nil { return err } if info.Name() == "manifest" { os.Remove(path) numRemoved++ } return nil })) if numRemoved == 0 { t.Fatalf("Expected to remove at least one app manifest for pod %v", p) } } // 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) != len(podManifests) { t.Fatalf("Expected %v pods, got %v pods", len(podManifests), len(resp.Pods)) } }) }
/* * Default net * --- * Host launches http server on all interfaces in the host netns * Container must be able to connect via any IP address of the host in the * default network, which is NATed * TODO: test connection to host on an outside interface */ func NewNetDefaultConnectivityTest() testutils.Test { return testutils.TestFunc(func(t *testing.T) { ctx := testutils.NewRktRunCtx() defer ctx.Cleanup() f := func(argument string) { httpPort, err := testutils.GetNextFreePort4() if err != nil { t.Fatalf("%v", err) } httpServeAddr := fmt.Sprintf("0.0.0.0:%v", httpPort) httpServeTimeout := 30 nonLoIPv4, err := testutils.GetNonLoIfaceIPv4() if err != nil { t.Fatalf("%v", err) } if nonLoIPv4 == "" { t.Skipf("Can not find any NAT'able IPv4 on the host, skipping..") } httpGetAddr := fmt.Sprintf("http://%v:%v", nonLoIPv4, httpPort) t.Log("Telling the child to connect via", httpGetAddr) testImageArgs := []string{fmt.Sprintf("--exec=/inspect --get-http=%v", httpGetAddr)} testImage := patchTestACI("rkt-inspect-networking.aci", testImageArgs...) defer os.Remove(testImage) hostname, err := os.Hostname() if err != nil { t.Fatalf("Error getting hostname: %v", err) } ga := testutils.NewGoroutineAssistant(t) ga.Add(2) // Host opens the server go func() { defer ga.Done() err := testutils.HTTPServe(httpServeAddr, httpServeTimeout) if err != nil { ga.Fatalf("Error during HTTPServe: %v", err) } }() // Child connects to host go func() { defer ga.Done() cmd := fmt.Sprintf("%s --debug --insecure-options=image run %s --mds-register=false %s", ctx.Cmd(), argument, testImage) child := ga.SpawnOrFail(cmd) defer ga.WaitOrFail(child) expectedRegex := `HTTP-Get received: (.*)\r` result, out, err := expectRegexWithOutput(child, expectedRegex) if err != nil { ga.Fatalf("Error: %v\nOutput: %v", err, out) } if result[1] != hostname { ga.Fatalf("Hostname received by client `%v` doesn't match `%v`", result[1], hostname) } }() ga.Wait() } f("--net=default") f("") }) }
func NewAPIServiceCgroupTest() testutils.Test { return testutils.TestFunc(func(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() aciFileName := patchTestACI("rkt-inspect-interactive.aci", "--exec=/inspect --read-stdin") defer os.Remove(aciFileName) runCmd := fmt.Sprintf("%s --insecure-options=image run --interactive %s", ctx.Cmd(), aciFileName) child := spawnOrFail(t, runCmd) var resp *v1alpha.ListPodsResponse var err error done := make(chan struct{}) // Wait the pods to be running. go func() { for { // 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 { allRunning := true for _, p := range resp.Pods { if p.State != v1alpha.PodState_POD_STATE_RUNNING || p.Pid == -1 { allRunning = false break } } if allRunning { t.Logf("Pods are running") close(done) return } } t.Logf("Pods are not in RUNNING state") time.Sleep(time.Second) } }() testutils.WaitOrTimeout(t, time.Second*60, done) var cgroups []string var subcgroups []string 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) if p.Cgroup != "" { cgroups = append(cgroups, p.Cgroup) subcgroups = append(subcgroups, filepath.Join(p.Cgroup, "system.slice")) } } // ListPods(detail=true). Filter according to the cgroup. t.Logf("Calling ListPods with cgroup filter %v", cgroups) resp, err = c.ListPods(context.Background(), &v1alpha.ListPodsRequest{ Detail: true, Filters: []*v1alpha.PodFilter{{Cgroups: cgroups}}, }) 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) } t.Logf("Calling ListPods with subcgroup filter %v", subcgroups) resp, err = c.ListPods(context.Background(), &v1alpha.ListPodsRequest{ Detail: true, Filters: []*v1alpha.PodFilter{{PodSubCgroups: subcgroups}}, }) 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) } // Terminate the pod. if err := child.SendLine("Good bye"); err != nil { t.Fatalf("Failed to send message to the pod: %v", err) } waitOrFail(t, child, 0) // Check that there's no cgroups returned for non-running pods. cgroups = []string{} resp, err = c.ListPods(context.Background(), &v1alpha.ListPodsRequest{}) for _, p := range resp.Pods { checkPodBasics(t, ctx, p) if p.Cgroup != "" { cgroups = append(cgroups, p.Cgroup) } } if len(cgroups) != 0 { t.Errorf("Unexpected cgroup returned by pods: %v", cgroups) } }) }
//Test that the CNI execution environment matches the spec func NewNetCNIEnvTest() testutils.Test { return testutils.TestFunc(func(t *testing.T) { ctx := testutils.NewRktRunCtx() defer ctx.Cleanup() iface, _, err := testutils.GetNonLoIfaceWithAddrs(netlink.FAMILY_V4) if err != nil { t.Fatalf("Error while getting non-lo host interface: %v\n", err) } if iface.Name == "" { t.Skipf("Cannot run test without non-lo host interface") } // Declares a network of type cniproxy, which will record state and // proxy through to $X_REAL_PLUGIN nt := networkTemplateT{ Name: "bridge0", Type: "cniproxy", Args: []string{"X_LOG=output.json", "X_REAL_PLUGIN=bridge"}, IpMasq: true, IsGateway: true, Master: iface.Name, Ipam: &ipamTemplateT{ Type: "host-local", Subnet: "11.11.3.0/24", Routes: []map[string]string{ {"dst": "0.0.0.0/0"}, }, }, } // bring the networking up, copy the proxy netdir := prepareTestNet(t, ctx, nt) defer os.RemoveAll(netdir) ga := testutils.NewGoroutineAssistant(t) ga.Add(1) go func() { defer ga.Done() appCmd := "--exec=/inspect -- --print-defaultgwv4 " cmd := fmt.Sprintf("%s --debug --insecure-options=image run --net=%v --mds-register=false %s %s", ctx.Cmd(), nt.NetParameter(), getInspectImagePath(), appCmd) child := ga.SpawnOrFail(cmd) defer ga.WaitOrFail(child) expectedRegex := "DefaultGWv4: 11.11.3.1" _, out, err := expectRegexTimeoutWithOutput(child, expectedRegex, 30*time.Second) if err != nil { ga.Fatalf("Error: %v\nOutput: %v", err, out) } }() ga.Wait() // Parse the log file proxyLog, err := parseCNIProxyLog(filepath.Join(netdir, "output.json")) if err != nil { t.Fatal("Failed to read cniproxy log", err) } // Check that the stdin matches the network config file expectedConfig, err := ioutil.ReadFile(filepath.Join(netdir, nt.Name+".conf")) if err != nil { t.Fatal("Failed to read network configuration", err) } if string(expectedConfig) != proxyLog.Stdin { t.Fatalf("CNI plugin stdin incorrect, expected <<%v>>, actual <<%v>>", expectedConfig, proxyLog.Stdin) } // Check that the environment is sane - these are regexes expectedEnv := map[string]string{ "CNI_VERSION": "0.3.0", "CNI_COMMAND": "ADD", "CNI_IFNAME": `eth\d`, "CNI_PATH": netdir, "CNI_NETNS": `/var/run/netns/cni-`, "CNI_CONTAINERID": `^[a-fA-F0-9-]{36}`, //UUID, close enough } for k, v := range expectedEnv { actual, exists := proxyLog.EnvMap[k] if !exists { t.Fatalf("Expected proxy CNI arg %s but not found", k) } re, err := regexp.Compile(v) if err != nil { t.Fatal("Invalid CNI env regex", v, err) } found := re.FindString(actual) if found == "" { t.Fatalf("CNI_ARG %s was %s but expected pattern %s", k, actual, v) } } }) }