portmap integration test: echo server runs in separate process
authorGabriel Rosenhouse <grosenhouse@pivotal.io>
Wed, 6 Sep 2017 06:36:12 +0000 (23:36 -0700)
committerGabriel Rosenhouse <grosenhouse@pivotal.io>
Wed, 6 Sep 2017 06:36:12 +0000 (23:36 -0700)
this way we're not mixing goroutines and namespaces

plugins/meta/portmap/echosvr/echosvr_test.go [new file with mode: 0644]
plugins/meta/portmap/echosvr/init_test.go [new file with mode: 0644]
plugins/meta/portmap/echosvr/main.go [new file with mode: 0644]
plugins/meta/portmap/portmap_integ_test.go
plugins/meta/portmap/portmap_suite_test.go

diff --git a/plugins/meta/portmap/echosvr/echosvr_test.go b/plugins/meta/portmap/echosvr/echosvr_test.go
new file mode 100644 (file)
index 0000000..7b6d7c8
--- /dev/null
@@ -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 (file)
index 0000000..51cc540
--- /dev/null
@@ -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 (file)
index 0000000..21eca21
--- /dev/null
@@ -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()
+}
index 58dc390..91a7518 100644 (file)
@@ -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())
 
index 4f615cb..3ab0a62 100644 (file)
 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{})
-
-       go func() {
-               err := netNS.Do(func(ns.NetNS) error {
-                       OpenEchoServer(portChan, closeChan)
-                       return nil
-               })
-               // Somehow the ns.Do failed
-               if err != nil {
-                       close(portChan)
-               }
-       }()
+func StartEchoServerInNamespace(netNS ns.NetNS) (int, *gexec.Session, error) {
+       session, err := startInNetNS(echoServerBinaryPath, netNS)
+       Expect(err).NotTo(HaveOccurred())
 
-       portNum := <-portChan
-       if portNum == 0 {
-               return 0, nil, fmt.Errorf("failed to execute server")
-       }
+       // 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())
 
-       return portNum, closeChan, nil
+       port, err := strconv.Atoi(portString)
+       Expect(err).NotTo(HaveOccurred())
+       return port, session, nil
 }