mirror of
https://github.com/moby/moby.git
synced 2026-01-11 10:41:43 +00:00
Add integration tests for Windows container functionality focusing on network drivers and container isolation modes. Signed-off-by: Sopho Merkviladze <smerkviladze@mirantis.com>
412 lines
15 KiB
Go
412 lines
15 KiB
Go
package container
|
|
|
|
import (
|
|
"context"
|
|
"strings"
|
|
"testing"
|
|
"time"
|
|
|
|
containertypes "github.com/moby/moby/api/types/container"
|
|
"github.com/moby/moby/api/types/mount"
|
|
"github.com/moby/moby/client"
|
|
"github.com/moby/moby/v2/integration/internal/container"
|
|
"github.com/moby/moby/v2/internal/testutil"
|
|
"gotest.tools/v3/assert"
|
|
is "gotest.tools/v3/assert/cmp"
|
|
)
|
|
|
|
// TestWindowsProcessIsolation validates process isolation on Windows.
|
|
func TestWindowsProcessIsolation(t *testing.T) {
|
|
ctx := setupTest(t)
|
|
apiClient := testEnv.APIClient()
|
|
|
|
testcases := []struct {
|
|
name string
|
|
description string
|
|
validate func(t *testing.T, ctx context.Context, id string)
|
|
}{
|
|
{
|
|
name: "Process isolation basic container lifecycle",
|
|
description: "Validate container can start, run, and stop with process isolation",
|
|
validate: func(t *testing.T, ctx context.Context, id string) {
|
|
// Verify container is running
|
|
ctrInfo := container.Inspect(ctx, t, apiClient, id)
|
|
assert.Check(t, is.Equal(ctrInfo.State.Running, true))
|
|
assert.Check(t, is.Equal(ctrInfo.HostConfig.Isolation, containertypes.IsolationProcess))
|
|
|
|
execCtx, cancel := context.WithTimeout(ctx, 10*time.Second)
|
|
defer cancel()
|
|
res := container.ExecT(execCtx, t, apiClient, id, []string{"cmd", "/c", "echo", "test"})
|
|
assert.Check(t, is.Equal(res.ExitCode, 0))
|
|
assert.Check(t, strings.Contains(res.Stdout(), "test"))
|
|
},
|
|
},
|
|
{
|
|
name: "Process isolation filesystem access",
|
|
description: "Validate filesystem operations work correctly with process isolation",
|
|
validate: func(t *testing.T, ctx context.Context, id string) {
|
|
execCtx, cancel := context.WithTimeout(ctx, 10*time.Second)
|
|
defer cancel()
|
|
|
|
// Create a test file
|
|
res := container.ExecT(execCtx, t, apiClient, id,
|
|
[]string{"cmd", "/c", "echo test123 > C:\\testfile.txt"})
|
|
assert.Check(t, is.Equal(res.ExitCode, 0))
|
|
|
|
// Read the test file
|
|
execCtx2, cancel2 := context.WithTimeout(ctx, 10*time.Second)
|
|
defer cancel2()
|
|
res2 := container.ExecT(execCtx2, t, apiClient, id,
|
|
[]string{"cmd", "/c", "type", "C:\\testfile.txt"})
|
|
assert.Check(t, is.Equal(res2.ExitCode, 0))
|
|
assert.Check(t, strings.Contains(res2.Stdout(), "test123"))
|
|
},
|
|
},
|
|
{
|
|
name: "Process isolation network connectivity",
|
|
description: "Validate network connectivity works with process isolation",
|
|
validate: func(t *testing.T, ctx context.Context, id string) {
|
|
execCtx, cancel := context.WithTimeout(ctx, 15*time.Second)
|
|
defer cancel()
|
|
|
|
// Test localhost connectivity
|
|
res := container.ExecT(execCtx, t, apiClient, id,
|
|
[]string{"ping", "-n", "1", "-w", "3000", "localhost"})
|
|
assert.Check(t, is.Equal(res.ExitCode, 0))
|
|
assert.Check(t, strings.Contains(res.Stdout(), "Reply from") ||
|
|
strings.Contains(res.Stdout(), "Received = 1"))
|
|
},
|
|
},
|
|
{
|
|
name: "Process isolation environment variables",
|
|
description: "Validate environment variables are properly isolated",
|
|
validate: func(t *testing.T, ctx context.Context, id string) {
|
|
execCtx, cancel := context.WithTimeout(ctx, 10*time.Second)
|
|
defer cancel()
|
|
|
|
// Check that container has expected environment variables
|
|
res := container.ExecT(execCtx, t, apiClient, id,
|
|
[]string{"cmd", "/c", "set"})
|
|
assert.Check(t, is.Equal(res.ExitCode, 0))
|
|
|
|
// Should have Windows-specific environment variables
|
|
stdout := res.Stdout()
|
|
assert.Check(t, strings.Contains(stdout, "COMPUTERNAME") ||
|
|
strings.Contains(stdout, "OS=Windows"))
|
|
},
|
|
},
|
|
{
|
|
name: "Process isolation CPU access",
|
|
description: "Validate container can access CPU information",
|
|
validate: func(t *testing.T, ctx context.Context, id string) {
|
|
execCtx, cancel := context.WithTimeout(ctx, 10*time.Second)
|
|
defer cancel()
|
|
|
|
// Check NUMBER_OF_PROCESSORS environment variable
|
|
res := container.ExecT(execCtx, t, apiClient, id,
|
|
[]string{"cmd", "/c", "echo", "%NUMBER_OF_PROCESSORS%"})
|
|
assert.Check(t, is.Equal(res.ExitCode, 0))
|
|
|
|
// Should return a number
|
|
output := strings.TrimSpace(res.Stdout())
|
|
assert.Check(t, output != "" && output != "%NUMBER_OF_PROCESSORS%",
|
|
"NUMBER_OF_PROCESSORS not set")
|
|
},
|
|
},
|
|
}
|
|
|
|
for _, tc := range testcases {
|
|
t.Run(tc.name, func(t *testing.T) {
|
|
ctx := testutil.StartSpan(ctx, t)
|
|
|
|
// Create and start container with process isolation
|
|
id := container.Run(ctx, t, apiClient,
|
|
container.WithIsolation(containertypes.IsolationProcess),
|
|
container.WithCmd("ping", "-t", "localhost"),
|
|
)
|
|
defer apiClient.ContainerRemove(ctx, id, client.ContainerRemoveOptions{Force: true})
|
|
|
|
tc.validate(t, ctx, id)
|
|
})
|
|
}
|
|
}
|
|
|
|
// TestWindowsHyperVIsolation validates Hyper-V isolation on Windows.
|
|
func TestWindowsHyperVIsolation(t *testing.T) {
|
|
ctx := setupTest(t)
|
|
apiClient := testEnv.APIClient()
|
|
|
|
testcases := []struct {
|
|
name string
|
|
description string
|
|
validate func(t *testing.T, ctx context.Context, id string)
|
|
}{
|
|
{
|
|
name: "Hyper-V isolation basic container lifecycle",
|
|
description: "Validate container can start, run, and stop with Hyper-V isolation",
|
|
validate: func(t *testing.T, ctx context.Context, id string) {
|
|
// Verify container is running
|
|
ctrInfo := container.Inspect(ctx, t, apiClient, id)
|
|
assert.Check(t, is.Equal(ctrInfo.State.Running, true))
|
|
assert.Check(t, is.Equal(ctrInfo.HostConfig.Isolation, containertypes.IsolationHyperV))
|
|
|
|
// Execute a simple command
|
|
execCtx, cancel := context.WithTimeout(ctx, 15*time.Second)
|
|
defer cancel()
|
|
res := container.ExecT(execCtx, t, apiClient, id, []string{"cmd", "/c", "echo", "hyperv-test"})
|
|
assert.Check(t, is.Equal(res.ExitCode, 0))
|
|
assert.Check(t, strings.Contains(res.Stdout(), "hyperv-test"))
|
|
},
|
|
},
|
|
{
|
|
name: "Hyper-V isolation filesystem operations",
|
|
description: "Validate filesystem isolation with Hyper-V",
|
|
validate: func(t *testing.T, ctx context.Context, id string) {
|
|
execCtx, cancel := context.WithTimeout(ctx, 15*time.Second)
|
|
defer cancel()
|
|
|
|
// Test file creation
|
|
res := container.ExecT(execCtx, t, apiClient, id,
|
|
[]string{"cmd", "/c", "echo hyperv-file > C:\\hvtest.txt"})
|
|
assert.Check(t, is.Equal(res.ExitCode, 0))
|
|
|
|
// Test file read
|
|
execCtx2, cancel2 := context.WithTimeout(ctx, 15*time.Second)
|
|
defer cancel2()
|
|
res2 := container.ExecT(execCtx2, t, apiClient, id,
|
|
[]string{"cmd", "/c", "type", "C:\\hvtest.txt"})
|
|
assert.Check(t, is.Equal(res2.ExitCode, 0))
|
|
assert.Check(t, strings.Contains(res2.Stdout(), "hyperv-file"))
|
|
},
|
|
},
|
|
{
|
|
name: "Hyper-V isolation network connectivity",
|
|
description: "Validate network works with Hyper-V isolation",
|
|
validate: func(t *testing.T, ctx context.Context, id string) {
|
|
execCtx, cancel := context.WithTimeout(ctx, 15*time.Second)
|
|
defer cancel()
|
|
|
|
// Test localhost connectivity
|
|
res := container.ExecT(execCtx, t, apiClient, id,
|
|
[]string{"ping", "-n", "1", "-w", "5000", "localhost"})
|
|
assert.Check(t, is.Equal(res.ExitCode, 0))
|
|
},
|
|
},
|
|
}
|
|
|
|
for _, tc := range testcases {
|
|
t.Run(tc.name, func(t *testing.T) {
|
|
ctx := testutil.StartSpan(ctx, t)
|
|
|
|
// Create and start container with Hyper-V isolation
|
|
id := container.Run(ctx, t, apiClient,
|
|
container.WithIsolation(containertypes.IsolationHyperV),
|
|
container.WithCmd("ping", "-t", "localhost"),
|
|
)
|
|
defer apiClient.ContainerRemove(ctx, id, client.ContainerRemoveOptions{Force: true})
|
|
|
|
tc.validate(t, ctx, id)
|
|
})
|
|
}
|
|
}
|
|
|
|
// TestWindowsIsolationComparison validates that both isolation modes can coexist
|
|
// and that containers can be created with different isolation modes on Windows.
|
|
func TestWindowsIsolationComparison(t *testing.T) {
|
|
ctx := setupTest(t)
|
|
apiClient := testEnv.APIClient()
|
|
|
|
// Create container with process isolation
|
|
processID := container.Run(ctx, t, apiClient,
|
|
container.WithIsolation(containertypes.IsolationProcess),
|
|
container.WithCmd("ping", "-t", "localhost"),
|
|
)
|
|
defer apiClient.ContainerRemove(ctx, processID, client.ContainerRemoveOptions{Force: true})
|
|
|
|
processInfo := container.Inspect(ctx, t, apiClient, processID)
|
|
assert.Check(t, is.Equal(processInfo.HostConfig.Isolation, containertypes.IsolationProcess))
|
|
assert.Check(t, is.Equal(processInfo.State.Running, true))
|
|
|
|
// Create container with Hyper-V isolation
|
|
hypervID := container.Run(ctx, t, apiClient,
|
|
container.WithIsolation(containertypes.IsolationHyperV),
|
|
container.WithCmd("ping", "-t", "localhost"),
|
|
)
|
|
defer apiClient.ContainerRemove(ctx, hypervID, client.ContainerRemoveOptions{Force: true})
|
|
|
|
hypervInfo := container.Inspect(ctx, t, apiClient, hypervID)
|
|
assert.Check(t, is.Equal(hypervInfo.HostConfig.Isolation, containertypes.IsolationHyperV))
|
|
assert.Check(t, is.Equal(hypervInfo.State.Running, true))
|
|
|
|
// Verify both containers can run simultaneously
|
|
processInfo2 := container.Inspect(ctx, t, apiClient, processID)
|
|
hypervInfo2 := container.Inspect(ctx, t, apiClient, hypervID)
|
|
assert.Check(t, is.Equal(processInfo2.State.Running, true))
|
|
assert.Check(t, is.Equal(hypervInfo2.State.Running, true))
|
|
}
|
|
|
|
// TestWindowsProcessIsolationResourceConstraints validates resource constraints
|
|
// work correctly with process isolation on Windows.
|
|
func TestWindowsProcessIsolationResourceConstraints(t *testing.T) {
|
|
ctx := setupTest(t)
|
|
apiClient := testEnv.APIClient()
|
|
|
|
testcases := []struct {
|
|
name string
|
|
cpuShares int64
|
|
nanoCPUs int64
|
|
memoryLimit int64
|
|
cpuCount int64
|
|
validateConfig func(t *testing.T, ctrInfo containertypes.InspectResponse)
|
|
}{
|
|
{
|
|
name: "CPU shares constraint - config only",
|
|
cpuShares: 512,
|
|
// Note: CPU shares are accepted by the API but NOT enforced on Windows.
|
|
// This test only verifies the configuration is stored correctly.
|
|
// Actual enforcement does not work - containers get equal CPU regardless of shares.
|
|
// Use NanoCPUs (--cpus flag) for actual CPU limiting on Windows.
|
|
validateConfig: func(t *testing.T, ctrInfo containertypes.InspectResponse) {
|
|
assert.Check(t, is.Equal(ctrInfo.HostConfig.CPUShares, int64(512)))
|
|
},
|
|
},
|
|
{
|
|
name: "CPU limit (NanoCPUs) constraint",
|
|
nanoCPUs: 2000000000, // 2.0 CPUs
|
|
// NanoCPUs enforce hard CPU limits on Windows (unlike CPUShares which don't work)
|
|
validateConfig: func(t *testing.T, ctrInfo containertypes.InspectResponse) {
|
|
assert.Check(t, is.Equal(ctrInfo.HostConfig.NanoCPUs, int64(2000000000)))
|
|
},
|
|
},
|
|
{
|
|
name: "Memory limit constraint",
|
|
memoryLimit: 512 * 1024 * 1024, // 512MB
|
|
// Memory limits enforce hard limits on container memory usage
|
|
validateConfig: func(t *testing.T, ctrInfo containertypes.InspectResponse) {
|
|
assert.Check(t, is.Equal(ctrInfo.HostConfig.Memory, int64(512*1024*1024)))
|
|
},
|
|
},
|
|
{
|
|
name: "CPU count constraint",
|
|
cpuCount: 2,
|
|
// CPU count limits the number of CPUs available to the container
|
|
validateConfig: func(t *testing.T, ctrInfo containertypes.InspectResponse) {
|
|
assert.Check(t, is.Equal(ctrInfo.HostConfig.CPUCount, int64(2)))
|
|
},
|
|
},
|
|
}
|
|
|
|
for _, tc := range testcases {
|
|
t.Run(tc.name, func(t *testing.T) {
|
|
ctx := testutil.StartSpan(ctx, t)
|
|
|
|
opts := []func(*container.TestContainerConfig){
|
|
container.WithIsolation(containertypes.IsolationProcess),
|
|
container.WithCmd("ping", "-t", "localhost"),
|
|
}
|
|
|
|
if tc.cpuShares > 0 {
|
|
opts = append(opts, func(config *container.TestContainerConfig) {
|
|
config.HostConfig.CPUShares = tc.cpuShares
|
|
})
|
|
}
|
|
|
|
if tc.nanoCPUs > 0 {
|
|
opts = append(opts, func(config *container.TestContainerConfig) {
|
|
config.HostConfig.NanoCPUs = tc.nanoCPUs
|
|
})
|
|
}
|
|
|
|
if tc.memoryLimit > 0 {
|
|
opts = append(opts, func(config *container.TestContainerConfig) {
|
|
config.HostConfig.Memory = tc.memoryLimit
|
|
})
|
|
}
|
|
|
|
if tc.cpuCount > 0 {
|
|
opts = append(opts, func(config *container.TestContainerConfig) {
|
|
config.HostConfig.CPUCount = tc.cpuCount
|
|
})
|
|
}
|
|
|
|
id := container.Run(ctx, t, apiClient, opts...)
|
|
defer apiClient.ContainerRemove(ctx, id, client.ContainerRemoveOptions{Force: true})
|
|
|
|
ctrInfo := container.Inspect(ctx, t, apiClient, id)
|
|
tc.validateConfig(t, ctrInfo)
|
|
})
|
|
}
|
|
}
|
|
|
|
// TestWindowsProcessIsolationVolumeMount validates volume mounting with process isolation on Windows.
|
|
func TestWindowsProcessIsolationVolumeMount(t *testing.T) {
|
|
ctx := setupTest(t)
|
|
apiClient := testEnv.APIClient()
|
|
|
|
volumeName := "process-iso-test-volume"
|
|
volRes, err := apiClient.VolumeCreate(ctx, client.VolumeCreateOptions{
|
|
Name: volumeName,
|
|
})
|
|
assert.NilError(t, err)
|
|
defer func() {
|
|
// Force volume removal in case container cleanup fails
|
|
apiClient.VolumeRemove(ctx, volRes.Volume.Name, client.VolumeRemoveOptions{Force: true})
|
|
}()
|
|
|
|
// Create container with volume mount
|
|
id := container.Run(ctx, t, apiClient,
|
|
container.WithIsolation(containertypes.IsolationProcess),
|
|
container.WithCmd("ping", "-t", "localhost"),
|
|
container.WithMount(mount.Mount{
|
|
Type: mount.TypeVolume,
|
|
Source: volumeName,
|
|
Target: "C:\\data",
|
|
}),
|
|
)
|
|
defer apiClient.ContainerRemove(ctx, id, client.ContainerRemoveOptions{Force: true})
|
|
|
|
// Write data to mounted volume
|
|
execCtx, cancel := context.WithTimeout(ctx, 10*time.Second)
|
|
defer cancel()
|
|
res := container.ExecT(execCtx, t, apiClient, id,
|
|
[]string{"cmd", "/c", "echo volume-test > C:\\data\\test.txt"})
|
|
assert.Check(t, is.Equal(res.ExitCode, 0))
|
|
|
|
// Read data from mounted volume
|
|
execCtx2, cancel2 := context.WithTimeout(ctx, 10*time.Second)
|
|
defer cancel2()
|
|
res2 := container.ExecT(execCtx2, t, apiClient, id,
|
|
[]string{"cmd", "/c", "type", "C:\\data\\test.txt"})
|
|
assert.Check(t, is.Equal(res2.ExitCode, 0))
|
|
assert.Check(t, strings.Contains(res2.Stdout(), "volume-test"))
|
|
|
|
// Verify container has volume mount
|
|
ctrInfo := container.Inspect(ctx, t, apiClient, id)
|
|
assert.Check(t, len(ctrInfo.Mounts) == 1)
|
|
assert.Check(t, is.Equal(ctrInfo.Mounts[0].Type, mount.TypeVolume))
|
|
assert.Check(t, is.Equal(ctrInfo.Mounts[0].Name, volumeName))
|
|
}
|
|
|
|
// TestWindowsHyperVIsolationResourceLimits validates resource limits work with Hyper-V isolation.
|
|
// This ensures Windows can properly enforce resource constraints on Hyper-V containers.
|
|
func TestWindowsHyperVIsolationResourceLimits(t *testing.T) {
|
|
ctx := setupTest(t)
|
|
apiClient := testEnv.APIClient()
|
|
|
|
// Create container with memory limit
|
|
memoryLimit := int64(512 * 1024 * 1024) // 512MB
|
|
id := container.Run(ctx, t, apiClient,
|
|
container.WithIsolation(containertypes.IsolationHyperV),
|
|
container.WithCmd("ping", "-t", "localhost"),
|
|
func(config *container.TestContainerConfig) {
|
|
config.HostConfig.Memory = memoryLimit
|
|
},
|
|
)
|
|
defer apiClient.ContainerRemove(ctx, id, client.ContainerRemoveOptions{Force: true})
|
|
|
|
// Verify resource limit is set
|
|
ctrInfo := container.Inspect(ctx, t, apiClient, id)
|
|
assert.Check(t, is.Equal(ctrInfo.HostConfig.Memory, memoryLimit))
|
|
assert.Check(t, is.Equal(ctrInfo.HostConfig.Isolation, containertypes.IsolationHyperV))
|
|
}
|