diff --git a/Makefile b/Makefile index a130dc65..f1d25ff3 100644 --- a/Makefile +++ b/Makefile @@ -8,6 +8,14 @@ ciargs = -e CIRCLECI -e "COVERALLS_TOKEN=$$COVERALLS_TOKEN" -e "INSIDECONTAINER= cidocker = docker run ${dockerargs} ${ciargs} $$EXTRA_ARGS ${container_env} ${build_image} CROSS_PLATFORMS = linux/amd64 linux/386 linux/arm windows/amd64 export PATH := $(CURDIR)/bin:$(PATH) +hostOS = ${shell go env GOHOSTOS} +ifeq (${hostOS}, solaris) + gnufind=gfind + gnutail=gtail +else + gnufind=find + gnutail=tail +endif all: ${build_image}.created build check integration-tests clean @@ -62,7 +70,40 @@ check-format: run-tests: @echo "Running tests... " @echo "mode: count" > coverage.coverprofile - @for dir in $$(find . -maxdepth 10 -not -path './.git*' -not -path '*/_*' -type d); do \ + @for dir in $$( ${gnufind} . -maxdepth 10 -not -path './.git*' -not -path '*/_*' -type d); do \ + if [ ${hostOS} == solaris ]; then \ + case "$$dir" in \ + "./cmd/dnet" ) \ + ;& \ + "./cmd/ovrouter" ) \ + ;& \ + "./ns" ) \ + ;& \ + "./iptables" ) \ + ;& \ + "./ipvs" ) \ + ;& \ + "./drivers/bridge" ) \ + ;& \ + "./drivers/host" ) \ + ;& \ + "./drivers/ipvlan" ) \ + ;& \ + "./drivers/macvlan" ) \ + ;& \ + "./drivers/overlay" ) \ + ;& \ + "./drivers/remote" ) \ + ;& \ + "./drivers/windows" ) \ + echo "Skipping $$dir on solaris host... "; \ + continue; \ + ;; \ + * )\ + echo "Entering $$dir ... "; \ + ;; \ + esac; \ + fi; \ if ls $$dir/*.go &> /dev/null; then \ pushd . &> /dev/null ; \ cd $$dir ; \ @@ -71,7 +112,7 @@ run-tests: if [ $$ret -ne 0 ]; then exit $$ret; fi ;\ popd &> /dev/null; \ if [ -f $$dir/profile.tmp ]; then \ - cat $$dir/profile.tmp | tail -n +2 >> coverage.coverprofile ; \ + cat $$dir/profile.tmp | ${gnutail} -n +2 >> coverage.coverprofile ; \ rm $$dir/profile.tmp ; \ fi ; \ fi ; \ diff --git a/api/api_linux_test.go b/api/api_linux_test.go new file mode 100644 index 00000000..2e5ced02 --- /dev/null +++ b/api/api_linux_test.go @@ -0,0 +1,18 @@ +package api + +import ( + "github.com/docker/libnetwork/drivers/bridge" + "github.com/docker/libnetwork/netlabel" +) + +func GetOpsMap(bridgeName, defaultMTU string) map[string]string { + if defaultMTU == "" { + return map[string]string{ + bridge.BridgeName: bridgeName, + } + } + return map[string]string{ + bridge.BridgeName: bridgeName, + netlabel.DriverMTU: defaultMTU, + } +} diff --git a/api/api_solaris_test.go b/api/api_solaris_test.go new file mode 100644 index 00000000..d667443e --- /dev/null +++ b/api/api_solaris_test.go @@ -0,0 +1,18 @@ +package api + +import ( + "github.com/docker/libnetwork/drivers/solaris/bridge" + "github.com/docker/libnetwork/netlabel" +) + +func GetOpsMap(bridgeName, defaultMTU string) map[string]string { + if defaultMTU == "" { + return map[string]string{ + bridge.BridgeName: bridgeName, + } + } + return map[string]string{ + bridge.BridgeName: bridgeName, + netlabel.DriverMTU: defaultMTU, + } +} diff --git a/api/api_test.go b/api/api_test.go index b1241188..ab837612 100644 --- a/api/api_test.go +++ b/api/api_test.go @@ -15,7 +15,6 @@ import ( "github.com/docker/docker/pkg/reexec" "github.com/docker/libnetwork" "github.com/docker/libnetwork/datastore" - "github.com/docker/libnetwork/drivers/bridge" "github.com/docker/libnetwork/netlabel" "github.com/docker/libnetwork/options" "github.com/docker/libnetwork/testutils" @@ -225,9 +224,7 @@ func TestCreateDeleteNetwork(t *testing.T) { t.Fatalf("Expected StatusBadRequest status code, got: %v", errRsp) } - dops := map[string]string{ - bridge.BridgeName: "abc", - } + dops := GetOpsMap("abc", "") nops := map[string]string{ netlabel.EnableIPv6: "true", } @@ -273,9 +270,7 @@ func TestGetNetworksAndEndpoints(t *testing.T) { } defer c.Stop() - ops := map[string]string{ - bridge.BridgeName: "api_test_nw", - } + ops := GetOpsMap("api_test_nw", "") nc := networkCreate{Name: "sh", NetworkType: bridgeNetType, DriverOpts: ops} body, err := json.Marshal(nc) if err != nil { @@ -544,7 +539,7 @@ func TestProcGetServices(t *testing.T) { t.Fatal(err) } - netName2 := "work-dev" + netName2 := "workdev" netOption = options.Generic{ netlabel.GenericData: options.Generic{ "BridgeName": netName2, @@ -1811,10 +1806,7 @@ func TestEndToEnd(t *testing.T) { handleRequest := NewHTTPHandler(c) - dops := map[string]string{ - bridge.BridgeName: "cdef", - netlabel.DriverMTU: "1460", - } + dops := GetOpsMap("cdef", "1460") nops := map[string]string{ netlabel.EnableIPv6: "true", } diff --git a/drivers/solaris/bridge/bridge.go b/drivers/solaris/bridge/bridge.go new file mode 100644 index 00000000..c3a87de0 --- /dev/null +++ b/drivers/solaris/bridge/bridge.go @@ -0,0 +1,1254 @@ +// +build solaris + +package bridge + +import ( + "bufio" + "errors" + "fmt" + "net" + "os" + "os/exec" + "strconv" + "strings" + "sync" + + "github.com/Sirupsen/logrus" + "github.com/docker/libnetwork/datastore" + "github.com/docker/libnetwork/discoverapi" + "github.com/docker/libnetwork/driverapi" + "github.com/docker/libnetwork/iptables" + "github.com/docker/libnetwork/netlabel" + "github.com/docker/libnetwork/netutils" + "github.com/docker/libnetwork/options" + "github.com/docker/libnetwork/portmapper" + "github.com/docker/libnetwork/types" +) + +const ( + networkType = "bridge" + + // DefaultBridgeName is the default name for the bridge interface managed + // by the driver when unspecified by the caller. + DefaultBridgeName = "docker0" + + // BridgeName label for bridge driver + BridgeName = "com.docker.network.bridge.name" + + // EnableIPMasquerade label for bridge driver + EnableIPMasquerade = "com.docker.network.bridge.enable_ip_masquerade" + + // EnableICC label + EnableICC = "com.docker.network.bridge.enable_icc" + + // DefaultBindingIP label + DefaultBindingIP = "com.docker.network.bridge.host_binding_ipv4" + + // DefaultBridge label + DefaultBridge = "com.docker.network.bridge.default_bridge" + + // DefaultGatewayV4AuxKey represents the default-gateway configured by the user + DefaultGatewayV4AuxKey = "DefaultGatewayIPv4" + + // DefaultGatewayV6AuxKey represents the ipv6 default-gateway configured by the user + DefaultGatewayV6AuxKey = "DefaultGatewayIPv6" +) + +// configuration info for the "bridge" driver. +type configuration struct { + EnableIPForwarding bool + EnableIPTables bool + EnableUserlandProxy bool +} + +// networkConfiguration for network specific configuration +type networkConfiguration struct { + ID string + BridgeName string + BridgeNameInternal string + EnableIPv6 bool + EnableIPMasquerade bool + EnableICC bool + Mtu int + DefaultBindingIntf string + DefaultBindingIP net.IP + DefaultBridge bool + // Internal fields set after ipam data parsing + AddressIPv4 *net.IPNet + AddressIPv6 *net.IPNet + DefaultGatewayIPv4 net.IP + DefaultGatewayIPv6 net.IP + dbIndex uint64 + dbExists bool + Internal bool +} + +// endpointConfiguration represents the user specified configuration for the sandbox endpoint +type endpointConfiguration struct { + MacAddress net.HardwareAddr + PortBindings []types.PortBinding + ExposedPorts []types.TransportPort +} + +// containerConfiguration represents the user specified configuration for a container +type containerConfiguration struct { + ParentEndpoints []string + ChildEndpoints []string +} + +// cnnectivityConfiguration represents the user specified configuration regarding the external connectivity +type connectivityConfiguration struct { + PortBindings []types.PortBinding + ExposedPorts []types.TransportPort +} + +type bridgeEndpoint struct { + id string + nid string + srcName string + addr *net.IPNet + addrv6 *net.IPNet + macAddress net.HardwareAddr + config *endpointConfiguration // User specified parameters + containerConfig *containerConfiguration + extConnConfig *connectivityConfiguration + portMapping []types.PortBinding // Operation port bindings + dbIndex uint64 + dbExists bool +} + +type bridgeInterface struct { + bridgeIPv4 *net.IPNet + bridgeIPv6 *net.IPNet + gatewayIPv4 net.IP + gatewayIPv6 net.IP +} + +type bridgeNetwork struct { + id string + bridge *bridgeInterface + config *networkConfiguration + endpoints map[string]*bridgeEndpoint // key: endpoint id + portMapper *portmapper.PortMapper + driver *driver // The network's driver + sync.Mutex +} + +type driver struct { + config *configuration + network *bridgeNetwork + natChain *iptables.ChainInfo + filterChain *iptables.ChainInfo + isolationChain *iptables.ChainInfo + networks map[string]*bridgeNetwork + store datastore.DataStore + sync.Mutex + defrouteIP net.IP +} + +// New constructs a new bridge driver +func newDriver() *driver { + return &driver{networks: map[string]*bridgeNetwork{}} +} + +// Init registers a new instance of bridge driver +func Init(dc driverapi.DriverCallback, config map[string]interface{}) error { + d := newDriver() + if err := d.configure(config); err != nil { + return err + } + + c := driverapi.Capability{ + DataScope: datastore.LocalScope, + } + return dc.RegisterDriver(networkType, d, c) +} + +func (d *driver) NetworkAllocate(id string, option map[string]string, ipV4Data, ipV6Data []driverapi.IPAMData) (map[string]string, error) { + return nil, types.NotImplementedErrorf("not implemented") +} + +func (d *driver) NetworkFree(id string) error { + return types.NotImplementedErrorf("not implemented") +} + +func (d *driver) EventNotify(etype driverapi.EventType, nid, tableName, key string, value []byte) { +} + +func (d *driver) CreateNetwork(id string, option map[string]interface{}, nInfo driverapi.NetworkInfo, ipV4Data, ipV6Data []driverapi.IPAMData) error { + if len(ipV4Data) == 0 || ipV4Data[0].Pool.String() == "0.0.0.0/0" { + return types.BadRequestErrorf("ipv4 pool is empty") + } + // Sanity checks + d.Lock() + if _, ok := d.networks[id]; ok { + d.Unlock() + return types.ForbiddenErrorf("network %s exists", id) + } + d.Unlock() + + // Parse and validate the config. It should not conflict with existing networks' config + config, err := parseNetworkOptions(d, id, option) + if err != nil { + return err + } + + err = config.processIPAM(id, ipV4Data, ipV6Data) + if err != nil { + return err + } + + if err = d.createNetwork(config); err != nil { + return err + } + + return d.storeUpdate(config) +} + +func newInterface(config *networkConfiguration) *bridgeInterface { + i := &bridgeInterface{} + + i.bridgeIPv4 = config.AddressIPv4 + i.gatewayIPv4 = config.AddressIPv4.IP + if config.BridgeName == "" { + config.BridgeName = DefaultBridgeName + } + return i +} + +// This function prunes the pf.conf for the firewall +// that enable the service successfully. +func fixPFConf() error { + conf := "/etc/firewall/pf.conf" + f, err := os.Open("/etc/firewall/pf.conf") + if err != nil { + return fmt.Errorf("cannot open %s: %v", conf, err) + } + defer f.Close() + + // Look for line beginning with "REMOVE THIS LINE" + modify := false + lines := []string{} + scanner := bufio.NewScanner(f) + for scanner.Scan() { + l := scanner.Text() + if strings.Contains(l, "REMOVE THIS LINE") { + modify = true + continue + } + lines = append(lines, fmt.Sprintf("%s\n", l)) + } + if err = scanner.Err(); err != nil { + return fmt.Errorf("cannot open %s: %v", conf, err) + } + + // No changes needed to fix pf.conf + if !modify { + return nil + } + + // Write back the file removing the line found above + tmpname := "/etc/firewall/pf.conf.tmp." + strconv.Itoa(os.Getpid()) + tmp, err := os.OpenFile(tmpname, + os.O_CREATE|os.O_TRUNC|os.O_WRONLY|os.O_APPEND, 0644) + if err != nil { + return fmt.Errorf("cannot open %s: %v", tmpname, err) + } + defer tmp.Close() + for _, l := range lines { + _, err = tmp.WriteString(l) + if err != nil { + return fmt.Errorf("cannot write to %s: %v", + tmpname, err) + } + } + if err = tmp.Sync(); err != nil { + return fmt.Errorf("cannot sync %s: %v", tmpname, err) + } + if err = os.Rename(tmpname, conf); err != nil { + return fmt.Errorf("cannot rename %s to %s: %v", + tmpname, conf, err) + } + return nil +} + +func (d *driver) initFirewall() error { + out, err := exec.Command("/usr/bin/svcs", "-Ho", "state", + "firewall").Output() + if err != nil { + return fmt.Errorf("cannot check firewall state: %v", err) + } + state := strings.TrimSpace(string(out)) + if state != "online" { + if state != "disabled" { + return fmt.Errorf("firewall service is in %s state. "+ + "please enable service manually.", state) + } + if err = fixPFConf(); err != nil { + return fmt.Errorf("cannot verify pf.conf: %v", err) + } + err = exec.Command("/usr/sbin/svcadm", "enable", "-ts", + "firewall").Run() + if err != nil { + return fmt.Errorf("cannot enable firewall service: %v", err) + } + } + out, err = exec.Command("/usr/sbin/pfctl", "-sr").Output() + if err != nil { + return fmt.Errorf("failed to list firewall rules: %v", err) + } + if strings.Contains(string(out), "anchor \"_auto/docker/*\" all") { + return nil + } + pfctlCmd := "(/usr/sbin/pfctl -sr; " + + "/usr/bin/echo \"anchor \\\"_auto/docker/*\\\"\") |" + + "/usr/sbin/pfctl -f -" + err = exec.Command("/usr/bin/bash", "-c", pfctlCmd).Run() + if err != nil { + return fmt.Errorf("failed to add docker firewall rules: %v", err) + } + return nil +} + +func (d *driver) initRouting() error { + err := exec.Command("/usr/sbin/ipadm", "set-prop", "-t", + "-p", "forwarding=on", "ipv4").Run() + if err != nil { + return fmt.Errorf("cannot switch-on IP forwarding: %v", err) + } + routeCmd := "/usr/sbin/ipadm show-addr -p -o addr " + + "`/usr/sbin/route get default | /usr/bin/grep interface | " + + "/usr/bin/awk '{print $2}'`" + out, err := exec.Command("/usr/bin/bash", "-c", routeCmd).Output() + if err != nil { + return fmt.Errorf("cannot get default route: %v", err) + } + defroute := strings.SplitN(string(out), "/", 2) + d.defrouteIP = net.ParseIP(defroute[0]) + if d.defrouteIP == nil { + return &ErrNoIPAddr{} + } + return nil +} + +func (d *driver) configure(option map[string]interface{}) error { + var err error + + if err = d.initFirewall(); err != nil { + return fmt.Errorf("failed to configure firewall: %v", err) + } + if err = d.initRouting(); err != nil { + return fmt.Errorf("failed to configure routing: %v", err) + } + if err = d.initStore(option); err != nil { + return fmt.Errorf("failed to initialize datastore: %v", err) + } + + return nil +} + +func (d *driver) getNetwork(id string) (*bridgeNetwork, error) { + d.Lock() + defer d.Unlock() + + if id == "" { + return nil, types.BadRequestErrorf("invalid network id: %s", id) + } + + if nw, ok := d.networks[id]; ok { + return nw, nil + } + + return nil, types.NotFoundErrorf("network not found: %s", id) +} + +// Return a slice of networks over which caller can iterate safely +func (d *driver) getNetworks() []*bridgeNetwork { + d.Lock() + defer d.Unlock() + + ls := make([]*bridgeNetwork, 0, len(d.networks)) + for _, nw := range d.networks { + ls = append(ls, nw) + } + return ls +} + +func bridgeSetup(config *networkConfiguration) error { + var err error + var bindingIntf string + + bridgeName := config.BridgeName + gwName := fmt.Sprintf("%s_gw0", bridgeName) + gwIP := config.AddressIPv4.String() + + if config.DefaultBindingIP == nil { + // Default to net0 if bindingIP is not provided. + bindingIntf = "net0" + } else { + ipadmCmd := "/usr/sbin/ipadm show-addr -p -o addrobj,addr |" + + "/usr/bin/grep " + config.DefaultBindingIP.String() + out, err := exec.Command("/usr/bin/bash", "-c", ipadmCmd).Output() + if err != nil { + logrus.Warnf("cannot find binding interface") + return err + } + bindingIntf = strings.SplitN(string(out), "/", 2)[0] + if bindingIntf == "" { + logrus.Warnf("cannot parse binding interface %s", string(out)) + return &ErrNoIPAddr{} + } + } + config.DefaultBindingIntf = bindingIntf + + err = exec.Command("/usr/sbin/dladm", "create-etherstub", + "-t", config.BridgeNameInternal).Run() + if err != nil { + logrus.Warnf("cannot create etherstub %s: %+v", config.BridgeNameInternal, err) + return err + } + err = exec.Command("/usr/sbin/dladm", "create-vnic", + "-t", "-l", config.BridgeNameInternal, gwName).Run() + if err != nil { + logrus.Warnf("cannot create vnic %s", gwName) + return err + } + err = exec.Command("/usr/sbin/ifconfig", gwName, + "plumb", gwIP, "up").Run() + if err != nil { + logrus.Warnf("cannot create gateway interface %s on %s", + gwIP, gwName) + return err + } + + tableName := "bridge_nw_subnets" + pfAnchor := fmt.Sprintf("_auto/docker/%s", tableName) + err = exec.Command("/usr/sbin/pfctl", "-a", pfAnchor, "-t", tableName, "-T", "add", gwIP).Run() + if err != nil { + logrus.Warnf("cannot add bridge network '%s' to PF table", bridgeName) + } + + pfCmd := fmt.Sprintf( + "/usr/bin/echo \"pass out on %s from %s:network to any nat-to (%s)\n"+ + "block in quick from { <%s>, ! %s } to %s\" |"+ + "/usr/sbin/pfctl -a _auto/docker/%s -f -", + bindingIntf, gwName, bindingIntf, + tableName, gwIP, gwIP, + bridgeName) + err = exec.Command("/usr/bin/bash", "-c", pfCmd).Run() + if err != nil { + logrus.Warnf("cannot add pf rule using: %s", pfCmd) + return err + } + + return nil +} + +func bridgeCleanup(config *networkConfiguration, logErr bool) { + var err error + + bridgeName := config.BridgeName + tableName := "bridge_nw_subnets" + gwName := fmt.Sprintf("%s_gw0", bridgeName) + gwIP := config.AddressIPv4.String() + pfAnchor := fmt.Sprintf("_auto/docker/%s", bridgeName) + tableAnchor := fmt.Sprintf("_auto/docker/%s", tableName) + + err = exec.Command("/usr/sbin/pfctl", "-a", pfAnchor, "-F", "all").Run() + if err != nil && logErr { + logrus.Warnf("cannot flush firewall rules") + } + err = exec.Command("/usr/sbin/ifconfig", gwName, "unplumb").Run() + if err != nil && logErr { + logrus.Warnf("cannot remove gateway interface") + } + err = exec.Command("/usr/sbin/dladm", "delete-vnic", + "-t", gwName).Run() + if err != nil && logErr { + logrus.Warnf("cannot delete vnic") + } + err = exec.Command("/usr/sbin/dladm", "delete-etherstub", + "-t", config.BridgeNameInternal).Run() + if err != nil && logErr { + logrus.Warnf("cannot delete etherstub") + } + err = exec.Command("/usr/sbin/pfctl", "-a", tableAnchor, "-t", tableName, "-T", "delete", gwIP).Run() + if err != nil && logErr { + logrus.Warnf("cannot remove bridge network '%s' from PF table", bridgeName) + } +} + +func (d *driver) createNetwork(config *networkConfiguration) error { + var err error + + logrus.Infof("Creating bridge network: %s %s %s", config.ID, + config.BridgeName, config.AddressIPv4) + + networkList := d.getNetworks() + for i, nw := range networkList { + nw.Lock() + nwConfig := nw.config + nw.Unlock() + if err := nwConfig.Conflicts(config); err != nil { + if config.DefaultBridge { + // We encountered and identified a stale default network + // We must delete it as libnetwork is the source of thruth + // The default network being created must be the only one + // This can happen only from docker 1.12 on ward + logrus.Infof("Removing stale default bridge network %s (%s)", nwConfig.ID, nwConfig.BridgeName) + if err := d.DeleteNetwork(nwConfig.ID); err != nil { + logrus.Warnf("Failed to remove stale default network: %s (%s): %v. Will remove from store.", nwConfig.ID, nwConfig.BridgeName, err) + d.storeDelete(nwConfig) + } + networkList = append(networkList[:i], networkList[i+1:]...) + } else { + return types.ForbiddenErrorf( + "cannot create network %s (%s): "+ + "conflicts with network %s (%s): %s", + nwConfig.BridgeName, config.ID, nw.id, + nw.config.BridgeName, err.Error()) + } + } + } + if config.DefaultBindingIP == nil || + config.DefaultBindingIP.IsUnspecified() { + config.DefaultBindingIP = d.defrouteIP + } + + // Create and set network handler in driver + network := &bridgeNetwork{ + id: config.ID, + endpoints: make(map[string]*bridgeEndpoint), + config: config, + portMapper: portmapper.New(""), + driver: d, + } + + d.Lock() + d.networks[config.ID] = network + d.Unlock() + + // On failure make sure to reset driver network handler to nil + defer func() { + if err != nil { + d.Lock() + delete(d.networks, config.ID) + d.Unlock() + } + }() + + // Create or retrieve the bridge L3 interface + bridgeIface := newInterface(config) + network.bridge = bridgeIface + + // Verify the network configuration does not conflict with previously installed + // networks. This step is needed now because driver might have now set the bridge + // name on this config struct. And because we need to check for possible address + // conflicts, so we need to check against operational networks. + if err = config.conflictsWithNetworks(config.ID, networkList); err != nil { + return err + } + + // We only attempt to create the bridge when the requested device name is + // the default one. + if config.BridgeName != DefaultBridgeName && config.DefaultBridge { + return NonDefaultBridgeExistError(config.BridgeName) + } + + bridgeCleanup(config, false) + err = bridgeSetup(config) + if err != nil { + return err + } + return nil +} + +func (d *driver) DeleteNetwork(nid string) error { + var err error + // Get network handler and remove it from driver + d.Lock() + n, ok := d.networks[nid] + d.Unlock() + + if !ok { + return types.InternalMaskableErrorf("network %s does not exist", nid) + } + d.Lock() + delete(d.networks, nid) + d.Unlock() + + // On failure set network handler back in driver, but + // only if is not already taken over by some other thread + defer func() { + if err != nil { + d.Lock() + if _, ok := d.networks[nid]; !ok { + d.networks[nid] = n + } + d.Unlock() + } + }() + + // Sanity check + if n == nil { + err = driverapi.ErrNoNetwork(nid) + return err + } + + // Cannot remove network if endpoints are still present + if len(n.endpoints) != 0 { + err = ActiveEndpointsError(n.id) + return err + } + bridgeCleanup(n.config, true) + logrus.Infof("Deleting bridge network: %s", nid[:12]) + return d.storeDelete(n.config) +} + +func (d *driver) CreateEndpoint(nid, eid string, ifInfo driverapi.InterfaceInfo, epOptions map[string]interface{}) error { + if ifInfo == nil { + return errors.New("invalid interface passed") + } + + // Get the network handler and make sure it exists + d.Lock() + n, ok := d.networks[nid] + d.Unlock() + + if !ok { + return types.NotFoundErrorf("network %s does not exist", nid) + } + if n == nil { + return driverapi.ErrNoNetwork(nid) + } + + // Sanity check + n.Lock() + if n.id != nid { + n.Unlock() + return InvalidNetworkIDError(nid) + } + n.Unlock() + + // Check if endpoint id is good and retrieve correspondent endpoint + ep, err := n.getEndpoint(eid) + if err != nil { + return err + } + + // Endpoint with that id exists either on desired or other sandbox + if ep != nil { + return driverapi.ErrEndpointExists(eid) + } + + // Try to convert the options to endpoint configuration + epConfig, err := parseEndpointOptions(epOptions) + if err != nil { + return err + } + + // Create and add the endpoint + n.Lock() + endpoint := &bridgeEndpoint{id: eid, config: epConfig} + n.endpoints[eid] = endpoint + n.Unlock() + + // On failure make sure to remove the endpoint + defer func() { + if err != nil { + n.Lock() + delete(n.endpoints, eid) + n.Unlock() + } + }() + + // Create the sandbox side pipe interface + if ifInfo.MacAddress() == nil { + // No MAC address assigned to interface. Generate a random MAC to assign + endpoint.macAddress = netutils.GenerateRandomMAC() + if err := ifInfo.SetMacAddress(endpoint.macAddress); err != nil { + logrus.Warnf("Unable to set mac address: %s to endpoint: %s", + endpoint.macAddress.String(), endpoint.id) + return err + } + } else { + endpoint.macAddress = ifInfo.MacAddress() + } + endpoint.addr = ifInfo.Address() + endpoint.addrv6 = ifInfo.AddressIPv6() + c := n.config + + // Program any required port mapping and store them in the endpoint + endpoint.portMapping, err = n.allocatePorts(endpoint, c.DefaultBindingIntf, c.DefaultBindingIP, true) + if err != nil { + return err + } + + return nil +} + +func (d *driver) DeleteEndpoint(nid, eid string) error { + var err error + + // Get the network handler and make sure it exists + d.Lock() + n, ok := d.networks[nid] + d.Unlock() + + if !ok { + return types.InternalMaskableErrorf("network %s does not exist", nid) + } + if n == nil { + return driverapi.ErrNoNetwork(nid) + } + + // Sanity Check + n.Lock() + if n.id != nid { + n.Unlock() + return InvalidNetworkIDError(nid) + } + n.Unlock() + + // Check endpoint id and if an endpoint is actually there + ep, err := n.getEndpoint(eid) + if err != nil { + return err + } + if ep == nil { + return EndpointNotFoundError(eid) + } + + // Remove it + n.Lock() + delete(n.endpoints, eid) + n.Unlock() + + // On failure make sure to set back ep in n.endpoints, but only + // if it hasn't been taken over already by some other thread. + defer func() { + if err != nil { + n.Lock() + if _, ok := n.endpoints[eid]; !ok { + n.endpoints[eid] = ep + } + n.Unlock() + } + }() + + err = n.releasePorts(ep) + if err != nil { + logrus.Warn(err) + } + + return nil +} + +func (d *driver) EndpointOperInfo(nid, eid string) (map[string]interface{}, error) { + // Get the network handler and make sure it exists + d.Lock() + n, ok := d.networks[nid] + d.Unlock() + if !ok { + return nil, types.NotFoundErrorf("network %s does not exist", nid) + } + if n == nil { + return nil, driverapi.ErrNoNetwork(nid) + } + + // Sanity check + n.Lock() + if n.id != nid { + n.Unlock() + return nil, InvalidNetworkIDError(nid) + } + n.Unlock() + + // Check if endpoint id is good and retrieve correspondent endpoint + ep, err := n.getEndpoint(eid) + if err != nil { + return nil, err + } + if ep == nil { + return nil, driverapi.ErrNoEndpoint(eid) + } + + m := make(map[string]interface{}) + + if ep.extConnConfig != nil && ep.extConnConfig.ExposedPorts != nil { + // Return a copy of the config data + epc := make([]types.TransportPort, 0, len(ep.extConnConfig.ExposedPorts)) + for _, tp := range ep.extConnConfig.ExposedPorts { + epc = append(epc, tp.GetCopy()) + } + m[netlabel.ExposedPorts] = epc + } + + if ep.portMapping != nil { + // Return a copy of the operational data + pmc := make([]types.PortBinding, 0, len(ep.portMapping)) + for _, pm := range ep.portMapping { + pmc = append(pmc, pm.GetCopy()) + } + m[netlabel.PortMap] = pmc + } + + if len(ep.macAddress) != 0 { + m[netlabel.MacAddress] = ep.macAddress + } + return m, nil +} + +// Join method is invoked when a Sandbox is attached to an endpoint. +func (d *driver) Join(nid, eid string, sboxKey string, jinfo driverapi.JoinInfo, options map[string]interface{}) error { + network, err := d.getNetwork(nid) + if err != nil { + return err + } + + endpoint, err := network.getEndpoint(eid) + if err != nil { + return err + } + + if endpoint == nil { + return EndpointNotFoundError(eid) + } + + endpoint.containerConfig, err = parseContainerOptions(options) + if err != nil { + return err + } + + err = jinfo.SetGateway(network.bridge.gatewayIPv4) + if err != nil { + return err + } + + err = jinfo.SetGatewayIPv6(network.bridge.gatewayIPv6) + if err != nil { + return err + } + + return nil +} + +func (d *driver) link(network *bridgeNetwork, endpoint *bridgeEndpoint, enable bool) error { + return nil +} + +// Leave method is invoked when a Sandbox detaches from an endpoint. +func (d *driver) Leave(nid, eid string) error { + network, err := d.getNetwork(nid) + if err != nil { + return types.InternalMaskableErrorf("%s", err) + } + + endpoint, err := network.getEndpoint(eid) + if err != nil { + return err + } + + if endpoint == nil { + return EndpointNotFoundError(eid) + } + + return nil +} + +func (d *driver) ProgramExternalConnectivity(nid, eid string, options map[string]interface{}) error { + network, err := d.getNetwork(nid) + if err != nil { + return err + } + + endpoint, err := network.getEndpoint(eid) + if err != nil { + return err + } + + if endpoint == nil { + return EndpointNotFoundError(eid) + } + + endpoint.extConnConfig, err = parseConnectivityOptions(options) + if err != nil { + return err + } + + // Program any required port mapping and store them in the endpoint + endpoint.portMapping, err = network.allocatePorts(endpoint, network.config.DefaultBindingIntf, network.config.DefaultBindingIP, true) + if err != nil { + return err + } + + if !network.config.EnableICC { + return d.link(network, endpoint, true) + } + + return nil +} + +func (d *driver) RevokeExternalConnectivity(nid, eid string) error { + network, err := d.getNetwork(nid) + if err != nil { + return err + } + + endpoint, err := network.getEndpoint(eid) + if err != nil { + return err + } + + if endpoint == nil { + return EndpointNotFoundError(eid) + } + + err = network.releasePorts(endpoint) + if err != nil { + logrus.Warn(err) + } + + return nil +} + +func (d *driver) Type() string { + return networkType +} + +// DiscoverNew is a notification for a new discovery event, such as a new node joining a cluster +func (d *driver) DiscoverNew(dType discoverapi.DiscoveryType, data interface{}) error { + return nil +} + +// DiscoverDelete is a notification for a discovery delete event, such as a node leaving a cluster +func (d *driver) DiscoverDelete(dType discoverapi.DiscoveryType, data interface{}) error { + return nil +} + +// Validate performs a static validation on the network configuration parameters. +// Whatever can be assessed a priori before attempting any programming. +func (c *networkConfiguration) Validate() error { + if c.Mtu < 0 { + return ErrInvalidMtu(c.Mtu) + } + + // If bridge v4 subnet is specified + if c.AddressIPv4 != nil { + // If default gw is specified, it must be part of bridge subnet + if c.DefaultGatewayIPv4 != nil { + if !c.AddressIPv4.Contains(c.DefaultGatewayIPv4) { + return &ErrInvalidGateway{} + } + } + } + + // If default v6 gw is specified, AddressIPv6 must be specified and gw must belong to AddressIPv6 subnet + if c.EnableIPv6 && c.DefaultGatewayIPv6 != nil { + if c.AddressIPv6 == nil || !c.AddressIPv6.Contains(c.DefaultGatewayIPv6) { + return &ErrInvalidGateway{} + } + } + return nil +} + +// Checks whether this network's configuration for the network with this id conflicts with any of the passed networks +func (c *networkConfiguration) conflictsWithNetworks(id string, others []*bridgeNetwork) error { + for _, nw := range others { + + nw.Lock() + nwID := nw.id + nwConfig := nw.config + nwBridge := nw.bridge + nw.Unlock() + + if nwID == id { + continue + } + // Verify the name (which may have been set by newInterface()) does not conflict with + // existing bridge interfaces. Ironically the system chosen name gets stored in the config... + // Basically we are checking if the two original configs were both empty. + if nwConfig.BridgeName == c.BridgeName { + return types.ForbiddenErrorf("conflicts with network %s (%s) by bridge name", nwID, nwConfig.BridgeName) + } + // If this network config specifies the AddressIPv4, we need + // to make sure it does not conflict with any previously allocated + // bridges. This could not be completely caught by the config conflict + // check, because networks which config does not specify the AddressIPv4 + // get their address and subnet selected by the driver (see electBridgeIPv4()) + if c.AddressIPv4 != nil { + if nwBridge.bridgeIPv4.Contains(c.AddressIPv4.IP) || + c.AddressIPv4.Contains(nwBridge.bridgeIPv4.IP) { + return types.ForbiddenErrorf("conflicts with network %s (%s) by ip network", nwID, nwConfig.BridgeName) + } + } + } + + return nil +} + +// Conflicts check if two NetworkConfiguration objects overlap +func (c *networkConfiguration) Conflicts(o *networkConfiguration) error { + if o == nil { + return fmt.Errorf("same configuration") + } + + // Also empty, becasue only one network with empty name is allowed + if c.BridgeName == o.BridgeName { + return fmt.Errorf("networks have same bridge name") + } + + // They must be in different subnets + if (c.AddressIPv4 != nil && o.AddressIPv4 != nil) && + (c.AddressIPv4.Contains(o.AddressIPv4.IP) || o.AddressIPv4.Contains(c.AddressIPv4.IP)) { + return fmt.Errorf("networks have overlapping IPv4") + } + + // They must be in different v6 subnets + if (c.AddressIPv6 != nil && o.AddressIPv6 != nil) && + (c.AddressIPv6.Contains(o.AddressIPv6.IP) || o.AddressIPv6.Contains(c.AddressIPv6.IP)) { + return fmt.Errorf("networks have overlapping IPv6") + } + + return nil +} + +func (c *networkConfiguration) fromLabels(labels map[string]string) error { + var err error + for label, value := range labels { + switch label { + case BridgeName: + c.BridgeName = value + case netlabel.DriverMTU: + if c.Mtu, err = strconv.Atoi(value); err != nil { + return parseErr(label, value, err.Error()) + } + case netlabel.EnableIPv6: + if c.EnableIPv6, err = strconv.ParseBool(value); err != nil { + return parseErr(label, value, err.Error()) + } + case EnableIPMasquerade: + if c.EnableIPMasquerade, err = strconv.ParseBool(value); err != nil { + return parseErr(label, value, err.Error()) + } + case EnableICC: + if c.EnableICC, err = strconv.ParseBool(value); err != nil { + return parseErr(label, value, err.Error()) + } + case DefaultBridge: + if c.DefaultBridge, err = strconv.ParseBool(value); err != nil { + return parseErr(label, value, err.Error()) + } + case DefaultBindingIP: + if c.DefaultBindingIP = net.ParseIP(value); c.DefaultBindingIP == nil { + return parseErr(label, value, "nil ip") + } + } + } + + return nil +} + +func parseErr(label, value, errString string) error { + return types.BadRequestErrorf("failed to parse %s value: %v (%s)", label, value, errString) +} + +func parseNetworkGenericOptions(data interface{}) (*networkConfiguration, error) { + var ( + err error + config *networkConfiguration + ) + + switch opt := data.(type) { + case *networkConfiguration: + config = opt + case map[string]string: + config = &networkConfiguration{ + EnableICC: true, + EnableIPMasquerade: true, + } + err = config.fromLabels(opt) + case options.Generic: + var opaqueConfig interface{} + if opaqueConfig, err = options.GenerateFromModel(opt, config); err == nil { + config = opaqueConfig.(*networkConfiguration) + } + default: + err = types.BadRequestErrorf("do not recognize network configuration format: %T", opt) + } + + return config, err +} + +func parseNetworkOptions(d *driver, id string, option options.Generic) (*networkConfiguration, error) { + var ( + err error + config = &networkConfiguration{} + ) + + // Parse generic label first, config will be re-assigned + if genData, ok := option[netlabel.GenericData]; ok && genData != nil { + if config, err = parseNetworkGenericOptions(genData); err != nil { + return nil, err + } + } + + // Process well-known labels next + if val, ok := option[netlabel.EnableIPv6]; ok { + config.EnableIPv6 = val.(bool) + } + + if val, ok := option[netlabel.Internal]; ok { + if internal, ok := val.(bool); ok && internal { + config.Internal = true + } + } + + // Finally validate the configuration + if err = config.Validate(); err != nil { + return nil, err + } + + if config.BridgeName == "" && config.DefaultBridge == false { + config.BridgeName = "br_" + id[:12] + "_0" + } + + lastChar := config.BridgeName[len(config.BridgeName)-1:] + if _, err = strconv.Atoi(lastChar); err != nil { + config.BridgeNameInternal = config.BridgeName + "_0" + } else { + config.BridgeNameInternal = config.BridgeName + } + + config.ID = id + return config, nil +} + +func (c *networkConfiguration) processIPAM(id string, ipamV4Data, ipamV6Data []driverapi.IPAMData) error { + if len(ipamV4Data) > 1 || len(ipamV6Data) > 1 { + return types.ForbiddenErrorf("bridge driver doesnt support multiple subnets") + } + + if len(ipamV4Data) == 0 { + return types.BadRequestErrorf("bridge network %s requires ipv4 configuration", id) + } + + if ipamV4Data[0].Gateway != nil { + c.AddressIPv4 = types.GetIPNetCopy(ipamV4Data[0].Gateway) + } + + if gw, ok := ipamV4Data[0].AuxAddresses[DefaultGatewayV4AuxKey]; ok { + c.DefaultGatewayIPv4 = gw.IP + } + + if len(ipamV6Data) > 0 { + c.AddressIPv6 = ipamV6Data[0].Pool + + if ipamV6Data[0].Gateway != nil { + c.AddressIPv6 = types.GetIPNetCopy(ipamV6Data[0].Gateway) + } + + if gw, ok := ipamV6Data[0].AuxAddresses[DefaultGatewayV6AuxKey]; ok { + c.DefaultGatewayIPv6 = gw.IP + } + } + + return nil +} + +func (n *bridgeNetwork) getEndpoint(eid string) (*bridgeEndpoint, error) { + n.Lock() + defer n.Unlock() + + if eid == "" { + return nil, InvalidEndpointIDError(eid) + } + + if ep, ok := n.endpoints[eid]; ok { + return ep, nil + } + + return nil, nil +} + +func parseEndpointOptions(epOptions map[string]interface{}) (*endpointConfiguration, error) { + if epOptions == nil { + return nil, nil + } + + ec := &endpointConfiguration{} + + if opt, ok := epOptions[netlabel.MacAddress]; ok { + if mac, ok := opt.(net.HardwareAddr); ok { + ec.MacAddress = mac + } else { + return nil, &ErrInvalidEndpointConfig{} + } + } + + if opt, ok := epOptions[netlabel.PortMap]; ok { + if bs, ok := opt.([]types.PortBinding); ok { + ec.PortBindings = bs + } else { + return nil, &ErrInvalidEndpointConfig{} + } + } + + if opt, ok := epOptions[netlabel.ExposedPorts]; ok { + if ports, ok := opt.([]types.TransportPort); ok { + ec.ExposedPorts = ports + } else { + return nil, &ErrInvalidEndpointConfig{} + } + } + + return ec, nil +} + +func parseContainerOptions(cOptions map[string]interface{}) (*containerConfiguration, error) { + if cOptions == nil { + return nil, nil + } + genericData := cOptions[netlabel.GenericData] + if genericData == nil { + return nil, nil + } + switch opt := genericData.(type) { + case options.Generic: + opaqueConfig, err := options.GenerateFromModel(opt, &containerConfiguration{}) + if err != nil { + return nil, err + } + return opaqueConfig.(*containerConfiguration), nil + case *containerConfiguration: + return opt, nil + default: + return nil, nil + } +} + +func parseConnectivityOptions(cOptions map[string]interface{}) (*connectivityConfiguration, error) { + if cOptions == nil { + return nil, nil + } + + cc := &connectivityConfiguration{} + + if opt, ok := cOptions[netlabel.PortMap]; ok { + if pb, ok := opt.([]types.PortBinding); ok { + cc.PortBindings = pb + } else { + return nil, types.BadRequestErrorf("Invalid port mapping data in connectivity configuration: %v", opt) + } + } + + if opt, ok := cOptions[netlabel.ExposedPorts]; ok { + if ports, ok := opt.([]types.TransportPort); ok { + cc.ExposedPorts = ports + } else { + return nil, types.BadRequestErrorf("Invalid exposed ports data in connectivity configuration: %v", opt) + } + } + + return cc, nil +} diff --git a/drivers/solaris/bridge/bridge_store.go b/drivers/solaris/bridge/bridge_store.go new file mode 100644 index 00000000..956e8290 --- /dev/null +++ b/drivers/solaris/bridge/bridge_store.go @@ -0,0 +1,384 @@ +// +build solaris + +package bridge + +import ( + "encoding/json" + "fmt" + "net" + + "github.com/Sirupsen/logrus" + "github.com/docker/libnetwork/datastore" + "github.com/docker/libnetwork/discoverapi" + "github.com/docker/libnetwork/netlabel" + "github.com/docker/libnetwork/types" +) + +const ( + // network config prefix was not specific enough. + // To be backward compatible, need custom endpoint + // prefix with different root + bridgePrefix = "bridge" + bridgeEndpointPrefix = "bridge-endpoint" +) + +func (d *driver) initStore(option map[string]interface{}) error { + if data, ok := option[netlabel.LocalKVClient]; ok { + var err error + dsc, ok := data.(discoverapi.DatastoreConfigData) + if !ok { + return types.InternalErrorf("incorrect data in datastore configuration: %v", data) + } + d.store, err = datastore.NewDataStoreFromConfig(dsc) + if err != nil { + return types.InternalErrorf("bridge driver failed to initialize data store: %v", err) + } + + err = d.populateNetworks() + if err != nil { + return err + } + + err = d.populateEndpoints() + if err != nil { + return err + } + } + + return nil +} + +func (d *driver) populateNetworks() error { + kvol, err := d.store.List(datastore.Key(bridgePrefix), &networkConfiguration{}) + if err != nil && err != datastore.ErrKeyNotFound { + return fmt.Errorf("failed to get bridge network configurations from store: %v", err) + } + + // It's normal for network configuration state to be empty. Just return. + if err == datastore.ErrKeyNotFound { + return nil + } + + for _, kvo := range kvol { + ncfg := kvo.(*networkConfiguration) + if err = d.createNetwork(ncfg); err != nil { + logrus.Warnf("could not create bridge network for id %s bridge name %s while booting up from persistent state: %v", ncfg.ID, ncfg.BridgeName, err) + } + logrus.Debugf("Network (%s) restored", ncfg.ID[0:7]) + } + + return nil +} + +func (d *driver) populateEndpoints() error { + kvol, err := d.store.List(datastore.Key(bridgeEndpointPrefix), &bridgeEndpoint{}) + if err != nil && err != datastore.ErrKeyNotFound { + return fmt.Errorf("failed to get bridge endpoints from store: %v", err) + } + + if err == datastore.ErrKeyNotFound { + return nil + } + + for _, kvo := range kvol { + ep := kvo.(*bridgeEndpoint) + n, ok := d.networks[ep.nid] + if !ok { + logrus.Debugf("Network (%s) not found for restored bridge endpoint (%s)", ep.nid[0:7], ep.id[0:7]) + logrus.Debugf("Deleting stale bridge endpoint (%s) from store", ep.nid[0:7]) + if err := d.storeDelete(ep); err != nil { + logrus.Debugf("Failed to delete stale bridge endpoint (%s) from store", ep.nid[0:7]) + } + continue + } + n.endpoints[ep.id] = ep + n.restorePortAllocations(ep) + logrus.Debugf("Endpoint (%s) restored to network (%s)", ep.id[0:7], ep.nid[0:7]) + } + + return nil +} + +func (d *driver) storeUpdate(kvObject datastore.KVObject) error { + if d.store == nil { + logrus.Warnf("bridge store not initialized. kv object %s is not added to the store", datastore.Key(kvObject.Key()...)) + return nil + } + + if err := d.store.PutObjectAtomic(kvObject); err != nil { + return fmt.Errorf("failed to update bridge store for object type %T: %v", kvObject, err) + } + + return nil +} + +func (d *driver) storeDelete(kvObject datastore.KVObject) error { + if d.store == nil { + logrus.Debugf("bridge store not initialized. kv object %s is not deleted from store", datastore.Key(kvObject.Key()...)) + return nil + } + +retry: + if err := d.store.DeleteObjectAtomic(kvObject); err != nil { + if err == datastore.ErrKeyModified { + if err := d.store.GetObject(datastore.Key(kvObject.Key()...), kvObject); err != nil { + return fmt.Errorf("could not update the kvobject to latest when trying to delete: %v", err) + } + goto retry + } + return err + } + + return nil +} + +func (ncfg *networkConfiguration) MarshalJSON() ([]byte, error) { + nMap := make(map[string]interface{}) + nMap["ID"] = ncfg.ID + nMap["BridgeName"] = ncfg.BridgeName + nMap["BridgeNameInternal"] = ncfg.BridgeNameInternal + nMap["EnableIPv6"] = ncfg.EnableIPv6 + nMap["EnableIPMasquerade"] = ncfg.EnableIPMasquerade + nMap["EnableICC"] = ncfg.EnableICC + nMap["Mtu"] = ncfg.Mtu + nMap["Internal"] = ncfg.Internal + nMap["DefaultBridge"] = ncfg.DefaultBridge + nMap["DefaultBindingIP"] = ncfg.DefaultBindingIP.String() + nMap["DefaultBindingIntf"] = ncfg.DefaultBindingIntf + nMap["DefaultGatewayIPv4"] = ncfg.DefaultGatewayIPv4.String() + nMap["DefaultGatewayIPv6"] = ncfg.DefaultGatewayIPv6.String() + + if ncfg.AddressIPv4 != nil { + nMap["AddressIPv4"] = ncfg.AddressIPv4.String() + } + + if ncfg.AddressIPv6 != nil { + nMap["AddressIPv6"] = ncfg.AddressIPv6.String() + } + + return json.Marshal(nMap) +} + +func (ncfg *networkConfiguration) UnmarshalJSON(b []byte) error { + var ( + err error + nMap map[string]interface{} + ) + + if err = json.Unmarshal(b, &nMap); err != nil { + return err + } + + if v, ok := nMap["AddressIPv4"]; ok { + if ncfg.AddressIPv4, err = types.ParseCIDR(v.(string)); err != nil { + return types.InternalErrorf("failed to decode bridge network address IPv4 after json unmarshal: %s", v.(string)) + } + } + + if v, ok := nMap["AddressIPv6"]; ok { + if ncfg.AddressIPv6, err = types.ParseCIDR(v.(string)); err != nil { + return types.InternalErrorf("failed to decode bridge network address IPv6 after json unmarshal: %s", v.(string)) + } + } + + ncfg.DefaultBridge = nMap["DefaultBridge"].(bool) + ncfg.DefaultBindingIP = net.ParseIP(nMap["DefaultBindingIP"].(string)) + ncfg.DefaultBindingIntf = nMap["DefaultBindingIntf"].(string) + ncfg.DefaultGatewayIPv4 = net.ParseIP(nMap["DefaultGatewayIPv4"].(string)) + ncfg.DefaultGatewayIPv6 = net.ParseIP(nMap["DefaultGatewayIPv6"].(string)) + ncfg.ID = nMap["ID"].(string) + ncfg.BridgeName = nMap["BridgeName"].(string) + ncfg.BridgeNameInternal = nMap["BridgeNameInternal"].(string) + ncfg.EnableIPv6 = nMap["EnableIPv6"].(bool) + ncfg.EnableIPMasquerade = nMap["EnableIPMasquerade"].(bool) + ncfg.EnableICC = nMap["EnableICC"].(bool) + ncfg.Mtu = int(nMap["Mtu"].(float64)) + if v, ok := nMap["Internal"]; ok { + ncfg.Internal = v.(bool) + } + + return nil +} + +func (ncfg *networkConfiguration) Key() []string { + return []string{bridgePrefix, ncfg.ID} +} + +func (ncfg *networkConfiguration) KeyPrefix() []string { + return []string{bridgePrefix} +} + +func (ncfg *networkConfiguration) Value() []byte { + b, err := json.Marshal(ncfg) + if err != nil { + return nil + } + return b +} + +func (ncfg *networkConfiguration) SetValue(value []byte) error { + return json.Unmarshal(value, ncfg) +} + +func (ncfg *networkConfiguration) Index() uint64 { + return ncfg.dbIndex +} + +func (ncfg *networkConfiguration) SetIndex(index uint64) { + ncfg.dbIndex = index + ncfg.dbExists = true +} + +func (ncfg *networkConfiguration) Exists() bool { + return ncfg.dbExists +} + +func (ncfg *networkConfiguration) Skip() bool { + return false +} + +func (ncfg *networkConfiguration) New() datastore.KVObject { + return &networkConfiguration{} +} + +func (ncfg *networkConfiguration) CopyTo(o datastore.KVObject) error { + dstNcfg := o.(*networkConfiguration) + *dstNcfg = *ncfg + return nil +} + +func (ncfg *networkConfiguration) DataScope() string { + return datastore.LocalScope +} + +func (ep *bridgeEndpoint) MarshalJSON() ([]byte, error) { + epMap := make(map[string]interface{}) + epMap["id"] = ep.id + epMap["nid"] = ep.nid + epMap["SrcName"] = ep.srcName + epMap["MacAddress"] = ep.macAddress.String() + epMap["Addr"] = ep.addr.String() + if ep.addrv6 != nil { + epMap["Addrv6"] = ep.addrv6.String() + } + epMap["Config"] = ep.config + epMap["ContainerConfig"] = ep.containerConfig + epMap["ExternalConnConfig"] = ep.extConnConfig + epMap["PortMapping"] = ep.portMapping + + return json.Marshal(epMap) +} + +func (ep *bridgeEndpoint) UnmarshalJSON(b []byte) error { + var ( + err error + epMap map[string]interface{} + ) + + if err = json.Unmarshal(b, &epMap); err != nil { + return fmt.Errorf("Failed to unmarshal to bridge endpoint: %v", err) + } + + if v, ok := epMap["MacAddress"]; ok { + if ep.macAddress, err = net.ParseMAC(v.(string)); err != nil { + return types.InternalErrorf("failed to decode bridge endpoint MAC address (%s) after json unmarshal: %v", v.(string), err) + } + } + if v, ok := epMap["Addr"]; ok { + if ep.addr, err = types.ParseCIDR(v.(string)); err != nil { + return types.InternalErrorf("failed to decode bridge endpoint IPv4 address (%s) after json unmarshal: %v", v.(string), err) + } + } + if v, ok := epMap["Addrv6"]; ok { + if ep.addrv6, err = types.ParseCIDR(v.(string)); err != nil { + return types.InternalErrorf("failed to decode bridge endpoint IPv6 address (%s) after json unmarshal: %v", v.(string), err) + } + } + ep.id = epMap["id"].(string) + ep.nid = epMap["nid"].(string) + ep.srcName = epMap["SrcName"].(string) + d, _ := json.Marshal(epMap["Config"]) + if err := json.Unmarshal(d, &ep.config); err != nil { + logrus.Warnf("Failed to decode endpoint config %v", err) + } + d, _ = json.Marshal(epMap["ContainerConfig"]) + if err := json.Unmarshal(d, &ep.containerConfig); err != nil { + logrus.Warnf("Failed to decode endpoint container config %v", err) + } + d, _ = json.Marshal(epMap["ExternalConnConfig"]) + if err := json.Unmarshal(d, &ep.extConnConfig); err != nil { + logrus.Warnf("Failed to decode endpoint external connectivity configuration %v", err) + } + d, _ = json.Marshal(epMap["PortMapping"]) + if err := json.Unmarshal(d, &ep.portMapping); err != nil { + logrus.Warnf("Failed to decode endpoint port mapping %v", err) + } + + return nil +} + +func (ep *bridgeEndpoint) Key() []string { + return []string{bridgeEndpointPrefix, ep.id} +} + +func (ep *bridgeEndpoint) KeyPrefix() []string { + return []string{bridgeEndpointPrefix} +} + +func (ep *bridgeEndpoint) Value() []byte { + b, err := json.Marshal(ep) + if err != nil { + return nil + } + return b +} + +func (ep *bridgeEndpoint) SetValue(value []byte) error { + return json.Unmarshal(value, ep) +} + +func (ep *bridgeEndpoint) Index() uint64 { + return ep.dbIndex +} + +func (ep *bridgeEndpoint) SetIndex(index uint64) { + ep.dbIndex = index + ep.dbExists = true +} + +func (ep *bridgeEndpoint) Exists() bool { + return ep.dbExists +} + +func (ep *bridgeEndpoint) Skip() bool { + return false +} + +func (ep *bridgeEndpoint) New() datastore.KVObject { + return &bridgeEndpoint{} +} + +func (ep *bridgeEndpoint) CopyTo(o datastore.KVObject) error { + dstEp := o.(*bridgeEndpoint) + *dstEp = *ep + return nil +} + +func (ep *bridgeEndpoint) DataScope() string { + return datastore.LocalScope +} + +func (n *bridgeNetwork) restorePortAllocations(ep *bridgeEndpoint) { + if ep.extConnConfig == nil || + ep.extConnConfig.ExposedPorts == nil || + ep.extConnConfig.PortBindings == nil { + return + } + tmp := ep.extConnConfig.PortBindings + ep.extConnConfig.PortBindings = ep.portMapping + _, err := n.allocatePorts(ep, n.config.DefaultBindingIntf, n.config.DefaultBindingIP, n.driver.config.EnableUserlandProxy) + if err != nil { + logrus.Warnf("Failed to reserve existing port mapping for endpoint %s:%v", ep.id[0:7], err) + } + ep.extConnConfig.PortBindings = tmp +} diff --git a/drivers/solaris/bridge/bridge_test.go b/drivers/solaris/bridge/bridge_test.go new file mode 100644 index 00000000..e27264a6 --- /dev/null +++ b/drivers/solaris/bridge/bridge_test.go @@ -0,0 +1,675 @@ +// +build solaris + +package bridge + +import ( + "bytes" + "encoding/json" + "net" + "testing" + + "github.com/docker/libnetwork/driverapi" + "github.com/docker/libnetwork/ipamutils" + "github.com/docker/libnetwork/netlabel" + "github.com/docker/libnetwork/netutils" + "github.com/docker/libnetwork/testutils" + "github.com/docker/libnetwork/types" +) + +func init() { + ipamutils.InitNetworks() +} + +func TestEndpointMarshalling(t *testing.T) { + ip1, _ := types.ParseCIDR("172.22.0.9/16") + ip2, _ := types.ParseCIDR("2001:db8::9") + mac, _ := net.ParseMAC("ac:bd:24:57:66:77") + e := &bridgeEndpoint{ + id: "d2c015a1fe5930650cbcd50493efba0500bcebd8ee1f4401a16319f8a567de33", + nid: "ee33fbb43c323f1920b6b35a0101552ac22ede960d0e5245e9738bccc68b2415", + addr: ip1, + addrv6: ip2, + macAddress: mac, + srcName: "veth123456", + config: &endpointConfiguration{MacAddress: mac}, + containerConfig: &containerConfiguration{ + ParentEndpoints: []string{"one", "due", "three"}, + ChildEndpoints: []string{"four", "five", "six"}, + }, + extConnConfig: &connectivityConfiguration{ + ExposedPorts: []types.TransportPort{ + { + Proto: 6, + Port: uint16(18), + }, + }, + PortBindings: []types.PortBinding{ + { + Proto: 6, + IP: net.ParseIP("17210.33.9.56"), + Port: uint16(18), + HostPort: uint16(3000), + HostPortEnd: uint16(14000), + }, + }, + }, + portMapping: []types.PortBinding{ + { + Proto: 17, + IP: net.ParseIP("172.33.9.56"), + Port: uint16(99), + HostIP: net.ParseIP("10.10.100.2"), + HostPort: uint16(9900), + HostPortEnd: uint16(10000), + }, + { + Proto: 6, + IP: net.ParseIP("171.33.9.56"), + Port: uint16(55), + HostIP: net.ParseIP("10.11.100.2"), + HostPort: uint16(5500), + HostPortEnd: uint16(55000), + }, + }, + } + + b, err := json.Marshal(e) + if err != nil { + t.Fatal(err) + } + + ee := &bridgeEndpoint{} + err = json.Unmarshal(b, ee) + if err != nil { + t.Fatal(err) + } + + if e.id != ee.id || e.nid != ee.nid || e.srcName != ee.srcName || !bytes.Equal(e.macAddress, ee.macAddress) || + !types.CompareIPNet(e.addr, ee.addr) || !types.CompareIPNet(e.addrv6, ee.addrv6) || + !compareEpConfig(e.config, ee.config) || + !compareContainerConfig(e.containerConfig, ee.containerConfig) || + !compareConnConfig(e.extConnConfig, ee.extConnConfig) || + !compareBindings(e.portMapping, ee.portMapping) { + t.Fatalf("JSON marsh/unmarsh failed.\nOriginal:\n%#v\nDecoded:\n%#v", e, ee) + } +} + +func compareEpConfig(a, b *endpointConfiguration) bool { + if a == b { + return true + } + if a == nil || b == nil { + return false + } + return bytes.Equal(a.MacAddress, b.MacAddress) +} + +func compareContainerConfig(a, b *containerConfiguration) bool { + if a == b { + return true + } + if a == nil || b == nil { + return false + } + if len(a.ParentEndpoints) != len(b.ParentEndpoints) || + len(a.ChildEndpoints) != len(b.ChildEndpoints) { + return false + } + for i := 0; i < len(a.ParentEndpoints); i++ { + if a.ParentEndpoints[i] != b.ParentEndpoints[i] { + return false + } + } + for i := 0; i < len(a.ChildEndpoints); i++ { + if a.ChildEndpoints[i] != b.ChildEndpoints[i] { + return false + } + } + return true +} + +func compareConnConfig(a, b *connectivityConfiguration) bool { + if a == b { + return true + } + if a == nil || b == nil { + return false + } + if len(a.ExposedPorts) != len(b.ExposedPorts) || + len(a.PortBindings) != len(b.PortBindings) { + return false + } + for i := 0; i < len(a.ExposedPorts); i++ { + if !a.ExposedPorts[i].Equal(&b.ExposedPorts[i]) { + return false + } + } + for i := 0; i < len(a.PortBindings); i++ { + if !a.PortBindings[i].Equal(&b.PortBindings[i]) { + return false + } + } + return true +} + +func compareBindings(a, b []types.PortBinding) bool { + if len(a) != len(b) { + return false + } + for i := 0; i < len(a); i++ { + if !a[i].Equal(&b[i]) { + return false + } + } + return true +} + +func getIPv4Data(t *testing.T) []driverapi.IPAMData { + ipd := driverapi.IPAMData{AddressSpace: "full"} + nw, _, err := netutils.ElectInterfaceAddresses("") + if err != nil { + t.Fatal(err) + } + ipd.Pool = nw + // Set network gateway to X.X.X.1 + ipd.Gateway = types.GetIPNetCopy(nw) + ipd.Gateway.IP[len(ipd.Gateway.IP)-1] = 1 + return []driverapi.IPAMData{ipd} +} + +func TestCreateFullOptions(t *testing.T) { + defer testutils.SetupTestOSContext(t)() + d := newDriver() + + config := &configuration{ + EnableIPForwarding: true, + EnableIPTables: true, + } + + // Test this scenario: Default gw address does not belong to + // container network and it's greater than bridge address + cnw, _ := types.ParseCIDR("172.16.122.0/24") + bnw, _ := types.ParseCIDR("172.16.0.0/24") + br, _ := types.ParseCIDR("172.16.0.1/16") + defgw, _ := types.ParseCIDR("172.16.0.100/16") + + genericOption := make(map[string]interface{}) + genericOption[netlabel.GenericData] = config + + if err := d.configure(genericOption); err != nil { + t.Fatalf("Failed to setup driver config: %v", err) + } + + netOption := make(map[string]interface{}) + netOption[netlabel.EnableIPv6] = true + netOption[netlabel.GenericData] = &networkConfiguration{ + BridgeName: DefaultBridgeName, + } + + ipdList := []driverapi.IPAMData{ + { + Pool: bnw, + Gateway: br, + AuxAddresses: map[string]*net.IPNet{DefaultGatewayV4AuxKey: defgw}, + }, + } + err := d.CreateNetwork("dummy", netOption, nil, ipdList, nil) + if err != nil { + t.Fatalf("Failed to create bridge: %v", err) + } + + // Verify the IP address allocated for the endpoint belongs to the container network + epOptions := make(map[string]interface{}) + te := newTestEndpoint(cnw, 10) + err = d.CreateEndpoint("dummy", "ep1", te.Interface(), epOptions) + if err != nil { + t.Fatalf("Failed to create an endpoint : %s", err.Error()) + } + + if !cnw.Contains(te.Interface().Address().IP) { + t.Fatalf("endpoint got assigned address outside of container network(%s): %s", cnw.String(), te.Interface().Address()) + } +} + +func TestCreateNoConfig(t *testing.T) { + if !testutils.IsRunningInContainer() { + defer testutils.SetupTestOSContext(t)() + } + d := newDriver() + + netconfig := &networkConfiguration{BridgeName: DefaultBridgeName} + genericOption := make(map[string]interface{}) + genericOption[netlabel.GenericData] = netconfig + + if err := d.CreateNetwork("dummy", genericOption, nil, getIPv4Data(t), nil); err != nil { + t.Fatalf("Failed to create bridge: %v", err) + } +} + +func TestCreateFullOptionsLabels(t *testing.T) { + if !testutils.IsRunningInContainer() { + defer testutils.SetupTestOSContext(t)() + } + d := newDriver() + + config := &configuration{ + EnableIPForwarding: true, + } + genericOption := make(map[string]interface{}) + genericOption[netlabel.GenericData] = config + + if err := d.configure(genericOption); err != nil { + t.Fatalf("Failed to setup driver config: %v", err) + } + + bndIPs := "127.0.0.1" + nwV6s := "2001:db8:2600:2700:2800::/80" + gwV6s := "2001:db8:2600:2700:2800::25/80" + nwV6, _ := types.ParseCIDR(nwV6s) + gwV6, _ := types.ParseCIDR(gwV6s) + + labels := map[string]string{ + BridgeName: DefaultBridgeName, + DefaultBridge: "true", + EnableICC: "true", + EnableIPMasquerade: "true", + DefaultBindingIP: bndIPs, + } + + netOption := make(map[string]interface{}) + netOption[netlabel.EnableIPv6] = true + netOption[netlabel.GenericData] = labels + + ipdList := getIPv4Data(t) + ipd6List := []driverapi.IPAMData{ + { + Pool: nwV6, + AuxAddresses: map[string]*net.IPNet{ + DefaultGatewayV6AuxKey: gwV6, + }, + }, + } + + err := d.CreateNetwork("dummy", netOption, nil, ipdList, ipd6List) + if err != nil { + t.Fatalf("Failed to create bridge: %v", err) + } + + nw, ok := d.networks["dummy"] + if !ok { + t.Fatalf("Cannot find dummy network in bridge driver") + } + + if nw.config.BridgeName != DefaultBridgeName { + t.Fatalf("incongruent name in bridge network") + } + + if !nw.config.EnableIPv6 { + t.Fatalf("incongruent EnableIPv6 in bridge network") + } + + if !nw.config.EnableICC { + t.Fatalf("incongruent EnableICC in bridge network") + } + + if !nw.config.EnableIPMasquerade { + t.Fatalf("incongruent EnableIPMasquerade in bridge network") + } + + bndIP := net.ParseIP(bndIPs) + if !bndIP.Equal(nw.config.DefaultBindingIP) { + t.Fatalf("Unexpected: %v", nw.config.DefaultBindingIP) + } + + if !types.CompareIPNet(nw.config.AddressIPv6, nwV6) { + t.Fatalf("Unexpected: %v", nw.config.AddressIPv6) + } + + if !gwV6.IP.Equal(nw.config.DefaultGatewayIPv6) { + t.Fatalf("Unexpected: %v", nw.config.DefaultGatewayIPv6) + } + + // In short here we are testing --fixed-cidr-v6 daemon option + // plus --mac-address run option + mac, _ := net.ParseMAC("aa:bb:cc:dd:ee:ff") + epOptions := map[string]interface{}{netlabel.MacAddress: mac} + te := newTestEndpoint(ipdList[0].Pool, 20) + err = d.CreateEndpoint("dummy", "ep1", te.Interface(), epOptions) + if err != nil { + t.Fatal(err) + } +} + +func TestCreate(t *testing.T) { + if !testutils.IsRunningInContainer() { + defer testutils.SetupTestOSContext(t)() + } + + d := newDriver() + + if err := d.configure(nil); err != nil { + t.Fatalf("Failed to setup driver config: %v", err) + } + + netconfig := &networkConfiguration{BridgeName: DefaultBridgeName} + genericOption := make(map[string]interface{}) + genericOption[netlabel.GenericData] = netconfig + + if err := d.CreateNetwork("dummy", genericOption, nil, getIPv4Data(t), nil); err != nil { + t.Fatalf("Failed to create bridge: %v", err) + } + + err := d.CreateNetwork("dummy", genericOption, nil, getIPv4Data(t), nil) + if err == nil { + t.Fatalf("Expected bridge driver to refuse creation of second network with default name") + } + if _, ok := err.(types.ForbiddenError); !ok { + t.Fatalf("Creation of second network with default name failed with unexpected error type") + } +} + +func TestCreateFail(t *testing.T) { + if !testutils.IsRunningInContainer() { + defer testutils.SetupTestOSContext(t)() + } + + d := newDriver() + + if err := d.configure(nil); err != nil { + t.Fatalf("Failed to setup driver config: %v", err) + } + + netconfig := &networkConfiguration{BridgeName: "dummy0", DefaultBridge: true} + genericOption := make(map[string]interface{}) + genericOption[netlabel.GenericData] = netconfig + + if err := d.CreateNetwork("dummy", genericOption, nil, getIPv4Data(t), nil); err == nil { + t.Fatal("Bridge creation was expected to fail") + } +} + +type testInterface struct { + mac net.HardwareAddr + addr *net.IPNet + addrv6 *net.IPNet + srcName string + dstName string +} + +type testEndpoint struct { + iface *testInterface + gw net.IP + gw6 net.IP + hostsPath string + resolvConfPath string + routes []types.StaticRoute +} + +func newTestEndpoint(nw *net.IPNet, ordinal byte) *testEndpoint { + addr := types.GetIPNetCopy(nw) + addr.IP[len(addr.IP)-1] = ordinal + return &testEndpoint{iface: &testInterface{addr: addr}} +} + +func (te *testEndpoint) Interface() driverapi.InterfaceInfo { + if te.iface != nil { + return te.iface + } + + return nil +} + +func (i *testInterface) MacAddress() net.HardwareAddr { + return i.mac +} + +func (i *testInterface) Address() *net.IPNet { + return i.addr +} + +func (i *testInterface) AddressIPv6() *net.IPNet { + return i.addrv6 +} + +func (i *testInterface) SetMacAddress(mac net.HardwareAddr) error { + if i.mac != nil { + return types.ForbiddenErrorf("endpoint interface MAC address present (%s). Cannot be modified with %s.", i.mac, mac) + } + if mac == nil { + return types.BadRequestErrorf("tried to set nil MAC address to endpoint interface") + } + i.mac = types.GetMacCopy(mac) + return nil +} + +func (i *testInterface) SetIPAddress(address *net.IPNet) error { + if address.IP == nil { + return types.BadRequestErrorf("tried to set nil IP address to endpoint interface") + } + if address.IP.To4() == nil { + return setAddress(&i.addrv6, address) + } + return setAddress(&i.addr, address) +} + +func setAddress(ifaceAddr **net.IPNet, address *net.IPNet) error { + if *ifaceAddr != nil { + return types.ForbiddenErrorf("endpoint interface IP present (%s). Cannot be modified with (%s).", *ifaceAddr, address) + } + *ifaceAddr = types.GetIPNetCopy(address) + return nil +} + +func (i *testInterface) SetNames(srcName string, dstName string) error { + i.srcName = srcName + i.dstName = dstName + return nil +} + +func (te *testEndpoint) InterfaceName() driverapi.InterfaceNameInfo { + if te.iface != nil { + return te.iface + } + + return nil +} + +func (te *testEndpoint) SetGateway(gw net.IP) error { + te.gw = gw + return nil +} + +func (te *testEndpoint) SetGatewayIPv6(gw6 net.IP) error { + te.gw6 = gw6 + return nil +} + +func (te *testEndpoint) AddStaticRoute(destination *net.IPNet, routeType int, nextHop net.IP) error { + te.routes = append(te.routes, types.StaticRoute{Destination: destination, RouteType: routeType, NextHop: nextHop}) + return nil +} + +func (te *testEndpoint) AddTableEntry(tableName string, key string, value []byte) error { + return nil +} + +func (te *testEndpoint) DisableGatewayService() {} + +func TestQueryEndpointInfo(t *testing.T) { + testQueryEndpointInfo(t, true) +} + +func testQueryEndpointInfo(t *testing.T, ulPxyEnabled bool) { + defer testutils.SetupTestOSContext(t)() + + d := newDriver() + + config := &configuration{ + EnableIPTables: true, + EnableUserlandProxy: ulPxyEnabled, + } + genericOption := make(map[string]interface{}) + genericOption[netlabel.GenericData] = config + + if err := d.configure(genericOption); err != nil { + t.Fatalf("Failed to setup driver config: %v", err) + } + + netconfig := &networkConfiguration{ + BridgeName: DefaultBridgeName, + EnableICC: false, + } + genericOption = make(map[string]interface{}) + genericOption[netlabel.GenericData] = netconfig + + ipdList := getIPv4Data(t) + err := d.CreateNetwork("net1", genericOption, nil, ipdList, nil) + if err != nil { + t.Fatalf("Failed to create bridge: %v", err) + } + + sbOptions := make(map[string]interface{}) + sbOptions[netlabel.PortMap] = getPortMapping() + + te := newTestEndpoint(ipdList[0].Pool, 11) + err = d.CreateEndpoint("net1", "ep1", te.Interface(), nil) + if err != nil { + t.Fatalf("Failed to create an endpoint : %s", err.Error()) + } + + err = d.Join("net1", "ep1", "sbox", te, sbOptions) + if err != nil { + t.Fatalf("Failed to join the endpoint: %v", err) + } + + err = d.ProgramExternalConnectivity("net1", "ep1", sbOptions) + if err != nil { + t.Fatalf("Failed to program external connectivity: %v", err) + } + + network, ok := d.networks["net1"] + if !ok { + t.Fatalf("Cannot find network %s inside driver", "net1") + } + ep, _ := network.endpoints["ep1"] + data, err := d.EndpointOperInfo(network.id, ep.id) + if err != nil { + t.Fatalf("Failed to ask for endpoint operational data: %v", err) + } + pmd, ok := data[netlabel.PortMap] + if !ok { + t.Fatalf("Endpoint operational data does not contain port mapping data") + } + pm, ok := pmd.([]types.PortBinding) + if !ok { + t.Fatalf("Unexpected format for port mapping in endpoint operational data") + } + if len(ep.portMapping) != len(pm) { + t.Fatalf("Incomplete data for port mapping in endpoint operational data") + } + for i, pb := range ep.portMapping { + if !pb.Equal(&pm[i]) { + t.Fatalf("Unexpected data for port mapping in endpoint operational data") + } + } + + err = d.RevokeExternalConnectivity("net1", "ep1") + if err != nil { + t.Fatal(err) + } + + // release host mapped ports + err = d.Leave("net1", "ep1") + if err != nil { + t.Fatal(err) + } +} + +func getExposedPorts() []types.TransportPort { + return []types.TransportPort{ + {Proto: types.TCP, Port: uint16(5000)}, + {Proto: types.UDP, Port: uint16(400)}, + {Proto: types.TCP, Port: uint16(600)}, + } +} + +func getPortMapping() []types.PortBinding { + return []types.PortBinding{ + {Proto: types.TCP, Port: uint16(230), HostPort: uint16(23000)}, + {Proto: types.UDP, Port: uint16(200), HostPort: uint16(22000)}, + {Proto: types.TCP, Port: uint16(120), HostPort: uint16(12000)}, + } +} + +func TestValidateConfig(t *testing.T) { + if !testutils.IsRunningInContainer() { + defer testutils.SetupTestOSContext(t)() + } + + // Test mtu + c := networkConfiguration{Mtu: -2} + err := c.Validate() + if err == nil { + t.Fatalf("Failed to detect invalid MTU number") + } + + c.Mtu = 9000 + err = c.Validate() + if err != nil { + t.Fatalf("unexpected validation error on MTU number") + } + + // Bridge network + _, network, _ := net.ParseCIDR("172.28.0.0/16") + c = networkConfiguration{ + AddressIPv4: network, + } + + err = c.Validate() + if err != nil { + t.Fatal(err) + } + + // Test v4 gw + c.DefaultGatewayIPv4 = net.ParseIP("172.27.30.234") + err = c.Validate() + if err == nil { + t.Fatalf("Failed to detect invalid default gateway") + } + + c.DefaultGatewayIPv4 = net.ParseIP("172.28.30.234") + err = c.Validate() + if err != nil { + t.Fatalf("Unexpected validation error on default gateway") + } + + // Test v6 gw + _, v6nw, _ := net.ParseCIDR("2001:db8:ae:b004::/64") + c = networkConfiguration{ + EnableIPv6: true, + AddressIPv6: v6nw, + DefaultGatewayIPv6: net.ParseIP("2001:db8:ac:b004::bad:a55"), + } + err = c.Validate() + if err == nil { + t.Fatalf("Failed to detect invalid v6 default gateway") + } + + c.DefaultGatewayIPv6 = net.ParseIP("2001:db8:ae:b004::bad:a55") + err = c.Validate() + if err != nil { + t.Fatalf("Unexpected validation error on v6 default gateway") + } + + c.AddressIPv6 = nil + err = c.Validate() + if err == nil { + t.Fatalf("Failed to detect invalid v6 default gateway") + } + + c.AddressIPv6 = nil + err = c.Validate() + if err == nil { + t.Fatalf("Failed to detect invalid v6 default gateway") + } +} diff --git a/drivers/solaris/bridge/errors.go b/drivers/solaris/bridge/errors.go new file mode 100644 index 00000000..ceb23889 --- /dev/null +++ b/drivers/solaris/bridge/errors.go @@ -0,0 +1,119 @@ +package bridge + +import "fmt" + +// ErrInvalidEndpointConfig error is returned when an endpoint create is attempted with an invalid endpoint configuration. +type ErrInvalidEndpointConfig struct{} + +func (eiec *ErrInvalidEndpointConfig) Error() string { + return "trying to create an endpoint with an invalid endpoint configuration" +} + +// BadRequest denotes the type of this error +func (eiec *ErrInvalidEndpointConfig) BadRequest() {} + +// ErrNoIPAddr error is returned when bridge has no IPv4 address configured. +type ErrNoIPAddr struct{} + +func (enip *ErrNoIPAddr) Error() string { + return "bridge has no IPv4 address configured" +} + +// InternalError denotes the type of this error +func (enip *ErrNoIPAddr) InternalError() {} + +// ErrInvalidGateway is returned when the user provided default gateway (v4/v6) is not not valid. +type ErrInvalidGateway struct{} + +func (eig *ErrInvalidGateway) Error() string { + return "default gateway ip must be part of the network" +} + +// BadRequest denotes the type of this error +func (eig *ErrInvalidGateway) BadRequest() {} + +// ErrInvalidMtu is returned when the user provided MTU is not valid. +type ErrInvalidMtu int + +func (eim ErrInvalidMtu) Error() string { + return fmt.Sprintf("invalid MTU number: %d", int(eim)) +} + +// BadRequest denotes the type of this error +func (eim ErrInvalidMtu) BadRequest() {} + +// ErrUnsupportedAddressType is returned when the specified address type is not supported. +type ErrUnsupportedAddressType string + +func (uat ErrUnsupportedAddressType) Error() string { + return fmt.Sprintf("unsupported address type: %s", string(uat)) +} + +// BadRequest denotes the type of this error +func (uat ErrUnsupportedAddressType) BadRequest() {} + +// ActiveEndpointsError is returned when there are +// still active endpoints in the network being deleted. +type ActiveEndpointsError string + +func (aee ActiveEndpointsError) Error() string { + return fmt.Sprintf("network %s has active endpoint", string(aee)) +} + +// Forbidden denotes the type of this error +func (aee ActiveEndpointsError) Forbidden() {} + +// InvalidNetworkIDError is returned when the passed +// network id for an existing network is not a known id. +type InvalidNetworkIDError string + +func (inie InvalidNetworkIDError) Error() string { + return fmt.Sprintf("invalid network id %s", string(inie)) +} + +// NotFound denotes the type of this error +func (inie InvalidNetworkIDError) NotFound() {} + +// InvalidEndpointIDError is returned when the passed +// endpoint id is not valid. +type InvalidEndpointIDError string + +func (ieie InvalidEndpointIDError) Error() string { + return fmt.Sprintf("invalid endpoint id: %s", string(ieie)) +} + +// BadRequest denotes the type of this error +func (ieie InvalidEndpointIDError) BadRequest() {} + +// EndpointNotFoundError is returned when the no endpoint +// with the passed endpoint id is found. +type EndpointNotFoundError string + +func (enfe EndpointNotFoundError) Error() string { + return fmt.Sprintf("endpoint not found: %s", string(enfe)) +} + +// NotFound denotes the type of this error +func (enfe EndpointNotFoundError) NotFound() {} + +// NonDefaultBridgeExistError is returned when a non-default +// bridge config is passed but it does not already exist. +type NonDefaultBridgeExistError string + +func (ndbee NonDefaultBridgeExistError) Error() string { + return fmt.Sprintf("bridge device with non default name %s must be created manually", string(ndbee)) +} + +// Forbidden denotes the type of this error +func (ndbee NonDefaultBridgeExistError) Forbidden() {} + +// NonDefaultBridgeNeedsIPError is returned when a non-default +// bridge config is passed but it has no ip configured +type NonDefaultBridgeNeedsIPError string + +func (ndbee NonDefaultBridgeNeedsIPError) Error() string { + return fmt.Sprintf("bridge device with non default name %s must have a valid IP address", string(ndbee)) +} + +// Forbidden denotes the type of this error +func (ndbee NonDefaultBridgeNeedsIPError) Forbidden() {} diff --git a/drivers/solaris/bridge/port_mapping.go b/drivers/solaris/bridge/port_mapping.go new file mode 100644 index 00000000..38162c75 --- /dev/null +++ b/drivers/solaris/bridge/port_mapping.go @@ -0,0 +1,225 @@ +// +build solaris + +package bridge + +import ( + "bytes" + "errors" + "fmt" + "net" + "os" + "os/exec" + + "github.com/Sirupsen/logrus" + "github.com/docker/libnetwork/types" +) + +var ( + defaultBindingIP = net.IPv4(0, 0, 0, 0) +) + +const ( + maxAllocatePortAttempts = 10 +) + +func addPFRules(epid, bindIntf string, bs []types.PortBinding) { + var id string + + if len(epid) > 12 { + id = epid[:12] + } else { + id = epid + } + + fname := "/var/lib/docker/network/files/pf." + id + + f, err := os.OpenFile(fname, + os.O_CREATE|os.O_WRONLY|os.O_APPEND, 0600) + if err != nil { + logrus.Warnf("cannot open temp pf file") + return + } + for _, b := range bs { + r := fmt.Sprintf( + "pass in on %s proto %s from any to (%s) "+ + "port %d rdr-to %s port %d\n", bindIntf, + b.Proto.String(), bindIntf, b.HostPort, + b.IP.String(), b.Port) + _, err = f.WriteString(r) + if err != nil { + logrus.Warnf("cannot write firewall rules to %s: %v", fname, err) + } + } + f.Close() + + anchor := fmt.Sprintf("_auto/docker/ep%s", id) + err = exec.Command("/usr/sbin/pfctl", "-a", anchor, "-f", fname).Run() + if err != nil { + logrus.Warnf("failed to add firewall rules: %v", err) + } + os.Remove(fname) +} + +func removePFRules(epid string) { + var id string + + if len(epid) > 12 { + id = epid[:12] + } else { + id = epid + } + + anchor := fmt.Sprintf("_auto/docker/ep%s", id) + err := exec.Command("/usr/sbin/pfctl", "-a", anchor, "-F", "all").Run() + if err != nil { + logrus.Warnf("failed to remove firewall rules: %v", err) + } +} + +func (n *bridgeNetwork) allocatePorts(ep *bridgeEndpoint, bindIntf string, reqDefBindIP net.IP, ulPxyEnabled bool) ([]types.PortBinding, error) { + if ep.extConnConfig == nil || ep.extConnConfig.PortBindings == nil { + return nil, nil + } + + defHostIP := defaultBindingIP + if reqDefBindIP != nil { + defHostIP = reqDefBindIP + } + + bs, err := n.allocatePortsInternal(ep.extConnConfig.PortBindings, bindIntf, ep.addr.IP, defHostIP, ulPxyEnabled) + if err != nil { + return nil, err + } + + // Add PF rules for port bindings, if any + if len(bs) > 0 { + addPFRules(ep.id, bindIntf, bs) + } + + return bs, err +} + +func (n *bridgeNetwork) allocatePortsInternal(bindings []types.PortBinding, bindIntf string, containerIP, defHostIP net.IP, ulPxyEnabled bool) ([]types.PortBinding, error) { + bs := make([]types.PortBinding, 0, len(bindings)) + for _, c := range bindings { + b := c.GetCopy() + if err := n.allocatePort(&b, containerIP, defHostIP); err != nil { + // On allocation failure,release previously + // allocated ports. On cleanup error, just log + // a warning message + if cuErr := n.releasePortsInternal(bs); cuErr != nil { + logrus.Warnf("Upon allocation failure "+ + "for %v, failed to clear previously "+ + "allocated port bindings: %v", b, cuErr) + } + return nil, err + } + bs = append(bs, b) + } + return bs, nil +} + +func (n *bridgeNetwork) allocatePort(bnd *types.PortBinding, containerIP, defHostIP net.IP) error { + var ( + host net.Addr + err error + ) + + // Store the container interface address in the operational binding + bnd.IP = containerIP + + // Adjust the host address in the operational binding + if len(bnd.HostIP) == 0 { + bnd.HostIP = defHostIP + } + + // Adjust HostPortEnd if this is not a range. + if bnd.HostPortEnd == 0 { + bnd.HostPortEnd = bnd.HostPort + } + + // Construct the container side transport address + container, err := bnd.ContainerAddr() + if err != nil { + return err + } + + // Try up to maxAllocatePortAttempts times to get a port that's + // not already allocated. + for i := 0; i < maxAllocatePortAttempts; i++ { + if host, err = n.portMapper.MapRange(container, bnd.HostIP, + int(bnd.HostPort), int(bnd.HostPortEnd), false); err == nil { + break + } + // There is no point in immediately retrying to map an + // explicitly chosen port. + if bnd.HostPort != 0 { + logrus.Warnf( + "Failed to allocate and map port %d-%d: %s", + bnd.HostPort, bnd.HostPortEnd, err) + break + } + logrus.Warnf("Failed to allocate and map port: %s, retry: %d", + err, i+1) + } + if err != nil { + return err + } + + // Save the host port (regardless it was or not specified in the + // binding) + switch netAddr := host.(type) { + case *net.TCPAddr: + bnd.HostPort = uint16(host.(*net.TCPAddr).Port) + return nil + case *net.UDPAddr: + bnd.HostPort = uint16(host.(*net.UDPAddr).Port) + return nil + default: + // For completeness + return ErrUnsupportedAddressType(fmt.Sprintf("%T", netAddr)) + } +} + +func (n *bridgeNetwork) releasePorts(ep *bridgeEndpoint) error { + err := n.releasePortsInternal(ep.portMapping) + if err != nil { + return nil + } + + // remove rules if there are any port mappings + if len(ep.portMapping) > 0 { + removePFRules(ep.id) + } + + return nil + +} + +func (n *bridgeNetwork) releasePortsInternal(bindings []types.PortBinding) error { + var errorBuf bytes.Buffer + + // Attempt to release all port bindings, do not stop on failure + for _, m := range bindings { + if err := n.releasePort(m); err != nil { + errorBuf.WriteString( + fmt.Sprintf( + "\ncould not release %v because of %v", + m, err)) + } + } + + if errorBuf.Len() != 0 { + return errors.New(errorBuf.String()) + } + return nil +} + +func (n *bridgeNetwork) releasePort(bnd types.PortBinding) error { + // Construct the host side transport address + host, err := bnd.HostAddr() + if err != nil { + return err + } + return n.portMapper.Unmap(host) +} diff --git a/drivers_solaris.go b/drivers_solaris.go index 89ae42c5..ba5d6a93 100644 --- a/drivers_solaris.go +++ b/drivers_solaris.go @@ -1,5 +1,13 @@ package libnetwork +import ( + "github.com/docker/libnetwork/drivers/null" + "github.com/docker/libnetwork/drivers/solaris/bridge" +) + func getInitializers() []initializer { - return []initializer{} + return []initializer{ + {bridge.Init, "bridge"}, + {null.Init, "null"}, + } } diff --git a/libnetwork_linux_test.go b/libnetwork_linux_test.go new file mode 100644 index 00000000..37121bd8 --- /dev/null +++ b/libnetwork_linux_test.go @@ -0,0 +1,995 @@ +package libnetwork_test + +import ( + "bytes" + "encoding/json" + "flag" + "fmt" + "io/ioutil" + "net" + "os" + "os/exec" + "runtime" + "strconv" + "strings" + "testing" + + log "github.com/Sirupsen/logrus" + "github.com/docker/docker/pkg/reexec" + "github.com/docker/libnetwork" + "github.com/docker/libnetwork/ipamapi" + "github.com/docker/libnetwork/netlabel" + "github.com/docker/libnetwork/options" + "github.com/docker/libnetwork/osl" + "github.com/docker/libnetwork/testutils" + "github.com/docker/libnetwork/types" + "github.com/opencontainers/runc/libcontainer" + "github.com/opencontainers/runc/libcontainer/configs" + "github.com/vishvananda/netlink" + "github.com/vishvananda/netns" +) + +func TestHost(t *testing.T) { + sbx1, err := controller.NewSandbox("host_c1", + libnetwork.OptionHostname("test1"), + libnetwork.OptionDomainname("docker.io"), + libnetwork.OptionExtraHost("web", "192.168.0.1"), + libnetwork.OptionUseDefaultSandbox()) + if err != nil { + t.Fatal(err) + } + defer func() { + if err := sbx1.Delete(); err != nil { + t.Fatal(err) + } + }() + + sbx2, err := controller.NewSandbox("host_c2", + libnetwork.OptionHostname("test2"), + libnetwork.OptionDomainname("docker.io"), + libnetwork.OptionExtraHost("web", "192.168.0.1"), + libnetwork.OptionUseDefaultSandbox()) + if err != nil { + t.Fatal(err) + } + defer func() { + if err := sbx2.Delete(); err != nil { + t.Fatal(err) + } + }() + + network, err := createTestNetwork("host", "testhost", options.Generic{}, nil, nil) + if err != nil { + t.Fatal(err) + } + + ep1, err := network.CreateEndpoint("testep1") + if err != nil { + t.Fatal(err) + } + + if err := ep1.Join(sbx1); err != nil { + t.Fatal(err) + } + + ep2, err := network.CreateEndpoint("testep2") + if err != nil { + t.Fatal(err) + } + + if err := ep2.Join(sbx2); err != nil { + t.Fatal(err) + } + + if err := ep1.Leave(sbx1); err != nil { + t.Fatal(err) + } + + if err := ep2.Leave(sbx2); err != nil { + t.Fatal(err) + } + + if err := ep1.Delete(false); err != nil { + t.Fatal(err) + } + + if err := ep2.Delete(false); err != nil { + t.Fatal(err) + } + + // Try to create another host endpoint and join/leave that. + cnt3, err := controller.NewSandbox("host_c3", + libnetwork.OptionHostname("test3"), + libnetwork.OptionDomainname("docker.io"), + libnetwork.OptionExtraHost("web", "192.168.0.1"), + libnetwork.OptionUseDefaultSandbox()) + if err != nil { + t.Fatal(err) + } + defer func() { + if err := cnt3.Delete(); err != nil { + t.Fatal(err) + } + }() + + ep3, err := network.CreateEndpoint("testep3") + if err != nil { + t.Fatal(err) + } + + if err := ep3.Join(sbx2); err != nil { + t.Fatal(err) + } + + if err := ep3.Leave(sbx2); err != nil { + t.Fatal(err) + } + + if err := ep3.Delete(false); err != nil { + t.Fatal(err) + } +} + +// Testing IPV6 from MAC address +func TestBridgeIpv6FromMac(t *testing.T) { + if !testutils.IsRunningInContainer() { + defer testutils.SetupTestOSContext(t)() + } + + netOption := options.Generic{ + netlabel.GenericData: options.Generic{ + "BridgeName": "testipv6mac", + "EnableICC": true, + "EnableIPMasquerade": true, + }, + } + ipamV4ConfList := []*libnetwork.IpamConf{{PreferredPool: "192.168.100.0/24", Gateway: "192.168.100.1"}} + ipamV6ConfList := []*libnetwork.IpamConf{{PreferredPool: "fe90::/64", Gateway: "fe90::22"}} + + network, err := controller.NewNetwork(bridgeNetType, "testipv6mac", "", + libnetwork.NetworkOptionGeneric(netOption), + libnetwork.NetworkOptionEnableIPv6(true), + libnetwork.NetworkOptionIpam(ipamapi.DefaultIPAM, "", ipamV4ConfList, ipamV6ConfList, nil), + libnetwork.NetworkOptionDeferIPv6Alloc(true)) + if err != nil { + t.Fatal(err) + } + + mac := net.HardwareAddr{0xaa, 0xbb, 0xcc, 0xdd, 0xee, 0xff} + epOption := options.Generic{netlabel.MacAddress: mac} + + ep, err := network.CreateEndpoint("testep", libnetwork.EndpointOptionGeneric(epOption)) + if err != nil { + t.Fatal(err) + } + + iface := ep.Info().Iface() + if !bytes.Equal(iface.MacAddress(), mac) { + t.Fatalf("Unexpected mac address: %v", iface.MacAddress()) + } + + ip, expIP, _ := net.ParseCIDR("fe90::aabb:ccdd:eeff/64") + expIP.IP = ip + if !types.CompareIPNet(expIP, iface.AddressIPv6()) { + t.Fatalf("Expected %v. Got: %v", expIP, iface.AddressIPv6()) + } + + if err := ep.Delete(false); err != nil { + t.Fatal(err) + } + + if err := network.Delete(); err != nil { + t.Fatal(err) + } +} + +func checkSandbox(t *testing.T, info libnetwork.EndpointInfo) { + key := info.Sandbox().Key() + sbNs, err := netns.GetFromPath(key) + if err != nil { + t.Fatalf("Failed to get network namespace path %q: %v", key, err) + } + defer sbNs.Close() + + nh, err := netlink.NewHandleAt(sbNs) + if err != nil { + t.Fatal(err) + } + + _, err = nh.LinkByName("eth0") + if err != nil { + t.Fatalf("Could not find the interface eth0 inside the sandbox: %v", err) + } + + _, err = nh.LinkByName("eth1") + if err != nil { + t.Fatalf("Could not find the interface eth1 inside the sandbox: %v", err) + } +} + +func TestEndpointJoin(t *testing.T) { + if !testutils.IsRunningInContainer() { + defer testutils.SetupTestOSContext(t)() + } + + // Create network 1 and add 2 endpoint: ep11, ep12 + netOption := options.Generic{ + netlabel.GenericData: options.Generic{ + "BridgeName": "testnetwork1", + "EnableICC": true, + "EnableIPMasquerade": true, + }, + } + ipamV6ConfList := []*libnetwork.IpamConf{{PreferredPool: "fe90::/64", Gateway: "fe90::22"}} + n1, err := controller.NewNetwork(bridgeNetType, "testnetwork1", "", + libnetwork.NetworkOptionGeneric(netOption), + libnetwork.NetworkOptionEnableIPv6(true), + libnetwork.NetworkOptionIpam(ipamapi.DefaultIPAM, "", nil, ipamV6ConfList, nil), + libnetwork.NetworkOptionDeferIPv6Alloc(true)) + if err != nil { + t.Fatal(err) + } + defer func() { + if err := n1.Delete(); err != nil { + t.Fatal(err) + } + }() + + ep1, err := n1.CreateEndpoint("ep1") + if err != nil { + t.Fatal(err) + } + defer func() { + if err := ep1.Delete(false); err != nil { + t.Fatal(err) + } + }() + + // Validate if ep.Info() only gives me IP address info and not names and gateway during CreateEndpoint() + info := ep1.Info() + iface := info.Iface() + if iface.Address() != nil && iface.Address().IP.To4() == nil { + t.Fatalf("Invalid IP address returned: %v", iface.Address()) + } + if iface.AddressIPv6() != nil && iface.AddressIPv6().IP == nil { + t.Fatalf("Invalid IPv6 address returned: %v", iface.Address()) + } + + if len(info.Gateway()) != 0 { + t.Fatalf("Expected empty gateway for an empty endpoint. Instead found a gateway: %v", info.Gateway()) + } + if len(info.GatewayIPv6()) != 0 { + t.Fatalf("Expected empty gateway for an empty ipv6 endpoint. Instead found a gateway: %v", info.GatewayIPv6()) + } + + if info.Sandbox() != nil { + t.Fatalf("Expected an empty sandbox key for an empty endpoint. Instead found a non-empty sandbox key: %s", info.Sandbox().Key()) + } + + // test invalid joins + err = ep1.Join(nil) + if err == nil { + t.Fatalf("Expected to fail join with nil Sandbox") + } + if _, ok := err.(types.BadRequestError); !ok { + t.Fatalf("Unexpected error type returned: %T", err) + } + + fsbx := &fakeSandbox{} + if err = ep1.Join(fsbx); err == nil { + t.Fatalf("Expected to fail join with invalid Sandbox") + } + if _, ok := err.(types.BadRequestError); !ok { + t.Fatalf("Unexpected error type returned: %T", err) + } + + sb, err := controller.NewSandbox(containerID, + libnetwork.OptionHostname("test"), + libnetwork.OptionDomainname("docker.io"), + libnetwork.OptionExtraHost("web", "192.168.0.1")) + if err != nil { + t.Fatal(err) + } + + defer func() { + if err := sb.Delete(); err != nil { + t.Fatal(err) + } + }() + + err = ep1.Join(sb) + if err != nil { + t.Fatal(err) + } + defer func() { + err = ep1.Leave(sb) + if err != nil { + t.Fatal(err) + } + }() + + // Validate if ep.Info() only gives valid gateway and sandbox key after has container has joined. + info = ep1.Info() + if len(info.Gateway()) == 0 { + t.Fatalf("Expected a valid gateway for a joined endpoint. Instead found an invalid gateway: %v", info.Gateway()) + } + if len(info.GatewayIPv6()) == 0 { + t.Fatalf("Expected a valid ipv6 gateway for a joined endpoint. Instead found an invalid gateway: %v", info.GatewayIPv6()) + } + + if info.Sandbox() == nil { + t.Fatalf("Expected an non-empty sandbox key for a joined endpoint. Instead found a empty sandbox key") + } + + // Check endpoint provided container information + if ep1.Info().Sandbox().Key() != sb.Key() { + t.Fatalf("Endpoint Info returned unexpected sandbox key: %s", sb.Key()) + } + + // Attempt retrieval of endpoint interfaces statistics + stats, err := sb.Statistics() + if err != nil { + t.Fatal(err) + } + if _, ok := stats["eth0"]; !ok { + t.Fatalf("Did not find eth0 statistics") + } + + // Now test the container joining another network + n2, err := createTestNetwork(bridgeNetType, "testnetwork2", + options.Generic{ + netlabel.GenericData: options.Generic{ + "BridgeName": "testnetwork2", + }, + }, nil, nil) + if err != nil { + t.Fatal(err) + } + defer func() { + if err := n2.Delete(); err != nil { + t.Fatal(err) + } + }() + + ep2, err := n2.CreateEndpoint("ep2") + if err != nil { + t.Fatal(err) + } + defer func() { + if err := ep2.Delete(false); err != nil { + t.Fatal(err) + } + }() + + err = ep2.Join(sb) + if err != nil { + t.Fatal(err) + } + defer func() { + err = ep2.Leave(sb) + if err != nil { + t.Fatal(err) + } + }() + + if ep1.Info().Sandbox().Key() != ep2.Info().Sandbox().Key() { + t.Fatalf("ep1 and ep2 returned different container sandbox key") + } + + checkSandbox(t, info) +} + +func TestExternalKey(t *testing.T) { + externalKeyTest(t, false) +} + +func externalKeyTest(t *testing.T, reexec bool) { + if !testutils.IsRunningInContainer() { + defer testutils.SetupTestOSContext(t)() + } + + n, err := createTestNetwork(bridgeNetType, "testnetwork", options.Generic{ + netlabel.GenericData: options.Generic{ + "BridgeName": "testnetwork", + }, + }, nil, nil) + if err != nil { + t.Fatal(err) + } + defer func() { + if err := n.Delete(); err != nil { + t.Fatal(err) + } + }() + + ep, err := n.CreateEndpoint("ep1") + if err != nil { + t.Fatal(err) + } + defer func() { + err = ep.Delete(false) + if err != nil { + t.Fatal(err) + } + }() + + ep2, err := n.CreateEndpoint("ep2") + if err != nil { + t.Fatal(err) + } + defer func() { + err = ep2.Delete(false) + if err != nil { + t.Fatal(err) + } + }() + + cnt, err := controller.NewSandbox(containerID, + libnetwork.OptionHostname("test"), + libnetwork.OptionDomainname("docker.io"), + libnetwork.OptionUseExternalKey(), + libnetwork.OptionExtraHost("web", "192.168.0.1")) + defer func() { + if err := cnt.Delete(); err != nil { + t.Fatal(err) + } + osl.GC() + }() + + // Join endpoint to sandbox before SetKey + err = ep.Join(cnt) + if err != nil { + t.Fatal(err) + } + defer func() { + err = ep.Leave(cnt) + if err != nil { + t.Fatal(err) + } + }() + + sbox := ep.Info().Sandbox() + if sbox == nil { + t.Fatalf("Expected to have a valid Sandbox") + } + + if reexec { + err := reexecSetKey("this-must-fail", containerID, controller.ID()) + if err == nil { + t.Fatalf("SetExternalKey must fail if the corresponding namespace is not created") + } + } else { + // Setting an non-existing key (namespace) must fail + if err := sbox.SetKey("this-must-fail"); err == nil { + t.Fatalf("Setkey must fail if the corresponding namespace is not created") + } + } + + // Create a new OS sandbox using the osl API before using it in SetKey + if extOsBox, err := osl.NewSandbox("ValidKey", true, false); err != nil { + t.Fatalf("Failed to create new osl sandbox") + } else { + defer func() { + if err := extOsBox.Destroy(); err != nil { + log.Warnf("Failed to remove os sandbox: %v", err) + } + }() + } + + if reexec { + err := reexecSetKey("ValidKey", containerID, controller.ID()) + if err != nil { + t.Fatalf("SetExternalKey failed with %v", err) + } + } else { + if err := sbox.SetKey("ValidKey"); err != nil { + t.Fatalf("Setkey failed with %v", err) + } + } + + // Join endpoint to sandbox after SetKey + err = ep2.Join(sbox) + if err != nil { + t.Fatal(err) + } + defer func() { + err = ep2.Leave(sbox) + if err != nil { + t.Fatal(err) + } + }() + + if ep.Info().Sandbox().Key() != ep2.Info().Sandbox().Key() { + t.Fatalf("ep1 and ep2 returned different container sandbox key") + } + + checkSandbox(t, ep.Info()) +} + +func reexecSetKey(key string, containerID string, controllerID string) error { + var ( + state libcontainer.State + b []byte + err error + ) + + state.NamespacePaths = make(map[configs.NamespaceType]string) + state.NamespacePaths[configs.NamespaceType("NEWNET")] = key + if b, err = json.Marshal(state); err != nil { + return err + } + cmd := &exec.Cmd{ + Path: reexec.Self(), + Args: append([]string{"libnetwork-setkey"}, containerID, controllerID), + Stdin: strings.NewReader(string(b)), + Stdout: os.Stdout, + Stderr: os.Stderr, + } + return cmd.Run() +} + +func TestEnableIPv6(t *testing.T) { + if !testutils.IsRunningInContainer() { + defer testutils.SetupTestOSContext(t)() + } + + tmpResolvConf := []byte("search pommesfrites.fr\nnameserver 12.34.56.78\nnameserver 2001:4860:4860::8888\n") + expectedResolvConf := []byte("search pommesfrites.fr\nnameserver 127.0.0.11\nnameserver 2001:4860:4860::8888\noptions ndots:0\n") + //take a copy of resolv.conf for restoring after test completes + resolvConfSystem, err := ioutil.ReadFile("/etc/resolv.conf") + if err != nil { + t.Fatal(err) + } + //cleanup + defer func() { + if err := ioutil.WriteFile("/etc/resolv.conf", resolvConfSystem, 0644); err != nil { + t.Fatal(err) + } + }() + + netOption := options.Generic{ + netlabel.EnableIPv6: true, + netlabel.GenericData: options.Generic{ + "BridgeName": "testnetwork", + }, + } + ipamV6ConfList := []*libnetwork.IpamConf{{PreferredPool: "fe99::/64", Gateway: "fe99::9"}} + + n, err := createTestNetwork("bridge", "testnetwork", netOption, nil, ipamV6ConfList) + if err != nil { + t.Fatal(err) + } + defer func() { + if err := n.Delete(); err != nil { + t.Fatal(err) + } + }() + + ep1, err := n.CreateEndpoint("ep1") + if err != nil { + t.Fatal(err) + } + + if err := ioutil.WriteFile("/etc/resolv.conf", tmpResolvConf, 0644); err != nil { + t.Fatal(err) + } + + resolvConfPath := "/tmp/libnetwork_test/resolv.conf" + defer os.Remove(resolvConfPath) + + sb, err := controller.NewSandbox(containerID, libnetwork.OptionResolvConfPath(resolvConfPath)) + if err != nil { + t.Fatal(err) + } + defer func() { + if err := sb.Delete(); err != nil { + t.Fatal(err) + } + }() + + err = ep1.Join(sb) + if err != nil { + t.Fatal(err) + } + + content, err := ioutil.ReadFile(resolvConfPath) + if err != nil { + t.Fatal(err) + } + + if !bytes.Equal(content, expectedResolvConf) { + t.Fatalf("Expected:\n%s\nGot:\n%s", string(expectedResolvConf), string(content)) + } + + if err != nil { + t.Fatal(err) + } +} + +func TestResolvConfHost(t *testing.T) { + if !testutils.IsRunningInContainer() { + defer testutils.SetupTestOSContext(t)() + } + + tmpResolvConf := []byte("search localhost.net\nnameserver 127.0.0.1\nnameserver 2001:4860:4860::8888\n") + + //take a copy of resolv.conf for restoring after test completes + resolvConfSystem, err := ioutil.ReadFile("/etc/resolv.conf") + if err != nil { + t.Fatal(err) + } + //cleanup + defer func() { + if err := ioutil.WriteFile("/etc/resolv.conf", resolvConfSystem, 0644); err != nil { + t.Fatal(err) + } + }() + + n, err := controller.NetworkByName("testhost") + if err != nil { + t.Fatal(err) + } + + ep1, err := n.CreateEndpoint("ep1", libnetwork.CreateOptionDisableResolution()) + if err != nil { + t.Fatal(err) + } + + if err := ioutil.WriteFile("/etc/resolv.conf", tmpResolvConf, 0644); err != nil { + t.Fatal(err) + } + + resolvConfPath := "/tmp/libnetwork_test/resolv.conf" + defer os.Remove(resolvConfPath) + + sb, err := controller.NewSandbox(containerID, + libnetwork.OptionResolvConfPath(resolvConfPath), + libnetwork.OptionOriginResolvConfPath("/etc/resolv.conf")) + if err != nil { + t.Fatal(err) + } + defer func() { + if err := sb.Delete(); err != nil { + t.Fatal(err) + } + }() + + err = ep1.Join(sb) + if err != nil { + t.Fatal(err) + } + defer func() { + err = ep1.Leave(sb) + if err != nil { + t.Fatal(err) + } + }() + + finfo, err := os.Stat(resolvConfPath) + if err != nil { + t.Fatal(err) + } + + fmode := (os.FileMode)(0644) + if finfo.Mode() != fmode { + t.Fatalf("Expected file mode %s, got %s", fmode.String(), finfo.Mode().String()) + } + + content, err := ioutil.ReadFile(resolvConfPath) + if err != nil { + t.Fatal(err) + } + + if !bytes.Equal(content, tmpResolvConf) { + t.Fatalf("Expected:\n%s\nGot:\n%s", string(tmpResolvConf), string(content)) + } +} + +func TestResolvConf(t *testing.T) { + if !testutils.IsRunningInContainer() { + defer testutils.SetupTestOSContext(t)() + } + + tmpResolvConf1 := []byte("search pommesfrites.fr\nnameserver 12.34.56.78\nnameserver 2001:4860:4860::8888\n") + tmpResolvConf2 := []byte("search pommesfrites.fr\nnameserver 112.34.56.78\nnameserver 2001:4860:4860::8888\n") + expectedResolvConf1 := []byte("search pommesfrites.fr\nnameserver 127.0.0.11\noptions ndots:0\n") + tmpResolvConf3 := []byte("search pommesfrites.fr\nnameserver 113.34.56.78\n") + + //take a copy of resolv.conf for restoring after test completes + resolvConfSystem, err := ioutil.ReadFile("/etc/resolv.conf") + if err != nil { + t.Fatal(err) + } + //cleanup + defer func() { + if err := ioutil.WriteFile("/etc/resolv.conf", resolvConfSystem, 0644); err != nil { + t.Fatal(err) + } + }() + + netOption := options.Generic{ + netlabel.GenericData: options.Generic{ + "BridgeName": "testnetwork", + }, + } + n, err := createTestNetwork("bridge", "testnetwork", netOption, nil, nil) + if err != nil { + t.Fatal(err) + } + defer func() { + if err := n.Delete(); err != nil { + t.Fatal(err) + } + }() + + ep, err := n.CreateEndpoint("ep") + if err != nil { + t.Fatal(err) + } + + if err := ioutil.WriteFile("/etc/resolv.conf", tmpResolvConf1, 0644); err != nil { + t.Fatal(err) + } + + resolvConfPath := "/tmp/libnetwork_test/resolv.conf" + defer os.Remove(resolvConfPath) + + sb1, err := controller.NewSandbox(containerID, libnetwork.OptionResolvConfPath(resolvConfPath)) + if err != nil { + t.Fatal(err) + } + defer func() { + if err := sb1.Delete(); err != nil { + t.Fatal(err) + } + }() + + err = ep.Join(sb1) + if err != nil { + t.Fatal(err) + } + + finfo, err := os.Stat(resolvConfPath) + if err != nil { + t.Fatal(err) + } + + fmode := (os.FileMode)(0644) + if finfo.Mode() != fmode { + t.Fatalf("Expected file mode %s, got %s", fmode.String(), finfo.Mode().String()) + } + + content, err := ioutil.ReadFile(resolvConfPath) + if err != nil { + t.Fatal(err) + } + + if !bytes.Equal(content, expectedResolvConf1) { + fmt.Printf("\n%v\n%v\n", expectedResolvConf1, content) + t.Fatalf("Expected:\n%s\nGot:\n%s", string(expectedResolvConf1), string(content)) + } + + err = ep.Leave(sb1) + if err != nil { + t.Fatal(err) + } + + if err := ioutil.WriteFile("/etc/resolv.conf", tmpResolvConf2, 0644); err != nil { + t.Fatal(err) + } + + sb2, err := controller.NewSandbox(containerID+"_2", libnetwork.OptionResolvConfPath(resolvConfPath)) + if err != nil { + t.Fatal(err) + } + defer func() { + if err := sb2.Delete(); err != nil { + t.Fatal(err) + } + }() + + err = ep.Join(sb2) + if err != nil { + t.Fatal(err) + } + + content, err = ioutil.ReadFile(resolvConfPath) + if err != nil { + t.Fatal(err) + } + + if !bytes.Equal(content, expectedResolvConf1) { + t.Fatalf("Expected:\n%s\nGot:\n%s", string(expectedResolvConf1), string(content)) + } + + if err := ioutil.WriteFile(resolvConfPath, tmpResolvConf3, 0644); err != nil { + t.Fatal(err) + } + + err = ep.Leave(sb2) + if err != nil { + t.Fatal(err) + } + + err = ep.Join(sb2) + if err != nil { + t.Fatal(err) + } + + content, err = ioutil.ReadFile(resolvConfPath) + if err != nil { + t.Fatal(err) + } + + if !bytes.Equal(content, tmpResolvConf3) { + t.Fatalf("Expected:\n%s\nGot:\n%s", string(tmpResolvConf3), string(content)) + } +} + +func parallelJoin(t *testing.T, rc libnetwork.Sandbox, ep libnetwork.Endpoint, thrNumber int) { + debugf("J%d.", thrNumber) + var err error + + sb := sboxes[thrNumber-1] + err = ep.Join(sb) + + runtime.LockOSThread() + if err != nil { + if _, ok := err.(types.ForbiddenError); !ok { + t.Fatalf("thread %d: %v", thrNumber, err) + } + debugf("JE%d(%v).", thrNumber, err) + } + debugf("JD%d.", thrNumber) +} + +func parallelLeave(t *testing.T, rc libnetwork.Sandbox, ep libnetwork.Endpoint, thrNumber int) { + debugf("L%d.", thrNumber) + var err error + + sb := sboxes[thrNumber-1] + + err = ep.Leave(sb) + runtime.LockOSThread() + if err != nil { + if _, ok := err.(types.ForbiddenError); !ok { + t.Fatalf("thread %d: %v", thrNumber, err) + } + debugf("LE%d(%v).", thrNumber, err) + } + debugf("LD%d.", thrNumber) +} + +func runParallelTests(t *testing.T, thrNumber int) { + var ( + ep libnetwork.Endpoint + sb libnetwork.Sandbox + err error + ) + + t.Parallel() + + pTest := flag.Lookup("test.parallel") + if pTest == nil { + t.Skip("Skipped because test.parallel flag not set;") + } + numParallel, err := strconv.Atoi(pTest.Value.String()) + if err != nil { + t.Fatal(err) + } + if numParallel < numThreads { + t.Skip("Skipped because t.parallel was less than ", numThreads) + } + + runtime.LockOSThread() + defer runtime.UnlockOSThread() + + if thrNumber == first { + createGlobalInstance(t) + } + + if thrNumber != first { + select { + case <-start: + } + + thrdone := make(chan struct{}) + done <- thrdone + defer close(thrdone) + + if thrNumber == last { + defer close(done) + } + + err = netns.Set(testns) + if err != nil { + t.Fatal(err) + } + } + defer netns.Set(origns) + + net1, err := controller.NetworkByName("testhost") + if err != nil { + t.Fatal(err) + } + if net1 == nil { + t.Fatal("Could not find testhost") + } + + net2, err := controller.NetworkByName("network2") + if err != nil { + t.Fatal(err) + } + if net2 == nil { + t.Fatal("Could not find network2") + } + + epName := fmt.Sprintf("pep%d", thrNumber) + + if thrNumber == first { + ep, err = net1.EndpointByName(epName) + } else { + ep, err = net2.EndpointByName(epName) + } + + if err != nil { + t.Fatal(err) + } + if ep == nil { + t.Fatal("Got nil ep with no error") + } + + cid := fmt.Sprintf("%drace", thrNumber) + controller.WalkSandboxes(libnetwork.SandboxContainerWalker(&sb, cid)) + if sb == nil { + t.Fatalf("Got nil sandbox for container: %s", cid) + } + + for i := 0; i < iterCnt; i++ { + parallelJoin(t, sb, ep, thrNumber) + parallelLeave(t, sb, ep, thrNumber) + } + + debugf("\n") + + err = sb.Delete() + if err != nil { + t.Fatal(err) + } + if thrNumber == first { + for thrdone := range done { + select { + case <-thrdone: + } + } + + testns.Close() + if err := net2.Delete(); err != nil { + t.Fatal(err) + } + } else { + err = ep.Delete(false) + if err != nil { + t.Fatal(err) + } + } +} + +func TestParallel1(t *testing.T) { + runParallelTests(t, 1) +} + +func TestParallel2(t *testing.T) { + runParallelTests(t, 2) +} + +func TestParallel3(t *testing.T) { + runParallelTests(t, 3) +} + +func TestNullIpam(t *testing.T) { + _, err := controller.NewNetwork(bridgeNetType, "testnetworkinternal", "", libnetwork.NetworkOptionIpam(ipamapi.NullIPAM, "", nil, nil, nil)) + if err == nil || err.Error() != "ipv4 pool is empty" { + t.Fatal("bridge network should complain empty pool") + } +} diff --git a/libnetwork_test.go b/libnetwork_test.go index d918a9c4..1eee7fa6 100644 --- a/libnetwork_test.go +++ b/libnetwork_test.go @@ -1,19 +1,12 @@ package libnetwork_test import ( - "bytes" - "encoding/json" - "flag" "fmt" "io/ioutil" "net" "net/http" "net/http/httptest" "os" - "os/exec" - "runtime" - "strconv" - "strings" "sync" "testing" @@ -27,12 +20,8 @@ import ( "github.com/docker/libnetwork/ipamapi" "github.com/docker/libnetwork/netlabel" "github.com/docker/libnetwork/options" - "github.com/docker/libnetwork/osl" "github.com/docker/libnetwork/testutils" "github.com/docker/libnetwork/types" - "github.com/opencontainers/runc/libcontainer" - "github.com/opencontainers/runc/libcontainer/configs" - "github.com/vishvananda/netlink" "github.com/vishvananda/netns" ) @@ -151,107 +140,6 @@ func TestNull(t *testing.T) { } } -func TestHost(t *testing.T) { - sbx1, err := controller.NewSandbox("host_c1", - libnetwork.OptionHostname("test1"), - libnetwork.OptionDomainname("docker.io"), - libnetwork.OptionExtraHost("web", "192.168.0.1"), - libnetwork.OptionUseDefaultSandbox()) - if err != nil { - t.Fatal(err) - } - defer func() { - if err := sbx1.Delete(); err != nil { - t.Fatal(err) - } - }() - - sbx2, err := controller.NewSandbox("host_c2", - libnetwork.OptionHostname("test2"), - libnetwork.OptionDomainname("docker.io"), - libnetwork.OptionExtraHost("web", "192.168.0.1"), - libnetwork.OptionUseDefaultSandbox()) - if err != nil { - t.Fatal(err) - } - defer func() { - if err := sbx2.Delete(); err != nil { - t.Fatal(err) - } - }() - - network, err := createTestNetwork("host", "testhost", options.Generic{}, nil, nil) - if err != nil { - t.Fatal(err) - } - - ep1, err := network.CreateEndpoint("testep1") - if err != nil { - t.Fatal(err) - } - - if err := ep1.Join(sbx1); err != nil { - t.Fatal(err) - } - - ep2, err := network.CreateEndpoint("testep2") - if err != nil { - t.Fatal(err) - } - - if err := ep2.Join(sbx2); err != nil { - t.Fatal(err) - } - - if err := ep1.Leave(sbx1); err != nil { - t.Fatal(err) - } - - if err := ep2.Leave(sbx2); err != nil { - t.Fatal(err) - } - - if err := ep1.Delete(false); err != nil { - t.Fatal(err) - } - - if err := ep2.Delete(false); err != nil { - t.Fatal(err) - } - - // Try to create another host endpoint and join/leave that. - cnt3, err := controller.NewSandbox("host_c3", - libnetwork.OptionHostname("test3"), - libnetwork.OptionDomainname("docker.io"), - libnetwork.OptionExtraHost("web", "192.168.0.1"), - libnetwork.OptionUseDefaultSandbox()) - if err != nil { - t.Fatal(err) - } - defer func() { - if err := cnt3.Delete(); err != nil { - t.Fatal(err) - } - }() - - ep3, err := network.CreateEndpoint("testep3") - if err != nil { - t.Fatal(err) - } - - if err := ep3.Join(sbx2); err != nil { - t.Fatal(err) - } - - if err := ep3.Leave(sbx2); err != nil { - t.Fatal(err) - } - - if err := ep3.Delete(false); err != nil { - t.Fatal(err) - } -} - func TestBridge(t *testing.T) { if !testutils.IsRunningInContainer() { defer testutils.SetupTestOSContext(t)() @@ -315,59 +203,6 @@ func TestBridge(t *testing.T) { } } -// Testing IPV6 from MAC address -func TestBridgeIpv6FromMac(t *testing.T) { - if !testutils.IsRunningInContainer() { - defer testutils.SetupTestOSContext(t)() - } - - netOption := options.Generic{ - netlabel.GenericData: options.Generic{ - "BridgeName": "testipv6mac", - "EnableICC": true, - "EnableIPMasquerade": true, - }, - } - ipamV4ConfList := []*libnetwork.IpamConf{{PreferredPool: "192.168.100.0/24", Gateway: "192.168.100.1"}} - ipamV6ConfList := []*libnetwork.IpamConf{{PreferredPool: "fe90::/64", Gateway: "fe90::22"}} - - network, err := controller.NewNetwork(bridgeNetType, "testipv6mac", "", - libnetwork.NetworkOptionGeneric(netOption), - libnetwork.NetworkOptionEnableIPv6(true), - libnetwork.NetworkOptionIpam(ipamapi.DefaultIPAM, "", ipamV4ConfList, ipamV6ConfList, nil), - libnetwork.NetworkOptionDeferIPv6Alloc(true)) - if err != nil { - t.Fatal(err) - } - - mac := net.HardwareAddr{0xaa, 0xbb, 0xcc, 0xdd, 0xee, 0xff} - epOption := options.Generic{netlabel.MacAddress: mac} - - ep, err := network.CreateEndpoint("testep", libnetwork.EndpointOptionGeneric(epOption)) - if err != nil { - t.Fatal(err) - } - - iface := ep.Info().Iface() - if !bytes.Equal(iface.MacAddress(), mac) { - t.Fatalf("Unexpected mac address: %v", iface.MacAddress()) - } - - ip, expIP, _ := net.ParseCIDR("fe90::aabb:ccdd:eeff/64") - expIP.IP = ip - if !types.CompareIPNet(expIP, iface.AddressIPv6()) { - t.Fatalf("Expected %v. Got: %v", expIP, iface.AddressIPv6()) - } - - if err := ep.Delete(false); err != nil { - t.Fatal(err) - } - - if err := network.Delete(); err != nil { - t.Fatal(err) - } -} - func TestUnknownDriver(t *testing.T) { if !testutils.IsRunningInContainer() { defer testutils.SetupTestOSContext(t)() @@ -966,202 +801,6 @@ func TestNetworkQuery(t *testing.T) { const containerID = "valid_c" -func checkSandbox(t *testing.T, info libnetwork.EndpointInfo) { - key := info.Sandbox().Key() - sbNs, err := netns.GetFromPath(key) - if err != nil { - t.Fatalf("Failed to get network namespace path %q: %v", key, err) - } - defer sbNs.Close() - - nh, err := netlink.NewHandleAt(sbNs) - if err != nil { - t.Fatal(err) - } - - _, err = nh.LinkByName("eth0") - if err != nil { - t.Fatalf("Could not find the interface eth0 inside the sandbox: %v", err) - } - - _, err = nh.LinkByName("eth1") - if err != nil { - t.Fatalf("Could not find the interface eth1 inside the sandbox: %v", err) - } -} - -func TestEndpointJoin(t *testing.T) { - if !testutils.IsRunningInContainer() { - defer testutils.SetupTestOSContext(t)() - } - - // Create network 1 and add 2 endpoint: ep11, ep12 - netOption := options.Generic{ - netlabel.GenericData: options.Generic{ - "BridgeName": "testnetwork1", - "EnableICC": true, - "EnableIPMasquerade": true, - }, - } - ipamV6ConfList := []*libnetwork.IpamConf{{PreferredPool: "fe90::/64", Gateway: "fe90::22"}} - n1, err := controller.NewNetwork(bridgeNetType, "testnetwork1", "", - libnetwork.NetworkOptionGeneric(netOption), - libnetwork.NetworkOptionEnableIPv6(true), - libnetwork.NetworkOptionIpam(ipamapi.DefaultIPAM, "", nil, ipamV6ConfList, nil), - libnetwork.NetworkOptionDeferIPv6Alloc(true)) - if err != nil { - t.Fatal(err) - } - defer func() { - if err := n1.Delete(); err != nil { - t.Fatal(err) - } - }() - - ep1, err := n1.CreateEndpoint("ep1") - if err != nil { - t.Fatal(err) - } - defer func() { - if err := ep1.Delete(false); err != nil { - t.Fatal(err) - } - }() - - // Validate if ep.Info() only gives me IP address info and not names and gateway during CreateEndpoint() - info := ep1.Info() - iface := info.Iface() - if iface.Address() != nil && iface.Address().IP.To4() == nil { - t.Fatalf("Invalid IP address returned: %v", iface.Address()) - } - if iface.AddressIPv6() != nil && iface.AddressIPv6().IP == nil { - t.Fatalf("Invalid IPv6 address returned: %v", iface.Address()) - } - - if len(info.Gateway()) != 0 { - t.Fatalf("Expected empty gateway for an empty endpoint. Instead found a gateway: %v", info.Gateway()) - } - if len(info.GatewayIPv6()) != 0 { - t.Fatalf("Expected empty gateway for an empty ipv6 endpoint. Instead found a gateway: %v", info.GatewayIPv6()) - } - - if info.Sandbox() != nil { - t.Fatalf("Expected an empty sandbox key for an empty endpoint. Instead found a non-empty sandbox key: %s", info.Sandbox().Key()) - } - - // test invalid joins - err = ep1.Join(nil) - if err == nil { - t.Fatalf("Expected to fail join with nil Sandbox") - } - if _, ok := err.(types.BadRequestError); !ok { - t.Fatalf("Unexpected error type returned: %T", err) - } - - fsbx := &fakeSandbox{} - if err = ep1.Join(fsbx); err == nil { - t.Fatalf("Expected to fail join with invalid Sandbox") - } - if _, ok := err.(types.BadRequestError); !ok { - t.Fatalf("Unexpected error type returned: %T", err) - } - - sb, err := controller.NewSandbox(containerID, - libnetwork.OptionHostname("test"), - libnetwork.OptionDomainname("docker.io"), - libnetwork.OptionExtraHost("web", "192.168.0.1")) - if err != nil { - t.Fatal(err) - } - - defer func() { - if err := sb.Delete(); err != nil { - t.Fatal(err) - } - }() - - err = ep1.Join(sb) - if err != nil { - t.Fatal(err) - } - defer func() { - err = ep1.Leave(sb) - if err != nil { - t.Fatal(err) - } - }() - - // Validate if ep.Info() only gives valid gateway and sandbox key after has container has joined. - info = ep1.Info() - if len(info.Gateway()) == 0 { - t.Fatalf("Expected a valid gateway for a joined endpoint. Instead found an invalid gateway: %v", info.Gateway()) - } - if len(info.GatewayIPv6()) == 0 { - t.Fatalf("Expected a valid ipv6 gateway for a joined endpoint. Instead found an invalid gateway: %v", info.GatewayIPv6()) - } - - if info.Sandbox() == nil { - t.Fatalf("Expected a non-empty sandbox key for a joined endpoint. Instead found an empty sandbox key") - } - - // Check endpoint provided container information - if ep1.Info().Sandbox().Key() != sb.Key() { - t.Fatalf("Endpoint Info returned unexpected sandbox key: %s", sb.Key()) - } - - // Attempt retrieval of endpoint interfaces statistics - stats, err := sb.Statistics() - if err != nil { - t.Fatal(err) - } - if _, ok := stats["eth0"]; !ok { - t.Fatalf("Did not find eth0 statistics") - } - - // Now test the container joining another network - n2, err := createTestNetwork(bridgeNetType, "testnetwork2", - options.Generic{ - netlabel.GenericData: options.Generic{ - "BridgeName": "testnetwork2", - }, - }, nil, nil) - if err != nil { - t.Fatal(err) - } - defer func() { - if err := n2.Delete(); err != nil { - t.Fatal(err) - } - }() - - ep2, err := n2.CreateEndpoint("ep2") - if err != nil { - t.Fatal(err) - } - defer func() { - if err := ep2.Delete(false); err != nil { - t.Fatal(err) - } - }() - - err = ep2.Join(sb) - if err != nil { - t.Fatal(err) - } - defer func() { - err = ep2.Leave(sb) - if err != nil { - t.Fatal(err) - } - }() - - if ep1.Info().Sandbox().Key() != ep2.Info().Sandbox().Key() { - t.Fatalf("ep1 and ep2 returned different container sandbox key") - } - - checkSandbox(t, info) -} - type fakeSandbox struct{} func (f *fakeSandbox) ID() string { @@ -1216,155 +855,6 @@ func (f *fakeSandbox) Endpoints() []libnetwork.Endpoint { return nil } -func TestExternalKey(t *testing.T) { - externalKeyTest(t, false) -} - -func externalKeyTest(t *testing.T, reexec bool) { - if !testutils.IsRunningInContainer() { - defer testutils.SetupTestOSContext(t)() - } - - n, err := createTestNetwork(bridgeNetType, "testnetwork", options.Generic{ - netlabel.GenericData: options.Generic{ - "BridgeName": "testnetwork", - }, - }, nil, nil) - if err != nil { - t.Fatal(err) - } - defer func() { - if err := n.Delete(); err != nil { - t.Fatal(err) - } - }() - - ep, err := n.CreateEndpoint("ep1") - if err != nil { - t.Fatal(err) - } - defer func() { - err = ep.Delete(false) - if err != nil { - t.Fatal(err) - } - }() - - ep2, err := n.CreateEndpoint("ep2") - if err != nil { - t.Fatal(err) - } - defer func() { - err = ep2.Delete(false) - if err != nil { - t.Fatal(err) - } - }() - - cnt, err := controller.NewSandbox(containerID, - libnetwork.OptionHostname("test"), - libnetwork.OptionDomainname("docker.io"), - libnetwork.OptionUseExternalKey(), - libnetwork.OptionExtraHost("web", "192.168.0.1")) - defer func() { - if err := cnt.Delete(); err != nil { - t.Fatal(err) - } - osl.GC() - }() - - // Join endpoint to sandbox before SetKey - err = ep.Join(cnt) - if err != nil { - t.Fatal(err) - } - defer func() { - err = ep.Leave(cnt) - if err != nil { - t.Fatal(err) - } - }() - - sbox := ep.Info().Sandbox() - if sbox == nil { - t.Fatalf("Expected to have a valid Sandbox") - } - - if reexec { - err := reexecSetKey("this-must-fail", containerID, controller.ID()) - if err == nil { - t.Fatalf("SetExternalKey must fail if the corresponding namespace is not created") - } - } else { - // Setting a non-existing key (namespace) must fail - if err := sbox.SetKey("this-must-fail"); err == nil { - t.Fatalf("Setkey must fail if the corresponding namespace is not created") - } - } - - // Create a new OS sandbox using the osl API before using it in SetKey - if extOsBox, err := osl.NewSandbox("ValidKey", true, false); err != nil { - t.Fatalf("Failed to create new osl sandbox") - } else { - defer func() { - if err := extOsBox.Destroy(); err != nil { - log.Warnf("Failed to remove os sandbox: %v", err) - } - }() - } - - if reexec { - err := reexecSetKey("ValidKey", containerID, controller.ID()) - if err != nil { - t.Fatalf("SetExternalKey failed with %v", err) - } - } else { - if err := sbox.SetKey("ValidKey"); err != nil { - t.Fatalf("Setkey failed with %v", err) - } - } - - // Join endpoint to sandbox after SetKey - err = ep2.Join(sbox) - if err != nil { - t.Fatal(err) - } - defer func() { - err = ep2.Leave(sbox) - if err != nil { - t.Fatal(err) - } - }() - - if ep.Info().Sandbox().Key() != ep2.Info().Sandbox().Key() { - t.Fatalf("ep1 and ep2 returned different container sandbox key") - } - - checkSandbox(t, ep.Info()) -} - -func reexecSetKey(key string, containerID string, controllerID string) error { - var ( - state libcontainer.State - b []byte - err error - ) - - state.NamespacePaths = make(map[configs.NamespaceType]string) - state.NamespacePaths[configs.NamespaceType("NEWNET")] = key - if b, err = json.Marshal(state); err != nil { - return err - } - cmd := &exec.Cmd{ - Path: reexec.Self(), - Args: append([]string{"libnetwork-setkey"}, containerID, controllerID), - Stdin: strings.NewReader(string(b)), - Stdout: os.Stdout, - Stderr: os.Stderr, - } - return cmd.Run() -} - func TestEndpointDeleteWithActiveContainer(t *testing.T) { if !testutils.IsRunningInContainer() { defer testutils.SetupTestOSContext(t)() @@ -1678,304 +1168,6 @@ func TestEndpointUpdateParent(t *testing.T) { } } -func TestEnableIPv6(t *testing.T) { - if !testutils.IsRunningInContainer() { - defer testutils.SetupTestOSContext(t)() - } - - tmpResolvConf := []byte("search pommesfrites.fr\nnameserver 12.34.56.78\nnameserver 2001:4860:4860::8888\n") - expectedResolvConf := []byte("search pommesfrites.fr\nnameserver 127.0.0.11\nnameserver 2001:4860:4860::8888\noptions ndots:0\n") - //take a copy of resolv.conf for restoring after test completes - resolvConfSystem, err := ioutil.ReadFile("/etc/resolv.conf") - if err != nil { - t.Fatal(err) - } - //cleanup - defer func() { - if err := ioutil.WriteFile("/etc/resolv.conf", resolvConfSystem, 0644); err != nil { - t.Fatal(err) - } - }() - - netOption := options.Generic{ - netlabel.EnableIPv6: true, - netlabel.GenericData: options.Generic{ - "BridgeName": "testnetwork", - }, - } - ipamV6ConfList := []*libnetwork.IpamConf{{PreferredPool: "fe99::/64", Gateway: "fe99::9"}} - - n, err := createTestNetwork("bridge", "testnetwork", netOption, nil, ipamV6ConfList) - if err != nil { - t.Fatal(err) - } - defer func() { - if err := n.Delete(); err != nil { - t.Fatal(err) - } - }() - - ep1, err := n.CreateEndpoint("ep1") - if err != nil { - t.Fatal(err) - } - - if err := ioutil.WriteFile("/etc/resolv.conf", tmpResolvConf, 0644); err != nil { - t.Fatal(err) - } - - resolvConfPath := "/tmp/libnetwork_test/resolv.conf" - defer os.Remove(resolvConfPath) - - sb, err := controller.NewSandbox(containerID, libnetwork.OptionResolvConfPath(resolvConfPath)) - if err != nil { - t.Fatal(err) - } - defer func() { - if err := sb.Delete(); err != nil { - t.Fatal(err) - } - }() - - err = ep1.Join(sb) - if err != nil { - t.Fatal(err) - } - - content, err := ioutil.ReadFile(resolvConfPath) - if err != nil { - t.Fatal(err) - } - - if !bytes.Equal(content, expectedResolvConf) { - t.Fatalf("Expected:\n%s\nGot:\n%s", string(expectedResolvConf), string(content)) - } - - if err != nil { - t.Fatal(err) - } -} - -func TestResolvConfHost(t *testing.T) { - if !testutils.IsRunningInContainer() { - defer testutils.SetupTestOSContext(t)() - } - - tmpResolvConf := []byte("search localhost.net\nnameserver 127.0.0.1\nnameserver 2001:4860:4860::8888\n") - - //take a copy of resolv.conf for restoring after test completes - resolvConfSystem, err := ioutil.ReadFile("/etc/resolv.conf") - if err != nil { - t.Fatal(err) - } - //cleanup - defer func() { - if err := ioutil.WriteFile("/etc/resolv.conf", resolvConfSystem, 0644); err != nil { - t.Fatal(err) - } - }() - - n, err := controller.NetworkByName("testhost") - if err != nil { - t.Fatal(err) - } - - ep1, err := n.CreateEndpoint("ep1", libnetwork.CreateOptionDisableResolution()) - if err != nil { - t.Fatal(err) - } - - if err := ioutil.WriteFile("/etc/resolv.conf", tmpResolvConf, 0644); err != nil { - t.Fatal(err) - } - - resolvConfPath := "/tmp/libnetwork_test/resolv.conf" - defer os.Remove(resolvConfPath) - - sb, err := controller.NewSandbox(containerID, - libnetwork.OptionResolvConfPath(resolvConfPath), - libnetwork.OptionOriginResolvConfPath("/etc/resolv.conf")) - if err != nil { - t.Fatal(err) - } - defer func() { - if err := sb.Delete(); err != nil { - t.Fatal(err) - } - }() - - err = ep1.Join(sb) - if err != nil { - t.Fatal(err) - } - defer func() { - err = ep1.Leave(sb) - if err != nil { - t.Fatal(err) - } - }() - - finfo, err := os.Stat(resolvConfPath) - if err != nil { - t.Fatal(err) - } - - fmode := (os.FileMode)(0644) - if finfo.Mode() != fmode { - t.Fatalf("Expected file mode %s, got %s", fmode.String(), finfo.Mode().String()) - } - - content, err := ioutil.ReadFile(resolvConfPath) - if err != nil { - t.Fatal(err) - } - - if !bytes.Equal(content, tmpResolvConf) { - t.Fatalf("Expected:\n%s\nGot:\n%s", string(tmpResolvConf), string(content)) - } -} - -func TestResolvConf(t *testing.T) { - if !testutils.IsRunningInContainer() { - defer testutils.SetupTestOSContext(t)() - } - - tmpResolvConf1 := []byte("search pommesfrites.fr\nnameserver 12.34.56.78\nnameserver 2001:4860:4860::8888\n") - tmpResolvConf2 := []byte("search pommesfrites.fr\nnameserver 112.34.56.78\nnameserver 2001:4860:4860::8888\n") - expectedResolvConf1 := []byte("search pommesfrites.fr\nnameserver 127.0.0.11\noptions ndots:0\n") - tmpResolvConf3 := []byte("search pommesfrites.fr\nnameserver 113.34.56.78\n") - - //take a copy of resolv.conf for restoring after test completes - resolvConfSystem, err := ioutil.ReadFile("/etc/resolv.conf") - if err != nil { - t.Fatal(err) - } - //cleanup - defer func() { - if err := ioutil.WriteFile("/etc/resolv.conf", resolvConfSystem, 0644); err != nil { - t.Fatal(err) - } - }() - - netOption := options.Generic{ - netlabel.GenericData: options.Generic{ - "BridgeName": "testnetwork", - }, - } - n, err := createTestNetwork("bridge", "testnetwork", netOption, nil, nil) - if err != nil { - t.Fatal(err) - } - defer func() { - if err := n.Delete(); err != nil { - t.Fatal(err) - } - }() - - ep, err := n.CreateEndpoint("ep") - if err != nil { - t.Fatal(err) - } - - if err := ioutil.WriteFile("/etc/resolv.conf", tmpResolvConf1, 0644); err != nil { - t.Fatal(err) - } - - resolvConfPath := "/tmp/libnetwork_test/resolv.conf" - defer os.Remove(resolvConfPath) - - sb1, err := controller.NewSandbox(containerID, libnetwork.OptionResolvConfPath(resolvConfPath)) - if err != nil { - t.Fatal(err) - } - defer func() { - if err := sb1.Delete(); err != nil { - t.Fatal(err) - } - }() - - err = ep.Join(sb1) - if err != nil { - t.Fatal(err) - } - - finfo, err := os.Stat(resolvConfPath) - if err != nil { - t.Fatal(err) - } - - fmode := (os.FileMode)(0644) - if finfo.Mode() != fmode { - t.Fatalf("Expected file mode %s, got %s", fmode.String(), finfo.Mode().String()) - } - - content, err := ioutil.ReadFile(resolvConfPath) - if err != nil { - t.Fatal(err) - } - - if !bytes.Equal(content, expectedResolvConf1) { - fmt.Printf("\n%v\n%v\n", expectedResolvConf1, content) - t.Fatalf("Expected:\n%s\nGot:\n%s", string(expectedResolvConf1), string(content)) - } - - err = ep.Leave(sb1) - if err != nil { - t.Fatal(err) - } - - if err := ioutil.WriteFile("/etc/resolv.conf", tmpResolvConf2, 0644); err != nil { - t.Fatal(err) - } - - sb2, err := controller.NewSandbox(containerID+"_2", libnetwork.OptionResolvConfPath(resolvConfPath)) - if err != nil { - t.Fatal(err) - } - defer func() { - if err := sb2.Delete(); err != nil { - t.Fatal(err) - } - }() - - err = ep.Join(sb2) - if err != nil { - t.Fatal(err) - } - - content, err = ioutil.ReadFile(resolvConfPath) - if err != nil { - t.Fatal(err) - } - - if !bytes.Equal(content, expectedResolvConf1) { - t.Fatalf("Expected:\n%s\nGot:\n%s", string(expectedResolvConf1), string(content)) - } - - if err := ioutil.WriteFile(resolvConfPath, tmpResolvConf3, 0644); err != nil { - t.Fatal(err) - } - - err = ep.Leave(sb2) - if err != nil { - t.Fatal(err) - } - - err = ep.Join(sb2) - if err != nil { - t.Fatal(err) - } - - content, err = ioutil.ReadFile(resolvConfPath) - if err != nil { - t.Fatal(err) - } - - if !bytes.Equal(content, tmpResolvConf3) { - t.Fatalf("Expected:\n%s\nGot:\n%s", string(tmpResolvConf3), string(content)) - } -} - func TestInvalidRemoteDriver(t *testing.T) { if !testutils.IsRunningInContainer() { t.Skip("Skipping test when not running inside a Container") @@ -2164,171 +1356,3 @@ func debugf(format string, a ...interface{}) (int, error) { return 0, nil } - -func parallelJoin(t *testing.T, rc libnetwork.Sandbox, ep libnetwork.Endpoint, thrNumber int) { - debugf("J%d.", thrNumber) - var err error - - sb := sboxes[thrNumber-1] - err = ep.Join(sb) - - runtime.LockOSThread() - if err != nil { - if _, ok := err.(types.ForbiddenError); !ok { - t.Fatalf("thread %d: %v", thrNumber, err) - } - debugf("JE%d(%v).", thrNumber, err) - } - debugf("JD%d.", thrNumber) -} - -func parallelLeave(t *testing.T, rc libnetwork.Sandbox, ep libnetwork.Endpoint, thrNumber int) { - debugf("L%d.", thrNumber) - var err error - - sb := sboxes[thrNumber-1] - - err = ep.Leave(sb) - runtime.LockOSThread() - if err != nil { - if _, ok := err.(types.ForbiddenError); !ok { - t.Fatalf("thread %d: %v", thrNumber, err) - } - debugf("LE%d(%v).", thrNumber, err) - } - debugf("LD%d.", thrNumber) -} - -func runParallelTests(t *testing.T, thrNumber int) { - var ( - ep libnetwork.Endpoint - sb libnetwork.Sandbox - err error - ) - - t.Parallel() - - pTest := flag.Lookup("test.parallel") - if pTest == nil { - t.Skip("Skipped because test.parallel flag not set;") - } - numParallel, err := strconv.Atoi(pTest.Value.String()) - if err != nil { - t.Fatal(err) - } - if numParallel < numThreads { - t.Skip("Skipped because t.parallel was less than ", numThreads) - } - - runtime.LockOSThread() - defer runtime.UnlockOSThread() - - if thrNumber == first { - createGlobalInstance(t) - } - - if thrNumber != first { - select { - case <-start: - } - - thrdone := make(chan struct{}) - done <- thrdone - defer close(thrdone) - - if thrNumber == last { - defer close(done) - } - - err = netns.Set(testns) - if err != nil { - t.Fatal(err) - } - } - defer netns.Set(origns) - - net1, err := controller.NetworkByName("testhost") - if err != nil { - t.Fatal(err) - } - if net1 == nil { - t.Fatal("Could not find testhost") - } - - net2, err := controller.NetworkByName("network2") - if err != nil { - t.Fatal(err) - } - if net2 == nil { - t.Fatal("Could not find network2") - } - - epName := fmt.Sprintf("pep%d", thrNumber) - - if thrNumber == first { - ep, err = net1.EndpointByName(epName) - } else { - ep, err = net2.EndpointByName(epName) - } - - if err != nil { - t.Fatal(err) - } - if ep == nil { - t.Fatal("Got nil ep with no error") - } - - cid := fmt.Sprintf("%drace", thrNumber) - controller.WalkSandboxes(libnetwork.SandboxContainerWalker(&sb, cid)) - if sb == nil { - t.Fatalf("Got nil sandbox for container: %s", cid) - } - - for i := 0; i < iterCnt; i++ { - parallelJoin(t, sb, ep, thrNumber) - parallelLeave(t, sb, ep, thrNumber) - } - - debugf("\n") - - err = sb.Delete() - if err != nil { - t.Fatal(err) - } - if thrNumber == first { - for thrdone := range done { - select { - case <-thrdone: - } - } - - testns.Close() - if err := net2.Delete(); err != nil { - t.Fatal(err) - } - } else { - err = ep.Delete(false) - if err != nil { - t.Fatal(err) - } - } -} - -func TestParallel1(t *testing.T) { - runParallelTests(t, 1) -} - -func TestParallel2(t *testing.T) { - runParallelTests(t, 2) -} - -func TestParallel3(t *testing.T) { - runParallelTests(t, 3) -} - -func TestNullIpam(t *testing.T) { - _, err := controller.NewNetwork(bridgeNetType, "testnetworkinternal", "", libnetwork.NetworkOptionIpam(ipamapi.NullIPAM, "", nil, nil, nil)) - if err == nil || err.Error() != "ipv4 pool is empty" { - t.Fatal("bridge network should complain empty pool") - } -} diff --git a/netutils/utils_solaris.go b/netutils/utils_solaris.go index d0356f62..12d453f7 100644 --- a/netutils/utils_solaris.go +++ b/netutils/utils_solaris.go @@ -1,13 +1,26 @@ +// +build solaris + package netutils -// Solaris: TODO - import ( + "fmt" "net" + "os/exec" + "strings" "github.com/docker/libnetwork/ipamutils" + "github.com/vishvananda/netlink" ) +var ( + networkGetRoutesFct func(netlink.Link, int) ([]netlink.Route, error) +) + +// CheckRouteOverlaps checks whether the passed network overlaps with any existing routes +func CheckRouteOverlaps(toCheck *net.IPNet) error { + return nil +} + // ElectInterfaceAddresses looks for an interface on the OS with the specified name // and returns its IPv4 and IPv6 addresses in CIDR form. If the interface does not exist, // it chooses from a predifined list the first IPv4 address which does not conflict @@ -15,18 +28,75 @@ import ( func ElectInterfaceAddresses(name string) (*net.IPNet, []*net.IPNet, error) { var ( v4Net *net.IPNet - err error ) - v4Net, err = FindAvailableNetwork(ipamutils.PredefinedBroadNetworks) + out, err := exec.Command("/usr/sbin/ipadm", "show-addr", + "-p", "-o", "addrobj,addr").Output() if err != nil { + fmt.Println("failed to list interfaces on system") return nil, nil, err } + alist := strings.Fields(string(out)) + for _, a := range alist { + linkandaddr := strings.SplitN(a, ":", 2) + if len(linkandaddr) != 2 { + fmt.Println("failed to check interfaces on system: ", a) + continue + } + gw := fmt.Sprintf("%s_gw0", name) + link := strings.Split(linkandaddr[0], "/")[0] + addr := linkandaddr[1] + if gw != link { + continue + } + _, ipnet, err := net.ParseCIDR(addr) + if err != nil { + fmt.Println("failed to parse address: ", addr) + continue + } + v4Net = ipnet + break + } + if v4Net == nil { + v4Net, err = FindAvailableNetwork(ipamutils.PredefinedBroadNetworks) + if err != nil { + return nil, nil, err + } + } return v4Net, nil, nil } // FindAvailableNetwork returns a network from the passed list which does not // overlap with existing interfaces in the system func FindAvailableNetwork(list []*net.IPNet) (*net.IPNet, error) { - return list[0], nil + out, err := exec.Command("/usr/sbin/ipadm", "show-addr", + "-p", "-o", "addr").Output() + + if err != nil { + fmt.Println("failed to list interfaces on system") + return nil, err + } + ipaddrs := strings.Fields(string(out)) + inuse := []*net.IPNet{} + for _, ip := range ipaddrs { + _, ipnet, err := net.ParseCIDR(ip) + if err != nil { + fmt.Println("failed to check interfaces on system: ", ip) + continue + } + inuse = append(inuse, ipnet) + } + for _, avail := range list { + is_avail := true + for _, ipnet := range inuse { + if NetworkOverlaps(avail, ipnet) { + is_avail = false + break + } + } + if is_avail { + return avail, nil + } + } + return nil, fmt.Errorf("no available network") } diff --git a/netutils/utils_test.go b/netutils/utils_test.go index c051d6e0..87fbe653 100644 --- a/netutils/utils_test.go +++ b/netutils/utils_test.go @@ -1,3 +1,5 @@ +// +build !solaris + package netutils import ( diff --git a/osl/sandbox_solaris.go b/osl/sandbox_solaris.go new file mode 100644 index 00000000..9de44e5a --- /dev/null +++ b/osl/sandbox_solaris.go @@ -0,0 +1,24 @@ +package osl + +// NewSandbox provides a new sandbox instance created in an os specific way +// provided a key which uniquely identifies the sandbox +func NewSandbox(key string, osCreate, isRestore bool) (Sandbox, error) { + return nil, nil +} + +// GenerateKey generates a sandbox key based on the passed +// container id. +func GenerateKey(containerID string) string { + maxLen := 12 + + if len(containerID) < maxLen { + maxLen = len(containerID) + } + + return containerID[:maxLen] +} + +// InitOSContext initializes OS context while configuring network resources +func InitOSContext() func() { + return func() {} +} diff --git a/osl/sandbox_test.go b/osl/sandbox_test.go index d3bd1ace..f4830625 100644 --- a/osl/sandbox_test.go +++ b/osl/sandbox_test.go @@ -1,3 +1,5 @@ +// +build !solaris + package osl import ( diff --git a/osl/sandbox_unsupported.go b/osl/sandbox_unsupported.go index 51a656c8..49184d60 100644 --- a/osl/sandbox_unsupported.go +++ b/osl/sandbox_unsupported.go @@ -1,4 +1,4 @@ -// +build !linux,!windows,!freebsd +// +build !linux,!windows,!freebsd,!solaris package osl diff --git a/osl/sandbox_unsupported_test.go b/osl/sandbox_unsupported_test.go index 42dbb7c2..9ece5ae0 100644 --- a/osl/sandbox_unsupported_test.go +++ b/osl/sandbox_unsupported_test.go @@ -1,4 +1,4 @@ -// +build !linux +// +build !linux,!solaris package osl diff --git a/portallocator/portallocator.go b/portallocator/portallocator.go index 240e94fc..b7f790be 100644 --- a/portallocator/portallocator.go +++ b/portallocator/portallocator.go @@ -1,11 +1,9 @@ package portallocator import ( - "bufio" "errors" "fmt" "net" - "os" "sync" ) @@ -106,26 +104,6 @@ func newInstance() *PortAllocator { } } -func getDynamicPortRange() (start int, end int, err error) { - const portRangeKernelParam = "/proc/sys/net/ipv4/ip_local_port_range" - portRangeFallback := fmt.Sprintf("using fallback port range %d-%d", DefaultPortRangeStart, DefaultPortRangeEnd) - file, err := os.Open(portRangeKernelParam) - if err != nil { - return 0, 0, fmt.Errorf("port allocator - %s due to error: %v", portRangeFallback, err) - } - - defer file.Close() - - n, err := fmt.Fscanf(bufio.NewReader(file), "%d\t%d", &start, &end) - if n != 2 || err != nil { - if err == nil { - err = fmt.Errorf("unexpected count of parsed numbers (%d)", n) - } - return 0, 0, fmt.Errorf("port allocator - failed to parse system ephemeral port range from %s - %s: %v", portRangeKernelParam, portRangeFallback, err) - } - return start, end, nil -} - // RequestPort requests new port from global ports pool for specified ip and proto. // If port is 0 it returns first free port. Otherwise it checks port availability // in proto's pool and returns that port or error if port is already busy. diff --git a/portallocator/portallocator_linux.go b/portallocator/portallocator_linux.go new file mode 100644 index 00000000..687f6dab --- /dev/null +++ b/portallocator/portallocator_linux.go @@ -0,0 +1,27 @@ +package portallocator + +import ( + "bufio" + "fmt" + "os" +) + +func getDynamicPortRange() (start int, end int, err error) { + const portRangeKernelParam = "/proc/sys/net/ipv4/ip_local_port_range" + portRangeFallback := fmt.Sprintf("using fallback port range %d-%d", DefaultPortRangeStart, DefaultPortRangeEnd) + file, err := os.Open(portRangeKernelParam) + if err != nil { + return 0, 0, fmt.Errorf("port allocator - %s due to error: %v", portRangeFallback, err) + } + + defer file.Close() + + n, err := fmt.Fscanf(bufio.NewReader(file), "%d\t%d", &start, &end) + if n != 2 || err != nil { + if err == nil { + err = fmt.Errorf("unexpected count of parsed numbers (%d)", n) + } + return 0, 0, fmt.Errorf("port allocator - failed to parse system ephemeral port range from %s - %s: %v", portRangeKernelParam, portRangeFallback, err) + } + return start, end, nil +} diff --git a/portallocator/portallocator_solaris.go b/portallocator/portallocator_solaris.go new file mode 100644 index 00000000..ccc20b13 --- /dev/null +++ b/portallocator/portallocator_solaris.go @@ -0,0 +1,5 @@ +package portallocator + +func getDynamicPortRange() (start int, end int, err error) { + return 32768, 65535, nil +} diff --git a/portmapper/proxy.go b/portmapper/proxy.go index 6a4adbb8..a5bdc553 100644 --- a/portmapper/proxy.go +++ b/portmapper/proxy.go @@ -7,8 +7,6 @@ import ( "net" "os" "os/exec" - "strconv" - "syscall" "time" ) @@ -25,36 +23,6 @@ type proxyCommand struct { cmd *exec.Cmd } -func newProxyCommand(proto string, hostIP net.IP, hostPort int, containerIP net.IP, containerPort int, proxyPath string) (userlandProxy, error) { - path := proxyPath - if proxyPath == "" { - cmd, err := exec.LookPath(userlandProxyCommandName) - if err != nil { - return nil, err - } - path = cmd - } - - args := []string{ - path, - "-proto", proto, - "-host-ip", hostIP.String(), - "-host-port", strconv.Itoa(hostPort), - "-container-ip", containerIP.String(), - "-container-port", strconv.Itoa(containerPort), - } - - return &proxyCommand{ - cmd: &exec.Cmd{ - Path: path, - Args: args, - SysProcAttr: &syscall.SysProcAttr{ - Pdeathsig: syscall.SIGTERM, // send a sigterm to the proxy if the daemon process dies - }, - }, - }, nil -} - func (p *proxyCommand) Start() error { r, w, err := os.Pipe() if err != nil { diff --git a/portmapper/proxy_linux.go b/portmapper/proxy_linux.go new file mode 100644 index 00000000..947cd0ba --- /dev/null +++ b/portmapper/proxy_linux.go @@ -0,0 +1,38 @@ +package portmapper + +import ( + "net" + "os/exec" + "strconv" + "syscall" +) + +func newProxyCommand(proto string, hostIP net.IP, hostPort int, containerIP net.IP, containerPort int, proxyPath string) (userlandProxy, error) { + path := proxyPath + if proxyPath == "" { + cmd, err := exec.LookPath(userlandProxyCommandName) + if err != nil { + return nil, err + } + path = cmd + } + + args := []string{ + path, + "-proto", proto, + "-host-ip", hostIP.String(), + "-host-port", strconv.Itoa(hostPort), + "-container-ip", containerIP.String(), + "-container-port", strconv.Itoa(containerPort), + } + + return &proxyCommand{ + cmd: &exec.Cmd{ + Path: path, + Args: args, + SysProcAttr: &syscall.SysProcAttr{ + Pdeathsig: syscall.SIGTERM, // send a sigterm to the proxy if the daemon process dies + }, + }, + }, nil +} diff --git a/portmapper/proxy_solaris.go b/portmapper/proxy_solaris.go new file mode 100644 index 00000000..dc70b5ed --- /dev/null +++ b/portmapper/proxy_solaris.go @@ -0,0 +1,34 @@ +package portmapper + +import ( + "net" + "os/exec" + "strconv" +) + +func newProxyCommand(proto string, hostIP net.IP, hostPort int, containerIP net.IP, containerPort int, proxyPath string) (userlandProxy, error) { + path := proxyPath + if proxyPath == "" { + cmd, err := exec.LookPath(userlandProxyCommandName) + if err != nil { + return nil, err + } + path = cmd + } + + args := []string{ + path, + "-proto", proto, + "-host-ip", hostIP.String(), + "-host-port", strconv.Itoa(hostPort), + "-container-ip", containerIP.String(), + "-container-port", strconv.Itoa(containerPort), + } + + return &proxyCommand{ + cmd: &exec.Cmd{ + Path: path, + Args: args, + }, + }, nil +} diff --git a/store_linux_test.go b/store_linux_test.go new file mode 100644 index 00000000..a4cdd823 --- /dev/null +++ b/store_linux_test.go @@ -0,0 +1,45 @@ +package libnetwork + +import ( + "os" + "testing" + + "github.com/docker/libkv/store" + "github.com/docker/libnetwork/datastore" +) + +func TestBoltdbBackend(t *testing.T) { + defer os.Remove(datastore.DefaultScopes("")[datastore.LocalScope].Client.Address) + testLocalBackend(t, "", "", nil) + defer os.Remove("/tmp/boltdb.db") + config := &store.Config{Bucket: "testBackend"} + testLocalBackend(t, "boltdb", "/tmp/boltdb.db", config) + +} + +func TestNoPersist(t *testing.T) { + cfgOptions, err := OptionBoltdbWithRandomDBFile() + if err != nil { + t.Fatalf("Error creating random boltdb file : %v", err) + } + ctrl, err := New(cfgOptions...) + if err != nil { + t.Fatalf("Error new controller: %v", err) + } + nw, err := ctrl.NewNetwork("host", "host", "", NetworkOptionPersist(false)) + if err != nil { + t.Fatalf("Error creating default \"host\" network: %v", err) + } + ep, err := nw.CreateEndpoint("newendpoint", []EndpointOption{}...) + if err != nil { + t.Fatalf("Error creating endpoint: %v", err) + } + store := ctrl.(*controller).getStore(datastore.LocalScope).KVStore() + if exists, _ := store.Exists(datastore.Key(datastore.NetworkKeyPrefix, string(nw.ID()))); exists { + t.Fatalf("Network with persist=false should not be stored in KV Store") + } + if exists, _ := store.Exists(datastore.Key([]string{datastore.EndpointKeyPrefix, string(nw.ID()), string(ep.ID())}...)); exists { + t.Fatalf("Endpoint in Network with persist=false should not be stored in KV Store") + } + store.Close() +} diff --git a/store_test.go b/store_test.go index 9ef9fcef..873660c0 100644 --- a/store_test.go +++ b/store_test.go @@ -3,7 +3,6 @@ package libnetwork import ( "fmt" "io/ioutil" - "os" "testing" "github.com/docker/libkv/store" @@ -31,15 +30,6 @@ func testNewController(t *testing.T, provider, url string) (NetworkController, e return New(cfgOptions...) } -func TestBoltdbBackend(t *testing.T) { - defer os.Remove(datastore.DefaultScopes("")[datastore.LocalScope].Client.Address) - testLocalBackend(t, "", "", nil) - defer os.Remove("/tmp/boltdb.db") - config := &store.Config{Bucket: "testBackend"} - testLocalBackend(t, "boltdb", "/tmp/boltdb.db", config) - -} - func testLocalBackend(t *testing.T, provider, url string, storeConfig *store.Config) { cfgOptions := []config.Option{} cfgOptions = append(cfgOptions, config.OptionLocalKVProvider(provider)) @@ -82,33 +72,6 @@ func testLocalBackend(t *testing.T, provider, url string, storeConfig *store.Con } } -func TestNoPersist(t *testing.T) { - cfgOptions, err := OptionBoltdbWithRandomDBFile() - if err != nil { - t.Fatalf("Error creating random boltdb file : %v", err) - } - ctrl, err := New(cfgOptions...) - if err != nil { - t.Fatalf("Error new controller: %v", err) - } - nw, err := ctrl.NewNetwork("host", "host", "", NetworkOptionPersist(false)) - if err != nil { - t.Fatalf("Error creating default \"host\" network: %v", err) - } - ep, err := nw.CreateEndpoint("newendpoint", []EndpointOption{}...) - if err != nil { - t.Fatalf("Error creating endpoint: %v", err) - } - store := ctrl.(*controller).getStore(datastore.LocalScope).KVStore() - if exists, _ := store.Exists(datastore.Key(datastore.NetworkKeyPrefix, string(nw.ID()))); exists { - t.Fatalf("Network with persist=false should not be stored in KV Store") - } - if exists, _ := store.Exists(datastore.Key([]string{datastore.EndpointKeyPrefix, string(nw.ID()), string(ep.ID())}...)); exists { - t.Fatalf("Endpoint in Network with persist=false should not be stored in KV Store") - } - store.Close() -} - // OptionBoltdbWithRandomDBFile function returns a random dir for local store backend func OptionBoltdbWithRandomDBFile() ([]config.Option, error) { tmp, err := ioutil.TempFile("", "libnetwork-") diff --git a/testutils/context_solaris.go b/testutils/context_solaris.go new file mode 100644 index 00000000..2a9eee2c --- /dev/null +++ b/testutils/context_solaris.go @@ -0,0 +1,25 @@ +// +build solaris + +package testutils + +import ( + "os" + "testing" +) + +// SetupTestOSContext joins a new network namespace, and returns its associated +// teardown function. +// +// Example usage: +// +// defer SetupTestOSContext(t)() +// +func SetupTestOSContext(t *testing.T) func() { + return func() { + } +} + +// RunningOnCircleCI returns true if being executed on libnetwork Circle CI setup +func RunningOnCircleCI() bool { + return os.Getenv("CIRCLECI") != "" +}