Add tests

Signed-off-by: Arnaud Porterie <arnaud.porterie@docker.com>
This commit is contained in:
Arnaud Porterie
2015-02-24 11:19:00 -08:00
parent 7f8fd9f41b
commit 383296d562
9 changed files with 316 additions and 5 deletions
+67
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -1,11 +1,11 @@
package libnetwork
import (
"log"
"os"
"runtime"
"syscall"
log "github.com/Sirupsen/logrus"
"github.com/vishvananda/netlink"
)
+33
View File
@@ -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()
}
}