Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
276 changes: 231 additions & 45 deletions cmd/nerdctl/network/network_inspect_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ package network
import (
"encoding/json"
"errors"
"net"
"os/exec"
"runtime"
"strings"
Expand All @@ -37,7 +38,7 @@ import (
"github.com/containerd/nerdctl/v2/pkg/testutil/nerdtest"
)

func TestNetworkInspect(t *testing.T) {
func TestNetworkInspectBasic(t *testing.T) {
testCase := nerdtest.Setup()

const (
Expand All @@ -46,15 +47,6 @@ func TestNetworkInspect(t *testing.T) {
testIPRange = "10.24.24.0/25"
)

testCase.Setup = func(data test.Data, helpers test.Helpers) {
helpers.Ensure("network", "create", data.Identifier("basenet"))
data.Labels().Set("basenet", data.Identifier("basenet"))
}

testCase.Cleanup = func(data test.Data, helpers test.Helpers) {
helpers.Anyhow("network", "rm", data.Identifier("basenet"))
}

testCase.SubTests = []*test.Case{
{
Description: "non existent network",
Expand Down Expand Up @@ -133,6 +125,59 @@ func TestNetworkInspect(t *testing.T) {
assert.Equal(t, dc[0].Name, "custom")
}),
},
{
Description: "basic",
// FIXME: IPAMConfig is not implemented on Windows yet
Require: require.Not(require.Windows),
Setup: func(data test.Data, helpers test.Helpers) {
helpers.Ensure("network", "create", "--label", "tag=testNetwork", "--subnet", testSubnet,
"--gateway", testGateway, "--ip-range", testIPRange, data.Identifier())
},
Cleanup: func(data test.Data, helpers test.Helpers) {
helpers.Anyhow("network", "rm", data.Identifier())
},
Command: func(data test.Data, helpers test.Helpers) test.TestableCommand {
return helpers.Command("network", "inspect", data.Identifier())
},
Expected: func(data test.Data, helpers test.Helpers) *test.Expected {
return &test.Expected{
ExitCode: 0,
Output: func(stdout string, t tig.T) {
var dc []dockercompat.Network

err := json.Unmarshal([]byte(stdout), &dc)
assert.NilError(t, err, "Unable to unmarshal output\n")
assert.Equal(t, 1, len(dc), "Unexpectedly got multiple results\n")
got := dc[0]

assert.Equal(t, got.Name, data.Identifier())
assert.Equal(t, got.Labels["tag"], "testNetwork")
assert.Equal(t, len(got.IPAM.Config), 1)
assert.Equal(t, got.IPAM.Config[0].Subnet, testSubnet)
assert.Equal(t, got.IPAM.Config[0].Gateway, testGateway)
assert.Equal(t, got.IPAM.Config[0].IPRange, testIPRange)
},
}
},
},
}

testCase.Run(t)
}

func TestNetworkInspectByID(t *testing.T) {
testCase := nerdtest.Setup()

testCase.Setup = func(data test.Data, helpers test.Helpers) {
helpers.Ensure("network", "create", data.Identifier("basenet"))
data.Labels().Set("basenet", data.Identifier("basenet"))
}

testCase.Cleanup = func(data test.Data, helpers test.Helpers) {
helpers.Anyhow("network", "rm", data.Identifier("basenet"))
}

testCase.SubTests = []*test.Case{
{
Description: "match exact id",
// See notes below
Expand Down Expand Up @@ -201,41 +246,6 @@ func TestNetworkInspect(t *testing.T) {
}
},
},
{
Description: "basic",
// FIXME: IPAMConfig is not implemented on Windows yet
Require: require.Not(require.Windows),
Setup: func(data test.Data, helpers test.Helpers) {
helpers.Ensure("network", "create", "--label", "tag=testNetwork", "--subnet", testSubnet,
"--gateway", testGateway, "--ip-range", testIPRange, data.Identifier())
},
Cleanup: func(data test.Data, helpers test.Helpers) {
helpers.Anyhow("network", "rm", data.Identifier())
},
Command: func(data test.Data, helpers test.Helpers) test.TestableCommand {
return helpers.Command("network", "inspect", data.Identifier())
},
Expected: func(data test.Data, helpers test.Helpers) *test.Expected {
return &test.Expected{
ExitCode: 0,
Output: func(stdout string, t tig.T) {
var dc []dockercompat.Network

err := json.Unmarshal([]byte(stdout), &dc)
assert.NilError(t, err, "Unable to unmarshal output\n")
assert.Equal(t, 1, len(dc), "Unexpectedly got multiple results\n")
got := dc[0]

assert.Equal(t, got.Name, data.Identifier())
assert.Equal(t, got.Labels["tag"], "testNetwork")
assert.Equal(t, len(got.IPAM.Config), 1)
assert.Equal(t, got.IPAM.Config[0].Subnet, testSubnet)
assert.Equal(t, got.IPAM.Config[0].Gateway, testGateway)
assert.Equal(t, got.IPAM.Config[0].IPRange, testIPRange)
},
}
},
},
{
Description: "with namespace",
Require: require.Not(nerdtest.Docker),
Expand Down Expand Up @@ -287,6 +297,15 @@ func TestNetworkInspect(t *testing.T) {
}
},
},
}

testCase.Run(t)
}

func TestNetworkInspectWithContainers(t *testing.T) {
testCase := nerdtest.Setup()

testCase.SubTests = []*test.Case{
{
Description: "Verify that only active containers appear in the network inspect output",
Setup: func(data test.Data, helpers test.Helpers) {
Expand Down Expand Up @@ -397,6 +416,173 @@ func TestNetworkInspect(t *testing.T) {
}
},
},
{
Description: "Test container network details",
Setup: func(data test.Data, helpers test.Helpers) {
helpers.Ensure("network", "create", data.Identifier("test-network"))

// See https://github.com/containerd/nerdctl/issues/4322
if runtime.GOOS == "windows" {
time.Sleep(time.Second)
}

// Create and start a container on this network
helpers.Ensure("run", "-d", "--name", data.Identifier("test-container"),
"--network", data.Identifier("test-network"),
testutil.CommonImage, "sleep", nerdtest.Infinity)

// Get container ID for later use
containerID := strings.Trim(helpers.Capture("inspect", data.Identifier("test-container"), "--format", "{{.Id}}"), "\n")
data.Labels().Set("containerID", containerID)
},
Cleanup: func(data test.Data, helpers test.Helpers) {
helpers.Anyhow("rm", "-f", data.Identifier("test-container"))
helpers.Anyhow("network", "remove", data.Identifier("test-network"))
},
Command: func(data test.Data, helpers test.Helpers) test.TestableCommand {
return helpers.Command("network", "inspect", data.Identifier("test-network"))
},
Expected: func(data test.Data, helpers test.Helpers) *test.Expected {
return &test.Expected{
Output: func(stdout string, t tig.T) {
var dc []dockercompat.Network
err := json.Unmarshal([]byte(stdout), &dc)
assert.NilError(t, err, "Unable to unmarshal output")
assert.Equal(t, 1, len(dc), "Expected exactly one network")

network := dc[0]
assert.Equal(t, network.Name, data.Identifier("test-network"))
assert.Equal(t, 1, len(network.Containers), "Expected exactly one container")

// Get the container details
containerID := data.Labels().Get("containerID")
container := network.Containers[containerID]

// Test container name
assert.Equal(t, container.Name, data.Identifier("test-container"))

// Windows InspectNetNS is not implemented
if runtime.GOOS != "windows" {
// Verify IPv4Address is not empty and has CIDR notation
assert.Assert(t, container.IPv4Address != "", "IPv4Address should not be empty")
assert.Assert(t, strings.Contains(container.IPv4Address, "/"), "IPv4Address should contain CIDR notation with /")

// Verify IPv4Address is within the network's subnet
if len(network.IPAM.Config) > 0 && network.IPAM.Config[0].Subnet != "" {
_, subnet, err := net.ParseCIDR(network.IPAM.Config[0].Subnet)
assert.NilError(t, err, "Failed to parse network subnet")

containerIP, _, err := net.ParseCIDR(container.IPv4Address)
assert.NilError(t, err, "Failed to parse container IPv4Address")
assert.Assert(t, subnet.Contains(containerIP), "IPv4Address should be within the network's subnet")
}

// Test MacAddress is present and has valid format
assert.Assert(t, container.MacAddress != "", "MacAddress should not be empty")

// Test IPv6Address is empty for IPv4-only network
assert.Equal(t, "", container.IPv6Address, "IPv6Address should be empty for IPv4-only network")
}
},
}
},
},
}

testCase.Run(t)
}

func TestNetworkInspectDualStack(t *testing.T) {
testCase := nerdtest.Setup()

testCase.SubTests = []*test.Case{
{
Description: "Test dual-stack network with both IPv4 and IPv6",
Require: require.Not(require.Windows), // NetNS not implemented on Windows
Setup: func(data test.Data, helpers test.Helpers) {
helpers.Ensure("network", "create",
"--ipv6",
"--subnet", "10.1.0.0/24",
"--subnet", "fd00::/64",
data.Identifier("test-dual-stack"))

// See https://github.com/containerd/nerdctl/issues/4322
if runtime.GOOS == "windows" {
time.Sleep(time.Second)
}

// Create and start a container on this dual-stack network
helpers.Ensure("run", "-d",
"--name", data.Identifier("test-container"),
"--network", data.Identifier("test-dual-stack"),
testutil.CommonImage, "sleep", nerdtest.Infinity)

// Get container ID for later use
containerID := strings.Trim(helpers.Capture("inspect", data.Identifier("test-container"), "--format", "{{.Id}}"), "\n")
data.Labels().Set("containerID", containerID)
},
Cleanup: func(data test.Data, helpers test.Helpers) {
helpers.Anyhow("rm", "-f", data.Identifier("test-container"))
helpers.Anyhow("network", "remove", data.Identifier("test-dual-stack"))
},
Command: func(data test.Data, helpers test.Helpers) test.TestableCommand {
return helpers.Command("network", "inspect", data.Identifier("test-dual-stack"))
},
Expected: func(data test.Data, helpers test.Helpers) *test.Expected {
return &test.Expected{
Output: func(stdout string, t tig.T) {
var dc []dockercompat.Network
err := json.Unmarshal([]byte(stdout), &dc)
assert.NilError(t, err, "Unable to unmarshal output")
assert.Equal(t, 1, len(dc), "Expected exactly one network")

network := dc[0]
assert.Equal(t, network.Name, data.Identifier("test-dual-stack"))
assert.Equal(t, 2, len(network.IPAM.Config), "Expected two subnets (IPv4 and IPv6)")

// Get the container details
containerID := data.Labels().Get("containerID")
container := network.Containers[containerID]

// Test container name
assert.Equal(t, container.Name, data.Identifier("test-container"))

// Parse both subnets
var ipv4Subnet, ipv6Subnet *net.IPNet
for _, config := range network.IPAM.Config {
if config.Subnet != "" {
_, subnet, err := net.ParseCIDR(config.Subnet)
assert.NilError(t, err, "Failed to parse subnet")
if subnet.IP.To4() != nil {
ipv4Subnet = subnet
} else {
ipv6Subnet = subnet
}
}
}

// Verify IPv4 address is present and within subnet
assert.Assert(t, container.IPv4Address != "", "IPv4Address should not be empty in dual-stack network")
ipv4, _, err := net.ParseCIDR(container.IPv4Address)
assert.NilError(t, err, "Failed to parse IPv4Address")
if ipv4Subnet != nil {
assert.Assert(t, ipv4Subnet.Contains(ipv4), "IPv4 address should be within the IPv4 subnet")
}

// Verify IPv6 address is present and within subnet
assert.Assert(t, container.IPv6Address != "", "IPv6Address should not be empty in dual-stack network")
ipv6, _, err := net.ParseCIDR(container.IPv6Address)
assert.NilError(t, err, "Failed to parse IPv6Address")
if ipv6Subnet != nil {
assert.Assert(t, ipv6Subnet.Contains(ipv6), "IPv6 address should be within the IPv6 subnet")
}

// Verify MAC address is present
assert.Assert(t, container.MacAddress != "", "MacAddress should not be empty")
},
}
},
},
}

testCase.Run(t)
Expand Down
Loading
Loading