687bdc7c71
The initial implementation followed the Swarm API, where PidsLimit is located in ContainerSpec. This is not the desired place for this property, so moving the field to TaskTemplate.Resources in our API. A similar change should be made in the SwarmKit API (likely keeping the old field for backward compatibility, because it was merged some releases back) Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
380 lines
13 KiB
Go
380 lines
13 KiB
Go
package service // import "github.com/docker/docker/integration/service"
|
|
|
|
import (
|
|
"context"
|
|
"testing"
|
|
|
|
"github.com/docker/docker/api/types"
|
|
"github.com/docker/docker/api/types/filters"
|
|
swarmtypes "github.com/docker/docker/api/types/swarm"
|
|
"github.com/docker/docker/api/types/versions"
|
|
"github.com/docker/docker/client"
|
|
"github.com/docker/docker/integration/internal/network"
|
|
"github.com/docker/docker/integration/internal/swarm"
|
|
"gotest.tools/v3/assert"
|
|
is "gotest.tools/v3/assert/cmp"
|
|
"gotest.tools/v3/poll"
|
|
"gotest.tools/v3/skip"
|
|
)
|
|
|
|
func TestServiceUpdateLabel(t *testing.T) {
|
|
skip.If(t, testEnv.DaemonInfo.OSType != "linux")
|
|
defer setupTest(t)()
|
|
d := swarm.NewSwarm(t, testEnv)
|
|
defer d.Stop(t)
|
|
cli := d.NewClientT(t)
|
|
defer cli.Close()
|
|
|
|
ctx := context.Background()
|
|
serviceName := "TestService_" + t.Name()
|
|
serviceID := swarm.CreateService(t, d, swarm.ServiceWithName(serviceName))
|
|
service := getService(t, cli, serviceID)
|
|
assert.Check(t, is.DeepEqual(service.Spec.Labels, map[string]string{}))
|
|
|
|
// add label to empty set
|
|
service.Spec.Labels["foo"] = "bar"
|
|
_, err := cli.ServiceUpdate(ctx, serviceID, service.Version, service.Spec, types.ServiceUpdateOptions{})
|
|
assert.NilError(t, err)
|
|
poll.WaitOn(t, serviceSpecIsUpdated(cli, serviceID, service.Version.Index), swarm.ServicePoll)
|
|
service = getService(t, cli, serviceID)
|
|
assert.Check(t, is.DeepEqual(service.Spec.Labels, map[string]string{"foo": "bar"}))
|
|
|
|
// add label to non-empty set
|
|
service.Spec.Labels["foo2"] = "bar"
|
|
_, err = cli.ServiceUpdate(ctx, serviceID, service.Version, service.Spec, types.ServiceUpdateOptions{})
|
|
assert.NilError(t, err)
|
|
poll.WaitOn(t, serviceSpecIsUpdated(cli, serviceID, service.Version.Index), swarm.ServicePoll)
|
|
service = getService(t, cli, serviceID)
|
|
assert.Check(t, is.DeepEqual(service.Spec.Labels, map[string]string{"foo": "bar", "foo2": "bar"}))
|
|
|
|
delete(service.Spec.Labels, "foo2")
|
|
_, err = cli.ServiceUpdate(ctx, serviceID, service.Version, service.Spec, types.ServiceUpdateOptions{})
|
|
assert.NilError(t, err)
|
|
poll.WaitOn(t, serviceSpecIsUpdated(cli, serviceID, service.Version.Index), swarm.ServicePoll)
|
|
service = getService(t, cli, serviceID)
|
|
assert.Check(t, is.DeepEqual(service.Spec.Labels, map[string]string{"foo": "bar"}))
|
|
|
|
delete(service.Spec.Labels, "foo")
|
|
_, err = cli.ServiceUpdate(ctx, serviceID, service.Version, service.Spec, types.ServiceUpdateOptions{})
|
|
assert.NilError(t, err)
|
|
poll.WaitOn(t, serviceSpecIsUpdated(cli, serviceID, service.Version.Index), swarm.ServicePoll)
|
|
service = getService(t, cli, serviceID)
|
|
assert.Check(t, is.DeepEqual(service.Spec.Labels, map[string]string{}))
|
|
|
|
// now make sure we can add again
|
|
service.Spec.Labels["foo"] = "bar"
|
|
_, err = cli.ServiceUpdate(ctx, serviceID, service.Version, service.Spec, types.ServiceUpdateOptions{})
|
|
assert.NilError(t, err)
|
|
poll.WaitOn(t, serviceSpecIsUpdated(cli, serviceID, service.Version.Index), swarm.ServicePoll)
|
|
service = getService(t, cli, serviceID)
|
|
assert.Check(t, is.DeepEqual(service.Spec.Labels, map[string]string{"foo": "bar"}))
|
|
|
|
err = cli.ServiceRemove(context.Background(), serviceID)
|
|
assert.NilError(t, err)
|
|
}
|
|
|
|
func TestServiceUpdateSecrets(t *testing.T) {
|
|
skip.If(t, testEnv.DaemonInfo.OSType != "linux")
|
|
defer setupTest(t)()
|
|
d := swarm.NewSwarm(t, testEnv)
|
|
defer d.Stop(t)
|
|
cli := d.NewClientT(t)
|
|
defer cli.Close()
|
|
|
|
ctx := context.Background()
|
|
secretName := "TestSecret_" + t.Name()
|
|
secretTarget := "targetName"
|
|
resp, err := cli.SecretCreate(ctx, swarmtypes.SecretSpec{
|
|
Annotations: swarmtypes.Annotations{
|
|
Name: secretName,
|
|
},
|
|
Data: []byte("TESTINGDATA"),
|
|
})
|
|
assert.NilError(t, err)
|
|
assert.Check(t, resp.ID != "")
|
|
|
|
serviceName := "TestService_" + t.Name()
|
|
serviceID := swarm.CreateService(t, d, swarm.ServiceWithName(serviceName))
|
|
service := getService(t, cli, serviceID)
|
|
|
|
// add secret
|
|
service.Spec.TaskTemplate.ContainerSpec.Secrets = append(service.Spec.TaskTemplate.ContainerSpec.Secrets,
|
|
&swarmtypes.SecretReference{
|
|
File: &swarmtypes.SecretReferenceFileTarget{
|
|
Name: secretTarget,
|
|
UID: "0",
|
|
GID: "0",
|
|
Mode: 0600,
|
|
},
|
|
SecretID: resp.ID,
|
|
SecretName: secretName,
|
|
},
|
|
)
|
|
_, err = cli.ServiceUpdate(ctx, serviceID, service.Version, service.Spec, types.ServiceUpdateOptions{})
|
|
assert.NilError(t, err)
|
|
poll.WaitOn(t, serviceIsUpdated(cli, serviceID), swarm.ServicePoll)
|
|
|
|
service = getService(t, cli, serviceID)
|
|
secrets := service.Spec.TaskTemplate.ContainerSpec.Secrets
|
|
assert.Assert(t, is.Equal(1, len(secrets)))
|
|
|
|
secret := *secrets[0]
|
|
assert.Check(t, is.Equal(secretName, secret.SecretName))
|
|
assert.Check(t, nil != secret.File)
|
|
assert.Check(t, is.Equal(secretTarget, secret.File.Name))
|
|
|
|
// remove
|
|
service.Spec.TaskTemplate.ContainerSpec.Secrets = []*swarmtypes.SecretReference{}
|
|
_, err = cli.ServiceUpdate(ctx, serviceID, service.Version, service.Spec, types.ServiceUpdateOptions{})
|
|
assert.NilError(t, err)
|
|
poll.WaitOn(t, serviceIsUpdated(cli, serviceID), swarm.ServicePoll)
|
|
service = getService(t, cli, serviceID)
|
|
assert.Check(t, is.Equal(0, len(service.Spec.TaskTemplate.ContainerSpec.Secrets)))
|
|
|
|
err = cli.ServiceRemove(context.Background(), serviceID)
|
|
assert.NilError(t, err)
|
|
}
|
|
|
|
func TestServiceUpdateConfigs(t *testing.T) {
|
|
skip.If(t, testEnv.DaemonInfo.OSType != "linux")
|
|
defer setupTest(t)()
|
|
d := swarm.NewSwarm(t, testEnv)
|
|
defer d.Stop(t)
|
|
cli := d.NewClientT(t)
|
|
defer cli.Close()
|
|
|
|
ctx := context.Background()
|
|
configName := "TestConfig_" + t.Name()
|
|
configTarget := "targetName"
|
|
resp, err := cli.ConfigCreate(ctx, swarmtypes.ConfigSpec{
|
|
Annotations: swarmtypes.Annotations{
|
|
Name: configName,
|
|
},
|
|
Data: []byte("TESTINGDATA"),
|
|
})
|
|
assert.NilError(t, err)
|
|
assert.Check(t, resp.ID != "")
|
|
|
|
serviceName := "TestService_" + t.Name()
|
|
serviceID := swarm.CreateService(t, d, swarm.ServiceWithName(serviceName))
|
|
service := getService(t, cli, serviceID)
|
|
|
|
// add config
|
|
service.Spec.TaskTemplate.ContainerSpec.Configs = append(service.Spec.TaskTemplate.ContainerSpec.Configs,
|
|
&swarmtypes.ConfigReference{
|
|
File: &swarmtypes.ConfigReferenceFileTarget{
|
|
Name: configTarget,
|
|
UID: "0",
|
|
GID: "0",
|
|
Mode: 0600,
|
|
},
|
|
ConfigID: resp.ID,
|
|
ConfigName: configName,
|
|
},
|
|
)
|
|
_, err = cli.ServiceUpdate(ctx, serviceID, service.Version, service.Spec, types.ServiceUpdateOptions{})
|
|
assert.NilError(t, err)
|
|
poll.WaitOn(t, serviceIsUpdated(cli, serviceID), swarm.ServicePoll)
|
|
|
|
service = getService(t, cli, serviceID)
|
|
configs := service.Spec.TaskTemplate.ContainerSpec.Configs
|
|
assert.Assert(t, is.Equal(1, len(configs)))
|
|
|
|
config := *configs[0]
|
|
assert.Check(t, is.Equal(configName, config.ConfigName))
|
|
assert.Check(t, nil != config.File)
|
|
assert.Check(t, is.Equal(configTarget, config.File.Name))
|
|
|
|
// remove
|
|
service.Spec.TaskTemplate.ContainerSpec.Configs = []*swarmtypes.ConfigReference{}
|
|
_, err = cli.ServiceUpdate(ctx, serviceID, service.Version, service.Spec, types.ServiceUpdateOptions{})
|
|
assert.NilError(t, err)
|
|
poll.WaitOn(t, serviceIsUpdated(cli, serviceID), swarm.ServicePoll)
|
|
service = getService(t, cli, serviceID)
|
|
assert.Check(t, is.Equal(0, len(service.Spec.TaskTemplate.ContainerSpec.Configs)))
|
|
|
|
err = cli.ServiceRemove(context.Background(), serviceID)
|
|
assert.NilError(t, err)
|
|
}
|
|
|
|
func TestServiceUpdateNetwork(t *testing.T) {
|
|
skip.If(t, testEnv.DaemonInfo.OSType != "linux")
|
|
defer setupTest(t)()
|
|
d := swarm.NewSwarm(t, testEnv)
|
|
defer d.Stop(t)
|
|
cli := d.NewClientT(t)
|
|
defer cli.Close()
|
|
|
|
ctx := context.Background()
|
|
|
|
// Create a overlay network
|
|
testNet := "testNet" + t.Name()
|
|
overlayID := network.CreateNoError(ctx, t, cli, testNet,
|
|
network.WithDriver("overlay"))
|
|
|
|
var instances uint64 = 1
|
|
// Create service with the overlay network
|
|
serviceName := "TestServiceUpdateNetworkRM_" + t.Name()
|
|
serviceID := swarm.CreateService(t, d,
|
|
swarm.ServiceWithReplicas(instances),
|
|
swarm.ServiceWithName(serviceName),
|
|
swarm.ServiceWithNetwork(testNet))
|
|
|
|
poll.WaitOn(t, swarm.RunningTasksCount(cli, serviceID, instances), swarm.ServicePoll)
|
|
service := getService(t, cli, serviceID)
|
|
netInfo, err := cli.NetworkInspect(ctx, testNet, types.NetworkInspectOptions{
|
|
Verbose: true,
|
|
Scope: "swarm",
|
|
})
|
|
assert.NilError(t, err)
|
|
assert.Assert(t, len(netInfo.Containers) == 2, "Expected 2 endpoints, one for container and one for LB Sandbox")
|
|
|
|
// Remove network from service
|
|
service.Spec.TaskTemplate.Networks = []swarmtypes.NetworkAttachmentConfig{}
|
|
_, err = cli.ServiceUpdate(ctx, serviceID, service.Version, service.Spec, types.ServiceUpdateOptions{})
|
|
assert.NilError(t, err)
|
|
poll.WaitOn(t, serviceIsUpdated(cli, serviceID), swarm.ServicePoll)
|
|
|
|
netInfo, err = cli.NetworkInspect(ctx, testNet, types.NetworkInspectOptions{
|
|
Verbose: true,
|
|
Scope: "swarm",
|
|
})
|
|
|
|
assert.NilError(t, err)
|
|
assert.Assert(t, len(netInfo.Containers) == 0, "Load balancing endpoint still exists in network")
|
|
|
|
err = cli.NetworkRemove(ctx, overlayID)
|
|
assert.NilError(t, err)
|
|
|
|
err = cli.ServiceRemove(ctx, serviceID)
|
|
assert.NilError(t, err)
|
|
}
|
|
|
|
// TestServiceUpdatePidsLimit tests creating and updating a service with PidsLimit
|
|
func TestServiceUpdatePidsLimit(t *testing.T) {
|
|
skip.If(
|
|
t, versions.LessThan(testEnv.DaemonAPIVersion(), "1.41"),
|
|
"setting pidslimit for services is not supported before api v1.41",
|
|
)
|
|
skip.If(t, testEnv.DaemonInfo.OSType != "linux")
|
|
tests := []struct {
|
|
name string
|
|
pidsLimit int64
|
|
expected int64
|
|
}{
|
|
{
|
|
name: "create service with PidsLimit 300",
|
|
pidsLimit: 300,
|
|
expected: 300,
|
|
},
|
|
{
|
|
name: "unset PidsLimit to 0",
|
|
pidsLimit: 0,
|
|
expected: 0,
|
|
},
|
|
{
|
|
name: "update PidsLimit to 100",
|
|
pidsLimit: 100,
|
|
expected: 100,
|
|
},
|
|
}
|
|
|
|
defer setupTest(t)()
|
|
d := swarm.NewSwarm(t, testEnv)
|
|
defer d.Stop(t)
|
|
cli := d.NewClientT(t)
|
|
defer func() { _ = cli.Close() }()
|
|
ctx := context.Background()
|
|
var (
|
|
serviceID string
|
|
service swarmtypes.Service
|
|
)
|
|
for i, tc := range tests {
|
|
t.Run(tc.name, func(t *testing.T) {
|
|
if i == 0 {
|
|
serviceID = swarm.CreateService(t, d, swarm.ServiceWithPidsLimit(tc.pidsLimit))
|
|
} else {
|
|
service = getService(t, cli, serviceID)
|
|
if service.Spec.TaskTemplate.Resources == nil {
|
|
service.Spec.TaskTemplate.Resources = &swarmtypes.ResourceRequirements{}
|
|
}
|
|
if service.Spec.TaskTemplate.Resources.Limits == nil {
|
|
service.Spec.TaskTemplate.Resources.Limits = &swarmtypes.Limit{}
|
|
}
|
|
service.Spec.TaskTemplate.Resources.Limits.Pids = tc.pidsLimit
|
|
_, err := cli.ServiceUpdate(ctx, serviceID, service.Version, service.Spec, types.ServiceUpdateOptions{})
|
|
assert.NilError(t, err)
|
|
poll.WaitOn(t, serviceIsUpdated(cli, serviceID), swarm.ServicePoll)
|
|
}
|
|
|
|
poll.WaitOn(t, swarm.RunningTasksCount(cli, serviceID, 1), swarm.ServicePoll)
|
|
service = getService(t, cli, serviceID)
|
|
container := getServiceTaskContainer(ctx, t, cli, serviceID)
|
|
assert.Equal(t, service.Spec.TaskTemplate.Resources.Limits.Pids, tc.expected)
|
|
if tc.expected == 0 {
|
|
if container.HostConfig.Resources.PidsLimit != nil {
|
|
t.Fatalf("Expected container.HostConfig.Resources.PidsLimit to be nil")
|
|
}
|
|
} else {
|
|
assert.Assert(t, container.HostConfig.Resources.PidsLimit != nil)
|
|
assert.Equal(t, *container.HostConfig.Resources.PidsLimit, tc.expected)
|
|
}
|
|
})
|
|
}
|
|
|
|
err := cli.ServiceRemove(ctx, serviceID)
|
|
assert.NilError(t, err)
|
|
}
|
|
|
|
func getServiceTaskContainer(ctx context.Context, t *testing.T, cli client.APIClient, serviceID string) types.ContainerJSON {
|
|
t.Helper()
|
|
filter := filters.NewArgs()
|
|
filter.Add("service", serviceID)
|
|
filter.Add("desired-state", "running")
|
|
tasks, err := cli.TaskList(ctx, types.TaskListOptions{Filters: filter})
|
|
assert.NilError(t, err)
|
|
assert.Assert(t, len(tasks) > 0)
|
|
|
|
ctr, err := cli.ContainerInspect(ctx, tasks[0].Status.ContainerStatus.ContainerID)
|
|
assert.NilError(t, err)
|
|
assert.Equal(t, ctr.State.Running, true)
|
|
return ctr
|
|
}
|
|
|
|
func getService(t *testing.T, cli client.ServiceAPIClient, serviceID string) swarmtypes.Service {
|
|
t.Helper()
|
|
service, _, err := cli.ServiceInspectWithRaw(context.Background(), serviceID, types.ServiceInspectOptions{})
|
|
assert.NilError(t, err)
|
|
return service
|
|
}
|
|
|
|
func serviceIsUpdated(client client.ServiceAPIClient, serviceID string) func(log poll.LogT) poll.Result {
|
|
return func(log poll.LogT) poll.Result {
|
|
service, _, err := client.ServiceInspectWithRaw(context.Background(), serviceID, types.ServiceInspectOptions{})
|
|
switch {
|
|
case err != nil:
|
|
return poll.Error(err)
|
|
case service.UpdateStatus != nil && service.UpdateStatus.State == swarmtypes.UpdateStateCompleted:
|
|
return poll.Success()
|
|
default:
|
|
if service.UpdateStatus != nil {
|
|
return poll.Continue("waiting for service %s to be updated, state: %s, message: %s", serviceID, service.UpdateStatus.State, service.UpdateStatus.Message)
|
|
}
|
|
return poll.Continue("waiting for service %s to be updated", serviceID)
|
|
}
|
|
}
|
|
}
|
|
|
|
func serviceSpecIsUpdated(client client.ServiceAPIClient, serviceID string, serviceOldVersion uint64) func(log poll.LogT) poll.Result {
|
|
return func(log poll.LogT) poll.Result {
|
|
service, _, err := client.ServiceInspectWithRaw(context.Background(), serviceID, types.ServiceInspectOptions{})
|
|
switch {
|
|
case err != nil:
|
|
return poll.Error(err)
|
|
case service.Version.Index > serviceOldVersion:
|
|
return poll.Success()
|
|
default:
|
|
return poll.Continue("waiting for service %s to be updated", serviceID)
|
|
}
|
|
}
|
|
}
|