github.com/moby/docker@v26.1.3+incompatible/integration/service/update_test.go (about)

     1  package service // import "github.com/docker/docker/integration/service"
     2  
     3  import (
     4  	"context"
     5  	"testing"
     6  
     7  	"github.com/docker/docker/api/types"
     8  	"github.com/docker/docker/api/types/filters"
     9  	swarmtypes "github.com/docker/docker/api/types/swarm"
    10  	"github.com/docker/docker/client"
    11  	"github.com/docker/docker/integration/internal/network"
    12  	"github.com/docker/docker/integration/internal/swarm"
    13  	"github.com/docker/docker/testutil"
    14  	"gotest.tools/v3/assert"
    15  	is "gotest.tools/v3/assert/cmp"
    16  	"gotest.tools/v3/poll"
    17  	"gotest.tools/v3/skip"
    18  )
    19  
    20  func TestServiceUpdateLabel(t *testing.T) {
    21  	skip.If(t, testEnv.DaemonInfo.OSType != "linux")
    22  	ctx := setupTest(t)
    23  
    24  	d := swarm.NewSwarm(ctx, t, testEnv)
    25  	defer d.Stop(t)
    26  	cli := d.NewClientT(t)
    27  	defer cli.Close()
    28  
    29  	serviceName := "TestService_" + t.Name()
    30  	serviceID := swarm.CreateService(ctx, t, d, swarm.ServiceWithName(serviceName))
    31  	service := getService(ctx, t, cli, serviceID)
    32  	assert.Check(t, is.DeepEqual(service.Spec.Labels, map[string]string{}))
    33  
    34  	// add label to empty set
    35  	service.Spec.Labels["foo"] = "bar"
    36  	_, err := cli.ServiceUpdate(ctx, serviceID, service.Version, service.Spec, types.ServiceUpdateOptions{})
    37  	assert.NilError(t, err)
    38  	poll.WaitOn(t, serviceSpecIsUpdated(ctx, cli, serviceID, service.Version.Index), swarm.ServicePoll)
    39  	service = getService(ctx, t, cli, serviceID)
    40  	assert.Check(t, is.DeepEqual(service.Spec.Labels, map[string]string{"foo": "bar"}))
    41  
    42  	// add label to non-empty set
    43  	service.Spec.Labels["foo2"] = "bar"
    44  	_, err = cli.ServiceUpdate(ctx, serviceID, service.Version, service.Spec, types.ServiceUpdateOptions{})
    45  	assert.NilError(t, err)
    46  	poll.WaitOn(t, serviceSpecIsUpdated(ctx, cli, serviceID, service.Version.Index), swarm.ServicePoll)
    47  	service = getService(ctx, t, cli, serviceID)
    48  	assert.Check(t, is.DeepEqual(service.Spec.Labels, map[string]string{"foo": "bar", "foo2": "bar"}))
    49  
    50  	delete(service.Spec.Labels, "foo2")
    51  	_, err = cli.ServiceUpdate(ctx, serviceID, service.Version, service.Spec, types.ServiceUpdateOptions{})
    52  	assert.NilError(t, err)
    53  	poll.WaitOn(t, serviceSpecIsUpdated(ctx, cli, serviceID, service.Version.Index), swarm.ServicePoll)
    54  	service = getService(ctx, t, cli, serviceID)
    55  	assert.Check(t, is.DeepEqual(service.Spec.Labels, map[string]string{"foo": "bar"}))
    56  
    57  	delete(service.Spec.Labels, "foo")
    58  	_, err = cli.ServiceUpdate(ctx, serviceID, service.Version, service.Spec, types.ServiceUpdateOptions{})
    59  	assert.NilError(t, err)
    60  	poll.WaitOn(t, serviceSpecIsUpdated(ctx, cli, serviceID, service.Version.Index), swarm.ServicePoll)
    61  	service = getService(ctx, t, cli, serviceID)
    62  	assert.Check(t, is.DeepEqual(service.Spec.Labels, map[string]string{}))
    63  
    64  	// now make sure we can add again
    65  	service.Spec.Labels["foo"] = "bar"
    66  	_, err = cli.ServiceUpdate(ctx, serviceID, service.Version, service.Spec, types.ServiceUpdateOptions{})
    67  	assert.NilError(t, err)
    68  	poll.WaitOn(t, serviceSpecIsUpdated(ctx, cli, serviceID, service.Version.Index), swarm.ServicePoll)
    69  	service = getService(ctx, t, cli, serviceID)
    70  	assert.Check(t, is.DeepEqual(service.Spec.Labels, map[string]string{"foo": "bar"}))
    71  
    72  	err = cli.ServiceRemove(ctx, serviceID)
    73  	assert.NilError(t, err)
    74  }
    75  
    76  func TestServiceUpdateSecrets(t *testing.T) {
    77  	skip.If(t, testEnv.DaemonInfo.OSType != "linux")
    78  	ctx := setupTest(t)
    79  
    80  	d := swarm.NewSwarm(ctx, t, testEnv)
    81  	defer d.Stop(t)
    82  	cli := d.NewClientT(t)
    83  	defer cli.Close()
    84  
    85  	secretName := "TestSecret_" + t.Name()
    86  	secretTarget := "targetName"
    87  	resp, err := cli.SecretCreate(ctx, swarmtypes.SecretSpec{
    88  		Annotations: swarmtypes.Annotations{
    89  			Name: secretName,
    90  		},
    91  		Data: []byte("TESTINGDATA"),
    92  	})
    93  	assert.NilError(t, err)
    94  	assert.Check(t, resp.ID != "")
    95  
    96  	serviceName := "TestService_" + t.Name()
    97  	serviceID := swarm.CreateService(ctx, t, d, swarm.ServiceWithName(serviceName))
    98  	service := getService(ctx, t, cli, serviceID)
    99  
   100  	// add secret
   101  	service.Spec.TaskTemplate.ContainerSpec.Secrets = append(service.Spec.TaskTemplate.ContainerSpec.Secrets,
   102  		&swarmtypes.SecretReference{
   103  			File: &swarmtypes.SecretReferenceFileTarget{
   104  				Name: secretTarget,
   105  				UID:  "0",
   106  				GID:  "0",
   107  				Mode: 0o600,
   108  			},
   109  			SecretID:   resp.ID,
   110  			SecretName: secretName,
   111  		},
   112  	)
   113  	_, err = cli.ServiceUpdate(ctx, serviceID, service.Version, service.Spec, types.ServiceUpdateOptions{})
   114  	assert.NilError(t, err)
   115  	poll.WaitOn(t, serviceIsUpdated(ctx, cli, serviceID), swarm.ServicePoll)
   116  
   117  	service = getService(ctx, t, cli, serviceID)
   118  	secrets := service.Spec.TaskTemplate.ContainerSpec.Secrets
   119  	assert.Assert(t, is.Equal(1, len(secrets)))
   120  
   121  	secret := *secrets[0]
   122  	assert.Check(t, is.Equal(secretName, secret.SecretName))
   123  	assert.Check(t, nil != secret.File)
   124  	assert.Check(t, is.Equal(secretTarget, secret.File.Name))
   125  
   126  	// remove
   127  	service.Spec.TaskTemplate.ContainerSpec.Secrets = []*swarmtypes.SecretReference{}
   128  	_, err = cli.ServiceUpdate(ctx, serviceID, service.Version, service.Spec, types.ServiceUpdateOptions{})
   129  	assert.NilError(t, err)
   130  	poll.WaitOn(t, serviceIsUpdated(ctx, cli, serviceID), swarm.ServicePoll)
   131  	service = getService(ctx, t, cli, serviceID)
   132  	assert.Check(t, is.Equal(0, len(service.Spec.TaskTemplate.ContainerSpec.Secrets)))
   133  
   134  	err = cli.ServiceRemove(ctx, serviceID)
   135  	assert.NilError(t, err)
   136  }
   137  
   138  func TestServiceUpdateConfigs(t *testing.T) {
   139  	skip.If(t, testEnv.DaemonInfo.OSType != "linux")
   140  	ctx := setupTest(t)
   141  
   142  	d := swarm.NewSwarm(ctx, t, testEnv)
   143  	defer d.Stop(t)
   144  	cli := d.NewClientT(t)
   145  	defer cli.Close()
   146  
   147  	configName := "TestConfig_" + t.Name()
   148  	configTarget := "targetName"
   149  	resp, err := cli.ConfigCreate(ctx, swarmtypes.ConfigSpec{
   150  		Annotations: swarmtypes.Annotations{
   151  			Name: configName,
   152  		},
   153  		Data: []byte("TESTINGDATA"),
   154  	})
   155  	assert.NilError(t, err)
   156  	assert.Check(t, resp.ID != "")
   157  
   158  	serviceName := "TestService_" + t.Name()
   159  	serviceID := swarm.CreateService(ctx, t, d, swarm.ServiceWithName(serviceName))
   160  	service := getService(ctx, t, cli, serviceID)
   161  
   162  	// add config
   163  	service.Spec.TaskTemplate.ContainerSpec.Configs = append(service.Spec.TaskTemplate.ContainerSpec.Configs,
   164  		&swarmtypes.ConfigReference{
   165  			File: &swarmtypes.ConfigReferenceFileTarget{
   166  				Name: configTarget,
   167  				UID:  "0",
   168  				GID:  "0",
   169  				Mode: 0o600,
   170  			},
   171  			ConfigID:   resp.ID,
   172  			ConfigName: configName,
   173  		},
   174  	)
   175  	_, err = cli.ServiceUpdate(ctx, serviceID, service.Version, service.Spec, types.ServiceUpdateOptions{})
   176  	assert.NilError(t, err)
   177  	poll.WaitOn(t, serviceIsUpdated(ctx, cli, serviceID), swarm.ServicePoll)
   178  
   179  	service = getService(ctx, t, cli, serviceID)
   180  	configs := service.Spec.TaskTemplate.ContainerSpec.Configs
   181  	assert.Assert(t, is.Equal(1, len(configs)))
   182  
   183  	config := *configs[0]
   184  	assert.Check(t, is.Equal(configName, config.ConfigName))
   185  	assert.Check(t, nil != config.File)
   186  	assert.Check(t, is.Equal(configTarget, config.File.Name))
   187  
   188  	// remove
   189  	service.Spec.TaskTemplate.ContainerSpec.Configs = []*swarmtypes.ConfigReference{}
   190  	_, err = cli.ServiceUpdate(ctx, serviceID, service.Version, service.Spec, types.ServiceUpdateOptions{})
   191  	assert.NilError(t, err)
   192  	poll.WaitOn(t, serviceIsUpdated(ctx, cli, serviceID), swarm.ServicePoll)
   193  	service = getService(ctx, t, cli, serviceID)
   194  	assert.Check(t, is.Equal(0, len(service.Spec.TaskTemplate.ContainerSpec.Configs)))
   195  
   196  	err = cli.ServiceRemove(ctx, serviceID)
   197  	assert.NilError(t, err)
   198  }
   199  
   200  func TestServiceUpdateNetwork(t *testing.T) {
   201  	skip.If(t, testEnv.DaemonInfo.OSType != "linux")
   202  	ctx := setupTest(t)
   203  
   204  	d := swarm.NewSwarm(ctx, t, testEnv)
   205  	defer d.Stop(t)
   206  	cli := d.NewClientT(t)
   207  	defer cli.Close()
   208  
   209  	// Create a overlay network
   210  	testNet := "testNet" + t.Name()
   211  	overlayID := network.CreateNoError(ctx, t, cli, testNet,
   212  		network.WithDriver("overlay"))
   213  
   214  	var instances uint64 = 1
   215  	// Create service with the overlay network
   216  	serviceName := "TestServiceUpdateNetworkRM_" + t.Name()
   217  	serviceID := swarm.CreateService(ctx, t, d,
   218  		swarm.ServiceWithReplicas(instances),
   219  		swarm.ServiceWithName(serviceName),
   220  		swarm.ServiceWithNetwork(testNet))
   221  
   222  	poll.WaitOn(t, swarm.RunningTasksCount(ctx, cli, serviceID, instances), swarm.ServicePoll)
   223  	service := getService(ctx, t, cli, serviceID)
   224  	netInfo, err := cli.NetworkInspect(ctx, testNet, types.NetworkInspectOptions{
   225  		Verbose: true,
   226  		Scope:   "swarm",
   227  	})
   228  	assert.NilError(t, err)
   229  	assert.Assert(t, len(netInfo.Containers) == 2, "Expected 2 endpoints, one for container and one for LB Sandbox")
   230  
   231  	// Remove network from service
   232  	service.Spec.TaskTemplate.Networks = []swarmtypes.NetworkAttachmentConfig{}
   233  	_, err = cli.ServiceUpdate(ctx, serviceID, service.Version, service.Spec, types.ServiceUpdateOptions{})
   234  	assert.NilError(t, err)
   235  	poll.WaitOn(t, serviceIsUpdated(ctx, cli, serviceID), swarm.ServicePoll)
   236  
   237  	netInfo, err = cli.NetworkInspect(ctx, testNet, types.NetworkInspectOptions{
   238  		Verbose: true,
   239  		Scope:   "swarm",
   240  	})
   241  
   242  	assert.NilError(t, err)
   243  	assert.Assert(t, len(netInfo.Containers) == 0, "Load balancing endpoint still exists in network")
   244  
   245  	err = cli.NetworkRemove(ctx, overlayID)
   246  	assert.NilError(t, err)
   247  
   248  	err = cli.ServiceRemove(ctx, serviceID)
   249  	assert.NilError(t, err)
   250  }
   251  
   252  // TestServiceUpdatePidsLimit tests creating and updating a service with PidsLimit
   253  func TestServiceUpdatePidsLimit(t *testing.T) {
   254  	skip.If(t, testEnv.DaemonInfo.OSType != "linux")
   255  	tests := []struct {
   256  		name      string
   257  		pidsLimit int64
   258  		expected  int64
   259  	}{
   260  		{
   261  			name:      "create service with PidsLimit 300",
   262  			pidsLimit: 300,
   263  			expected:  300,
   264  		},
   265  		{
   266  			name:      "unset PidsLimit to 0",
   267  			pidsLimit: 0,
   268  			expected:  0,
   269  		},
   270  		{
   271  			name:      "update PidsLimit to 100",
   272  			pidsLimit: 100,
   273  			expected:  100,
   274  		},
   275  	}
   276  
   277  	ctx := setupTest(t)
   278  
   279  	d := swarm.NewSwarm(ctx, t, testEnv)
   280  	defer d.Stop(t)
   281  	cli := d.NewClientT(t)
   282  	defer func() { _ = cli.Close() }()
   283  	var (
   284  		serviceID string
   285  		service   swarmtypes.Service
   286  	)
   287  	for i, tc := range tests {
   288  		tc := tc
   289  		t.Run(tc.name, func(t *testing.T) {
   290  			ctx := testutil.StartSpan(ctx, t)
   291  			if i == 0 {
   292  				serviceID = swarm.CreateService(ctx, t, d, swarm.ServiceWithPidsLimit(tc.pidsLimit))
   293  			} else {
   294  				service = getService(ctx, t, cli, serviceID)
   295  				if service.Spec.TaskTemplate.Resources == nil {
   296  					service.Spec.TaskTemplate.Resources = &swarmtypes.ResourceRequirements{}
   297  				}
   298  				if service.Spec.TaskTemplate.Resources.Limits == nil {
   299  					service.Spec.TaskTemplate.Resources.Limits = &swarmtypes.Limit{}
   300  				}
   301  				service.Spec.TaskTemplate.Resources.Limits.Pids = tc.pidsLimit
   302  				_, err := cli.ServiceUpdate(ctx, serviceID, service.Version, service.Spec, types.ServiceUpdateOptions{})
   303  				assert.NilError(t, err)
   304  				poll.WaitOn(t, serviceIsUpdated(ctx, cli, serviceID), swarm.ServicePoll)
   305  			}
   306  
   307  			poll.WaitOn(t, swarm.RunningTasksCount(ctx, cli, serviceID, 1), swarm.ServicePoll)
   308  			service = getService(ctx, t, cli, serviceID)
   309  			container := getServiceTaskContainer(ctx, t, cli, serviceID)
   310  			assert.Equal(t, service.Spec.TaskTemplate.Resources.Limits.Pids, tc.expected)
   311  			if tc.expected == 0 {
   312  				if container.HostConfig.Resources.PidsLimit != nil {
   313  					t.Fatalf("Expected container.HostConfig.Resources.PidsLimit to be nil")
   314  				}
   315  			} else {
   316  				assert.Assert(t, container.HostConfig.Resources.PidsLimit != nil)
   317  				assert.Equal(t, *container.HostConfig.Resources.PidsLimit, tc.expected)
   318  			}
   319  		})
   320  	}
   321  
   322  	err := cli.ServiceRemove(ctx, serviceID)
   323  	assert.NilError(t, err)
   324  }
   325  
   326  func getServiceTaskContainer(ctx context.Context, t *testing.T, cli client.APIClient, serviceID string) types.ContainerJSON {
   327  	t.Helper()
   328  	tasks, err := cli.TaskList(ctx, types.TaskListOptions{
   329  		Filters: filters.NewArgs(
   330  			filters.Arg("service", serviceID),
   331  			filters.Arg("desired-state", "running"),
   332  		),
   333  	})
   334  	assert.NilError(t, err)
   335  	assert.Assert(t, len(tasks) > 0)
   336  
   337  	ctr, err := cli.ContainerInspect(ctx, tasks[0].Status.ContainerStatus.ContainerID)
   338  	assert.NilError(t, err)
   339  	assert.Equal(t, ctr.State.Running, true)
   340  	return ctr
   341  }
   342  
   343  func getService(ctx context.Context, t *testing.T, cli client.ServiceAPIClient, serviceID string) swarmtypes.Service {
   344  	t.Helper()
   345  	service, _, err := cli.ServiceInspectWithRaw(ctx, serviceID, types.ServiceInspectOptions{})
   346  	assert.NilError(t, err)
   347  	return service
   348  }
   349  
   350  func serviceIsUpdated(ctx context.Context, client client.ServiceAPIClient, serviceID string) func(log poll.LogT) poll.Result {
   351  	return func(log poll.LogT) poll.Result {
   352  		service, _, err := client.ServiceInspectWithRaw(ctx, serviceID, types.ServiceInspectOptions{})
   353  		switch {
   354  		case err != nil:
   355  			return poll.Error(err)
   356  		case service.UpdateStatus != nil && service.UpdateStatus.State == swarmtypes.UpdateStateCompleted:
   357  			return poll.Success()
   358  		default:
   359  			if service.UpdateStatus != nil {
   360  				return poll.Continue("waiting for service %s to be updated, state: %s, message: %s", serviceID, service.UpdateStatus.State, service.UpdateStatus.Message)
   361  			}
   362  			return poll.Continue("waiting for service %s to be updated", serviceID)
   363  		}
   364  	}
   365  }
   366  
   367  func serviceSpecIsUpdated(ctx context.Context, client client.ServiceAPIClient, serviceID string, serviceOldVersion uint64) func(log poll.LogT) poll.Result {
   368  	return func(log poll.LogT) poll.Result {
   369  		service, _, err := client.ServiceInspectWithRaw(ctx, serviceID, types.ServiceInspectOptions{})
   370  		switch {
   371  		case err != nil:
   372  			return poll.Error(err)
   373  		case service.Version.Index > serviceOldVersion:
   374  			return poll.Success()
   375  		default:
   376  			return poll.Continue("waiting for service %s to be updated", serviceID)
   377  		}
   378  	}
   379  }