Browse Source

Make default options for newly created networks configurable

Signed-off-by: Alex Stockinger <alex@atomicjar.com>
Co-authored-by: Sergei Egorov <bsideup@gmail.com>
Co-authored-by: Cory Snider <corhere@gmail.com>
Alex Stockinger 3 năm trước cách đây
mục cha
commit
91c2b12205
6 tập tin đã thay đổi với 200 bổ sung1 xóa
  1. 1 0
      cmd/dockerd/config.go
  2. 3 0
      daemon/config/config.go
  3. 14 1
      daemon/network.go
  4. 74 0
      integration/network/network_test.go
  5. 77 0
      opts/opts.go
  6. 31 0
      opts/opts_test.go

+ 1 - 0
cmd/dockerd/config.go

@@ -27,6 +27,7 @@ func installCommonConfigFlags(conf *config.Config, flags *pflag.FlagSet) error {
 	flags.StringVar(&conf.ContainerdAddr, "containerd", "", "containerd grpc address")
 	flags.BoolVar(&conf.CriContainerd, "cri-containerd", false, "start containerd with cri")
 
+	flags.Var(opts.NewNamedMapMapOpts("default-network-opts", conf.DefaultNetworkOpts, nil), "default-network-opt", "Default network options")
 	flags.IntVar(&conf.Mtu, "mtu", conf.Mtu, "Set the containers network MTU")
 	flags.IntVar(&conf.NetworkControlPlaneMTU, "network-control-plane-mtu", conf.NetworkControlPlaneMTU, "Network Control plane MTU")
 	flags.IntVar(&conf.NetworkDiagnosticPort, "network-diagnostic-port", 0, "TCP port number of the network diagnostic server")

+ 3 - 0
daemon/config/config.go

@@ -126,6 +126,8 @@ type NetworkConfig struct {
 	DefaultAddressPools opts.PoolsOpt `json:"default-address-pools,omitempty"`
 	// NetworkControlPlaneMTU allows to specify the control plane MTU, this will allow to optimize the network use in some components
 	NetworkControlPlaneMTU int `json:"network-control-plane-mtu,omitempty"`
+	// Default options for newly created networks
+	DefaultNetworkOpts map[string]map[string]string `json:"default-network-opts,omitempty"`
 }
 
 // TLSOptions defines TLS configuration for the daemon server.
@@ -289,6 +291,7 @@ func New() (*Config, error) {
 			Mtu:                    DefaultNetworkMtu,
 			NetworkConfig: NetworkConfig{
 				NetworkControlPlaneMTU: DefaultNetworkMtu,
+				DefaultNetworkOpts:     make(map[string]map[string]string),
 			},
 			ContainerdNamespace:       DefaultContainersNamespace,
 			ContainerdPluginNamespace: DefaultPluginNamespace,

+ 14 - 1
daemon/network.go

@@ -315,9 +315,22 @@ func (daemon *Daemon) createNetwork(create types.NetworkCreateRequest, id string
 		driver = c.Config().DefaultDriver
 	}
 
+	networkOptions := make(map[string]string)
+	for k, v := range create.Options {
+		networkOptions[k] = v
+	}
+	if defaultOpts, ok := daemon.configStore.DefaultNetworkOpts[driver]; create.ConfigFrom == nil && ok {
+		for k, v := range defaultOpts {
+			if _, ok := networkOptions[k]; !ok {
+				logrus.WithFields(logrus.Fields{"driver": driver, "network": id, k: v}).Debug("Applying network default option")
+				networkOptions[k] = v
+			}
+		}
+	}
+
 	nwOptions := []libnetwork.NetworkOption{
 		libnetwork.NetworkOptionEnableIPv6(create.EnableIPv6),
-		libnetwork.NetworkOptionDriverOpts(create.Options),
+		libnetwork.NetworkOptionDriverOpts(networkOptions),
 		libnetwork.NetworkOptionLabels(create.Labels),
 		libnetwork.NetworkOptionAttachable(create.Attachable),
 		libnetwork.NetworkOptionIngress(create.Ingress),

+ 74 - 0
integration/network/network_test.go

@@ -4,12 +4,14 @@ import (
 	"bytes"
 	"context"
 	"encoding/json"
+	"fmt"
 	"net/http"
 	"os/exec"
 	"strings"
 	"testing"
 
 	"github.com/docker/docker/api/types"
+	ntypes "github.com/docker/docker/api/types/network"
 	"github.com/docker/docker/integration/internal/container"
 	"github.com/docker/docker/integration/internal/network"
 	"github.com/docker/docker/testutil/daemon"
@@ -175,3 +177,75 @@ func TestHostIPv4BridgeLabel(t *testing.T) {
 	// Make sure the SNAT rule exists
 	icmd.RunCommand("iptables", "-t", "nat", "-C", "POSTROUTING", "-s", out.IPAM.Config[0].Subnet, "!", "-o", bridgeName, "-j", "SNAT", "--to-source", ipv4SNATAddr).Assert(t, icmd.Success)
 }
+
+func TestDefaultNetworkOpts(t *testing.T) {
+	skip.If(t, testEnv.OSType == "windows")
+	skip.If(t, testEnv.IsRemoteDaemon)
+	skip.If(t, testEnv.IsRootless, "rootless mode has different view of network")
+
+	tests := []struct {
+		name       string
+		mtu        int
+		configFrom bool
+		args       []string
+	}{
+		{
+			name: "default value",
+			mtu:  1500,
+			args: []string{},
+		},
+		{
+			name: "cmdline value",
+			mtu:  1234,
+			args: []string{"--default-network-opt", "bridge=com.docker.network.driver.mtu=1234"},
+		},
+		{
+			name:       "config-from value",
+			configFrom: true,
+			mtu:        1233,
+			args:       []string{"--default-network-opt", "bridge=com.docker.network.driver.mtu=1234"},
+		},
+	}
+
+	for _, tc := range tests {
+		tc := tc
+		t.Run(tc.name, func(t *testing.T) {
+			d := daemon.New(t)
+			d.StartWithBusybox(t, tc.args...)
+			defer d.Stop(t)
+			c := d.NewClientT(t)
+			defer c.Close()
+			ctx := context.Background()
+
+			if tc.configFrom {
+				// Create a new network config
+				network.CreateNoError(ctx, t, c, "from-net", func(create *types.NetworkCreate) {
+					create.ConfigOnly = true
+					create.Options = map[string]string{
+						"com.docker.network.driver.mtu": fmt.Sprint(tc.mtu),
+					}
+				})
+				defer c.NetworkRemove(ctx, "from-net")
+			}
+
+			// Create a new network
+			networkName := "testnet"
+			network.CreateNoError(ctx, t, c, networkName, func(create *types.NetworkCreate) {
+				if tc.configFrom {
+					create.ConfigFrom = &ntypes.ConfigReference{
+						Network: "from-net",
+					}
+				}
+			})
+			defer c.NetworkRemove(ctx, networkName)
+
+			// Start a container to inspect the MTU of its network interface
+			id1 := container.Run(ctx, t, c, container.WithNetworkMode(networkName))
+			defer c.ContainerRemove(ctx, id1, types.ContainerRemoveOptions{Force: true})
+
+			result, err := container.Exec(ctx, c, id1, []string{"ip", "l", "show", "eth0"})
+			assert.NilError(t, err)
+			assert.Check(t, is.Contains(result.Combined(), fmt.Sprintf(" mtu %d ", tc.mtu)), "Network MTU should have been set to %d", tc.mtu)
+		})
+	}
+}

+ 77 - 0
opts/opts.go

@@ -146,6 +146,83 @@ func (o *NamedListOpts) Name() string {
 	return o.name
 }
 
+// NamedMapMapOpts is a MapMapOpts with a configuration name.
+// This struct is useful to keep reference to the assigned
+// field name in the internal configuration struct.
+type NamedMapMapOpts struct {
+	name string
+	MapMapOpts
+}
+
+// NewNamedMapMapOpts creates a reference to a new NamedMapOpts struct.
+func NewNamedMapMapOpts(name string, values map[string]map[string]string, validator ValidatorFctType) *NamedMapMapOpts {
+	return &NamedMapMapOpts{
+		name:       name,
+		MapMapOpts: *NewMapMapOpts(values, validator),
+	}
+}
+
+// Name returns the name of the NamedListOpts in the configuration.
+func (o *NamedMapMapOpts) Name() string {
+	return o.name
+}
+
+// MapMapOpts holds a map of maps of values and a validation function.
+type MapMapOpts struct {
+	values    map[string]map[string]string
+	validator ValidatorFctType
+}
+
+// Set validates if needed the input value and add it to the
+// internal map, by splitting on '='.
+func (opts *MapMapOpts) Set(value string) error {
+	if opts.validator != nil {
+		v, err := opts.validator(value)
+		if err != nil {
+			return err
+		}
+		value = v
+	}
+	rk, rv, found := strings.Cut(value, "=")
+	if !found {
+		return fmt.Errorf("invalid value %q for map option, should be root-key=key=value", value)
+	}
+	k, v, found := strings.Cut(rv, "=")
+	if !found {
+		return fmt.Errorf("invalid value %q for map option, should be root-key=key=value", value)
+	}
+	if _, ok := opts.values[rk]; !ok {
+		opts.values[rk] = make(map[string]string)
+	}
+	opts.values[rk][k] = v
+	return nil
+}
+
+// GetAll returns the values of MapOpts as a map.
+func (opts *MapMapOpts) GetAll() map[string]map[string]string {
+	return opts.values
+}
+
+func (opts *MapMapOpts) String() string {
+	return fmt.Sprintf("%v", opts.values)
+}
+
+// Type returns a string name for this Option type
+func (opts *MapMapOpts) Type() string {
+	return "mapmap"
+}
+
+// NewMapMapOpts creates a new MapMapOpts with the specified map of values and a validator.
+func NewMapMapOpts(values map[string]map[string]string, validator ValidatorFctType) *MapMapOpts {
+	if values == nil {
+		values = make(map[string]map[string]string)
+	}
+	return &MapMapOpts{
+		values:    values,
+		validator: validator,
+	}
+}
+
 // MapOpts holds a map of values and a validation function.
 type MapOpts struct {
 	values    map[string]string

+ 31 - 0
opts/opts_test.go

@@ -334,3 +334,34 @@ func TestParseLink(t *testing.T) {
 		assert.Check(t, is.Equal(alias, "bar"))
 	})
 }
+
+func TestMapMapOpts(t *testing.T) {
+	tmpMap := make(map[string]map[string]string)
+	validator := func(val string) (string, error) {
+		if strings.HasPrefix(val, "invalid-key=") {
+			return "", fmt.Errorf("invalid key %s", val)
+		}
+		return val, nil
+	}
+	o := NewMapMapOpts(tmpMap, validator)
+	o.Set("r1=k11=v11")
+	assert.Check(t, is.DeepEqual(tmpMap, map[string]map[string]string{"r1": {"k11": "v11"}}))
+
+	o.Set("r2=k21=v21")
+	assert.Check(t, is.Len(tmpMap, 2))
+
+	if err := o.Set("invalid-syntax"); err == nil {
+		t.Error("invalid mapping syntax is not being caught")
+	}
+
+	if err := o.Set("k=invalid-syntax"); err == nil {
+		t.Error("invalid value syntax is not being caught")
+	}
+
+	o.Set("r1=k12=v12")
+	assert.Check(t, is.DeepEqual(tmpMap["r1"], map[string]string{"k11": "v11", "k12": "v12"}))
+
+	if o.Set("invalid-key={\"k\":\"v\"}") == nil {
+		t.Error("validator is not being called")
+	}
+}