Просмотр исходного кода

Enable Network labels in backend

- Allow labels to be passed to network driver during network create

Signed-off-by: Alessandro Boch <aboch@docker.com>
Alessandro Boch 9 лет назад
Родитель
Сommit
02386e85d5

+ 36 - 67
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

+ 52 - 0
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, nil, 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()

+ 18 - 0
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"
+)

+ 36 - 18
libnetwork/netlabel/labels.go

@@ -1,7 +1,6 @@
 package netlabel
 
 import (
-	"fmt"
 	"strings"
 )
 
@@ -31,6 +30,9 @@ const (
 	//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"
 
@@ -77,35 +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) (string, string, error) {
-	kv := strings.SplitN(label, "=", 2)
-	if len(kv) != 2 {
-		return "", "", fmt.Errorf("invalid label: %s", label)
+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 kv[0], kv[1], nil
+	return
 }
 
-// ToMap converts a list of labels in amap of (key,value) pairs
+// 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 {
-		if k, v, err := KeyValue(l); err == nil {
-			m[k] = v
-		}
+		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
+}

+ 48 - 40
libnetwork/netlabel/labels_test.go

@@ -6,44 +6,30 @@ import (
 	_ "github.com/docker/libnetwork/testutils"
 )
 
-func TestKeyValue(t *testing.T) {
-	input := []struct {
-		label string
-		key   string
-		value string
-		good  bool
-	}{
-		{"name=joe", "name", "joe", true},
-		{"age=24", "age", "24", true},
-		{"address:1234 First st.", "", "", false},
-		{"girlfriend=", "girlfriend", "", true},
-		{"nickname=o=u=8", "nickname", "o=u=8", true},
-		{"", "", "", false},
-	}
+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, err := KeyValue(i.label)
-		if k != i.key || v != i.value || i.good != (err == nil) {
-			t.Fatalf("unexpected: %s, %s, %v", k, v, err)
+		k, v := KeyValue(i.label)
+		if k != i.key || v != i.value {
+			t.Fatalf("unexpected: %s, %s", k, v)
 		}
 	}
 }
 
 func TestToMap(t *testing.T) {
-	input := []struct {
-		label string
-		key   string
-		value string
-		good  bool
-	}{
-		{"name=joe", "name", "joe", true},
-		{"age=24", "age", "24", true},
-		{"address:1234 First st.", "", "", false},
-		{"girlfriend=", "girlfriend", "", true},
-		{"nickname=o=u=8", "nickname", "o=u=8", true},
-		{"", "", "", false},
-	}
-
 	lista := make([]string, len(input))
 	for ind, i := range input {
 		lista[ind] = i.label
@@ -51,19 +37,41 @@ func TestToMap(t *testing.T) {
 
 	mappa := ToMap(lista)
 
-	if len(mappa) != len(lista)-2 {
+	if len(mappa) != len(lista) {
 		t.Fatalf("Incorrect map length. Expected %d. Got %d", len(lista), len(mappa))
 	}
 
 	for _, i := range input {
-		if i.good {
-			if v, ok := mappa[i.key]; !ok || v != i.value {
-				t.Fatalf("Cannot find key or value for key: %s", i.key)
-			}
-		} else {
-			if _, ok := mappa[i.key]; ok {
-				t.Fatalf("Found invalid key in map: %s", i.key)
-			}
+		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)
 		}
 	}
 }

+ 60 - 2
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()
+}

+ 92 - 0
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 {

+ 36 - 0
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