mirror of
https://github.com/moby/moby.git
synced 2026-01-11 18:51:37 +00:00
- relates to96b29f5a1f- similar to08e4e88482The daemon currently provides support for API versions all the way back to v1.24, which is the version of the API that shipped with docker 1.12.0 (released in 2016). Such old versions of the client are rare, and supporting older API versions has accumulated significant amounts of code to remain backward-compatible (which is largely untested, and a "best-effort" at most). This patch updates the minimum API version to v1.44, matching the minimum version of the client, and matching the API version of docker v25.0, which is the oldest supported version (through Mirantis MCR). The intent is to start deprecating older API versions when daemons implementing them reach EOL. This patch does not yet remove backward-compatibility code for older API versions, and the DOCKER_MIN_API_VERSION environment variable allows overriding the minimum version (to allow restoring the behavior from before this patch), however, API versions below v1.44 should be considered "best effort", and we may remove compatibility code to provide "degraded" support. With this patch the daemon defaults to API v1.44 as minimum: docker version Client: Version: 28.5.0 API version: 1.51 Go version: go1.24.7 Git commit: 887030f Built: Thu Oct 2 14:54:39 2025 OS/Arch: linux/arm64 Context: default Server: Engine: Version: dev API version: 1.52 (minimum version 1.44) .... Trying to use an older version of the API produces an error: DOCKER_API_VERSION=1.43 docker version Client: Version: 28.5.0 API version: 1.43 (downgraded from 1.51) Go version: go1.24.7 Git commit: 887030f Built: Thu Oct 2 14:54:39 2025 OS/Arch: linux/arm64 Context: default Error response from daemon: client version 1.43 is too old. Minimum supported API version is 1.44, please upgrade your client to a newer version To restore the previous minimum, users can start the daemon with the DOCKER_MIN_API_VERSION environment variable set: DOCKER_MIN_API_VERSION=1.24 dockerd API 1.24 is the oldest supported API version; docker version Client: Version: 28.5.0 API version: 1.24 (downgraded from 1.51) Go version: go1.24.7 Git commit: 887030f Built: Thu Oct 2 14:54:39 2025 OS/Arch: linux/arm64 Context: default Server: Engine: Version: dev API version: 1.52 (minimum version 1.24) .... When using the `DOCKER_MIN_API_VERSION` with a version of the API that is not supported, an error is produced when starting the daemon; DOCKER_MIN_API_VERSION=1.23 dockerd --validate invalid DOCKER_MIN_API_VERSION: minimum supported API version is 1.24: 1.23 DOCKER_MIN_API_VERSION=1.99 dockerd --validate invalid DOCKER_MIN_API_VERSION: maximum supported API version is 1.52: 1.99 Specifying a malformed API version also produces the same error; DOCKER_MIN_API_VERSION=hello dockerd --validate invalid DOCKER_MIN_API_VERSION: minimum supported API version is 1.24: hello Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
283 lines
8.9 KiB
Go
283 lines
8.9 KiB
Go
package networking
|
|
|
|
import (
|
|
"net/netip"
|
|
"testing"
|
|
|
|
"github.com/moby/moby/client"
|
|
"github.com/moby/moby/v2/daemon/libnetwork/drivers/bridge"
|
|
"github.com/moby/moby/v2/integration/internal/container"
|
|
"github.com/moby/moby/v2/integration/internal/network"
|
|
"github.com/moby/moby/v2/internal/testutil"
|
|
"github.com/moby/moby/v2/internal/testutil/daemon"
|
|
"gotest.tools/v3/assert"
|
|
is "gotest.tools/v3/assert/cmp"
|
|
"gotest.tools/v3/skip"
|
|
)
|
|
|
|
// TestMACAddrOnRestart is a regression test for https://github.com/moby/moby/issues/47146
|
|
// - Start a container, let it use a generated MAC address.
|
|
// - Stop that container.
|
|
// - Start a second container, it'll also use a generated MAC address.
|
|
// (It's likely to recycle the first container's MAC address.)
|
|
// - Restart the first container.
|
|
// (The bug was that it kept its original MAC address, now already in-use.)
|
|
// - Check that the two containers have different MAC addresses.
|
|
func TestMACAddrOnRestart(t *testing.T) {
|
|
skip.If(t, testEnv.DaemonInfo.OSType == "windows")
|
|
|
|
ctx := setupTest(t)
|
|
|
|
d := daemon.New(t)
|
|
d.StartWithBusybox(ctx, t)
|
|
defer d.Stop(t)
|
|
|
|
c := d.NewClientT(t)
|
|
defer c.Close()
|
|
|
|
const netName = "testmacaddrs"
|
|
network.CreateNoError(ctx, t, c, netName,
|
|
network.WithDriver("bridge"),
|
|
network.WithOption(bridge.BridgeName, netName))
|
|
defer network.RemoveNoError(ctx, t, c, netName)
|
|
|
|
const ctr1Name = "ctr1"
|
|
id1 := container.Run(ctx, t, c,
|
|
container.WithName(ctr1Name),
|
|
container.WithImage("busybox:latest"),
|
|
container.WithCmd("top"),
|
|
container.WithNetworkMode(netName))
|
|
defer c.ContainerRemove(ctx, id1, client.ContainerRemoveOptions{
|
|
Force: true,
|
|
})
|
|
err := c.ContainerStop(ctx, ctr1Name, client.ContainerStopOptions{})
|
|
assert.Assert(t, is.Nil(err))
|
|
|
|
// Start a second container, giving the daemon a chance to recycle the first container's
|
|
// IP and MAC addresses.
|
|
const ctr2Name = "ctr2"
|
|
id2 := container.Run(ctx, t, c,
|
|
container.WithName(ctr2Name),
|
|
container.WithImage("busybox:latest"),
|
|
container.WithCmd("top"),
|
|
container.WithNetworkMode(netName))
|
|
defer c.ContainerRemove(ctx, id2, client.ContainerRemoveOptions{
|
|
Force: true,
|
|
})
|
|
|
|
// Restart the first container.
|
|
err = c.ContainerStart(ctx, ctr1Name, client.ContainerStartOptions{})
|
|
assert.Assert(t, is.Nil(err))
|
|
|
|
// Check that the containers ended up with different MAC addresses.
|
|
|
|
ctr1Inspect := container.Inspect(ctx, t, c, ctr1Name)
|
|
ctr1MAC := ctr1Inspect.NetworkSettings.Networks[netName].MacAddress
|
|
|
|
ctr2Inspect := container.Inspect(ctx, t, c, ctr2Name)
|
|
ctr2MAC := ctr2Inspect.NetworkSettings.Networks[netName].MacAddress
|
|
|
|
assert.Check(t, ctr1MAC != ctr2MAC,
|
|
"expected containers to have different MAC addresses; got %q for both", ctr1MAC)
|
|
}
|
|
|
|
// Check that a configured MAC address is restored after a container restart,
|
|
// and after a daemon restart.
|
|
func TestCfgdMACAddrOnRestart(t *testing.T) {
|
|
skip.If(t, testEnv.DaemonInfo.OSType == "windows")
|
|
|
|
ctx := setupTest(t)
|
|
|
|
d := daemon.New(t)
|
|
d.StartWithBusybox(ctx, t)
|
|
defer d.Stop(t)
|
|
|
|
c := d.NewClientT(t)
|
|
defer c.Close()
|
|
|
|
const netName = "testcfgmacaddr"
|
|
network.CreateNoError(ctx, t, c, netName,
|
|
network.WithDriver("bridge"),
|
|
network.WithOption(bridge.BridgeName, netName))
|
|
defer network.RemoveNoError(ctx, t, c, netName)
|
|
|
|
const wantMAC = "02:42:ac:11:00:42"
|
|
const ctr1Name = "ctr1"
|
|
id1 := container.Run(ctx, t, c,
|
|
container.WithName(ctr1Name),
|
|
container.WithImage("busybox:latest"),
|
|
container.WithCmd("top"),
|
|
container.WithNetworkMode(netName),
|
|
container.WithMacAddress(netName, wantMAC))
|
|
defer c.ContainerRemove(ctx, id1, client.ContainerRemoveOptions{
|
|
Force: true,
|
|
})
|
|
|
|
inspect := container.Inspect(ctx, t, c, ctr1Name)
|
|
gotMAC := inspect.NetworkSettings.Networks[netName].MacAddress
|
|
assert.Check(t, is.Equal(wantMAC, gotMAC))
|
|
|
|
startAndCheck := func() {
|
|
t.Helper()
|
|
err := c.ContainerStart(ctx, ctr1Name, client.ContainerStartOptions{})
|
|
assert.Assert(t, is.Nil(err))
|
|
inspect = container.Inspect(ctx, t, c, ctr1Name)
|
|
gotMAC = inspect.NetworkSettings.Networks[netName].MacAddress
|
|
assert.Check(t, is.Equal(wantMAC, gotMAC))
|
|
}
|
|
|
|
// Restart the container, check that the MAC address is restored.
|
|
err := c.ContainerStop(ctx, ctr1Name, client.ContainerStopOptions{})
|
|
assert.Assert(t, is.Nil(err))
|
|
startAndCheck()
|
|
|
|
// Restart the daemon, check that the MAC address is restored.
|
|
err = c.ContainerStop(ctx, ctr1Name, client.ContainerStopOptions{})
|
|
assert.Assert(t, is.Nil(err))
|
|
d.Restart(t)
|
|
startAndCheck()
|
|
}
|
|
|
|
// Regression test for https://github.com/moby/moby/issues/47228 - check that a
|
|
// generated MAC address is not included in the Config section of 'inspect'
|
|
// output, but a configured address is.
|
|
func TestInspectCfgdMAC(t *testing.T) {
|
|
skip.If(t, testEnv.DaemonInfo.OSType == "windows")
|
|
|
|
ctx := setupTest(t)
|
|
|
|
d := daemon.New(t, daemon.WithEnvVars("DOCKER_MIN_API_VERSION=1.43"))
|
|
d.StartWithBusybox(ctx, t)
|
|
defer d.Stop(t)
|
|
|
|
testcases := []struct {
|
|
name string
|
|
desiredMAC string
|
|
netName string
|
|
ctrWide bool
|
|
}{
|
|
{
|
|
name: "generated address default bridge",
|
|
netName: "bridge",
|
|
},
|
|
{
|
|
name: "configured address default bridge",
|
|
desiredMAC: "02:42:ac:11:00:42",
|
|
netName: "bridge",
|
|
},
|
|
{
|
|
name: "generated address custom bridge",
|
|
netName: "testnet",
|
|
},
|
|
{
|
|
name: "configured address custom bridge",
|
|
desiredMAC: "02:42:ac:11:00:42",
|
|
netName: "testnet",
|
|
},
|
|
{
|
|
name: "ctr-wide address default bridge",
|
|
desiredMAC: "02:42:ac:11:00:42",
|
|
netName: "bridge",
|
|
ctrWide: true,
|
|
},
|
|
}
|
|
|
|
for _, tc := range testcases {
|
|
t.Run(tc.name, func(t *testing.T) {
|
|
ctx := testutil.StartSpan(ctx, t)
|
|
|
|
var copts []client.Opt
|
|
if tc.ctrWide {
|
|
copts = append(copts, client.WithVersion("1.43"))
|
|
}
|
|
c := d.NewClientT(t, copts...)
|
|
defer c.Close()
|
|
|
|
if tc.netName != "bridge" {
|
|
const netName = "inspectcfgmac"
|
|
network.CreateNoError(ctx, t, c, netName,
|
|
network.WithDriver("bridge"),
|
|
network.WithOption(bridge.BridgeName, netName))
|
|
defer network.RemoveNoError(ctx, t, c, netName)
|
|
}
|
|
|
|
const ctrName = "ctr"
|
|
opts := []func(*container.TestContainerConfig){
|
|
container.WithName(ctrName),
|
|
container.WithCmd("top"),
|
|
container.WithImage("busybox:latest"),
|
|
}
|
|
// Don't specify the network name for the bridge network, because that
|
|
// exercises a different code path (the network name isn't set until the
|
|
// container starts, until then it's "default").
|
|
if tc.netName != "bridge" {
|
|
opts = append(opts, container.WithNetworkMode(tc.netName))
|
|
}
|
|
if tc.desiredMAC != "" {
|
|
if tc.ctrWide {
|
|
opts = append(opts, container.WithContainerWideMacAddress(tc.desiredMAC))
|
|
} else {
|
|
opts = append(opts, container.WithMacAddress(tc.netName, tc.desiredMAC))
|
|
}
|
|
}
|
|
id := container.Create(ctx, t, c, opts...)
|
|
defer c.ContainerRemove(ctx, id, client.ContainerRemoveOptions{
|
|
Force: true,
|
|
})
|
|
|
|
inspect := container.Inspect(ctx, t, c, ctrName)
|
|
configMAC := inspect.Config.MacAddress //nolint:staticcheck // ignore SA1019: field is deprecated, but still used on API < v1.44.
|
|
assert.Check(t, is.DeepEqual(configMAC, tc.desiredMAC))
|
|
})
|
|
}
|
|
}
|
|
|
|
// Regression test for https://github.com/moby/moby/issues/47441
|
|
// Migration of a container-wide MAC address to the new per-endpoint setting,
|
|
// where NetworkMode uses network id, and the key in endpoint settings is the
|
|
// network name.
|
|
func TestWatchtowerCreate(t *testing.T) {
|
|
skip.If(t, testEnv.DaemonInfo.OSType == "windows", "no macvlan")
|
|
|
|
ctx := setupTest(t)
|
|
|
|
d := daemon.New(t, daemon.WithEnvVars("DOCKER_MIN_API_VERSION=1.25"))
|
|
d.StartWithBusybox(ctx, t)
|
|
defer d.Stop(t)
|
|
|
|
c := d.NewClientT(t, client.WithVersion("1.25"))
|
|
defer c.Close()
|
|
|
|
// Create a "/29" network, with a single address in iprange for IPAM to
|
|
// allocate, but no gateway address. So, the gateway will get the single
|
|
// free address. It'll only be possible to start a container by explicitly
|
|
// assigning an address.
|
|
const netName = "wtmvl"
|
|
netId := network.CreateNoError(ctx, t, c, netName,
|
|
network.WithIPAMRange("172.30.0.0/29", "172.30.0.1/32", ""),
|
|
network.WithDriver("macvlan"),
|
|
)
|
|
defer network.RemoveNoError(ctx, t, c, netName)
|
|
|
|
// Start a container, using the network's id in NetworkMode but its name
|
|
// in EndpointsConfig. (The container-wide MAC address must be merged with
|
|
// the endpoint config containing the preferred IP address, but the names
|
|
// don't match.)
|
|
const ctrName = "ctr1"
|
|
const ctrIP = "172.30.0.2"
|
|
const ctrMAC = "02:42:ac:11:00:42"
|
|
id := container.Run(ctx, t, c,
|
|
container.WithName(ctrName),
|
|
container.WithNetworkMode(netId),
|
|
container.WithContainerWideMacAddress(ctrMAC),
|
|
container.WithIPv4(netName, ctrIP),
|
|
)
|
|
defer c.ContainerRemove(ctx, id, client.ContainerRemoveOptions{Force: true})
|
|
|
|
// Check that the container got the expected addresses.
|
|
inspect := container.Inspect(ctx, t, c, ctrName)
|
|
netSettings := inspect.NetworkSettings.Networks[netName]
|
|
assert.Check(t, is.Equal(netSettings.IPAddress, netip.MustParseAddr(ctrIP)))
|
|
assert.Check(t, is.Equal(netSettings.MacAddress, ctrMAC))
|
|
}
|