moby/integration/service/update_test.go
Sebastiaan van Stijn 687bdc7c71
API: swarm: move PidsLimit to TaskTemplate.Resources
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>
2020-06-05 12:50:38 +02:00

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)
}
}
}