create_test.go 19 KB

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