From 173b3c364ebedd56bd5b6c190a9b41f592ba57d6 Mon Sep 17 00:00:00 2001 From: Alessandro Boch Date: Tue, 13 Dec 2016 15:04:59 -0800 Subject: [PATCH] Allow user to control the default address pools - Via daemon flag --default-address-pools base=,size= Signed-off-by: Elango Siva --- cmd/dockerd/config_unix.go | 3 + daemon/config/config.go | 7 ++ daemon/config/config_unix.go | 1 - daemon/daemon.go | 4 + daemon/daemon_unix.go | 3 + integration/network/service_test.go | 179 ++++++++++++++++++++++++++++ opts/address_pools.go | 84 +++++++++++++ opts/address_pools_test.go | 20 ++++ 8 files changed, 300 insertions(+), 1 deletion(-) create mode 100644 opts/address_pools.go create mode 100644 opts/address_pools_test.go diff --git a/cmd/dockerd/config_unix.go b/cmd/dockerd/config_unix.go index a3b0e36a08..37acd38be4 100644 --- a/cmd/dockerd/config_unix.go +++ b/cmd/dockerd/config_unix.go @@ -18,6 +18,7 @@ func installConfigFlags(conf *config.Config, flags *pflag.FlagSet) { installUnixConfigFlags(conf, flags) conf.Ulimits = make(map[string]*units.Ulimit) + conf.NetworkConfig.DefaultAddressPools = opts.PoolsOpt{} // Set default value for `--default-shm-size` conf.ShmSize = opts.MemBytes(config.DefaultShmSize) @@ -44,4 +45,6 @@ func installConfigFlags(conf *config.Config, flags *pflag.FlagSet) { flags.Var(&conf.ShmSize, "default-shm-size", "Default shm size for containers") flags.BoolVar(&conf.NoNewPrivileges, "no-new-privileges", false, "Set no-new-privileges by default for new containers") flags.StringVar(&conf.IpcMode, "default-ipc-mode", config.DefaultIpcMode, `Default mode for containers ipc ("shareable" | "private")`) + flags.Var(&conf.NetworkConfig.DefaultAddressPools, "default-address-pool", "Default address pools for node specific local networks") + } diff --git a/daemon/config/config.go b/daemon/config/config.go index e86d025a5a..bc4dea9c3c 100644 --- a/daemon/config/config.go +++ b/daemon/config/config.go @@ -71,6 +71,12 @@ type commonBridgeConfig struct { FixedCIDR string `json:"fixed-cidr,omitempty"` } +// NetworkConfig stores the daemon-wide networking configurations +type NetworkConfig struct { + // Default address pools for docker networks + DefaultAddressPools opts.PoolsOpt `json:"default-address-pools,omitempty"` +} + // CommonTLSOptions defines TLS configuration for the daemon server. // It includes json tags to deserialize configuration from a file // using the same names that the flags in the command line use. @@ -173,6 +179,7 @@ type CommonConfig struct { LogConfig BridgeConfig // bridgeConfig holds bridge network specific configuration. + NetworkConfig registry.ServiceOptions sync.Mutex diff --git a/daemon/config/config_unix.go b/daemon/config/config_unix.go index b71fc9d75b..ee091b8bfe 100644 --- a/daemon/config/config_unix.go +++ b/daemon/config/config_unix.go @@ -23,7 +23,6 @@ type Config struct { // These fields are common to all unix platforms. CommonUnixConfig - // Fields below here are platform specific. CgroupParent string `json:"cgroup-parent,omitempty"` EnableSelinuxSupport bool `json:"selinux-enabled,omitempty"` diff --git a/daemon/daemon.go b/daemon/daemon.go index 9812c0be6c..93a1ea9a76 100644 --- a/daemon/daemon.go +++ b/daemon/daemon.go @@ -1223,6 +1223,10 @@ func (daemon *Daemon) networkOptions(dconfig *config.Config, pg plugingetter.Plu options = append(options, nwconfig.OptionLabels(dconfig.Labels)) options = append(options, driverOptions(dconfig)...) + if len(dconfig.NetworkConfig.DefaultAddressPools.Value()) > 0 { + options = append(options, nwconfig.OptionDefaultAddressPoolConfig(dconfig.NetworkConfig.DefaultAddressPools.Value())) + } + if daemon.configStore != nil && daemon.configStore.LiveRestoreEnabled && len(activeSandboxes) != 0 { options = append(options, nwconfig.OptionActiveSandboxes(activeSandboxes)) } diff --git a/daemon/daemon_unix.go b/daemon/daemon_unix.go index 620af83d69..f6f0f166bd 100644 --- a/daemon/daemon_unix.go +++ b/daemon/daemon_unix.go @@ -833,6 +833,9 @@ func (daemon *Daemon) initNetworkController(config *config.Config, activeSandbox if err = n.Delete(); err != nil { return nil, fmt.Errorf("could not delete the default bridge network: %v", err) } + if len(config.NetworkConfig.DefaultAddressPools.Value()) > 0 && !daemon.configStore.LiveRestoreEnabled { + removeDefaultBridgeInterface() + } } if !config.DisableBridge { diff --git a/integration/network/service_test.go b/integration/network/service_test.go index 4ff2e3858a..1203aa0eeb 100644 --- a/integration/network/service_test.go +++ b/integration/network/service_test.go @@ -9,10 +9,189 @@ import ( swarmtypes "github.com/docker/docker/api/types/swarm" "github.com/docker/docker/client" "github.com/docker/docker/integration/internal/swarm" + "github.com/docker/docker/internal/test/daemon" "github.com/gotestyourself/gotestyourself/assert" + "github.com/gotestyourself/gotestyourself/icmd" "github.com/gotestyourself/gotestyourself/poll" ) +// delInterface removes given network interface +func delInterface(t *testing.T, ifName string) { + icmd.RunCommand("ip", "link", "delete", ifName).Assert(t, icmd.Success) + icmd.RunCommand("iptables", "-t", "nat", "--flush").Assert(t, icmd.Success) + icmd.RunCommand("iptables", "--flush").Assert(t, icmd.Success) +} + +func TestDaemonRestartWithLiveRestore(t *testing.T) { + d := daemon.New(t) + defer d.Stop(t) + d.Start(t) + d.Restart(t, "--live-restore=true", + "--default-address-pool", "base=175.30.0.0/16,size=16", + "--default-address-pool", "base=175.33.0.0/16,size=24") + + // Verify bridge network's subnet + cli, err := d.NewClient() + assert.Assert(t, err) + defer cli.Close() + out, err := cli.NetworkInspect(context.Background(), "bridge", types.NetworkInspectOptions{}) + assert.NilError(t, err) + // Make sure docker0 doesn't get override with new IP in live restore case + assert.Equal(t, out.IPAM.Config[0].Subnet, "172.18.0.0/16") +} + +func TestDaemonDefaultNetworkPools(t *testing.T) { + // Remove docker0 bridge and the start daemon defining the predefined address pools + defaultNetworkBridge := "docker0" + delInterface(t, defaultNetworkBridge) + d := daemon.New(t) + defer d.Stop(t) + d.Start(t, + "--default-address-pool", "base=175.30.0.0/16,size=16", + "--default-address-pool", "base=175.33.0.0/16,size=24") + + // Verify bridge network's subnet + cli, err := d.NewClient() + assert.Assert(t, err) + defer cli.Close() + out, err := cli.NetworkInspect(context.Background(), "bridge", types.NetworkInspectOptions{}) + assert.NilError(t, err) + assert.Equal(t, out.IPAM.Config[0].Subnet, "175.30.0.0/16") + + // Create a bridge network and verify its subnet is the second default pool + name := "elango" + networkCreate := types.NetworkCreate{ + CheckDuplicate: false, + } + networkCreate.Driver = "bridge" + _, err = cli.NetworkCreate(context.Background(), name, networkCreate) + assert.NilError(t, err) + out, err = cli.NetworkInspect(context.Background(), name, types.NetworkInspectOptions{}) + assert.NilError(t, err) + assert.Equal(t, out.IPAM.Config[0].Subnet, "175.33.0.0/24") + + // Create a bridge network and verify its subnet is the third default pool + name = "saanvi" + networkCreate = types.NetworkCreate{ + CheckDuplicate: false, + } + networkCreate.Driver = "bridge" + _, err = cli.NetworkCreate(context.Background(), name, networkCreate) + assert.NilError(t, err) + out, err = cli.NetworkInspect(context.Background(), name, types.NetworkInspectOptions{}) + assert.NilError(t, err) + assert.Equal(t, out.IPAM.Config[0].Subnet, "175.33.1.0/24") + delInterface(t, defaultNetworkBridge) + +} + +func TestDaemonRestartWithExistingNetwork(t *testing.T) { + defaultNetworkBridge := "docker0" + d := daemon.New(t) + d.Start(t) + defer d.Stop(t) + // Verify bridge network's subnet + cli, err := d.NewClient() + assert.Assert(t, err) + defer cli.Close() + + // Create a bridge network + name := "elango" + networkCreate := types.NetworkCreate{ + CheckDuplicate: false, + } + networkCreate.Driver = "bridge" + _, err = cli.NetworkCreate(context.Background(), name, networkCreate) + assert.NilError(t, err) + out, err := cli.NetworkInspect(context.Background(), name, types.NetworkInspectOptions{}) + assert.NilError(t, err) + networkip := out.IPAM.Config[0].Subnet + + // Restart daemon with default address pool option + d.Restart(t, + "--default-address-pool", "base=175.30.0.0/16,size=16", + "--default-address-pool", "base=175.33.0.0/16,size=24") + + out1, err := cli.NetworkInspect(context.Background(), name, types.NetworkInspectOptions{}) + assert.NilError(t, err) + assert.Equal(t, out1.IPAM.Config[0].Subnet, networkip) + delInterface(t, defaultNetworkBridge) +} + +func TestDaemonRestartWithExistingNetworkWithDefaultPoolRange(t *testing.T) { + defaultNetworkBridge := "docker0" + d := daemon.New(t) + d.Start(t) + defer d.Stop(t) + // Verify bridge network's subnet + cli, err := d.NewClient() + assert.Assert(t, err) + defer cli.Close() + + // Create a bridge network + name := "elango" + networkCreate := types.NetworkCreate{ + CheckDuplicate: false, + } + networkCreate.Driver = "bridge" + _, err = cli.NetworkCreate(context.Background(), name, networkCreate) + assert.NilError(t, err) + out, err := cli.NetworkInspect(context.Background(), name, types.NetworkInspectOptions{}) + assert.NilError(t, err) + networkip := out.IPAM.Config[0].Subnet + + // Create a bridge network + name = "sthira" + networkCreate = types.NetworkCreate{ + CheckDuplicate: false, + } + networkCreate.Driver = "bridge" + _, err = cli.NetworkCreate(context.Background(), name, networkCreate) + assert.NilError(t, err) + out, err = cli.NetworkInspect(context.Background(), name, types.NetworkInspectOptions{}) + assert.NilError(t, err) + networkip2 := out.IPAM.Config[0].Subnet + + // Restart daemon with default address pool option + d.Restart(t, + "--default-address-pool", "base=175.18.0.0/16,size=16", + "--default-address-pool", "base=175.19.0.0/16,size=24") + + // Create a bridge network + name = "saanvi" + networkCreate = types.NetworkCreate{ + CheckDuplicate: false, + } + networkCreate.Driver = "bridge" + _, err = cli.NetworkCreate(context.Background(), name, networkCreate) + assert.NilError(t, err) + out1, err := cli.NetworkInspect(context.Background(), name, types.NetworkInspectOptions{}) + assert.NilError(t, err) + + assert.Check(t, (out1.IPAM.Config[0].Subnet != networkip)) + assert.Check(t, (out1.IPAM.Config[0].Subnet != networkip2)) + delInterface(t, defaultNetworkBridge) +} + +func TestDaemonWithBipAndDefaultNetworkPool(t *testing.T) { + defaultNetworkBridge := "docker0" + d := daemon.New(t) + defer d.Stop(t) + d.Start(t, "--bip=172.60.0.1/16", + "--default-address-pool", "base=175.30.0.0/16,size=16", + "--default-address-pool", "base=175.33.0.0/16,size=24") + + // Verify bridge network's subnet + cli, err := d.NewClient() + assert.Assert(t, err) + defer cli.Close() + out, err := cli.NetworkInspect(context.Background(), "bridge", types.NetworkInspectOptions{}) + assert.NilError(t, err) + // Make sure BIP IP doesn't get override with new default address pool . + assert.Equal(t, out.IPAM.Config[0].Subnet, "172.60.0.1/16") + delInterface(t, defaultNetworkBridge) +} + func TestServiceWithPredefinedNetwork(t *testing.T) { defer setupTest(t)() d := swarm.NewSwarm(t, testEnv) diff --git a/opts/address_pools.go b/opts/address_pools.go new file mode 100644 index 0000000000..99c89bb626 --- /dev/null +++ b/opts/address_pools.go @@ -0,0 +1,84 @@ +package opts + +import ( + "encoding/csv" + "encoding/json" + "fmt" + "strconv" + "strings" + + types "github.com/docker/libnetwork/ipamutils" +) + +// PoolsOpt is a Value type for parsing the default address pools definitions +type PoolsOpt struct { + values []*types.NetworkToSplit +} + +// UnmarshalJSON fills values structure info from JSON input +func (p *PoolsOpt) UnmarshalJSON(raw []byte) error { + return json.Unmarshal(raw, &(p.values)) +} + +// Set predefined pools +func (p *PoolsOpt) Set(value string) error { + csvReader := csv.NewReader(strings.NewReader(value)) + fields, err := csvReader.Read() + if err != nil { + return err + } + + poolsDef := types.NetworkToSplit{} + + for _, field := range fields { + parts := strings.SplitN(field, "=", 2) + if len(parts) != 2 { + return fmt.Errorf("invalid field '%s' must be a key=value pair", field) + } + + key := strings.ToLower(parts[0]) + value := strings.ToLower(parts[1]) + + switch key { + case "base": + poolsDef.Base = value + case "size": + size, err := strconv.Atoi(value) + if err != nil { + return fmt.Errorf("invalid size value: %q (must be integer): %v", value, err) + } + poolsDef.Size = size + default: + return fmt.Errorf("unexpected key '%s' in '%s'", key, field) + } + } + + p.values = append(p.values, &poolsDef) + + return nil +} + +// Type returns the type of this option +func (p *PoolsOpt) Type() string { + return "pool-options" +} + +// String returns a string repr of this option +func (p *PoolsOpt) String() string { + pools := []string{} + for _, pool := range p.values { + repr := fmt.Sprintf("%s %d", pool.Base, pool.Size) + pools = append(pools, repr) + } + return strings.Join(pools, ", ") +} + +// Value returns the mounts +func (p *PoolsOpt) Value() []*types.NetworkToSplit { + return p.values +} + +// Name returns the flag name of this option +func (p *PoolsOpt) Name() string { + return "default-address-pools" +} diff --git a/opts/address_pools_test.go b/opts/address_pools_test.go new file mode 100644 index 0000000000..7f9c709968 --- /dev/null +++ b/opts/address_pools_test.go @@ -0,0 +1,20 @@ +package opts // import "github.com/docker/docker/opts" + +import ( + "testing" +) + +func TestAddressPoolOpt(t *testing.T) { + poolopt := &PoolsOpt{} + var addresspool = "base=175.30.0.0/16,size=16" + var invalidAddresspoolString = "base=175.30.0.0/16,size=16, base=175.33.0.0/16,size=24" + + if err := poolopt.Set(addresspool); err != nil { + t.Fatal(err) + } + + if err := poolopt.Set(invalidAddresspoolString); err == nil { + t.Fatal(err) + } + +}