Files
moby/integration/service/network_linux_test.go
Sebastiaan van Stijn dae3650dcc client: rename/deprecate WithVersion, WithVersionFromEnv
Add WithAPIVersion and WithAPIVersionFromEnv to be more clear on
the intent, and to align with other related options and fields.

Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
2025-11-12 22:39:05 +01:00

337 lines
11 KiB
Go

package service
import (
stdnet "net"
"net/netip"
"strings"
"testing"
"time"
"github.com/google/go-cmp/cmp/cmpopts"
"github.com/moby/moby/api/types/network"
swarmtypes "github.com/moby/moby/api/types/swarm"
"github.com/moby/moby/client"
"github.com/moby/moby/v2/daemon/libnetwork/scope"
"github.com/moby/moby/v2/integration/internal/container"
net "github.com/moby/moby/v2/integration/internal/network"
"github.com/moby/moby/v2/integration/internal/swarm"
"github.com/moby/moby/v2/integration/internal/testutils/networking"
"github.com/moby/moby/v2/internal/testutil/daemon"
"gotest.tools/v3/assert"
is "gotest.tools/v3/assert/cmp"
"gotest.tools/v3/golden"
"gotest.tools/v3/icmd"
"gotest.tools/v3/poll"
"gotest.tools/v3/skip"
)
func TestDockerNetworkConnectAliasPreV144(t *testing.T) {
ctx := setupTest(t)
d := swarm.NewSwarm(ctx, t, testEnv, daemon.WithEnvVars("DOCKER_MIN_API_VERSION=1.43"))
defer d.Stop(t)
apiClient := d.NewClientT(t, client.WithAPIVersion("1.43"))
defer apiClient.Close()
name := t.Name() + "test-alias"
net.CreateNoError(ctx, t, apiClient, name,
net.WithDriver("overlay"),
net.WithAttachable(),
)
cID1 := container.Create(ctx, t, apiClient, func(c *container.TestContainerConfig) {
c.NetworkingConfig = &network.NetworkingConfig{
EndpointsConfig: map[string]*network.EndpointSettings{
name: {},
},
}
})
_, err := apiClient.NetworkConnect(ctx, name, client.NetworkConnectOptions{
Container: cID1,
EndpointConfig: &network.EndpointSettings{
Aliases: []string{
"aaa",
},
},
})
assert.NilError(t, err)
_, err = apiClient.ContainerStart(ctx, cID1, client.ContainerStartOptions{})
assert.NilError(t, err)
ng1, err := apiClient.ContainerInspect(ctx, cID1, client.ContainerInspectOptions{})
assert.NilError(t, err)
assert.Check(t, is.Equal(len(ng1.Container.NetworkSettings.Networks[name].Aliases), 2))
assert.Check(t, is.Equal(ng1.Container.NetworkSettings.Networks[name].Aliases[0], "aaa"))
cID2 := container.Create(ctx, t, apiClient, func(c *container.TestContainerConfig) {
c.NetworkingConfig = &network.NetworkingConfig{
EndpointsConfig: map[string]*network.EndpointSettings{
name: {},
},
}
})
_, err = apiClient.NetworkConnect(ctx, name, client.NetworkConnectOptions{
Container: cID2,
EndpointConfig: &network.EndpointSettings{
Aliases: []string{
"bbb",
},
},
})
assert.NilError(t, err)
_, err = apiClient.ContainerStart(ctx, cID2, client.ContainerStartOptions{})
assert.NilError(t, err)
ng2, err := apiClient.ContainerInspect(ctx, cID2, client.ContainerInspectOptions{})
assert.NilError(t, err)
assert.Check(t, is.Equal(len(ng2.Container.NetworkSettings.Networks[name].Aliases), 2))
assert.Check(t, is.Equal(ng2.Container.NetworkSettings.Networks[name].Aliases[0], "bbb"))
}
func TestDockerNetworkReConnect(t *testing.T) {
ctx := setupTest(t)
d := swarm.NewSwarm(ctx, t, testEnv)
defer d.Stop(t)
apiClient := d.NewClientT(t)
defer apiClient.Close()
name := t.Name() + "dummyNet"
net.CreateNoError(ctx, t, apiClient, name,
net.WithDriver("overlay"),
net.WithAttachable(),
)
c1 := container.Create(ctx, t, apiClient, func(c *container.TestContainerConfig) {
c.NetworkingConfig = &network.NetworkingConfig{
EndpointsConfig: map[string]*network.EndpointSettings{
name: {},
},
}
})
_, err := apiClient.NetworkConnect(ctx, name, client.NetworkConnectOptions{
Container: c1,
EndpointConfig: &network.EndpointSettings{},
})
assert.NilError(t, err)
_, err = apiClient.ContainerStart(ctx, c1, client.ContainerStartOptions{})
assert.NilError(t, err)
n1, err := apiClient.ContainerInspect(ctx, c1, client.ContainerInspectOptions{})
assert.NilError(t, err)
_, err = apiClient.NetworkConnect(ctx, name, client.NetworkConnectOptions{
Container: c1,
EndpointConfig: &network.EndpointSettings{},
})
assert.ErrorContains(t, err, "is already attached to network")
n2, err := apiClient.ContainerInspect(ctx, c1, client.ContainerInspectOptions{})
assert.NilError(t, err)
assert.Check(t, is.DeepEqual(n1.Container, n2.Container, cmpopts.EquateComparable(netip.Addr{}, netip.Prefix{})))
}
// Check that a swarm-scoped network can't have EnableIPv4=false.
func TestSwarmNoDisableIPv4(t *testing.T) {
ctx := setupTest(t)
d := swarm.NewSwarm(ctx, t, testEnv)
defer d.Stop(t)
apiClient := d.NewClientT(t)
defer apiClient.Close()
_, err := net.Create(ctx, apiClient, "overlay-v6-only",
net.WithDriver("overlay"),
net.WithAttachable(),
net.WithIPv4(false),
)
assert.Check(t, is.ErrorContains(err, "IPv4 cannot be disabled in a Swarm scoped network"))
}
// Regression test for https://github.com/docker/cli/issues/5857
func TestSwarmScopedNetFromConfig(t *testing.T) {
skip.If(t, testEnv.IsRootless, "rootless mode doesn't support Swarm-mode")
ctx := setupTest(t)
d := swarm.NewSwarm(ctx, t, testEnv)
defer d.Stop(t)
c := d.NewClientT(t)
defer c.Close()
const configNetName = "config-net"
_ = net.CreateNoError(ctx, t, c, configNetName,
net.WithDriver("bridge"),
net.WithConfigOnly(true),
)
const swarmNetName = "swarm-net"
_, err := net.Create(ctx, c, swarmNetName,
net.WithDriver("bridge"),
net.WithConfigFrom(configNetName),
net.WithAttachable(),
net.WithScope(scope.Swarm),
)
assert.NilError(t, err)
serviceID := swarm.CreateService(ctx, t, d,
swarm.ServiceWithName("test-ssnfc"),
swarm.ServiceWithNetwork(swarmNetName),
)
defer func() {
_, err := c.ServiceRemove(ctx, serviceID, client.ServiceRemoveOptions{})
assert.NilError(t, err)
}()
poll.WaitOn(t, swarm.RunningTasksCount(ctx, c, serviceID, 1), swarm.ServicePoll)
}
// Check that, when swarm has ports mapped to the host, the iptables jump to
// DOCKER-INGRESS remains in place after a daemon restart.
// Regression test for https://github.com/moby/moby/pull/49538
func TestDockerIngressChainPosition(t *testing.T) {
skip.If(t, testEnv.IsRemoteDaemon)
skip.If(t, testEnv.IsRootless, "rootless mode doesn't support Swarm-mode")
skip.If(t, testEnv.FirewallBackendDriver() == "nftables")
skip.If(t, networking.FirewalldRunning(), "can't use firewalld in host netns to add rules in L3Segment")
ctx := setupTest(t)
// Run the test in its own netns, to avoid interfering with iptables on the test host.
const hostAddr = "192.168.111.222"
const l3SegHost = "dicp"
l3 := networking.NewL3Segment(t, "test-"+l3SegHost)
defer l3.Destroy(t)
l3.AddHost(t, l3SegHost, "ns-"+l3SegHost, "eth0", netip.MustParsePrefix(hostAddr+"/24"))
// Check the published port is accessible.
checkHTTP := func(_ poll.LogT) poll.Result {
var res *icmd.Result
// This is called from inside a "Do()" thread in the docker host's netns, but it
// uses poll.WaitOn - which runs the command in a different goroutine.
l3.Hosts[l3SegHost].Do(t, func() {
res = icmd.RunCommand("wget", "-T1", "-t1", "-O-",
"http://"+stdnet.JoinHostPort(hostAddr, "8080"))
})
// A "404 Not Found" means the server responded, but it's got nothing to serve.
if !strings.Contains(res.Stderr(), "404 Not Found") {
return poll.Continue("404 Not Found not found in: %s", res.Stderr())
}
return poll.Success()
}
// Check the jump to DOCKER-INGRESS is at the top of the DOCKER-FORWARD chain.
checkChain := func() {
t.Helper()
res := icmd.RunCommand("iptables", "-S", "DOCKER-FORWARD")
assert.NilError(t, res.Error, "stderr: %s", res.Stderr())
// Only compare the first (fixed) part of the chain - per-bridge rules may be
// re-ordered when the daemon restarts.
out := strings.SplitAfter(res.Stdout(), "\n")
if len(out) > 5 {
out = out[:5]
}
golden.Assert(t, strings.Join(out, ""), t.Name()+"_docker_forward.golden")
}
l3.Hosts[l3SegHost].Do(t, func() {
d := swarm.NewSwarm(ctx, t, testEnv, daemon.WithSwarmIptables(true))
defer d.Stop(t)
c := d.NewClientT(t)
defer c.Close()
serviceID := swarm.CreateService(ctx, t, d,
swarm.ServiceWithName("test-dicp"),
swarm.ServiceWithCommand([]string{"httpd", "-f"}),
swarm.ServiceWithEndpoint(&swarmtypes.EndpointSpec{
Ports: []swarmtypes.PortConfig{
{
Protocol: "tcp",
TargetPort: 80,
PublishedPort: 8080,
PublishMode: swarmtypes.PortConfigPublishModeIngress,
},
},
}),
)
defer func() {
_, err := c.ServiceRemove(ctx, serviceID, client.ServiceRemoveOptions{})
assert.NilError(t, err)
}()
t.Log("Waiting for the service to start")
poll.WaitOn(t, swarm.RunningTasksCount(ctx, c, serviceID, 1), swarm.ServicePoll)
t.Log("Checking http access to the service")
poll.WaitOn(t, checkHTTP, poll.WithTimeout(30*time.Second))
checkChain()
t.Log("Restarting the daemon")
d.Restart(t)
t.Log("Waiting for the service to start")
poll.WaitOn(t, swarm.RunningTasksCount(ctx, c, serviceID, 1), swarm.ServicePoll)
t.Log("Checking http access to the service")
// It takes a while before this works ...
poll.WaitOn(t, checkHTTP, poll.WithTimeout(30*time.Second))
checkChain()
})
}
func TestRestoreIngressRulesOnFirewalldReload(t *testing.T) {
skip.If(t, testEnv.IsRemoteDaemon)
skip.If(t, testEnv.IsRootless, "rootless mode doesn't support Swarm-mode")
skip.If(t, testEnv.FirewallBackendDriver() != "iptables+firewalld", "nftables backend doesn't support Swarm-mode")
skip.If(t, !networking.FirewalldRunning(), "Need firewalld to test restoration ingress rules")
ctx := setupTest(t)
// Check the published port is accessible.
checkHTTP := func(_ poll.LogT) poll.Result {
res := icmd.RunCommand("curl", "-v", "-o", "/dev/null", "-w", "%{http_code}\n",
"http://"+stdnet.JoinHostPort("localhost", "8080"))
// A "404 Not Found" means the server responded, but it's got nothing to serve.
if !strings.Contains(res.Stdout(), "404") {
return poll.Continue("404 - not found in: %s, %+v", res.Stdout(), res)
}
return poll.Success()
}
d := swarm.NewSwarm(ctx, t, testEnv, daemon.WithSwarmIptables(true))
defer d.Stop(t)
c := d.NewClientT(t)
defer c.Close()
serviceID := swarm.CreateService(ctx, t, d,
swarm.ServiceWithName("test-ingress-on-firewalld-reload"),
swarm.ServiceWithCommand([]string{"httpd", "-f"}),
swarm.ServiceWithEndpoint(&swarmtypes.EndpointSpec{
Ports: []swarmtypes.PortConfig{
{
Protocol: "tcp",
TargetPort: 80,
PublishedPort: 8080,
PublishMode: swarmtypes.PortConfigPublishModeIngress,
},
},
}),
)
defer func() {
_, err := c.ServiceRemove(ctx, serviceID, client.ServiceRemoveOptions{})
assert.NilError(t, err)
}()
t.Log("Waiting for the service to start")
poll.WaitOn(t, swarm.RunningTasksCount(ctx, c, serviceID, 1), swarm.ServicePoll)
t.Log("Checking http access to the service")
poll.WaitOn(t, checkHTTP, poll.WithTimeout(30*time.Second))
t.Log("Firewalld reload")
networking.FirewalldReload(t, d)
t.Log("Checking http access to the service")
// It takes a while before this works ...
poll.WaitOn(t, checkHTTP, poll.WithTimeout(30*time.Second))
}