moby/integration/service/update_test.go
Brian Goff e8dc902781 Wire up tests to support otel tracing
Integration tests will now configure clients to propagate traces as well
as create spans for all tests.

Some extra changes were needed (or desired for trace propagation) in the
test helpers to pass through tracing spans via context.

Signed-off-by: Brian Goff <cpuguy83@gmail.com>
2023-09-07 18:38:22 +00:00

384 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"
"github.com/docker/docker/testutil"
"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")
ctx := setupTest(t)
d := swarm.NewSwarm(ctx, t, testEnv)
defer d.Stop(t)
cli := d.NewClientT(t)
defer cli.Close()
serviceName := "TestService_" + t.Name()
serviceID := swarm.CreateService(ctx, t, d, swarm.ServiceWithName(serviceName))
service := getService(ctx, 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(ctx, cli, serviceID, service.Version.Index), swarm.ServicePoll)
service = getService(ctx, 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(ctx, cli, serviceID, service.Version.Index), swarm.ServicePoll)
service = getService(ctx, 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(ctx, cli, serviceID, service.Version.Index), swarm.ServicePoll)
service = getService(ctx, 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(ctx, cli, serviceID, service.Version.Index), swarm.ServicePoll)
service = getService(ctx, 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(ctx, cli, serviceID, service.Version.Index), swarm.ServicePoll)
service = getService(ctx, t, cli, serviceID)
assert.Check(t, is.DeepEqual(service.Spec.Labels, map[string]string{"foo": "bar"}))
err = cli.ServiceRemove(ctx, serviceID)
assert.NilError(t, err)
}
func TestServiceUpdateSecrets(t *testing.T) {
skip.If(t, testEnv.DaemonInfo.OSType != "linux")
ctx := setupTest(t)
d := swarm.NewSwarm(ctx, t, testEnv)
defer d.Stop(t)
cli := d.NewClientT(t)
defer cli.Close()
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(ctx, t, d, swarm.ServiceWithName(serviceName))
service := getService(ctx, 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: 0o600,
},
SecretID: resp.ID,
SecretName: secretName,
},
)
_, err = cli.ServiceUpdate(ctx, serviceID, service.Version, service.Spec, types.ServiceUpdateOptions{})
assert.NilError(t, err)
poll.WaitOn(t, serviceIsUpdated(ctx, cli, serviceID), swarm.ServicePoll)
service = getService(ctx, 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(ctx, cli, serviceID), swarm.ServicePoll)
service = getService(ctx, t, cli, serviceID)
assert.Check(t, is.Equal(0, len(service.Spec.TaskTemplate.ContainerSpec.Secrets)))
err = cli.ServiceRemove(ctx, serviceID)
assert.NilError(t, err)
}
func TestServiceUpdateConfigs(t *testing.T) {
skip.If(t, testEnv.DaemonInfo.OSType != "linux")
ctx := setupTest(t)
d := swarm.NewSwarm(ctx, t, testEnv)
defer d.Stop(t)
cli := d.NewClientT(t)
defer cli.Close()
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(ctx, t, d, swarm.ServiceWithName(serviceName))
service := getService(ctx, 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: 0o600,
},
ConfigID: resp.ID,
ConfigName: configName,
},
)
_, err = cli.ServiceUpdate(ctx, serviceID, service.Version, service.Spec, types.ServiceUpdateOptions{})
assert.NilError(t, err)
poll.WaitOn(t, serviceIsUpdated(ctx, cli, serviceID), swarm.ServicePoll)
service = getService(ctx, 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(ctx, cli, serviceID), swarm.ServicePoll)
service = getService(ctx, t, cli, serviceID)
assert.Check(t, is.Equal(0, len(service.Spec.TaskTemplate.ContainerSpec.Configs)))
err = cli.ServiceRemove(ctx, serviceID)
assert.NilError(t, err)
}
func TestServiceUpdateNetwork(t *testing.T) {
skip.If(t, testEnv.DaemonInfo.OSType != "linux")
ctx := setupTest(t)
d := swarm.NewSwarm(ctx, t, testEnv)
defer d.Stop(t)
cli := d.NewClientT(t)
defer cli.Close()
// 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(ctx, t, d,
swarm.ServiceWithReplicas(instances),
swarm.ServiceWithName(serviceName),
swarm.ServiceWithNetwork(testNet))
poll.WaitOn(t, swarm.RunningTasksCount(ctx, cli, serviceID, instances), swarm.ServicePoll)
service := getService(ctx, 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(ctx, 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,
},
}
ctx := setupTest(t)
d := swarm.NewSwarm(ctx, t, testEnv)
defer d.Stop(t)
cli := d.NewClientT(t)
defer func() { _ = cli.Close() }()
var (
serviceID string
service swarmtypes.Service
)
for i, tc := range tests {
tc := tc
t.Run(tc.name, func(t *testing.T) {
ctx := testutil.StartSpan(ctx, t)
if i == 0 {
serviceID = swarm.CreateService(ctx, t, d, swarm.ServiceWithPidsLimit(tc.pidsLimit))
} else {
service = getService(ctx, 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(ctx, cli, serviceID), swarm.ServicePoll)
}
poll.WaitOn(t, swarm.RunningTasksCount(ctx, cli, serviceID, 1), swarm.ServicePoll)
service = getService(ctx, 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()
tasks, err := cli.TaskList(ctx, types.TaskListOptions{
Filters: filters.NewArgs(
filters.Arg("service", serviceID),
filters.Arg("desired-state", "running"),
),
})
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(ctx context.Context, t *testing.T, cli client.ServiceAPIClient, serviceID string) swarmtypes.Service {
t.Helper()
service, _, err := cli.ServiceInspectWithRaw(ctx, serviceID, types.ServiceInspectOptions{})
assert.NilError(t, err)
return service
}
func serviceIsUpdated(ctx context.Context, client client.ServiceAPIClient, serviceID string) func(log poll.LogT) poll.Result {
return func(log poll.LogT) poll.Result {
service, _, err := client.ServiceInspectWithRaw(ctx, 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(ctx context.Context, client client.ServiceAPIClient, serviceID string, serviceOldVersion uint64) func(log poll.LogT) poll.Result {
return func(log poll.LogT) poll.Result {
service, _, err := client.ServiceInspectWithRaw(ctx, 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)
}
}
}