Enable Network labels in backend

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

Signed-off-by: Alessandro Boch <aboch@docker.com>
This commit is contained in:
Alessandro Boch 2015-10-06 12:08:54 -07:00
parent b7c2b8111f
commit 02386e85d5
8 changed files with 378 additions and 127 deletions

View file

@ -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

View file

@ -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()

View file

@ -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"
)

View file

@ -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
}

View file

@ -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)
}
}
}

View file

@ -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()
}

View file

@ -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 {

View file

@ -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