diff --git a/libnetwork/api/api.go b/libnetwork/api/api.go index 8bd88d9a58..61b65e7034 100644 --- a/libnetwork/api/api.go +++ b/libnetwork/api/api.go @@ -8,7 +8,6 @@ import ( "strings" "github.com/docker/libnetwork" - "github.com/docker/libnetwork/netlabel" "github.com/docker/libnetwork/types" "github.com/gorilla/mux" ) @@ -52,9 +51,6 @@ const ( urlSbPID = "sandbox-partial-id" urlCnID = "container-id" urlCnPID = "container-partial-id" - - // BridgeNetworkDriver is the built-in default for Network Driver - BridgeNetworkDriver = "bridge" ) // NewHTTPHandler creates and initialize the HTTP handler to serve the requests for libnetwork @@ -222,16 +218,6 @@ func buildSandboxResource(sb libnetwork.Sandbox) *sandboxResource { Options Parsers *****************/ -func (nc *networkCreate) parseOptions() []libnetwork.NetworkOption { - var setFctList []libnetwork.NetworkOption - - if nc.Options != nil { - setFctList = append(setFctList, libnetwork.NetworkOptionGeneric(nc.Options)) - } - - return setFctList -} - func (sc *sandboxCreate) parseOptions() []libnetwork.SandboxOption { var setFctList []libnetwork.SandboxOption if sc.HostName != "" { @@ -278,21 +264,6 @@ func processCreateDefaults(c libnetwork.NetworkController, nc *networkCreate) { if nc.NetworkType == "" { nc.NetworkType = c.Config().Daemon.DefaultDriver } - if nc.NetworkType == BridgeNetworkDriver { - if nc.Options == nil { - nc.Options = make(map[string]interface{}) - } - genericData, ok := nc.Options[netlabel.GenericData] - if !ok { - genericData = make(map[string]interface{}) - } - gData := genericData.(map[string]interface{}) - - if _, ok := gData["BridgeName"]; !ok { - gData["BridgeName"] = nc.Name - } - nc.Options[netlabel.GenericData] = genericData - } } /*************************** @@ -307,7 +278,7 @@ func procCreateNetwork(c libnetwork.NetworkController, vars map[string]string, b } processCreateDefaults(c, &create) - nw, err := c.NewNetwork(create.NetworkType, create.Name, create.parseOptions()...) + nw, err := c.NewNetwork(create.NetworkType, create.Name, libnetwork.NetworkOptionLabels(create.Labels)) if err != nil { return "", convertNetworkError(err) } diff --git a/libnetwork/api/api_test.go b/libnetwork/api/api_test.go index d64ca008c6..502c8fb0d9 100644 --- a/libnetwork/api/api_test.go +++ b/libnetwork/api/api_test.go @@ -15,6 +15,7 @@ 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" @@ -26,12 +27,6 @@ const ( bridgeName = "docker0" ) -func getEmptyGenericOption() map[string]interface{} { - genericOption := make(map[string]interface{}) - genericOption[netlabel.GenericData] = options.Generic{} - return genericOption -} - func i2s(i interface{}) string { s, ok := i.(string) if !ok { @@ -111,6 +106,22 @@ func createTestNetwork(t *testing.T, network string) (libnetwork.NetworkControll return c, nw } +func getExposedPorts() []types.TransportPort { + return []types.TransportPort{ + types.TransportPort{Proto: types.TCP, Port: uint16(5000)}, + types.TransportPort{Proto: types.UDP, Port: uint16(400)}, + types.TransportPort{Proto: types.TCP, Port: uint16(600)}, + } +} + +func getPortMapping() []types.PortBinding { + return []types.PortBinding{ + types.PortBinding{Proto: types.TCP, Port: uint16(230), HostPort: uint16(23000)}, + types.PortBinding{Proto: types.UDP, Port: uint16(200), HostPort: uint16(22000)}, + types.PortBinding{Proto: types.TCP, Port: uint16(120), HostPort: uint16(12000)}, + } +} + func TestMain(m *testing.M) { if reexec.Init() { return @@ -214,15 +225,11 @@ func TestCreateDeleteNetwork(t *testing.T) { t.Fatalf("Expected StatusBadRequest status code, got: %v", errRsp) } - ops := options.Generic{ - netlabel.EnableIPv6: true, - netlabel.GenericData: map[string]string{ - "BridgeName": "abc", - "FixedCIDRv6": "fe80::1/64", - "AddressIP": "172.28.30.254/24", - }, + ops := []string{ + bridge.BridgeName + "=abc", + netlabel.EnableIPv6 + "=true", } - nc := networkCreate{Name: "network_1", NetworkType: bridgeNetType, Options: ops} + nc := networkCreate{Name: "network_1", NetworkType: bridgeNetType, Labels: ops} goodBody, err := json.Marshal(nc) if err != nil { t.Fatal(err) @@ -250,6 +257,29 @@ func TestCreateDeleteNetwork(t *testing.T) { if errRsp != &successResponse { t.Fatalf("Unexepected failure: %v", errRsp) } + + // Create with labels + labels := []string{ + netlabel.EnableIPv6 + "=true", + bridge.BridgeName + "=abc", + } + nc = networkCreate{Name: "network_2", NetworkType: bridgeNetType, Labels: labels} + goodBody, err = json.Marshal(nc) + if err != nil { + t.Fatal(err) + } + + _, errRsp = procCreateNetwork(c, vars, goodBody) + if errRsp != &createdResponse { + t.Fatalf("Unexepected failure: %v", errRsp) + } + + vars[urlNwName] = "network_2" + _, errRsp = procDeleteNetwork(c, vars, nil) + if errRsp != &successResponse { + t.Fatalf("Unexepected failure: %v", errRsp) + } + } func TestGetNetworksAndEndpoints(t *testing.T) { @@ -264,13 +294,10 @@ func TestGetNetworksAndEndpoints(t *testing.T) { } defer c.Stop() - ops := options.Generic{ - netlabel.GenericData: map[string]string{ - "BridgeName": "api_test_nw", - }, + ops := []string{ + bridge.BridgeName + "=api_test_nw", } - - nc := networkCreate{Name: "sh", NetworkType: bridgeNetType, Options: ops} + nc := networkCreate{Name: "sh", NetworkType: bridgeNetType, Labels: ops} body, err := json.Marshal(nc) if err != nil { t.Fatal(err) @@ -287,17 +314,9 @@ func TestGetNetworksAndEndpoints(t *testing.T) { } ec1 := endpointCreate{ - Name: "ep1", - ExposedPorts: []types.TransportPort{ - types.TransportPort{Proto: types.TCP, Port: uint16(5000)}, - types.TransportPort{Proto: types.UDP, Port: uint16(400)}, - types.TransportPort{Proto: types.TCP, Port: uint16(600)}, - }, - PortMapping: []types.PortBinding{ - types.PortBinding{Proto: types.TCP, Port: uint16(230), HostPort: uint16(23000)}, - types.PortBinding{Proto: types.UDP, Port: uint16(200), HostPort: uint16(22000)}, - types.PortBinding{Proto: types.TCP, Port: uint16(120), HostPort: uint16(12000)}, - }, + Name: "ep1", + ExposedPorts: getExposedPorts(), + PortMapping: getPortMapping(), } b1, err := json.Marshal(ec1) if err != nil { @@ -439,10 +458,10 @@ func TestGetNetworksAndEndpoints(t *testing.T) { nr1 := i2n(inr1) delete(vars, urlNwName) - vars[urlNwID] = "cacca" + vars[urlNwID] = "acacac" _, errRsp = procGetNetwork(c, vars, nil) if errRsp == &successResponse { - t.Fatalf("Unexepected failure: %v", errRsp) + t.Fatalf("Expected failure. Got: %v", errRsp) } vars[urlNwID] = nid inr2, errRsp := procGetNetwork(c, vars, nil) @@ -825,18 +844,10 @@ func TestProcPublishUnpublishService(t *testing.T) { } sp := servicePublish{ - Name: "web", - Network: "network", - ExposedPorts: []types.TransportPort{ - types.TransportPort{Proto: types.TCP, Port: uint16(6000)}, - types.TransportPort{Proto: types.UDP, Port: uint16(500)}, - types.TransportPort{Proto: types.TCP, Port: uint16(700)}, - }, - PortMapping: []types.PortBinding{ - types.PortBinding{Proto: types.TCP, Port: uint16(1230), HostPort: uint16(37000)}, - types.PortBinding{Proto: types.UDP, Port: uint16(1200), HostPort: uint16(36000)}, - types.PortBinding{Proto: types.TCP, Port: uint16(1120), HostPort: uint16(35000)}, - }, + Name: "web", + Network: "network", + ExposedPorts: getExposedPorts(), + PortMapping: getPortMapping(), } b, err = json.Marshal(sp) if err != nil { @@ -1330,6 +1341,7 @@ func TestJoinLeave(t *testing.T) { t.Fatalf("Expected failure, got: %v", errRsp) } + // bad labels vars[urlEpName] = "endpoint" key, errRsp := procJoinEndpoint(c, vars, jlb) if errRsp != &successResponse { @@ -1818,21 +1830,14 @@ func TestEndToEnd(t *testing.T) { handleRequest := NewHTTPHandler(c) - ops := options.Generic{ - netlabel.EnableIPv6: true, - netlabel.GenericData: map[string]string{ - "BridgeName": "cdef", - "FixedCIDRv6": "fe80:2000::1/64", - "EnableIPv6": "true", - "Mtu": "1460", - "EnableIPTables": "true", - "AddressIP": "172.28.30.254/16", - "EnableUserlandProxy": "true", - }, + ops := []string{ + bridge.BridgeName + "=cdef", + netlabel.EnableIPv6 + "=true", + netlabel.DriverMTU + "=1460", } // Create network - nc := networkCreate{Name: "network-fiftyfive", NetworkType: bridgeNetType, Options: ops} + nc := networkCreate{Name: "network-fiftyfive", NetworkType: bridgeNetType, Labels: ops} body, err := json.Marshal(nc) if err != nil { t.Fatal(err) diff --git a/libnetwork/api/types.go b/libnetwork/api/types.go index 6218932d00..bfe0d72c4f 100644 --- a/libnetwork/api/types.go +++ b/libnetwork/api/types.go @@ -26,7 +26,6 @@ type sandboxResource struct { ID string `json:"id"` Key string `json:"key"` ContainerID string `json:"container_id"` - // will add more fields once labels change is in } /*********** @@ -35,9 +34,9 @@ type sandboxResource struct { // networkCreate is the expected body of the "create network" http request message type networkCreate struct { - Name string `json:"name"` - NetworkType string `json:"network_type"` - Options map[string]interface{} `json:"options"` + Name string `json:"name"` + NetworkType string `json:"network_type"` + Labels []string `json:"labels"` } // endpointCreate represents the body of the "create endpoint" http request message diff --git a/libnetwork/client/network.go b/libnetwork/client/network.go index a244ad5f69..fa80011590 100644 --- a/libnetwork/client/network.go +++ b/libnetwork/client/network.go @@ -48,8 +48,8 @@ func (cli *NetworkCli) CmdNetworkCreate(chain string, args ...string) error { } // Construct network create request body - ops := make(map[string]interface{}) - nc := networkCreate{Name: cmd.Arg(0), NetworkType: *flDriver, Options: ops} + var labels []string + nc := networkCreate{Name: cmd.Arg(0), NetworkType: *flDriver, Labels: labels} obj, _, err := readBody(cli.call("POST", "/networks", nc, nil)) if err != nil { return err diff --git a/libnetwork/client/types.go b/libnetwork/client/types.go index 1848884509..b7984028e2 100644 --- a/libnetwork/client/types.go +++ b/libnetwork/client/types.go @@ -34,9 +34,9 @@ type SandboxResource struct { // networkCreate is the expected body of the "create network" http request message type networkCreate struct { - Name string `json:"name"` - NetworkType string `json:"network_type"` - Options map[string]interface{} `json:"options"` + Name string `json:"name"` + NetworkType string `json:"network_type"` + Labels []string `json:"labels"` } // serviceCreate represents the body of the "publish service" http request message diff --git a/libnetwork/cmd/dnet/dnet.go b/libnetwork/cmd/dnet/dnet.go index 6b2a526326..4f43c4fd68 100644 --- a/libnetwork/cmd/dnet/dnet.go +++ b/libnetwork/cmd/dnet/dnet.go @@ -184,7 +184,7 @@ func createDefaultNetwork(c libnetwork.NetworkController) { if nw != "" && d != "" { // Bridge driver is special due to legacy reasons if d == "bridge" { - genericOption[netlabel.GenericData] = map[string]interface{}{ + genericOption[netlabel.GenericData] = map[string]string{ "BridgeName": "docker0", "DefaultBridge": "true", } diff --git a/libnetwork/drivers/bridge/bridge.go b/libnetwork/drivers/bridge/bridge.go index bbf2cd2fd6..ecfd5d14c5 100644 --- a/libnetwork/drivers/bridge/bridge.go +++ b/libnetwork/drivers/bridge/bridge.go @@ -178,7 +178,7 @@ func (c *networkConfiguration) Conflicts(o *networkConfiguration) error { // Also empty, becasue only one network with empty name is allowed if c.BridgeName == o.BridgeName { - return fmt.Errorf("networks have same name") + return fmt.Errorf("networks have same bridge name") } // They must be in different subnets @@ -196,79 +196,46 @@ func (c *networkConfiguration) Conflicts(o *networkConfiguration) error { return nil } -// fromMap retrieve the configuration data from the map form. -func (c *networkConfiguration) fromMap(data map[string]interface{}) error { +func (c *networkConfiguration) fromLabels(labels map[string]string) error { var err error - - if i, ok := data["BridgeName"]; ok && i != nil { - if c.BridgeName, ok = i.(string); !ok { - return types.BadRequestErrorf("invalid type for BridgeName value") - } - } - - if i, ok := data["Mtu"]; ok && i != nil { - if s, ok := i.(string); ok { - if c.Mtu, err = strconv.Atoi(s); err != nil { - return types.BadRequestErrorf("failed to parse Mtu value: %s", 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()) } - } else { - return types.BadRequestErrorf("invalid type for Mtu value") - } - } - - if i, ok := data["EnableIPv6"]; ok && i != nil { - if s, ok := i.(string); ok { - if c.EnableIPv6, err = strconv.ParseBool(s); err != nil { - return types.BadRequestErrorf("failed to parse EnableIPv6 value: %s", err.Error()) + case netlabel.EnableIPv6: + if c.EnableIPv6, err = strconv.ParseBool(value); err != nil { + return parseErr(label, value, err.Error()) } - } else { - return types.BadRequestErrorf("invalid type for EnableIPv6 value") - } - } - - if i, ok := data["EnableIPMasquerade"]; ok && i != nil { - if s, ok := i.(string); ok { - if c.EnableIPMasquerade, err = strconv.ParseBool(s); err != nil { - return types.BadRequestErrorf("failed to parse EnableIPMasquerade value: %s", err.Error()) + case EnableIPMasquerade: + if c.EnableIPMasquerade, err = strconv.ParseBool(value); err != nil { + return parseErr(label, value, err.Error()) } - } else { - return types.BadRequestErrorf("invalid type for EnableIPMasquerade value") - } - } - - if i, ok := data["EnableICC"]; ok && i != nil { - if s, ok := i.(string); ok { - if c.EnableICC, err = strconv.ParseBool(s); err != nil { - return types.BadRequestErrorf("failed to parse EnableICC value: %s", err.Error()) + case EnableICC: + if c.EnableICC, err = strconv.ParseBool(value); err != nil { + return parseErr(label, value, err.Error()) } - } else { - return types.BadRequestErrorf("invalid type for EnableICC value") - } - } - - if i, ok := data["DefaultBridge"]; ok && i != nil { - if s, ok := i.(string); ok { - if c.DefaultBridge, err = strconv.ParseBool(s); err != nil { - return types.BadRequestErrorf("failed to parse DefaultBridge value: %s", err.Error()) + case DefaultBridge: + if c.DefaultBridge, err = strconv.ParseBool(value); err != nil { + return parseErr(label, value, err.Error()) } - } else { - return types.BadRequestErrorf("invalid type for DefaultBridge value") - } - } - - if i, ok := data["DefaultBindingIP"]; ok && i != nil { - if s, ok := i.(string); ok { - if c.DefaultBindingIP = net.ParseIP(s); c.DefaultBindingIP == nil { - return types.BadRequestErrorf("failed to parse DefaultBindingIP value") + case DefaultBindingIP: + if c.DefaultBindingIP = net.ParseIP(value); c.DefaultBindingIP == nil { + return parseErr(label, value, "nil ip") } - } else { - return types.BadRequestErrorf("invalid type for DefaultBindingIP value") } } return nil } +func parseErr(label, value, errString string) error { + return types.BadRequestErrorf("failed to parse %s value: %v (%s)", label, value, errString) +} + func (n *bridgeNetwork) getDriverChains() (*iptables.ChainInfo, *iptables.ChainInfo, error) { n.Lock() defer n.Unlock() @@ -442,12 +409,12 @@ func parseNetworkGenericOptions(data interface{}) (*networkConfiguration, error) switch opt := data.(type) { case *networkConfiguration: config = opt - case map[string]interface{}: + case map[string]string: config = &networkConfiguration{ EnableICC: true, EnableIPMasquerade: true, } - err = config.fromMap(opt) + err = config.fromLabels(opt) case options.Generic: var opaqueConfig interface{} if opaqueConfig, err = options.GenerateFromModel(opt, config); err == nil { @@ -491,8 +458,10 @@ func (c *networkConfiguration) processIPAM(id string, ipamV4Data, ipamV6Data []d } func parseNetworkOptions(id string, option options.Generic) (*networkConfiguration, error) { - var err error - config := &networkConfiguration{} + var ( + err error + config = &networkConfiguration{} + ) // Parse generic label first, config will be re-assigned if genData, ok := option[netlabel.GenericData]; ok && genData != nil { @@ -502,8 +471,8 @@ func parseNetworkOptions(id string, option options.Generic) (*networkConfigurati } // Process well-known labels next - if _, ok := option[netlabel.EnableIPv6]; ok { - config.EnableIPv6 = option[netlabel.EnableIPv6].(bool) + if val, ok := option[netlabel.EnableIPv6]; ok { + config.EnableIPv6 = val.(bool) } // Finally validate the configuration diff --git a/libnetwork/drivers/bridge/bridge_test.go b/libnetwork/drivers/bridge/bridge_test.go index e1891af8fe..b7677eeb85 100644 --- a/libnetwork/drivers/bridge/bridge_test.go +++ b/libnetwork/drivers/bridge/bridge_test.go @@ -97,6 +97,58 @@ func TestCreateNoConfig(t *testing.T) { } } +func TestCreateFullOptionsLabels(t *testing.T) { + 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) + } + + labels := map[string]string{ + BridgeName: "cu", + netlabel.EnableIPv6: "true", + EnableICC: "true", + EnableIPMasquerade: "true", + DefaultBindingIP: "127.0.0.1", + } + + netOption := make(map[string]interface{}) + netOption[netlabel.GenericData] = labels + + err := d.CreateNetwork("dummy", netOption, getIPv4Data(t), nil) + 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 != "cu" { + 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") + } +} + func TestCreate(t *testing.T) { defer testutils.SetupTestOSContext(t)() d := newDriver() diff --git a/libnetwork/drivers/bridge/labels.go b/libnetwork/drivers/bridge/labels.go new file mode 100644 index 0000000000..7447bd3f93 --- /dev/null +++ b/libnetwork/drivers/bridge/labels.go @@ -0,0 +1,18 @@ +package bridge + +const ( + // 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" +) diff --git a/libnetwork/netlabel/labels.go b/libnetwork/netlabel/labels.go index dd30fbe5ae..71dff08632 100644 --- a/libnetwork/netlabel/labels.go +++ b/libnetwork/netlabel/labels.go @@ -1,6 +1,8 @@ package netlabel -import "strings" +import ( + "strings" +) const ( // Prefix constant marks the reserved label space for libnetwork @@ -22,12 +24,15 @@ const ( // MacAddress constant represents Mac Address config of a Container MacAddress = Prefix + ".endpoint.macaddress" - // ExposedPorts constant represents exposedports of a Container + // ExposedPorts constant represents the container's Exposed Ports ExposedPorts = Prefix + ".endpoint.exposedports" //EnableIPv6 constant represents enabling IPV6 at network level EnableIPv6 = Prefix + ".enable_ipv6" + // DriverMTU constant represents the MTU size for the network driver + DriverMTU = DriverPrefix + ".mtu" + // OverlayBindInterface constant represents overlay driver bind interface OverlayBindInterface = DriverPrefix + ".overlay.bind_interface" @@ -74,15 +79,51 @@ func MakeKVProviderConfig(scope string) string { } // Key extracts the key portion of the label -func Key(label string) string { - kv := strings.SplitN(label, "=", 2) - - return kv[0] +func Key(label string) (key string) { + if kv := strings.SplitN(label, "=", 2); len(kv) > 0 { + key = kv[0] + } + return } // Value extracts the value portion of the label -func Value(label string) string { - kv := strings.SplitN(label, "=", 2) - - return kv[1] +func Value(label string) (value string) { + if kv := strings.SplitN(label, "=", 2); len(kv) > 1 { + value = kv[1] + } + return +} + +// KeyValue decomposes the label in the (key,value) pair +func KeyValue(label string) (key string, value string) { + if kv := strings.SplitN(label, "=", 2); len(kv) > 0 { + key = kv[0] + if len(kv) > 1 { + value = kv[1] + } + } + return +} + +// ToMap converts a list of labels in a map of (key,value) pairs +func ToMap(labels []string) map[string]string { + m := make(map[string]string, len(labels)) + for _, l := range labels { + k, v := KeyValue(l) + m[k] = v + } + return m +} + +// FromMap converts a map of (key,value) pairs in a lsit of labels +func FromMap(m map[string]string) []string { + l := make([]string, 0, len(m)) + for k, v := range m { + s := k + if v != "" { + s = s + "=" + v + } + l = append(l, s) + } + return l } diff --git a/libnetwork/netlabel/labels_test.go b/libnetwork/netlabel/labels_test.go new file mode 100644 index 0000000000..892dfe5b33 --- /dev/null +++ b/libnetwork/netlabel/labels_test.go @@ -0,0 +1,77 @@ +package netlabel + +import ( + "testing" + + _ "github.com/docker/libnetwork/testutils" +) + +var input = []struct { + label string + key string + value string +}{ + {"com.directory.person.name=joe", "com.directory.person.name", "joe"}, + {"com.directory.person.age=24", "com.directory.person.age", "24"}, + {"com.directory.person.address=1234 First st.", "com.directory.person.address", "1234 First st."}, + {"com.directory.person.friends=", "com.directory.person.friends", ""}, + {"com.directory.person.nickname=o=u=8", "com.directory.person.nickname", "o=u=8"}, + {"", "", ""}, + {"com.directory.person.student", "com.directory.person.student", ""}, +} + +func TestKeyValue(t *testing.T) { + for _, i := range input { + k, v := KeyValue(i.label) + if k != i.key || v != i.value { + t.Fatalf("unexpected: %s, %s", k, v) + } + } +} + +func TestToMap(t *testing.T) { + lista := make([]string, len(input)) + for ind, i := range input { + lista[ind] = i.label + } + + mappa := ToMap(lista) + + if len(mappa) != len(lista) { + t.Fatalf("Incorrect map length. Expected %d. Got %d", len(lista), len(mappa)) + } + + for _, i := range input { + if v, ok := mappa[i.key]; !ok || v != i.value { + t.Fatalf("Cannot find key or value for key: %s", i.key) + } + } +} + +func TestFromMap(t *testing.T) { + var m map[string]string + lbls := FromMap(m) + if len(lbls) != 0 { + t.Fatalf("unexpected lbls length") + } + + m = make(map[string]string, 3) + m["peso"] = "85" + m["statura"] = "170" + m["maschio"] = "" + + lbls = FromMap(m) + if len(lbls) != 3 { + t.Fatalf("unexpected lbls length") + } + + for _, l := range lbls { + switch l { + case "peso=85": + case "statura=170": + case "maschio": + default: + t.Fatalf("unexpected label: %s", l) + } + } +} diff --git a/libnetwork/network.go b/libnetwork/network.go index 4537273d2f..da2be66902 100644 --- a/libnetwork/network.go +++ b/libnetwork/network.go @@ -4,6 +4,7 @@ import ( "encoding/json" "fmt" "net" + "strconv" "sync" log "github.com/Sirupsen/logrus" @@ -49,6 +50,15 @@ type Network interface { // EndpointByID returns the Endpoint which has the passed id. If not found, the error ErrNoSuchEndpoint is returned. EndpointByID(id string) (Endpoint, error) + + // Return certain operational data belonging to this network + Info() NetworkInfo +} + +// NetworkInfo returns operational information about the network +type NetworkInfo interface { + Labels() []string + Scope() string } // EndpointWalker is a client provided function which will be used to walk the Endpoints. @@ -150,7 +160,6 @@ type network struct { dbExists bool persist bool stopWatchCh chan struct{} - scope string drvOnce *sync.Once sync.Mutex } @@ -382,6 +391,18 @@ func (n *network) UnmarshalJSON(b []byte) (err error) { if v, ok := netMap["generic"]; ok { n.generic = v.(map[string]interface{}) + // Restore labels in their map[string]string form + if v, ok := n.generic[netlabel.GenericData]; ok { + var lmap map[string]string + ba, err := json.Marshal(v) + if err != nil { + return err + } + if err := json.Unmarshal(ba, &lmap); err != nil { + return err + } + n.generic[netlabel.GenericData] = lmap + } } if v, ok := netMap["persist"]; ok { n.persist = v.(bool) @@ -391,7 +412,6 @@ func (n *network) UnmarshalJSON(b []byte) (err error) { } else { n.ipamType = ipamapi.DefaultIPAM } - if v, ok := netMap["addrSpace"]; ok { n.addrSpace = v.(string) } @@ -451,6 +471,25 @@ func NetworkOptionIpam(ipamDriver string, addrSpace string, ipV4 []*IpamConf, ip } } +// NetworkOptionLabels function returns an option setter for any parameter described by a map +func NetworkOptionLabels(labels []string) NetworkOption { + return func(n *network) { + if n.generic == nil { + n.generic = make(map[string]interface{}) + } + opts := netlabel.ToMap(labels) + // Store the options + n.generic[netlabel.GenericData] = opts + // Decode and store the endpoint options of libnetwork interest + if val, ok := opts[netlabel.EnableIPv6]; ok { + var err error + if n.enableIPv6, err = strconv.ParseBool(val); err != nil { + log.Warnf("Failed to parse %s' value: %s (%s)", netlabel.EnableIPv6, val, err.Error()) + } + } + } +} + func (n *network) processOptions(options ...NetworkOption) { for _, opt := range options { if opt != nil { @@ -1003,3 +1042,22 @@ func (n *network) deriveAddressSpace() (string, error) { } return ipd.defaultLocalAddressSpace, nil } + +func (n *network) Info() NetworkInfo { + return n +} + +func (n *network) Labels() []string { + n.Lock() + defer n.Unlock() + if n.generic != nil { + if m, ok := n.generic[netlabel.GenericData]; ok { + return netlabel.FromMap(m.(map[string]string)) + } + } + return []string{} +} + +func (n *network) Scope() string { + return n.driverScope() +} diff --git a/libnetwork/types/types.go b/libnetwork/types/types.go index 22463c9540..7ada9643c0 100644 --- a/libnetwork/types/types.go +++ b/libnetwork/types/types.go @@ -5,6 +5,7 @@ import ( "bytes" "fmt" "net" + "strconv" "strings" ) @@ -17,11 +18,46 @@ type TransportPort struct { Port uint16 } +// Equal checks if this instance of Transportport is equal to the passed one +func (t *TransportPort) Equal(o *TransportPort) bool { + if t == o { + return true + } + + if o == nil { + return false + } + + if t.Proto != o.Proto || t.Port != o.Port { + return false + } + + return true +} + // GetCopy returns a copy of this TransportPort structure instance func (t *TransportPort) GetCopy() TransportPort { return TransportPort{Proto: t.Proto, Port: t.Port} } +// String returns the TransportPort structure in string form +func (t *TransportPort) String() string { + return fmt.Sprintf("%s/%d", t.Proto.String(), t.Port) +} + +// FromString reads the TransportPort structure from string +func (t *TransportPort) FromString(s string) error { + ps := strings.Split(s, "/") + if len(ps) == 2 { + t.Proto = ParseProtocol(ps[0]) + if p, err := strconv.ParseUint(ps[1], 10, 16); err == nil { + t.Port = uint16(p) + return nil + } + } + return BadRequestErrorf("invalid format for transport port: %s", s) +} + // PortBinding represent a port binding between the container and the host type PortBinding struct { Proto Protocol @@ -68,6 +104,62 @@ func (p *PortBinding) GetCopy() PortBinding { } } +// String return the PortBinding structure in string form +func (p *PortBinding) String() string { + ret := fmt.Sprintf("%s/", p.Proto) + if p.IP != nil { + ret = fmt.Sprintf("%s%s", ret, p.IP.String()) + } + ret = fmt.Sprintf("%s:%d/", ret, p.Port) + if p.HostIP != nil { + ret = fmt.Sprintf("%s%s", ret, p.HostIP.String()) + } + ret = fmt.Sprintf("%s:%d", ret, p.HostPort) + return ret +} + +// FromString reads the TransportPort structure from string +func (p *PortBinding) FromString(s string) error { + ps := strings.Split(s, "/") + if len(ps) != 3 { + return BadRequestErrorf("invalid format for port binding: %s", s) + } + + p.Proto = ParseProtocol(ps[0]) + + var err error + if p.IP, p.Port, err = parseIPPort(ps[1]); err != nil { + return BadRequestErrorf("failed to parse Container IP/Port in port binding: %s", err.Error()) + } + + if p.HostIP, p.HostPort, err = parseIPPort(ps[2]); err != nil { + return BadRequestErrorf("failed to parse Host IP/Port in port binding: %s", err.Error()) + } + + return nil +} + +func parseIPPort(s string) (net.IP, uint16, error) { + pp := strings.Split(s, ":") + if len(pp) != 2 { + return nil, 0, BadRequestErrorf("invalid format: %s", s) + } + + var ip net.IP + if pp[0] != "" { + if ip = net.ParseIP(pp[0]); ip == nil { + return nil, 0, BadRequestErrorf("invalid ip: %s", pp[0]) + } + } + + port, err := strconv.ParseUint(pp[1], 10, 16) + if err != nil { + return nil, 0, BadRequestErrorf("invalid port: %s", pp[1]) + } + + return ip, uint16(port), nil +} + // Equal checks if this instance of PortBinding is equal to the passed one func (p *PortBinding) Equal(o *PortBinding) bool { if p == o { diff --git a/libnetwork/types/types_test.go b/libnetwork/types/types_test.go index e5a94d22c2..c144df1f5c 100644 --- a/libnetwork/types/types_test.go +++ b/libnetwork/types/types_test.go @@ -8,6 +8,42 @@ import ( var runningInContainer = flag.Bool("incontainer", false, "Indicates if the test is running in a container") +func TestTransportPortConv(t *testing.T) { + sform := "tcp/23" + tp := &TransportPort{Proto: TCP, Port: uint16(23)} + + if sform != tp.String() { + t.Fatalf("String() method failed") + } + + rc := new(TransportPort) + if err := rc.FromString(sform); err != nil { + t.Fatal(err) + } + if !tp.Equal(rc) { + t.Fatalf("FromString() method failed") + } +} + +func TestTransportPortBindingConv(t *testing.T) { + sform := "tcp/172.28.30.23:80/112.0.43.56:8001" + pb := &PortBinding{ + Proto: TCP, + IP: net.IPv4(172, 28, 30, 23), + Port: uint16(80), + HostIP: net.IPv4(112, 0, 43, 56), + HostPort: uint16(8001), + } + + rc := new(PortBinding) + if err := rc.FromString(sform); err != nil { + t.Fatal(err) + } + if !pb.Equal(rc) { + t.Fatalf("FromString() method failed") + } +} + func TestErrorConstructors(t *testing.T) { var err error