diff --git a/plugins/meta/portmap/echosvr/echosvr_test.go b/plugins/meta/portmap/echosvr/echosvr_test.go new file mode 100644 index 00000000..7b6d7c8f --- /dev/null +++ b/plugins/meta/portmap/echosvr/echosvr_test.go @@ -0,0 +1,74 @@ +package main_test + +import ( + "fmt" + "io/ioutil" + "net" + "os/exec" + "strings" + + . "github.com/onsi/ginkgo" + . "github.com/onsi/gomega" + "github.com/onsi/gomega/gbytes" + "github.com/onsi/gomega/gexec" +) + +var binaryPath string + +var _ = SynchronizedBeforeSuite(func() []byte { + binaryPath, err := gexec.Build("github.com/containernetworking/plugins/plugins/meta/portmap/echosvr") + Expect(err).NotTo(HaveOccurred()) + return []byte(binaryPath) +}, func(data []byte) { + binaryPath = string(data) +}) + +var _ = SynchronizedAfterSuite(func() {}, func() { + gexec.CleanupBuildArtifacts() +}) + +var _ = Describe("Echosvr", func() { + var session *gexec.Session + BeforeEach(func() { + var err error + cmd := exec.Command(binaryPath) + session, err = gexec.Start(cmd, GinkgoWriter, GinkgoWriter) + Expect(err).NotTo(HaveOccurred()) + }) + + AfterEach(func() { + session.Terminate().Wait() + }) + + It("starts and doesn't terminate immediately", func() { + Consistently(session).ShouldNot(gexec.Exit()) + }) + + tryConnect := func() (net.Conn, error) { + programOutput := session.Out.Contents() + addr := strings.TrimSpace(string(programOutput)) + + conn, err := net.Dial("tcp", addr) + if err != nil { + return nil, err + } + return conn, err + } + + It("prints its listening address to stdout", func() { + Eventually(session.Out).Should(gbytes.Say("\n")) + conn, err := tryConnect() + Expect(err).NotTo(HaveOccurred()) + conn.Close() + }) + + It("will echo data back to us", func() { + Eventually(session.Out).Should(gbytes.Say("\n")) + conn, err := tryConnect() + Expect(err).NotTo(HaveOccurred()) + defer conn.Close() + + fmt.Fprintf(conn, "hello") + Expect(ioutil.ReadAll(conn)).To(Equal([]byte("hello"))) + }) +}) diff --git a/plugins/meta/portmap/echosvr/init_test.go b/plugins/meta/portmap/echosvr/init_test.go new file mode 100644 index 00000000..51cc5403 --- /dev/null +++ b/plugins/meta/portmap/echosvr/init_test.go @@ -0,0 +1,13 @@ +package main_test + +import ( + . "github.com/onsi/ginkgo" + . "github.com/onsi/gomega" + + "testing" +) + +func TestEchosvr(t *testing.T) { + RegisterFailHandler(Fail) + RunSpecs(t, "Echosvr Suite") +} diff --git a/plugins/meta/portmap/echosvr/main.go b/plugins/meta/portmap/echosvr/main.go new file mode 100644 index 00000000..21eca219 --- /dev/null +++ b/plugins/meta/portmap/echosvr/main.go @@ -0,0 +1,32 @@ +package main + +import ( + "fmt" + "net" +) + +func main() { + listener, err := net.Listen("tcp", ":") + if err != nil { + panic(err) + } + _, port, err := net.SplitHostPort(listener.Addr().String()) + if err != nil { + panic(err) + } + fmt.Printf("127.0.0.1:%s\n", port) + for { + conn, err := listener.Accept() + if err != nil { + panic(err) + } + go handleConnection(conn) + } +} + +func handleConnection(conn net.Conn) { + buf := make([]byte, 512) + nBytesRead, _ := conn.Read(buf) + conn.Write(buf[0:nBytesRead]) + conn.Close() +} diff --git a/plugins/meta/portmap/portmap_integ_test.go b/plugins/meta/portmap/portmap_integ_test.go index 58dc3900..91a7518f 100644 --- a/plugins/meta/portmap/portmap_integ_test.go +++ b/plugins/meta/portmap/portmap_integ_test.go @@ -28,19 +28,20 @@ import ( "github.com/coreos/go-iptables/iptables" . "github.com/onsi/ginkgo" . "github.com/onsi/gomega" + "github.com/onsi/gomega/gexec" "github.com/vishvananda/netlink" ) const TIMEOUT = 90 var _ = Describe("portmap integration tests", func() { - rand.Seed(time.Now().UTC().UnixNano()) - - var configList *libcni.NetworkConfigList - var cniConf *libcni.CNIConfig - var targetNS ns.NetNS - var containerPort int - var closeChan chan interface{} + var ( + configList *libcni.NetworkConfigList + cniConf *libcni.CNIConfig + targetNS ns.NetNS + containerPort int + session *gexec.Session + ) BeforeEach(func() { var err error @@ -80,12 +81,12 @@ var _ = Describe("portmap integration tests", func() { fmt.Fprintln(GinkgoWriter, "namespace:", targetNS.Path()) // Start an echo server and get the port - containerPort, closeChan, err = RunEchoServerInNS(targetNS) + containerPort, session, err = StartEchoServerInNamespace(targetNS) Expect(err).NotTo(HaveOccurred()) - }) AfterEach(func() { + session.Terminate().Wait() if targetNS != nil { targetNS.Close() } @@ -163,7 +164,7 @@ var _ = Describe("portmap integration tests", func() { snatOK := testEchoServer(fmt.Sprintf("%s:%d", "127.0.0.1", hostPort)) // Cleanup - close(closeChan) + session.Terminate() err = deleteNetwork() Expect(err).NotTo(HaveOccurred()) diff --git a/plugins/meta/portmap/portmap_suite_test.go b/plugins/meta/portmap/portmap_suite_test.go index 4f615cb8..3ab0a626 100644 --- a/plugins/meta/portmap/portmap_suite_test.go +++ b/plugins/meta/portmap/portmap_suite_test.go @@ -15,89 +15,64 @@ package main import ( - "fmt" + "math/rand" "net" - "time" + "os/exec" + "path/filepath" + "strconv" + "strings" "github.com/containernetworking/plugins/pkg/ns" . "github.com/onsi/ginkgo" + "github.com/onsi/ginkgo/config" . "github.com/onsi/gomega" + "github.com/onsi/gomega/gbytes" + "github.com/onsi/gomega/gexec" "testing" ) func TestPortmap(t *testing.T) { + rand.Seed(config.GinkgoConfig.RandomSeed) + RegisterFailHandler(Fail) RunSpecs(t, "portmap Suite") } -// OpenEchoServer opens a server that listens until closeChan is closed. -// It opens on a random port and sends the port number on portChan when -// the server is up and running. If an error is encountered, closes portChan. -// If closeChan is closed, closes the socket. -func OpenEchoServer(portChan chan<- int, closeChan <-chan interface{}) error { - laddr, err := net.ResolveTCPAddr("tcp", "0.0.0.0:0") - if err != nil { - close(portChan) - return err - } - sock, err := net.ListenTCP("tcp", laddr) - if err != nil { - close(portChan) - return err - } - defer sock.Close() +var echoServerBinaryPath string - switch addr := sock.Addr().(type) { - case *net.TCPAddr: - portChan <- addr.Port - default: - close(portChan) - return fmt.Errorf("addr cast failed!") - } - for { - select { - case <-closeChan: - break - default: - } +var _ = SynchronizedBeforeSuite(func() []byte { + binaryPath, err := gexec.Build("github.com/containernetworking/plugins/plugins/meta/portmap/echosvr") + Expect(err).NotTo(HaveOccurred()) + return []byte(binaryPath) +}, func(data []byte) { + echoServerBinaryPath = string(data) +}) - sock.SetDeadline(time.Now().Add(time.Second)) - con, err := sock.AcceptTCP() - if err != nil { - if opErr, ok := err.(*net.OpError); ok && opErr.Timeout() { - continue - } - continue - } +var _ = SynchronizedAfterSuite(func() {}, func() { + gexec.CleanupBuildArtifacts() +}) - buf := make([]byte, 512) - con.Read(buf) - con.Write(buf) - con.Close() - } +func startInNetNS(binPath string, netNS ns.NetNS) (*gexec.Session, error) { + baseName := filepath.Base(netNS.Path()) + // we are relying on the netNS path living in /var/run/netns + // where `ip netns exec` can find it + cmd := exec.Command("ip", "netns", "exec", baseName, binPath) + session, err := gexec.Start(cmd, GinkgoWriter, GinkgoWriter) + return session, err } -func RunEchoServerInNS(netNS ns.NetNS) (int, chan interface{}, error) { - portChan := make(chan int) - closeChan := make(chan interface{}) +func StartEchoServerInNamespace(netNS ns.NetNS) (int, *gexec.Session, error) { + session, err := startInNetNS(echoServerBinaryPath, netNS) + Expect(err).NotTo(HaveOccurred()) - go func() { - err := netNS.Do(func(ns.NetNS) error { - OpenEchoServer(portChan, closeChan) - return nil - }) - // Somehow the ns.Do failed - if err != nil { - close(portChan) - } - }() + // wait for it to print it's address on stdout + Eventually(session.Out).Should(gbytes.Say("\n")) + _, portString, err := net.SplitHostPort(strings.TrimSpace(string(session.Out.Contents()))) + Expect(err).NotTo(HaveOccurred()) - portNum := <-portChan - if portNum == 0 { - return 0, nil, fmt.Errorf("failed to execute server") - } - - return portNum, closeChan, nil + port, err := strconv.Atoi(portString) + Expect(err).NotTo(HaveOccurred()) + return port, session, nil }