github.com/docker/docker@v299999999.0.0-20200612211812-aaf470eca7b5+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/api/types/versions"
    11  	"github.com/docker/docker/client"
    12  	"github.com/docker/docker/integration/internal/network"
    13  	"github.com/docker/docker/integration/internal/swarm"
    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  	defer setupTest(t)()
    23  	d := swarm.NewSwarm(t, testEnv)
    24  	defer d.Stop(t)
    25  	cli := d.NewClientT(t)
    26  	defer cli.Close()
    27  
    28  	ctx := context.Background()
    29  	serviceName := "TestService_" + t.Name()
    30  	serviceID := swarm.CreateService(t, d, swarm.ServiceWithName(serviceName))
    31  	service := getService(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(cli, serviceID, service.Version.Index), swarm.ServicePoll)
    39  	service = getService(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(cli, serviceID, service.Version.Index), swarm.ServicePoll)
    47  	service = getService(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(cli, serviceID, service.Version.Index), swarm.ServicePoll)
    54  	service = getService(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(cli, serviceID, service.Version.Index), swarm.ServicePoll)
    61  	service = getService(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(cli, serviceID, service.Version.Index), swarm.ServicePoll)
    69  	service = getService(t, cli, serviceID)
    70  	assert.Check(t, is.DeepEqual(service.Spec.Labels, map[string]string{"foo": "bar"}))
    71  
    72  	err = cli.ServiceRemove(context.Background(), serviceID)
    73  	assert.NilError(t, err)
    74  }
    75  
    76  func TestServiceUpdateSecrets(t *testing.T) {
    77  	skip.If(t, testEnv.DaemonInfo.OSType != "linux")
    78  	defer setupTest(t)()
    79  	d := swarm.NewSwarm(t, testEnv)
    80  	defer d.Stop(t)
    81  	cli := d.NewClientT(t)
    82  	defer cli.Close()
    83  
    84  	ctx := context.Background()
    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(t, d, swarm.ServiceWithName(serviceName))
    98  	service := getService(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: 0600,
   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(cli, serviceID), swarm.ServicePoll)
   116  
   117  	service = getService(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(cli, serviceID), swarm.ServicePoll)
   131  	service = getService(t, cli, serviceID)
   132  	assert.Check(t, is.Equal(0, len(service.Spec.TaskTemplate.ContainerSpec.Secrets)))
   133  
   134  	err = cli.ServiceRemove(context.Background(), serviceID)
   135  	assert.NilError(t, err)
   136  }
   137  
   138  func TestServiceUpdateConfigs(t *testing.T) {
   139  	skip.If(t, testEnv.DaemonInfo.OSType != "linux")
   140  	defer setupTest(t)()
   141  	d := swarm.NewSwarm(t, testEnv)
   142  	defer d.Stop(t)
   143  	cli := d.NewClientT(t)
   144  	defer cli.Close()
   145  
   146  	ctx := context.Background()
   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(t, d, swarm.ServiceWithName(serviceName))
   160  	service := getService(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: 0600,
   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(cli, serviceID), swarm.ServicePoll)
   178  
   179  	service = getService(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(cli, serviceID), swarm.ServicePoll)
   193  	service = getService(t, cli, serviceID)
   194  	assert.Check(t, is.Equal(0, len(service.Spec.TaskTemplate.ContainerSpec.Configs)))
   195  
   196  	err = cli.ServiceRemove(context.Background(), serviceID)
   197  	assert.NilError(t, err)
   198  }
   199  
   200  func TestServiceUpdateNetwork(t *testing.T) {
   201  	skip.If(t, testEnv.DaemonInfo.OSType != "linux")
   202  	defer setupTest(t)()
   203  	d := swarm.NewSwarm(t, testEnv)
   204  	defer d.Stop(t)
   205  	cli := d.NewClientT(t)
   206  	defer cli.Close()
   207  
   208  	ctx := context.Background()
   209  
   210  	// Create a overlay network
   211  	testNet := "testNet" + t.Name()
   212  	overlayID := network.CreateNoError(ctx, t, cli, testNet,
   213  		network.WithDriver("overlay"))
   214  
   215  	var instances uint64 = 1
   216  	// Create service with the overlay network
   217  	serviceName := "TestServiceUpdateNetworkRM_" + t.Name()
   218  	serviceID := swarm.CreateService(t, d,
   219  		swarm.ServiceWithReplicas(instances),
   220  		swarm.ServiceWithName(serviceName),
   221  		swarm.ServiceWithNetwork(testNet))
   222  
   223  	poll.WaitOn(t, swarm.RunningTasksCount(cli, serviceID, instances), swarm.ServicePoll)
   224  	service := getService(t, cli, serviceID)
   225  	netInfo, err := cli.NetworkInspect(ctx, testNet, types.NetworkInspectOptions{
   226  		Verbose: true,
   227  		Scope:   "swarm",
   228  	})
   229  	assert.NilError(t, err)
   230  	assert.Assert(t, len(netInfo.Containers) == 2, "Expected 2 endpoints, one for container and one for LB Sandbox")
   231  
   232  	// Remove network from service
   233  	service.Spec.TaskTemplate.Networks = []swarmtypes.NetworkAttachmentConfig{}
   234  	_, err = cli.ServiceUpdate(ctx, serviceID, service.Version, service.Spec, types.ServiceUpdateOptions{})
   235  	assert.NilError(t, err)
   236  	poll.WaitOn(t, serviceIsUpdated(cli, serviceID), swarm.ServicePoll)
   237  
   238  	netInfo, err = cli.NetworkInspect(ctx, testNet, types.NetworkInspectOptions{
   239  		Verbose: true,
   240  		Scope:   "swarm",
   241  	})
   242  
   243  	assert.NilError(t, err)
   244  	assert.Assert(t, len(netInfo.Containers) == 0, "Load balancing endpoint still exists in network")
   245  
   246  	err = cli.NetworkRemove(ctx, overlayID)
   247  	assert.NilError(t, err)
   248  
   249  	err = cli.ServiceRemove(ctx, serviceID)
   250  	assert.NilError(t, err)
   251  }
   252  
   253  // TestServiceUpdatePidsLimit tests creating and updating a service with PidsLimit
   254  func TestServiceUpdatePidsLimit(t *testing.T) {
   255  	skip.If(
   256  		t, versions.LessThan(testEnv.DaemonAPIVersion(), "1.41"),
   257  		"setting pidslimit for services is not supported before api v1.41",
   258  	)
   259  	skip.If(t, testEnv.DaemonInfo.OSType != "linux")
   260  	tests := []struct {
   261  		name      string
   262  		pidsLimit int64
   263  		expected  int64
   264  	}{
   265  		{
   266  			name:      "create service with PidsLimit 300",
   267  			pidsLimit: 300,
   268  			expected:  300,
   269  		},
   270  		{
   271  			name:      "unset PidsLimit to 0",
   272  			pidsLimit: 0,
   273  			expected:  0,
   274  		},
   275  		{
   276  			name:      "update PidsLimit to 100",
   277  			pidsLimit: 100,
   278  			expected:  100,
   279  		},
   280  	}
   281  
   282  	defer setupTest(t)()
   283  	d := swarm.NewSwarm(t, testEnv)
   284  	defer d.Stop(t)
   285  	cli := d.NewClientT(t)
   286  	defer func() { _ = cli.Close() }()
   287  	ctx := context.Background()
   288  	var (
   289  		serviceID string
   290  		service   swarmtypes.Service
   291  	)
   292  	for i, tc := range tests {
   293  		t.Run(tc.name, func(t *testing.T) {
   294  			if i == 0 {
   295  				serviceID = swarm.CreateService(t, d, swarm.ServiceWithPidsLimit(tc.pidsLimit))
   296  			} else {
   297  				service = getService(t, cli, serviceID)
   298  				if service.Spec.TaskTemplate.Resources == nil {
   299  					service.Spec.TaskTemplate.Resources = &swarmtypes.ResourceRequirements{}
   300  				}
   301  				if service.Spec.TaskTemplate.Resources.Limits == nil {
   302  					service.Spec.TaskTemplate.Resources.Limits = &swarmtypes.Limit{}
   303  				}
   304  				service.Spec.TaskTemplate.Resources.Limits.Pids = tc.pidsLimit
   305  				_, err := cli.ServiceUpdate(ctx, serviceID, service.Version, service.Spec, types.ServiceUpdateOptions{})
   306  				assert.NilError(t, err)
   307  				poll.WaitOn(t, serviceIsUpdated(cli, serviceID), swarm.ServicePoll)
   308  			}
   309  
   310  			poll.WaitOn(t, swarm.RunningTasksCount(cli, serviceID, 1), swarm.ServicePoll)
   311  			service = getService(t, cli, serviceID)
   312  			container := getServiceTaskContainer(ctx, t, cli, serviceID)
   313  			assert.Equal(t, service.Spec.TaskTemplate.Resources.Limits.Pids, tc.expected)
   314  			if tc.expected == 0 {
   315  				if container.HostConfig.Resources.PidsLimit != nil {
   316  					t.Fatalf("Expected container.HostConfig.Resources.PidsLimit to be nil")
   317  				}
   318  			} else {
   319  				assert.Assert(t, container.HostConfig.Resources.PidsLimit != nil)
   320  				assert.Equal(t, *container.HostConfig.Resources.PidsLimit, tc.expected)
   321  			}
   322  		})
   323  	}
   324  
   325  	err := cli.ServiceRemove(ctx, serviceID)
   326  	assert.NilError(t, err)
   327  }
   328  
   329  func getServiceTaskContainer(ctx context.Context, t *testing.T, cli client.APIClient, serviceID string) types.ContainerJSON {
   330  	t.Helper()
   331  	filter := filters.NewArgs()
   332  	filter.Add("service", serviceID)
   333  	filter.Add("desired-state", "running")
   334  	tasks, err := cli.TaskList(ctx, types.TaskListOptions{Filters: filter})
   335  	assert.NilError(t, err)
   336  	assert.Assert(t, len(tasks) > 0)
   337  
   338  	ctr, err := cli.ContainerInspect(ctx, tasks[0].Status.ContainerStatus.ContainerID)
   339  	assert.NilError(t, err)
   340  	assert.Equal(t, ctr.State.Running, true)
   341  	return ctr
   342  }
   343  
   344  func getService(t *testing.T, cli client.ServiceAPIClient, serviceID string) swarmtypes.Service {
   345  	t.Helper()
   346  	service, _, err := cli.ServiceInspectWithRaw(context.Background(), serviceID, types.ServiceInspectOptions{})
   347  	assert.NilError(t, err)
   348  	return service
   349  }
   350  
   351  func serviceIsUpdated(client client.ServiceAPIClient, serviceID string) func(log poll.LogT) poll.Result {
   352  	return func(log poll.LogT) poll.Result {
   353  		service, _, err := client.ServiceInspectWithRaw(context.Background(), serviceID, types.ServiceInspectOptions{})
   354  		switch {
   355  		case err != nil:
   356  			return poll.Error(err)
   357  		case service.UpdateStatus != nil && service.UpdateStatus.State == swarmtypes.UpdateStateCompleted:
   358  			return poll.Success()
   359  		default:
   360  			if service.UpdateStatus != nil {
   361  				return poll.Continue("waiting for service %s to be updated, state: %s, message: %s", serviceID, service.UpdateStatus.State, service.UpdateStatus.Message)
   362  			}
   363  			return poll.Continue("waiting for service %s to be updated", serviceID)
   364  		}
   365  	}
   366  }
   367  
   368  func serviceSpecIsUpdated(client client.ServiceAPIClient, serviceID string, serviceOldVersion uint64) func(log poll.LogT) poll.Result {
   369  	return func(log poll.LogT) poll.Result {
   370  		service, _, err := client.ServiceInspectWithRaw(context.Background(), serviceID, types.ServiceInspectOptions{})
   371  		switch {
   372  		case err != nil:
   373  			return poll.Error(err)
   374  		case service.Version.Index > serviceOldVersion:
   375  			return poll.Success()
   376  		default:
   377  			return poll.Continue("waiting for service %s to be updated", serviceID)
   378  		}
   379  	}
   380  }