Przeglądaj źródła

Add tests

Signed-off-by: Arnaud Porterie <arnaud.porterie@docker.com>
Arnaud Porterie 10 lat temu
rodzic
commit
38f01266e3

+ 67 - 0
libnetwork/bridge/resolvconf.go

@@ -0,0 +1,67 @@
+package bridge
+
+import (
+	"bytes"
+	"io/ioutil"
+	"regexp"
+)
+
+const (
+	ipv4NumBlock = `(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)`
+	ipv4Address  = `(` + ipv4NumBlock + `\.){3}` + ipv4NumBlock
+
+	// This is not an IPv6 address verifier as it will accept a super-set of IPv6, and also
+	// will *not match* IPv4-Embedded IPv6 Addresses (RFC6052), but that and other variants
+	// -- e.g. other link-local types -- either won't work in containers or are unnecessary.
+	// For readability and sufficiency for Docker purposes this seemed more reasonable than a
+	// 1000+ character regexp with exact and complete IPv6 validation
+	ipv6Address = `([0-9A-Fa-f]{0,4}:){2,7}([0-9A-Fa-f]{0,4})`
+)
+
+var nsRegexp = regexp.MustCompile(`^\s*nameserver\s*((` + ipv4Address + `)|(` + ipv6Address + `))\s*$`)
+
+func readResolvConf() ([]byte, error) {
+	resolv, err := ioutil.ReadFile("/etc/resolv.conf")
+	if err != nil {
+		return nil, err
+	}
+	return resolv, nil
+}
+
+// getLines parses input into lines and strips away comments.
+func getLines(input []byte, commentMarker []byte) [][]byte {
+	lines := bytes.Split(input, []byte("\n"))
+	var output [][]byte
+	for _, currentLine := range lines {
+		var commentIndex = bytes.Index(currentLine, commentMarker)
+		if commentIndex == -1 {
+			output = append(output, currentLine)
+		} else {
+			output = append(output, currentLine[:commentIndex])
+		}
+	}
+	return output
+}
+
+// GetNameserversAsCIDR returns nameservers (if any) listed in
+// /etc/resolv.conf as CIDR blocks (e.g., "1.2.3.4/32")
+// This function's output is intended for net.ParseCIDR
+func getNameserversAsCIDR(resolvConf []byte) []string {
+	nameservers := []string{}
+	for _, nameserver := range getNameservers(resolvConf) {
+		nameservers = append(nameservers, nameserver+"/32")
+	}
+	return nameservers
+}
+
+// GetNameservers returns nameservers (if any) listed in /etc/resolv.conf
+func getNameservers(resolvConf []byte) []string {
+	nameservers := []string{}
+	for _, line := range getLines(resolvConf, []byte("#")) {
+		var ns = nsRegexp.FindSubmatch(line)
+		if len(ns) > 0 {
+			nameservers = append(nameservers, string(ns[1]))
+		}
+	}
+	return nameservers
+}

+ 11 - 1
libnetwork/bridge/setup_device.go

@@ -39,7 +39,17 @@ func SetupDevice(i *Interface) error {
 
 // SetupDeviceUp ups the given bridge interface.
 func SetupDeviceUp(i *Interface) error {
-	return netlink.LinkSetUp(i.Link)
+	err := netlink.LinkSetUp(i.Link)
+	if err != nil {
+		return err
+	}
+
+	// Attempt to update the bridge interface to refresh the flags status,
+	// ignoring any failure to do so.
+	if lnk, err := netlink.LinkByName(i.Config.BridgeName); err == nil {
+		i.Link = lnk
+	}
+	return nil
 }
 
 func generateRandomMAC() net.HardwareAddr {

+ 77 - 0
libnetwork/bridge/setup_device_test.go

@@ -0,0 +1,77 @@
+package bridge
+
+import (
+	"bytes"
+	"net"
+	"strings"
+	"testing"
+
+	"github.com/docker/libnetwork"
+	"github.com/vishvananda/netlink"
+)
+
+func TestSetupNewBridge(t *testing.T) {
+	defer libnetwork.SetupTestNetNS(t)()
+
+	br := &Interface{
+		Config: &Configuration{
+			BridgeName: DefaultBridgeName,
+		},
+	}
+	if err := SetupDevice(br); err != nil {
+		t.Fatalf("Bridge creation failed: %v", err)
+	}
+	if br.Link == nil {
+		t.Fatal("Interface link is nil (expected valid link)")
+	}
+	if _, err := netlink.LinkByName(DefaultBridgeName); err != nil {
+		t.Fatalf("Failed to retrieve bridge device: %v", err)
+	}
+	if br.Link.Attrs().Flags&net.FlagUp == net.FlagUp {
+		t.Fatalf("Interface should be created down")
+	}
+}
+
+func TestSetupNewNonDefaultBridge(t *testing.T) {
+	defer libnetwork.SetupTestNetNS(t)()
+
+	br := &Interface{
+		Config: &Configuration{
+			BridgeName: "test0",
+		},
+	}
+	if err := SetupDevice(br); err == nil || !strings.Contains(err.Error(), "non default name") {
+		t.Fatalf("Expected bridge creation failure with \"non default name\", got: %v", err)
+	}
+}
+
+func TestSetupDeviceUp(t *testing.T) {
+	defer libnetwork.SetupTestNetNS(t)()
+
+	br := &Interface{
+		Config: &Configuration{
+			BridgeName: DefaultBridgeName,
+		},
+	}
+	if err := SetupDevice(br); err != nil {
+		t.Fatalf("Bridge creation failed: %v", err)
+	}
+	if err := SetupDeviceUp(br); err != nil {
+		t.Fatalf("Failed to up bridge device: %v", err)
+	}
+
+	lnk, _ := netlink.LinkByName(DefaultBridgeName)
+	if lnk.Attrs().Flags&net.FlagUp != net.FlagUp {
+		t.Fatalf("Interface should be up")
+	}
+}
+
+func TestGenerateRandomMAC(t *testing.T) {
+	defer libnetwork.SetupTestNetNS(t)()
+
+	mac1 := generateRandomMAC()
+	mac2 := generateRandomMAC()
+	if bytes.Compare(mac1, mac2) == 0 {
+		t.Fatalf("Generated twice the same MAC address %v", mac1)
+	}
+}

+ 2 - 3
libnetwork/bridge/setup_ipv4.go

@@ -5,7 +5,6 @@ import (
 	"net"
 
 	log "github.com/Sirupsen/logrus"
-	"github.com/docker/docker/pkg/networkfs/resolvconf"
 	"github.com/vishvananda/netlink"
 )
 
@@ -61,8 +60,8 @@ func electBridgeIPv4(config *Configuration) (*net.IPNet, error) {
 	// can't read /etc/resolv.conf. So instead we skip the append if resolvConf
 	// is nil. It either doesn't exist, or we can't read it for some reason.
 	nameservers := []string{}
-	if resolvConf, _ := resolvconf.Get(); resolvConf != nil {
-		nameservers = append(nameservers, resolvconf.GetNameserversAsCIDR(resolvConf)...)
+	if resolvConf, _ := readResolvConf(); resolvConf != nil {
+		nameservers = append(nameservers, getNameserversAsCIDR(resolvConf)...)
 	}
 
 	// Try to automatically elect appropriate brige IPv4 settings.

+ 79 - 0
libnetwork/bridge/setup_ipv4_test.go

@@ -0,0 +1,79 @@
+package bridge
+
+import (
+	"net"
+	"testing"
+
+	"github.com/docker/libnetwork"
+	"github.com/vishvananda/netlink"
+)
+
+func setupTestInterface(t *testing.T) *Interface {
+	br := &Interface{
+		Config: &Configuration{
+			BridgeName: DefaultBridgeName,
+		},
+	}
+	if err := SetupDevice(br); err != nil {
+		t.Fatalf("Bridge creation failed: %v", err)
+	}
+	return br
+}
+
+func TestSetupBridgeIPv4Fixed(t *testing.T) {
+	defer libnetwork.SetupTestNetNS(t)()
+
+	ip, netw, err := net.ParseCIDR("192.168.1.1/24")
+	if err != nil {
+		t.Fatalf("Failed to parse bridge IPv4: %v", err)
+	}
+
+	br := setupTestInterface(t)
+	br.Config.AddressIPv4 = &net.IPNet{IP: ip, Mask: netw.Mask}
+	if err := SetupBridgeIPv4(br); err != nil {
+		t.Fatalf("Failed to setup bridge IPv4: %v", err)
+	}
+
+	addrsv4, err := netlink.AddrList(br.Link, netlink.FAMILY_V4)
+	if err != nil {
+		t.Fatalf("Failed to list device IPv4 addresses: %v", err)
+	}
+
+	var found bool
+	for _, addr := range addrsv4 {
+		if br.Config.AddressIPv4.String() == addr.IPNet.String() {
+			found = true
+			break
+		}
+	}
+
+	if !found {
+		t.Fatalf("Bridge device does not have requested IPv4 address %v", br.Config.AddressIPv4)
+	}
+}
+
+func TestSetupBridgeIPv4Auto(t *testing.T) {
+	defer libnetwork.SetupTestNetNS(t)()
+
+	br := setupTestInterface(t)
+	if err := SetupBridgeIPv4(br); err != nil {
+		t.Fatalf("Failed to setup bridge IPv4: %v", err)
+	}
+
+	addrsv4, err := netlink.AddrList(br.Link, netlink.FAMILY_V4)
+	if err != nil {
+		t.Fatalf("Failed to list device IPv4 addresses: %v", err)
+	}
+
+	var found bool
+	for _, addr := range addrsv4 {
+		if bridgeNetworks[0].String() == addr.IPNet.String() {
+			found = true
+			break
+		}
+	}
+
+	if !found {
+		t.Fatalf("Bridge device does not have the automatic IPv4 address %v", bridgeNetworks[0].String())
+	}
+}

+ 8 - 0
libnetwork/namespace.go

@@ -1,5 +1,7 @@
 package libnetwork
 
+import "syscall"
+
 type networkNamespace struct {
 	path       string
 	interfaces []*Interface
@@ -29,3 +31,9 @@ func (n *networkNamespace) Interfaces() []*Interface {
 func (n *networkNamespace) Path() string {
 	return n.path
 }
+
+func (n *networkNamespace) Destroy() error {
+	// Assuming no running process is executing in this network namespace,
+	// unmounting is sufficient to destroy it.
+	return syscall.Unmount(n.path, syscall.MNT_DETACH)
+}

+ 38 - 0
libnetwork/pkg/options/options_test.go

@@ -2,6 +2,7 @@ package options
 
 import (
 	"reflect"
+	"strings"
 	"testing"
 )
 
@@ -23,6 +24,39 @@ func TestGenerate(t *testing.T) {
 		t.Fatal(err)
 	}
 
+	cast, ok := result.(Model)
+	if !ok {
+		t.Fatalf("result has unexpected type %s", reflect.TypeOf(result))
+	}
+	if expected := 1; cast.Int != expected {
+		t.Fatalf("wrong value for field Int: expected %v, got %v", expected, cast.Int)
+	}
+	if expected := 'b'; cast.Rune != expected {
+		t.Fatalf("wrong value for field Rune: expected %v, got %v", expected, cast.Rune)
+	}
+	if expected := 2.0; cast.Float64 != expected {
+		t.Fatalf("wrong value for field Int: expected %v, got %v", expected, cast.Float64)
+	}
+}
+
+func TestGeneratePtr(t *testing.T) {
+	gen := NewGeneric()
+	gen["Int"] = 1
+	gen["Rune"] = 'b'
+	gen["Float64"] = 2.0
+
+	type Model struct {
+		Int     int
+		Rune    rune
+		Float64 float64
+	}
+
+	result, err := GenerateFromModel(gen, &Model{})
+
+	if err != nil {
+		t.Fatal(err)
+	}
+
 	cast, ok := result.(*Model)
 	if !ok {
 		t.Fatalf("result has unexpected type %s", reflect.TypeOf(result))
@@ -44,6 +78,8 @@ func TestGenerateMissingField(t *testing.T) {
 
 	if _, ok := err.(NoSuchFieldError); !ok {
 		t.Fatalf("expected NoSuchFieldError, got %#v", err)
+	} else if expected := "no field"; !strings.Contains(err.Error(), expected) {
+		t.Fatalf("expected %q in error message, got %s", expected, err.Error())
 	}
 }
 
@@ -53,5 +89,7 @@ func TestFieldCannotBeSet(t *testing.T) {
 
 	if _, ok := err.(CannotSetFieldError); !ok {
 		t.Fatalf("expected CannotSetFieldError, got %#v", err)
+	} else if expected := "cannot set field"; !strings.Contains(err.Error(), expected) {
+		t.Fatalf("expected %q in error message, got %s", expected, err.Error())
 	}
 }

+ 1 - 1
libnetwork/reexec_netns_create.go

@@ -1,11 +1,11 @@
 package libnetwork
 
 import (
-	"log"
 	"os"
 	"runtime"
 	"syscall"
 
+	log "github.com/Sirupsen/logrus"
 	"github.com/vishvananda/netlink"
 )
 

+ 33 - 0
libnetwork/testing.go

@@ -0,0 +1,33 @@
+package libnetwork
+
+import (
+	"runtime"
+	"syscall"
+	"testing"
+)
+
+// SetupTestNetNS joins a new network namespace, and returns its associated
+// teardown function.
+//
+// Example usage:
+//
+//     defer SetupTestNetNS(t)()
+//
+func SetupTestNetNS(t *testing.T) func() {
+	runtime.LockOSThread()
+	if err := syscall.Unshare(syscall.CLONE_NEWNET); err != nil {
+		t.Fatalf("Failed to enter netns: %v", err)
+	}
+
+	fd, err := syscall.Open("/proc/self/ns/net", syscall.O_RDONLY, 0)
+	if err != nil {
+		t.Fatal("Failed to open netns file")
+	}
+
+	return func() {
+		if err := syscall.Close(fd); err != nil {
+			t.Logf("Warning: netns closing failed (%v)", err)
+		}
+		runtime.UnlockOSThread()
+	}
+}