mirror of
https://github.com/psviderski/uncloud.git
synced 2026-08-26 11:03:34 +00:00
347 lines
8.5 KiB
Go
347 lines
8.5 KiB
Go
package api
|
|
|
|
import (
|
|
"github.com/docker/docker/api/types"
|
|
"github.com/docker/docker/api/types/container"
|
|
"github.com/stretchr/testify/assert"
|
|
"github.com/stretchr/testify/require"
|
|
"net/netip"
|
|
"reflect"
|
|
"testing"
|
|
)
|
|
|
|
func TestContainer_ServiceSpec(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
init := true
|
|
ctr := &Container{ContainerJSON: types.ContainerJSON{
|
|
ContainerJSONBase: &types.ContainerJSONBase{
|
|
HostConfig: &container.HostConfig{
|
|
Binds: []string{"/host/path:/container/path"},
|
|
Init: &init,
|
|
},
|
|
},
|
|
Config: &container.Config{
|
|
Cmd: []string{"/app/server"},
|
|
Image: "app:latest",
|
|
Labels: map[string]string{
|
|
LabelServiceID: "test-service-id",
|
|
LabelServiceName: "test-service-name",
|
|
LabelServicePorts: "app.example.com:8000/https",
|
|
},
|
|
},
|
|
}}
|
|
|
|
expectedSpec := ServiceSpec{
|
|
Container: ContainerSpec{
|
|
Command: []string{"/app/server"},
|
|
Image: "app:latest",
|
|
Init: &init,
|
|
Volumes: []string{"/host/path:/container/path"},
|
|
},
|
|
Name: "test-service-name",
|
|
Ports: []PortSpec{
|
|
{
|
|
Hostname: "app.example.com",
|
|
ContainerPort: 8000,
|
|
Protocol: ProtocolHTTPS,
|
|
Mode: PortModeIngress,
|
|
},
|
|
},
|
|
}
|
|
|
|
spec, err := ctr.ServiceSpec()
|
|
require.NoError(t, err)
|
|
assert.True(t, reflect.DeepEqual(spec, expectedSpec))
|
|
}
|
|
|
|
func TestContainer_Healthy(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
t.Run("exited", func(t *testing.T) {
|
|
t.Parallel()
|
|
c := &Container{ContainerJSON: types.ContainerJSON{
|
|
ContainerJSONBase: &types.ContainerJSONBase{
|
|
State: &types.ContainerState{
|
|
Running: false,
|
|
Dead: false,
|
|
ExitCode: 0,
|
|
},
|
|
},
|
|
}}
|
|
assert.False(t, c.Healthy())
|
|
})
|
|
|
|
t.Run("running with no health check", func(t *testing.T) {
|
|
t.Parallel()
|
|
c := &Container{ContainerJSON: types.ContainerJSON{
|
|
ContainerJSONBase: &types.ContainerJSONBase{
|
|
State: &types.ContainerState{
|
|
Running: true,
|
|
},
|
|
},
|
|
}}
|
|
assert.True(t, c.Healthy())
|
|
})
|
|
|
|
t.Run("running and healthy", func(t *testing.T) {
|
|
t.Parallel()
|
|
c := &Container{ContainerJSON: types.ContainerJSON{
|
|
ContainerJSONBase: &types.ContainerJSONBase{
|
|
State: &types.ContainerState{
|
|
Running: true,
|
|
Health: &types.Health{
|
|
Status: types.Healthy,
|
|
},
|
|
},
|
|
},
|
|
}}
|
|
assert.True(t, c.Healthy())
|
|
})
|
|
|
|
t.Run("running but unhealthy", func(t *testing.T) {
|
|
t.Parallel()
|
|
c := &Container{ContainerJSON: types.ContainerJSON{
|
|
ContainerJSONBase: &types.ContainerJSONBase{
|
|
State: &types.ContainerState{
|
|
Running: true,
|
|
Health: &types.Health{
|
|
Status: types.Unhealthy,
|
|
},
|
|
},
|
|
},
|
|
}}
|
|
assert.False(t, c.Healthy())
|
|
})
|
|
|
|
t.Run("running with health starting", func(t *testing.T) {
|
|
t.Parallel()
|
|
c := &Container{ContainerJSON: types.ContainerJSON{
|
|
ContainerJSONBase: &types.ContainerJSONBase{
|
|
State: &types.ContainerState{
|
|
Running: true,
|
|
Health: &types.Health{
|
|
Status: "starting",
|
|
},
|
|
},
|
|
},
|
|
}}
|
|
assert.False(t, c.Healthy())
|
|
})
|
|
|
|
t.Run("dead", func(t *testing.T) {
|
|
t.Parallel()
|
|
c := &Container{ContainerJSON: types.ContainerJSON{
|
|
ContainerJSONBase: &types.ContainerJSONBase{
|
|
State: &types.ContainerState{
|
|
Dead: true,
|
|
Running: false,
|
|
},
|
|
},
|
|
}}
|
|
assert.False(t, c.Healthy())
|
|
})
|
|
|
|
t.Run("restarting", func(t *testing.T) {
|
|
t.Parallel()
|
|
c := &Container{ContainerJSON: types.ContainerJSON{
|
|
ContainerJSONBase: &types.ContainerJSONBase{
|
|
State: &types.ContainerState{
|
|
Restarting: true,
|
|
Running: true,
|
|
ExitCode: 1,
|
|
},
|
|
},
|
|
}}
|
|
assert.False(t, c.Healthy())
|
|
})
|
|
|
|
t.Run("paused", func(t *testing.T) {
|
|
t.Parallel()
|
|
c := &Container{ContainerJSON: types.ContainerJSON{
|
|
ContainerJSONBase: &types.ContainerJSONBase{
|
|
State: &types.ContainerState{
|
|
Paused: true,
|
|
Running: true,
|
|
},
|
|
},
|
|
}}
|
|
assert.False(t, c.Healthy())
|
|
})
|
|
}
|
|
|
|
func TestContainer_ConflictingServicePorts(t *testing.T) {
|
|
tests := []struct {
|
|
name string
|
|
containerPorts string
|
|
checkPorts []PortSpec
|
|
want []PortSpec
|
|
wantErr bool
|
|
}{
|
|
{
|
|
name: "no conflicts when container has no ports",
|
|
containerPorts: "",
|
|
checkPorts: []PortSpec{
|
|
{Mode: PortModeHost, PublishedPort: 8080, ContainerPort: 80, Protocol: ProtocolTCP},
|
|
},
|
|
want: nil,
|
|
wantErr: false,
|
|
},
|
|
{
|
|
name: "host mode ports with same published port and protocol conflict",
|
|
containerPorts: "8080:80/tcp@host",
|
|
checkPorts: []PortSpec{
|
|
{Mode: PortModeHost, PublishedPort: 8080, ContainerPort: 80, Protocol: ProtocolTCP},
|
|
},
|
|
want: []PortSpec{
|
|
{Mode: PortModeHost, PublishedPort: 8080, ContainerPort: 80, Protocol: ProtocolTCP},
|
|
},
|
|
wantErr: false,
|
|
},
|
|
{
|
|
name: "host mode ports with same port but different protocols don't conflict",
|
|
containerPorts: "8080:80/tcp@host",
|
|
checkPorts: []PortSpec{
|
|
{Mode: PortModeHost, PublishedPort: 8080, ContainerPort: 80, Protocol: ProtocolUDP},
|
|
},
|
|
want: nil,
|
|
wantErr: false,
|
|
},
|
|
{
|
|
name: "multiple protocols on same port don't conflict",
|
|
containerPorts: "8080:80/tcp@host,8080:80/udp@host",
|
|
checkPorts: []PortSpec{
|
|
{Mode: PortModeHost, PublishedPort: 8080, ContainerPort: 80, Protocol: ProtocolUDP},
|
|
},
|
|
want: []PortSpec{
|
|
{Mode: PortModeHost, PublishedPort: 8080, ContainerPort: 80, Protocol: ProtocolUDP},
|
|
},
|
|
wantErr: false,
|
|
},
|
|
{
|
|
name: "host mode ports with different published ports don't conflict",
|
|
containerPorts: "8080:80/tcp@host",
|
|
checkPorts: []PortSpec{
|
|
{Mode: PortModeHost, PublishedPort: 8081, ContainerPort: 80, Protocol: ProtocolTCP},
|
|
},
|
|
want: nil,
|
|
wantErr: false,
|
|
},
|
|
{
|
|
name: "host mode ports with same published port but different host IPs don't conflict",
|
|
containerPorts: "127.0.0.1:8080:80/tcp@host",
|
|
checkPorts: []PortSpec{
|
|
{
|
|
Mode: PortModeHost,
|
|
HostIP: netip.MustParseAddr("127.0.0.2"),
|
|
PublishedPort: 8080,
|
|
ContainerPort: 80,
|
|
Protocol: ProtocolTCP,
|
|
},
|
|
},
|
|
want: nil,
|
|
wantErr: false,
|
|
},
|
|
{
|
|
name: "host mode ports with same published port, protocol, and host IP conflict",
|
|
containerPorts: "127.0.0.1:8080:80/tcp@host",
|
|
checkPorts: []PortSpec{
|
|
{
|
|
Mode: PortModeHost,
|
|
HostIP: netip.MustParseAddr("127.0.0.1"),
|
|
PublishedPort: 8080,
|
|
ContainerPort: 80,
|
|
Protocol: ProtocolTCP,
|
|
},
|
|
},
|
|
want: []PortSpec{
|
|
{
|
|
Mode: PortModeHost,
|
|
HostIP: netip.MustParseAddr("127.0.0.1"),
|
|
PublishedPort: 8080,
|
|
ContainerPort: 80,
|
|
Protocol: ProtocolTCP,
|
|
},
|
|
},
|
|
wantErr: false,
|
|
},
|
|
{
|
|
name: "host mode port with no host IP conflicts with specific host IP on same port and protocol",
|
|
containerPorts: "8080:80/tcp@host",
|
|
checkPorts: []PortSpec{
|
|
{
|
|
Mode: PortModeHost,
|
|
HostIP: netip.MustParseAddr("127.0.0.1"),
|
|
PublishedPort: 8080,
|
|
ContainerPort: 80,
|
|
Protocol: ProtocolTCP,
|
|
},
|
|
},
|
|
want: []PortSpec{
|
|
{
|
|
Mode: PortModeHost,
|
|
HostIP: netip.MustParseAddr("127.0.0.1"),
|
|
PublishedPort: 8080,
|
|
ContainerPort: 80,
|
|
Protocol: ProtocolTCP,
|
|
},
|
|
},
|
|
wantErr: false,
|
|
},
|
|
{
|
|
name: "host mode port with no host IP doesn't conflict with different protocol",
|
|
containerPorts: "8080:80/tcp@host",
|
|
checkPorts: []PortSpec{
|
|
{
|
|
Mode: PortModeHost,
|
|
HostIP: netip.MustParseAddr("127.0.0.1"),
|
|
PublishedPort: 8080,
|
|
ContainerPort: 80,
|
|
Protocol: ProtocolUDP,
|
|
},
|
|
},
|
|
want: nil,
|
|
wantErr: false,
|
|
},
|
|
{
|
|
name: "ingress mode ports don't conflict with host mode ports",
|
|
containerPorts: "8080:80/tcp",
|
|
checkPorts: []PortSpec{
|
|
{Mode: PortModeHost, PublishedPort: 8080, ContainerPort: 80, Protocol: ProtocolTCP},
|
|
},
|
|
want: nil,
|
|
wantErr: false,
|
|
},
|
|
{
|
|
name: "container with invalid port spec returns error",
|
|
containerPorts: "invalid:port:spec",
|
|
checkPorts: []PortSpec{
|
|
{Mode: PortModeHost, PublishedPort: 8080, ContainerPort: 80, Protocol: ProtocolTCP},
|
|
},
|
|
want: nil,
|
|
wantErr: true,
|
|
},
|
|
}
|
|
|
|
for _, tt := range tests {
|
|
t.Run(tt.name, func(t *testing.T) {
|
|
ctr := &Container{ContainerJSON: types.ContainerJSON{
|
|
Config: &container.Config{
|
|
Labels: map[string]string{
|
|
LabelServicePorts: tt.containerPorts,
|
|
},
|
|
},
|
|
}}
|
|
|
|
got, err := ctr.ConflictingServicePorts(tt.checkPorts)
|
|
if tt.wantErr {
|
|
require.Error(t, err)
|
|
return
|
|
}
|
|
|
|
require.NoError(t, err)
|
|
assert.Equal(t, tt.want, got)
|
|
})
|
|
}
|
|
}
|