github.com/rumpl/bof@v23.0.0-rc.2+incompatible/integration/service/create_test.go (about)

     1  package service // import "github.com/docker/docker/integration/service"
     2  
     3  import (
     4  	"context"
     5  	"fmt"
     6  	"io"
     7  	"strings"
     8  	"testing"
     9  	"time"
    10  
    11  	"github.com/docker/docker/api/types"
    12  	"github.com/docker/docker/api/types/filters"
    13  	"github.com/docker/docker/api/types/strslice"
    14  	swarmtypes "github.com/docker/docker/api/types/swarm"
    15  	"github.com/docker/docker/api/types/versions"
    16  	"github.com/docker/docker/client"
    17  	"github.com/docker/docker/errdefs"
    18  	"github.com/docker/docker/integration/internal/network"
    19  	"github.com/docker/docker/integration/internal/swarm"
    20  	"github.com/docker/docker/testutil/daemon"
    21  	"gotest.tools/v3/assert"
    22  	is "gotest.tools/v3/assert/cmp"
    23  	"gotest.tools/v3/poll"
    24  	"gotest.tools/v3/skip"
    25  )
    26  
    27  func TestServiceCreateInit(t *testing.T) {
    28  	defer setupTest(t)()
    29  	t.Run("daemonInitDisabled", testServiceCreateInit(false))
    30  	t.Run("daemonInitEnabled", testServiceCreateInit(true))
    31  }
    32  
    33  func testServiceCreateInit(daemonEnabled bool) func(t *testing.T) {
    34  	return func(t *testing.T) {
    35  		var ops = []daemon.Option{}
    36  
    37  		if daemonEnabled {
    38  			ops = append(ops, daemon.WithInit())
    39  		}
    40  		d := swarm.NewSwarm(t, testEnv, ops...)
    41  		defer d.Stop(t)
    42  		client := d.NewClientT(t)
    43  		defer client.Close()
    44  
    45  		booleanTrue := true
    46  		booleanFalse := false
    47  
    48  		serviceID := swarm.CreateService(t, d)
    49  		poll.WaitOn(t, swarm.RunningTasksCount(client, serviceID, 1), swarm.ServicePoll)
    50  		i := inspectServiceContainer(t, client, serviceID)
    51  		// HostConfig.Init == nil means that it delegates to daemon configuration
    52  		assert.Check(t, i.HostConfig.Init == nil)
    53  
    54  		serviceID = swarm.CreateService(t, d, swarm.ServiceWithInit(&booleanTrue))
    55  		poll.WaitOn(t, swarm.RunningTasksCount(client, serviceID, 1), swarm.ServicePoll)
    56  		i = inspectServiceContainer(t, client, serviceID)
    57  		assert.Check(t, is.Equal(true, *i.HostConfig.Init))
    58  
    59  		serviceID = swarm.CreateService(t, d, swarm.ServiceWithInit(&booleanFalse))
    60  		poll.WaitOn(t, swarm.RunningTasksCount(client, serviceID, 1), swarm.ServicePoll)
    61  		i = inspectServiceContainer(t, client, serviceID)
    62  		assert.Check(t, is.Equal(false, *i.HostConfig.Init))
    63  	}
    64  }
    65  
    66  func inspectServiceContainer(t *testing.T, client client.APIClient, serviceID string) types.ContainerJSON {
    67  	t.Helper()
    68  	filter := filters.NewArgs()
    69  	filter.Add("label", fmt.Sprintf("com.docker.swarm.service.id=%s", serviceID))
    70  	containers, err := client.ContainerList(context.Background(), types.ContainerListOptions{Filters: filter})
    71  	assert.NilError(t, err)
    72  	assert.Check(t, is.Len(containers, 1))
    73  
    74  	i, err := client.ContainerInspect(context.Background(), containers[0].ID)
    75  	assert.NilError(t, err)
    76  	return i
    77  }
    78  
    79  func TestCreateServiceMultipleTimes(t *testing.T) {
    80  	skip.If(t, testEnv.DaemonInfo.OSType == "windows")
    81  	defer setupTest(t)()
    82  	d := swarm.NewSwarm(t, testEnv)
    83  	defer d.Stop(t)
    84  	client := d.NewClientT(t)
    85  	defer client.Close()
    86  	ctx := context.Background()
    87  
    88  	overlayName := "overlay1_" + t.Name()
    89  	overlayID := network.CreateNoError(ctx, t, client, overlayName,
    90  		network.WithCheckDuplicate(),
    91  		network.WithDriver("overlay"),
    92  	)
    93  
    94  	var instances uint64 = 4
    95  
    96  	serviceName := "TestService_" + t.Name()
    97  	serviceSpec := []swarm.ServiceSpecOpt{
    98  		swarm.ServiceWithReplicas(instances),
    99  		swarm.ServiceWithName(serviceName),
   100  		swarm.ServiceWithNetwork(overlayName),
   101  	}
   102  
   103  	serviceID := swarm.CreateService(t, d, serviceSpec...)
   104  	poll.WaitOn(t, swarm.RunningTasksCount(client, serviceID, instances), swarm.ServicePoll)
   105  
   106  	_, _, err := client.ServiceInspectWithRaw(context.Background(), serviceID, types.ServiceInspectOptions{})
   107  	assert.NilError(t, err)
   108  
   109  	err = client.ServiceRemove(context.Background(), serviceID)
   110  	assert.NilError(t, err)
   111  
   112  	poll.WaitOn(t, swarm.NoTasksForService(ctx, client, serviceID), swarm.ServicePoll)
   113  
   114  	serviceID2 := swarm.CreateService(t, d, serviceSpec...)
   115  	poll.WaitOn(t, swarm.RunningTasksCount(client, serviceID2, instances), swarm.ServicePoll)
   116  
   117  	err = client.ServiceRemove(context.Background(), serviceID2)
   118  	assert.NilError(t, err)
   119  
   120  	// we can't just wait on no tasks for the service, counter-intuitively.
   121  	// Tasks may briefly exist but not show up, if they are are in the process
   122  	// of being deallocated. To avoid this case, we should retry network remove
   123  	// a few times, to give tasks time to be deallcoated
   124  	poll.WaitOn(t, swarm.NoTasksForService(ctx, client, serviceID2), swarm.ServicePoll)
   125  
   126  	for retry := 0; retry < 5; retry++ {
   127  		err = client.NetworkRemove(context.Background(), overlayID)
   128  		// TODO(dperny): using strings.Contains for error checking is awful,
   129  		// but so is the fact that swarm functions don't return errdefs errors.
   130  		// I don't have time at this moment to fix the latter, so I guess I'll
   131  		// go with the former.
   132  		//
   133  		// The full error we're looking for is something like this:
   134  		//
   135  		// Error response from daemon: rpc error: code = FailedPrecondition desc = network %v is in use by task %v
   136  		//
   137  		// The safest way to catch this, I think, will be to match on "is in
   138  		// use by", as this is an uninterrupted string that best identifies
   139  		// this error.
   140  		if err == nil || !strings.Contains(err.Error(), "is in use by") {
   141  			// if there is no error, or the error isn't this kind of error,
   142  			// then we'll break the loop body, and either fail the test or
   143  			// continue.
   144  			break
   145  		}
   146  	}
   147  	assert.NilError(t, err)
   148  
   149  	poll.WaitOn(t, network.IsRemoved(context.Background(), client, overlayID), poll.WithTimeout(1*time.Minute), poll.WithDelay(10*time.Second))
   150  }
   151  
   152  func TestCreateServiceConflict(t *testing.T) {
   153  	skip.If(t, testEnv.DaemonInfo.OSType == "windows")
   154  	defer setupTest(t)()
   155  	d := swarm.NewSwarm(t, testEnv)
   156  	defer d.Stop(t)
   157  	c := d.NewClientT(t)
   158  	defer c.Close()
   159  	ctx := context.Background()
   160  
   161  	serviceName := "TestService_" + t.Name()
   162  	serviceSpec := []swarm.ServiceSpecOpt{
   163  		swarm.ServiceWithName(serviceName),
   164  	}
   165  
   166  	swarm.CreateService(t, d, serviceSpec...)
   167  
   168  	spec := swarm.CreateServiceSpec(t, serviceSpec...)
   169  	_, err := c.ServiceCreate(ctx, spec, types.ServiceCreateOptions{})
   170  	assert.Check(t, errdefs.IsConflict(err))
   171  	assert.ErrorContains(t, err, "service "+serviceName+" already exists")
   172  }
   173  
   174  func TestCreateServiceMaxReplicas(t *testing.T) {
   175  	defer setupTest(t)()
   176  	d := swarm.NewSwarm(t, testEnv)
   177  	defer d.Stop(t)
   178  	client := d.NewClientT(t)
   179  	defer client.Close()
   180  
   181  	var maxReplicas uint64 = 2
   182  	serviceSpec := []swarm.ServiceSpecOpt{
   183  		swarm.ServiceWithReplicas(maxReplicas),
   184  		swarm.ServiceWithMaxReplicas(maxReplicas),
   185  	}
   186  
   187  	serviceID := swarm.CreateService(t, d, serviceSpec...)
   188  	poll.WaitOn(t, swarm.RunningTasksCount(client, serviceID, maxReplicas), swarm.ServicePoll)
   189  
   190  	_, _, err := client.ServiceInspectWithRaw(context.Background(), serviceID, types.ServiceInspectOptions{})
   191  	assert.NilError(t, err)
   192  }
   193  
   194  func TestCreateWithDuplicateNetworkNames(t *testing.T) {
   195  	skip.If(t, testEnv.DaemonInfo.OSType == "windows")
   196  	defer setupTest(t)()
   197  	d := swarm.NewSwarm(t, testEnv)
   198  	defer d.Stop(t)
   199  	client := d.NewClientT(t)
   200  	defer client.Close()
   201  	ctx := context.Background()
   202  
   203  	name := "foo_" + t.Name()
   204  	n1 := network.CreateNoError(ctx, t, client, name, network.WithDriver("bridge"))
   205  	n2 := network.CreateNoError(ctx, t, client, name, network.WithDriver("bridge"))
   206  
   207  	// Duplicates with name but with different driver
   208  	n3 := network.CreateNoError(ctx, t, client, name, network.WithDriver("overlay"))
   209  
   210  	// Create Service with the same name
   211  	var instances uint64 = 1
   212  
   213  	serviceName := "top_" + t.Name()
   214  	serviceID := swarm.CreateService(t, d,
   215  		swarm.ServiceWithReplicas(instances),
   216  		swarm.ServiceWithName(serviceName),
   217  		swarm.ServiceWithNetwork(name),
   218  	)
   219  
   220  	poll.WaitOn(t, swarm.RunningTasksCount(client, serviceID, instances), swarm.ServicePoll)
   221  
   222  	resp, _, err := client.ServiceInspectWithRaw(ctx, serviceID, types.ServiceInspectOptions{})
   223  	assert.NilError(t, err)
   224  	assert.Check(t, is.Equal(n3, resp.Spec.TaskTemplate.Networks[0].Target))
   225  
   226  	// Remove Service, and wait for its tasks to be removed
   227  	err = client.ServiceRemove(ctx, serviceID)
   228  	assert.NilError(t, err)
   229  	poll.WaitOn(t, swarm.NoTasksForService(ctx, client, serviceID), swarm.ServicePoll)
   230  
   231  	// Remove networks
   232  	err = client.NetworkRemove(context.Background(), n3)
   233  	assert.NilError(t, err)
   234  
   235  	err = client.NetworkRemove(context.Background(), n2)
   236  	assert.NilError(t, err)
   237  
   238  	err = client.NetworkRemove(context.Background(), n1)
   239  	assert.NilError(t, err)
   240  
   241  	// Make sure networks have been destroyed.
   242  	poll.WaitOn(t, network.IsRemoved(context.Background(), client, n3), poll.WithTimeout(1*time.Minute), poll.WithDelay(10*time.Second))
   243  	poll.WaitOn(t, network.IsRemoved(context.Background(), client, n2), poll.WithTimeout(1*time.Minute), poll.WithDelay(10*time.Second))
   244  	poll.WaitOn(t, network.IsRemoved(context.Background(), client, n1), poll.WithTimeout(1*time.Minute), poll.WithDelay(10*time.Second))
   245  }
   246  
   247  func TestCreateServiceSecretFileMode(t *testing.T) {
   248  	skip.If(t, testEnv.DaemonInfo.OSType == "windows")
   249  	defer setupTest(t)()
   250  	d := swarm.NewSwarm(t, testEnv)
   251  	defer d.Stop(t)
   252  	client := d.NewClientT(t)
   253  	defer client.Close()
   254  
   255  	ctx := context.Background()
   256  	secretName := "TestSecret_" + t.Name()
   257  	secretResp, err := client.SecretCreate(ctx, swarmtypes.SecretSpec{
   258  		Annotations: swarmtypes.Annotations{
   259  			Name: secretName,
   260  		},
   261  		Data: []byte("TESTSECRET"),
   262  	})
   263  	assert.NilError(t, err)
   264  
   265  	var instances uint64 = 1
   266  	serviceName := "TestService_" + t.Name()
   267  	serviceID := swarm.CreateService(t, d,
   268  		swarm.ServiceWithReplicas(instances),
   269  		swarm.ServiceWithName(serviceName),
   270  		swarm.ServiceWithCommand([]string{"/bin/sh", "-c", "ls -l /etc/secret && sleep inf"}),
   271  		swarm.ServiceWithSecret(&swarmtypes.SecretReference{
   272  			File: &swarmtypes.SecretReferenceFileTarget{
   273  				Name: "/etc/secret",
   274  				UID:  "0",
   275  				GID:  "0",
   276  				Mode: 0777,
   277  			},
   278  			SecretID:   secretResp.ID,
   279  			SecretName: secretName,
   280  		}),
   281  	)
   282  
   283  	poll.WaitOn(t, swarm.RunningTasksCount(client, serviceID, instances), swarm.ServicePoll)
   284  
   285  	body, err := client.ServiceLogs(ctx, serviceID, types.ContainerLogsOptions{
   286  		Tail:       "1",
   287  		ShowStdout: true,
   288  	})
   289  	assert.NilError(t, err)
   290  	defer body.Close()
   291  
   292  	content, err := io.ReadAll(body)
   293  	assert.NilError(t, err)
   294  	assert.Check(t, is.Contains(string(content), "-rwxrwxrwx"))
   295  
   296  	err = client.ServiceRemove(ctx, serviceID)
   297  	assert.NilError(t, err)
   298  	poll.WaitOn(t, swarm.NoTasksForService(ctx, client, serviceID), swarm.ServicePoll)
   299  
   300  	err = client.SecretRemove(ctx, secretName)
   301  	assert.NilError(t, err)
   302  }
   303  
   304  func TestCreateServiceConfigFileMode(t *testing.T) {
   305  	skip.If(t, testEnv.DaemonInfo.OSType == "windows")
   306  	defer setupTest(t)()
   307  	d := swarm.NewSwarm(t, testEnv)
   308  	defer d.Stop(t)
   309  	client := d.NewClientT(t)
   310  	defer client.Close()
   311  
   312  	ctx := context.Background()
   313  	configName := "TestConfig_" + t.Name()
   314  	configResp, err := client.ConfigCreate(ctx, swarmtypes.ConfigSpec{
   315  		Annotations: swarmtypes.Annotations{
   316  			Name: configName,
   317  		},
   318  		Data: []byte("TESTCONFIG"),
   319  	})
   320  	assert.NilError(t, err)
   321  
   322  	var instances uint64 = 1
   323  	serviceName := "TestService_" + t.Name()
   324  	serviceID := swarm.CreateService(t, d,
   325  		swarm.ServiceWithName(serviceName),
   326  		swarm.ServiceWithCommand([]string{"/bin/sh", "-c", "ls -l /etc/config && sleep inf"}),
   327  		swarm.ServiceWithReplicas(instances),
   328  		swarm.ServiceWithConfig(&swarmtypes.ConfigReference{
   329  			File: &swarmtypes.ConfigReferenceFileTarget{
   330  				Name: "/etc/config",
   331  				UID:  "0",
   332  				GID:  "0",
   333  				Mode: 0777,
   334  			},
   335  			ConfigID:   configResp.ID,
   336  			ConfigName: configName,
   337  		}),
   338  	)
   339  
   340  	poll.WaitOn(t, swarm.RunningTasksCount(client, serviceID, instances))
   341  
   342  	body, err := client.ServiceLogs(ctx, serviceID, types.ContainerLogsOptions{
   343  		Tail:       "1",
   344  		ShowStdout: true,
   345  	})
   346  	assert.NilError(t, err)
   347  	defer body.Close()
   348  
   349  	content, err := io.ReadAll(body)
   350  	assert.NilError(t, err)
   351  	assert.Check(t, is.Contains(string(content), "-rwxrwxrwx"))
   352  
   353  	err = client.ServiceRemove(ctx, serviceID)
   354  	assert.NilError(t, err)
   355  	poll.WaitOn(t, swarm.NoTasksForService(ctx, client, serviceID))
   356  
   357  	err = client.ConfigRemove(ctx, configName)
   358  	assert.NilError(t, err)
   359  }
   360  
   361  // TestServiceCreateSysctls tests that a service created with sysctl options in
   362  // the ContainerSpec correctly applies those options.
   363  //
   364  // To test this, we're going to create a service with the sysctl option
   365  //
   366  //	{"net.ipv4.ip_nonlocal_bind": "0"}
   367  //
   368  // We'll get the service's tasks to get the container ID, and then we'll
   369  // inspect the container. If the output of the container inspect contains the
   370  // sysctl option with the correct value, we can assume that the sysctl has been
   371  // plumbed correctly.
   372  //
   373  // Next, we'll remove that service and create a new service with that option
   374  // set to 1. This means that no matter what the default is, we can be confident
   375  // that the sysctl option is applying as intended.
   376  //
   377  // Additionally, we'll do service and task inspects to verify that the inspect
   378  // output includes the desired sysctl option.
   379  //
   380  // We're using net.ipv4.ip_nonlocal_bind because it's something that I'm fairly
   381  // confident won't be modified by the container runtime, and won't blow
   382  // anything up in the test environment
   383  func TestCreateServiceSysctls(t *testing.T) {
   384  	skip.If(
   385  		t, versions.LessThan(testEnv.DaemonAPIVersion(), "1.40"),
   386  		"setting service sysctls is unsupported before api v1.40",
   387  	)
   388  
   389  	defer setupTest(t)()
   390  	d := swarm.NewSwarm(t, testEnv)
   391  	defer d.Stop(t)
   392  	client := d.NewClientT(t)
   393  	defer client.Close()
   394  
   395  	ctx := context.Background()
   396  
   397  	// run thie block twice, so that no matter what the default value of
   398  	// net.ipv4.ip_nonlocal_bind is, we can verify that setting the sysctl
   399  	// options works
   400  	for _, expected := range []string{"0", "1"} {
   401  		// store the map we're going to be using everywhere.
   402  		expectedSysctls := map[string]string{"net.ipv4.ip_nonlocal_bind": expected}
   403  
   404  		// Create the service with the sysctl options
   405  		var instances uint64 = 1
   406  		serviceID := swarm.CreateService(t, d,
   407  			swarm.ServiceWithSysctls(expectedSysctls),
   408  		)
   409  
   410  		// wait for the service to converge to 1 running task as expected
   411  		poll.WaitOn(t, swarm.RunningTasksCount(client, serviceID, instances))
   412  
   413  		// we're going to check 3 things:
   414  		//
   415  		//   1. Does the container, when inspected, have the sysctl option set?
   416  		//   2. Does the task have the sysctl in the spec?
   417  		//   3. Does the service have the sysctl in the spec?
   418  		//
   419  		// if all 3 of these things are true, we know that the sysctl has been
   420  		// plumbed correctly through the engine.
   421  		//
   422  		// We don't actually have to get inside the container and check its
   423  		// logs or anything. If we see the sysctl set on the container inspect,
   424  		// we know that the sysctl is plumbed correctly. everything below that
   425  		// level has been tested elsewhere. (thanks @thaJeztah, because an
   426  		// earlier version of this test had to get container logs and was much
   427  		// more complex)
   428  
   429  		// get all of the tasks of the service, so we can get the container
   430  		filter := filters.NewArgs()
   431  		filter.Add("service", serviceID)
   432  		tasks, err := client.TaskList(ctx, types.TaskListOptions{
   433  			Filters: filter,
   434  		})
   435  		assert.NilError(t, err)
   436  		assert.Check(t, is.Equal(len(tasks), 1))
   437  
   438  		// verify that the container has the sysctl option set
   439  		ctnr, err := client.ContainerInspect(ctx, tasks[0].Status.ContainerStatus.ContainerID)
   440  		assert.NilError(t, err)
   441  		assert.DeepEqual(t, ctnr.HostConfig.Sysctls, expectedSysctls)
   442  
   443  		// verify that the task has the sysctl option set in the task object
   444  		assert.DeepEqual(t, tasks[0].Spec.ContainerSpec.Sysctls, expectedSysctls)
   445  
   446  		// verify that the service also has the sysctl set in the spec.
   447  		service, _, err := client.ServiceInspectWithRaw(ctx, serviceID, types.ServiceInspectOptions{})
   448  		assert.NilError(t, err)
   449  		assert.DeepEqual(t,
   450  			service.Spec.TaskTemplate.ContainerSpec.Sysctls, expectedSysctls,
   451  		)
   452  	}
   453  }
   454  
   455  // TestServiceCreateCapabilities tests that a service created with capabilities options in
   456  // the ContainerSpec correctly applies those options.
   457  //
   458  // To test this, we're going to create a service with the capabilities option
   459  //
   460  //	[]string{"CAP_NET_RAW", "CAP_SYS_CHROOT"}
   461  //
   462  // We'll get the service's tasks to get the container ID, and then we'll
   463  // inspect the container. If the output of the container inspect contains the
   464  // capabilities option with the correct value, we can assume that the capabilities has been
   465  // plumbed correctly.
   466  func TestCreateServiceCapabilities(t *testing.T) {
   467  	skip.If(
   468  		t, versions.LessThan(testEnv.DaemonAPIVersion(), "1.41"),
   469  		"setting service capabilities is unsupported before api v1.41",
   470  	)
   471  
   472  	defer setupTest(t)()
   473  	d := swarm.NewSwarm(t, testEnv)
   474  	defer d.Stop(t)
   475  	client := d.NewClientT(t)
   476  	defer client.Close()
   477  
   478  	ctx := context.Background()
   479  
   480  	// store the map we're going to be using everywhere.
   481  	capAdd := []string{"CAP_SYS_CHROOT"}
   482  	capDrop := []string{"CAP_NET_RAW"}
   483  
   484  	// Create the service with the capabilities options
   485  	var instances uint64 = 1
   486  	serviceID := swarm.CreateService(t, d,
   487  		swarm.ServiceWithCapabilities(capAdd, capDrop),
   488  	)
   489  
   490  	// wait for the service to converge to 1 running task as expected
   491  	poll.WaitOn(t, swarm.RunningTasksCount(client, serviceID, instances))
   492  
   493  	// we're going to check 3 things:
   494  	//
   495  	//   1. Does the container, when inspected, have the capabilities option set?
   496  	//   2. Does the task have the capabilities in the spec?
   497  	//   3. Does the service have the capabilities in the spec?
   498  	//
   499  	// if all 3 of these things are true, we know that the capabilities has been
   500  	// plumbed correctly through the engine.
   501  	//
   502  	// We don't actually have to get inside the container and check its
   503  	// logs or anything. If we see the capabilities set on the container inspect,
   504  	// we know that the capabilities is plumbed correctly. everything below that
   505  	// level has been tested elsewhere.
   506  
   507  	// get all of the tasks of the service, so we can get the container
   508  	filter := filters.NewArgs()
   509  	filter.Add("service", serviceID)
   510  	tasks, err := client.TaskList(ctx, types.TaskListOptions{
   511  		Filters: filter,
   512  	})
   513  	assert.NilError(t, err)
   514  	assert.Check(t, is.Equal(len(tasks), 1))
   515  
   516  	// verify that the container has the capabilities option set
   517  	ctnr, err := client.ContainerInspect(ctx, tasks[0].Status.ContainerStatus.ContainerID)
   518  	assert.NilError(t, err)
   519  	assert.DeepEqual(t, ctnr.HostConfig.CapAdd, strslice.StrSlice(capAdd))
   520  	assert.DeepEqual(t, ctnr.HostConfig.CapDrop, strslice.StrSlice(capDrop))
   521  
   522  	// verify that the task has the capabilities option set in the task object
   523  	assert.DeepEqual(t, tasks[0].Spec.ContainerSpec.CapabilityAdd, capAdd)
   524  	assert.DeepEqual(t, tasks[0].Spec.ContainerSpec.CapabilityDrop, capDrop)
   525  
   526  	// verify that the service also has the capabilities set in the spec.
   527  	service, _, err := client.ServiceInspectWithRaw(ctx, serviceID, types.ServiceInspectOptions{})
   528  	assert.NilError(t, err)
   529  	assert.DeepEqual(t, service.Spec.TaskTemplate.ContainerSpec.CapabilityAdd, capAdd)
   530  	assert.DeepEqual(t, service.Spec.TaskTemplate.ContainerSpec.CapabilityDrop, capDrop)
   531  }