github.com/Prakhar-Agarwal-byte/moby@v0.0.0-20231027092010-a14e3e8ab87e/integration/service/update_test.go (about)

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