create_test.go 18 KB

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