Browse code

api: normalize the default NetworkMode

The NetworkMode "default" is now normalized into the value it
aliases ("bridge" on Linux and "nat" on Windows) by the
ContainerCreate endpoint, the legacy image builder, Swarm's
cluster executor and by the container restore codepath.

builder-next is left untouched as it already uses the normalized
value (ie. bridge).

Going forward, this will make maintenance easier as there's one
less NetworkMode to care about.

Signed-off-by: Albin Kerouanton <albinker@gmail.com>

Albin Kerouanton authored on 2024/02/22 19:18:36
Showing 4 changed files
... ...
@@ -456,15 +456,27 @@ func (s *containerRouter) postContainersCreate(ctx context.Context, w http.Respo
456 456
 	if hostConfig == nil {
457 457
 		hostConfig = &container.HostConfig{}
458 458
 	}
459
-	if hostConfig.NetworkMode == "" {
460
-		hostConfig.NetworkMode = "default"
461
-	}
462 459
 	if networkingConfig == nil {
463 460
 		networkingConfig = &network.NetworkingConfig{}
464 461
 	}
465 462
 	if networkingConfig.EndpointsConfig == nil {
466 463
 		networkingConfig.EndpointsConfig = make(map[string]*network.EndpointSettings)
467 464
 	}
465
+	// The NetworkMode "default" is used as a way to express a container should
466
+	// be attached to the OS-dependant default network, in an OS-independent
467
+	// way. Doing this conversion as soon as possible ensures we have less
468
+	// NetworkMode to handle down the path (including in the
469
+	// backward-compatibility layer we have just below).
470
+	//
471
+	// Note that this is not the only place where this conversion has to be
472
+	// done (as there are various other places where containers get created).
473
+	if hostConfig.NetworkMode == "" || hostConfig.NetworkMode.IsDefault() {
474
+		hostConfig.NetworkMode = runconfig.DefaultDaemonNetworkMode()
475
+		if nw, ok := networkingConfig.EndpointsConfig[network.NetworkDefault]; ok {
476
+			networkingConfig.EndpointsConfig[hostConfig.NetworkMode.NetworkName()] = nw
477
+			delete(networkingConfig.EndpointsConfig, network.NetworkDefault)
478
+		}
479
+	}
468 480
 
469 481
 	version := httputils.VersionFromContext(ctx)
470 482
 
... ...
@@ -15,11 +15,13 @@ import (
15 15
 	"github.com/docker/docker/api/types"
16 16
 	"github.com/docker/docker/api/types/backend"
17 17
 	"github.com/docker/docker/api/types/container"
18
+	"github.com/docker/docker/api/types/network"
18 19
 	"github.com/docker/docker/builder"
19 20
 	"github.com/docker/docker/image"
20 21
 	"github.com/docker/docker/pkg/archive"
21 22
 	"github.com/docker/docker/pkg/chrootarchive"
22 23
 	"github.com/docker/docker/pkg/stringid"
24
+	"github.com/docker/docker/runconfig"
23 25
 	"github.com/docker/go-connections/nat"
24 26
 	ocispec "github.com/opencontainers/image-spec/specs-go/v1"
25 27
 	"github.com/pkg/errors"
... ...
@@ -377,12 +379,21 @@ func hostConfigFromOptions(options *types.ImageBuildOptions) *container.HostConf
377 377
 		Ulimits:      options.Ulimits,
378 378
 	}
379 379
 
380
+	// We need to make sure no empty string or "default" NetworkMode is
381
+	// provided to the daemon as it doesn't support them.
382
+	//
383
+	// This is in line with what the ContainerCreate API endpoint does.
384
+	networkMode := options.NetworkMode
385
+	if networkMode == "" || networkMode == network.NetworkDefault {
386
+		networkMode = runconfig.DefaultDaemonNetworkMode().NetworkName()
387
+	}
388
+
380 389
 	hc := &container.HostConfig{
381 390
 		SecurityOpt: options.SecurityOpt,
382 391
 		Isolation:   options.Isolation,
383 392
 		ShmSize:     options.ShmSize,
384 393
 		Resources:   resources,
385
-		NetworkMode: container.NetworkMode(options.NetworkMode),
394
+		NetworkMode: container.NetworkMode(networkMode),
386 395
 		// Set a log config to override any default value set on the daemon
387 396
 		LogConfig:  defaultLogConfig,
388 397
 		ExtraHosts: options.ExtraHosts,
... ...
@@ -17,12 +17,14 @@ import (
17 17
 	"github.com/docker/docker/api/types/backend"
18 18
 	containertypes "github.com/docker/docker/api/types/container"
19 19
 	"github.com/docker/docker/api/types/events"
20
+	"github.com/docker/docker/api/types/network"
20 21
 	"github.com/docker/docker/api/types/registry"
21 22
 	containerpkg "github.com/docker/docker/container"
22 23
 	"github.com/docker/docker/daemon"
23 24
 	"github.com/docker/docker/daemon/cluster/convert"
24 25
 	executorpkg "github.com/docker/docker/daemon/cluster/executor"
25 26
 	"github.com/docker/docker/libnetwork"
27
+	"github.com/docker/docker/runconfig"
26 28
 	volumeopts "github.com/docker/docker/volume/service/opts"
27 29
 	gogotypes "github.com/gogo/protobuf/types"
28 30
 	"github.com/moby/swarmkit/v2/agent/exec"
... ...
@@ -290,14 +292,34 @@ func (c *containerAdapter) waitForDetach(ctx context.Context) error {
290 290
 }
291 291
 
292 292
 func (c *containerAdapter) create(ctx context.Context) error {
293
+	hostConfig := c.container.hostConfig(c.dependencies.Volumes())
294
+	netConfig := c.container.createNetworkingConfig(c.backend)
295
+
296
+	// We need to make sure no empty string or "default" NetworkMode is
297
+	// provided to the daemon as it doesn't support them.
298
+	//
299
+	// This is in line with what the ContainerCreate API endpoint does, but
300
+	// unlike that endpoint we can't do that in the ServiceCreate endpoint as
301
+	// the cluster leader and the current node might not be running on the same
302
+	// OS. Since the normalized value isn't the same on Windows and Linux, we
303
+	// need to make this normalization happen once we're sure we won't make a
304
+	// cross-OS API call.
305
+	if hostConfig.NetworkMode == "" || hostConfig.NetworkMode.IsDefault() {
306
+		hostConfig.NetworkMode = runconfig.DefaultDaemonNetworkMode()
307
+		if v, ok := netConfig.EndpointsConfig[network.NetworkDefault]; ok {
308
+			delete(netConfig.EndpointsConfig, network.NetworkDefault)
309
+			netConfig.EndpointsConfig[runconfig.DefaultDaemonNetworkMode().NetworkName()] = v
310
+		}
311
+	}
312
+
293 313
 	var cr containertypes.CreateResponse
294 314
 	var err error
295 315
 	if cr, err = c.backend.CreateManagedContainer(ctx, backend.ContainerCreateConfig{
296 316
 		Name:       c.container.name(),
297 317
 		Config:     c.container.config(),
298
-		HostConfig: c.container.hostConfig(c.dependencies.Volumes()),
318
+		HostConfig: hostConfig,
299 319
 		// Use the first network in container create
300
-		NetworkingConfig: c.container.createNetworkingConfig(c.backend),
320
+		NetworkingConfig: netConfig,
301 321
 	}); err != nil {
302 322
 		return err
303 323
 	}
... ...
@@ -32,6 +32,7 @@ import (
32 32
 	"github.com/docker/docker/api/types/backend"
33 33
 	containertypes "github.com/docker/docker/api/types/container"
34 34
 	imagetypes "github.com/docker/docker/api/types/image"
35
+	networktypes "github.com/docker/docker/api/types/network"
35 36
 	registrytypes "github.com/docker/docker/api/types/registry"
36 37
 	"github.com/docker/docker/api/types/swarm"
37 38
 	"github.com/docker/docker/api/types/volume"
... ...
@@ -373,6 +374,21 @@ func (daemon *Daemon) restore(cfg *configStore) error {
373 373
 						Type: local.Name,
374 374
 					}
375 375
 				}
376
+
377
+				// Normalize the "default" network mode into the network mode
378
+				// it aliases ("bridge on Linux and "nat" on Windows). This is
379
+				// also done by the container router, for new containers. But
380
+				// we need to do it here too to handle containers that were
381
+				// created prior to v26.0.
382
+				//
383
+				// TODO(aker): remove this migration code once the next LTM version of MCR is released.
384
+				if c.HostConfig.NetworkMode.IsDefault() {
385
+					c.HostConfig.NetworkMode = runconfig.DefaultDaemonNetworkMode()
386
+					if nw, ok := c.NetworkSettings.Networks[networktypes.NetworkDefault]; ok {
387
+						c.NetworkSettings.Networks[c.HostConfig.NetworkMode.NetworkName()] = nw
388
+						delete(c.NetworkSettings.Networks, networktypes.NetworkDefault)
389
+					}
390
+				}
376 391
 			}
377 392
 
378 393
 			if err := daemon.checkpointAndSave(c); err != nil {