create_test.go 16 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474
  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/container"
  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"
  19. "github.com/docker/docker/testutil/daemon"
  20. "gotest.tools/v3/assert"
  21. is "gotest.tools/v3/assert/cmp"
  22. "gotest.tools/v3/poll"
  23. "gotest.tools/v3/skip"
  24. )
  25. func TestServiceCreateInit(t *testing.T) {
  26. ctx := setupTest(t)
  27. t.Run("daemonInitDisabled", testServiceCreateInit(ctx, false))
  28. t.Run("daemonInitEnabled", testServiceCreateInit(ctx, true))
  29. }
  30. func testServiceCreateInit(ctx context.Context, daemonEnabled bool) func(t *testing.T) {
  31. return func(t *testing.T) {
  32. _ = testutil.StartSpan(ctx, t)
  33. ops := []daemon.Option{}
  34. if daemonEnabled {
  35. ops = append(ops, daemon.WithInit())
  36. }
  37. d := swarm.NewSwarm(ctx, t, testEnv, ops...)
  38. defer d.Stop(t)
  39. client := d.NewClientT(t)
  40. defer client.Close()
  41. booleanTrue := true
  42. booleanFalse := false
  43. serviceID := swarm.CreateService(ctx, t, d)
  44. poll.WaitOn(t, swarm.RunningTasksCount(ctx, client, serviceID, 1), swarm.ServicePoll)
  45. i := inspectServiceContainer(ctx, t, client, serviceID)
  46. // HostConfig.Init == nil means that it delegates to daemon configuration
  47. assert.Check(t, i.HostConfig.Init == nil)
  48. serviceID = swarm.CreateService(ctx, t, d, swarm.ServiceWithInit(&booleanTrue))
  49. poll.WaitOn(t, swarm.RunningTasksCount(ctx, client, serviceID, 1), swarm.ServicePoll)
  50. i = inspectServiceContainer(ctx, t, client, serviceID)
  51. assert.Check(t, is.Equal(true, *i.HostConfig.Init))
  52. serviceID = swarm.CreateService(ctx, t, d, swarm.ServiceWithInit(&booleanFalse))
  53. poll.WaitOn(t, swarm.RunningTasksCount(ctx, client, serviceID, 1), swarm.ServicePoll)
  54. i = inspectServiceContainer(ctx, t, client, serviceID)
  55. assert.Check(t, is.Equal(false, *i.HostConfig.Init))
  56. }
  57. }
  58. func inspectServiceContainer(ctx context.Context, t *testing.T, client client.APIClient, serviceID string) types.ContainerJSON {
  59. t.Helper()
  60. containers, err := client.ContainerList(ctx, container.ListOptions{
  61. Filters: filters.NewArgs(filters.Arg("label", "com.docker.swarm.service.id="+serviceID)),
  62. })
  63. assert.NilError(t, err)
  64. assert.Check(t, is.Len(containers, 1))
  65. i, err := client.ContainerInspect(ctx, containers[0].ID)
  66. assert.NilError(t, err)
  67. return i
  68. }
  69. func TestCreateServiceMultipleTimes(t *testing.T) {
  70. skip.If(t, testEnv.DaemonInfo.OSType == "windows")
  71. ctx := setupTest(t)
  72. d := swarm.NewSwarm(ctx, t, testEnv)
  73. defer d.Stop(t)
  74. client := d.NewClientT(t)
  75. defer client.Close()
  76. overlayName := "overlay1_" + t.Name()
  77. overlayID := network.CreateNoError(ctx, t, client, overlayName,
  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(ctx, t, d, serviceSpec...)
  88. poll.WaitOn(t, swarm.RunningTasksCount(ctx, client, serviceID, instances), swarm.ServicePoll)
  89. _, _, err := client.ServiceInspectWithRaw(ctx, serviceID, types.ServiceInspectOptions{})
  90. assert.NilError(t, err)
  91. err = client.ServiceRemove(ctx, serviceID)
  92. assert.NilError(t, err)
  93. poll.WaitOn(t, swarm.NoTasksForService(ctx, client, serviceID), swarm.ServicePoll)
  94. serviceID2 := swarm.CreateService(ctx, t, d, serviceSpec...)
  95. poll.WaitOn(t, swarm.RunningTasksCount(ctx, client, serviceID2, instances), swarm.ServicePoll)
  96. err = client.ServiceRemove(ctx, 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(ctx, 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(ctx, 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. ctx := setupTest(t)
  130. d := swarm.NewSwarm(ctx, t, testEnv)
  131. defer d.Stop(t)
  132. c := d.NewClientT(t)
  133. defer c.Close()
  134. serviceName := "TestService_" + t.Name()
  135. serviceSpec := []swarm.ServiceSpecOpt{
  136. swarm.ServiceWithName(serviceName),
  137. }
  138. swarm.CreateService(ctx, 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. ctx := setupTest(t)
  146. d := swarm.NewSwarm(ctx, 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(ctx, t, d, serviceSpec...)
  156. poll.WaitOn(t, swarm.RunningTasksCount(ctx, client, serviceID, maxReplicas), swarm.ServicePoll)
  157. _, _, err := client.ServiceInspectWithRaw(ctx, serviceID, types.ServiceInspectOptions{})
  158. assert.NilError(t, err)
  159. }
  160. func TestCreateServiceSecretFileMode(t *testing.T) {
  161. skip.If(t, testEnv.DaemonInfo.OSType == "windows")
  162. ctx := setupTest(t)
  163. d := swarm.NewSwarm(ctx, t, testEnv)
  164. defer d.Stop(t)
  165. client := d.NewClientT(t)
  166. defer client.Close()
  167. secretName := "TestSecret_" + t.Name()
  168. secretResp, err := client.SecretCreate(ctx, swarmtypes.SecretSpec{
  169. Annotations: swarmtypes.Annotations{
  170. Name: secretName,
  171. },
  172. Data: []byte("TESTSECRET"),
  173. })
  174. assert.NilError(t, err)
  175. var instances uint64 = 1
  176. serviceName := "TestService_" + t.Name()
  177. serviceID := swarm.CreateService(ctx, t, d,
  178. swarm.ServiceWithReplicas(instances),
  179. swarm.ServiceWithName(serviceName),
  180. swarm.ServiceWithCommand([]string{"/bin/sh", "-c", "ls -l /etc/secret && sleep inf"}),
  181. swarm.ServiceWithSecret(&swarmtypes.SecretReference{
  182. File: &swarmtypes.SecretReferenceFileTarget{
  183. Name: "/etc/secret",
  184. UID: "0",
  185. GID: "0",
  186. Mode: 0o777,
  187. },
  188. SecretID: secretResp.ID,
  189. SecretName: secretName,
  190. }),
  191. )
  192. poll.WaitOn(t, swarm.RunningTasksCount(ctx, client, serviceID, instances), swarm.ServicePoll)
  193. body, err := client.ServiceLogs(ctx, serviceID, types.ContainerLogsOptions{
  194. Tail: "1",
  195. ShowStdout: true,
  196. })
  197. assert.NilError(t, err)
  198. defer body.Close()
  199. content, err := io.ReadAll(body)
  200. assert.NilError(t, err)
  201. assert.Check(t, is.Contains(string(content), "-rwxrwxrwx"))
  202. err = client.ServiceRemove(ctx, serviceID)
  203. assert.NilError(t, err)
  204. poll.WaitOn(t, swarm.NoTasksForService(ctx, client, serviceID), swarm.ServicePoll)
  205. err = client.SecretRemove(ctx, secretName)
  206. assert.NilError(t, err)
  207. }
  208. func TestCreateServiceConfigFileMode(t *testing.T) {
  209. skip.If(t, testEnv.DaemonInfo.OSType == "windows")
  210. ctx := setupTest(t)
  211. d := swarm.NewSwarm(ctx, t, testEnv)
  212. defer d.Stop(t)
  213. client := d.NewClientT(t)
  214. defer client.Close()
  215. configName := "TestConfig_" + t.Name()
  216. configResp, err := client.ConfigCreate(ctx, swarmtypes.ConfigSpec{
  217. Annotations: swarmtypes.Annotations{
  218. Name: configName,
  219. },
  220. Data: []byte("TESTCONFIG"),
  221. })
  222. assert.NilError(t, err)
  223. var instances uint64 = 1
  224. serviceName := "TestService_" + t.Name()
  225. serviceID := swarm.CreateService(ctx, t, d,
  226. swarm.ServiceWithName(serviceName),
  227. swarm.ServiceWithCommand([]string{"/bin/sh", "-c", "ls -l /etc/config && sleep inf"}),
  228. swarm.ServiceWithReplicas(instances),
  229. swarm.ServiceWithConfig(&swarmtypes.ConfigReference{
  230. File: &swarmtypes.ConfigReferenceFileTarget{
  231. Name: "/etc/config",
  232. UID: "0",
  233. GID: "0",
  234. Mode: 0o777,
  235. },
  236. ConfigID: configResp.ID,
  237. ConfigName: configName,
  238. }),
  239. )
  240. poll.WaitOn(t, swarm.RunningTasksCount(ctx, client, serviceID, instances))
  241. body, err := client.ServiceLogs(ctx, serviceID, types.ContainerLogsOptions{
  242. Tail: "1",
  243. ShowStdout: true,
  244. })
  245. assert.NilError(t, err)
  246. defer body.Close()
  247. content, err := io.ReadAll(body)
  248. assert.NilError(t, err)
  249. assert.Check(t, is.Contains(string(content), "-rwxrwxrwx"))
  250. err = client.ServiceRemove(ctx, serviceID)
  251. assert.NilError(t, err)
  252. poll.WaitOn(t, swarm.NoTasksForService(ctx, client, serviceID))
  253. err = client.ConfigRemove(ctx, configName)
  254. assert.NilError(t, err)
  255. }
  256. // TestServiceCreateSysctls tests that a service created with sysctl options in
  257. // the ContainerSpec correctly applies those options.
  258. //
  259. // To test this, we're going to create a service with the sysctl option
  260. //
  261. // {"net.ipv4.ip_nonlocal_bind": "0"}
  262. //
  263. // We'll get the service's tasks to get the container ID, and then we'll
  264. // inspect the container. If the output of the container inspect contains the
  265. // sysctl option with the correct value, we can assume that the sysctl has been
  266. // plumbed correctly.
  267. //
  268. // Next, we'll remove that service and create a new service with that option
  269. // set to 1. This means that no matter what the default is, we can be confident
  270. // that the sysctl option is applying as intended.
  271. //
  272. // Additionally, we'll do service and task inspects to verify that the inspect
  273. // output includes the desired sysctl option.
  274. //
  275. // We're using net.ipv4.ip_nonlocal_bind because it's something that I'm fairly
  276. // confident won't be modified by the container runtime, and won't blow
  277. // anything up in the test environment
  278. func TestCreateServiceSysctls(t *testing.T) {
  279. skip.If(
  280. t, versions.LessThan(testEnv.DaemonAPIVersion(), "1.40"),
  281. "setting service sysctls is unsupported before api v1.40",
  282. )
  283. ctx := setupTest(t)
  284. d := swarm.NewSwarm(ctx, t, testEnv)
  285. defer d.Stop(t)
  286. client := d.NewClientT(t)
  287. defer client.Close()
  288. // run thie block twice, so that no matter what the default value of
  289. // net.ipv4.ip_nonlocal_bind is, we can verify that setting the sysctl
  290. // options works
  291. for _, expected := range []string{"0", "1"} {
  292. // store the map we're going to be using everywhere.
  293. expectedSysctls := map[string]string{"net.ipv4.ip_nonlocal_bind": expected}
  294. // Create the service with the sysctl options
  295. var instances uint64 = 1
  296. serviceID := swarm.CreateService(ctx, t, d,
  297. swarm.ServiceWithSysctls(expectedSysctls),
  298. )
  299. // wait for the service to converge to 1 running task as expected
  300. poll.WaitOn(t, swarm.RunningTasksCount(ctx, client, serviceID, instances))
  301. // we're going to check 3 things:
  302. //
  303. // 1. Does the container, when inspected, have the sysctl option set?
  304. // 2. Does the task have the sysctl in the spec?
  305. // 3. Does the service have the sysctl in the spec?
  306. //
  307. // if all 3 of these things are true, we know that the sysctl has been
  308. // plumbed correctly through the engine.
  309. //
  310. // We don't actually have to get inside the container and check its
  311. // logs or anything. If we see the sysctl set on the container inspect,
  312. // we know that the sysctl is plumbed correctly. everything below that
  313. // level has been tested elsewhere. (thanks @thaJeztah, because an
  314. // earlier version of this test had to get container logs and was much
  315. // more complex)
  316. // get all tasks of the service, so we can get the container
  317. tasks, err := client.TaskList(ctx, types.TaskListOptions{
  318. Filters: filters.NewArgs(filters.Arg("service", serviceID)),
  319. })
  320. assert.NilError(t, err)
  321. assert.Check(t, is.Equal(len(tasks), 1))
  322. // verify that the container has the sysctl option set
  323. ctnr, err := client.ContainerInspect(ctx, tasks[0].Status.ContainerStatus.ContainerID)
  324. assert.NilError(t, err)
  325. assert.DeepEqual(t, ctnr.HostConfig.Sysctls, expectedSysctls)
  326. // verify that the task has the sysctl option set in the task object
  327. assert.DeepEqual(t, tasks[0].Spec.ContainerSpec.Sysctls, expectedSysctls)
  328. // verify that the service also has the sysctl set in the spec.
  329. service, _, err := client.ServiceInspectWithRaw(ctx, serviceID, types.ServiceInspectOptions{})
  330. assert.NilError(t, err)
  331. assert.DeepEqual(t,
  332. service.Spec.TaskTemplate.ContainerSpec.Sysctls, expectedSysctls,
  333. )
  334. }
  335. }
  336. // TestServiceCreateCapabilities tests that a service created with capabilities options in
  337. // the ContainerSpec correctly applies those options.
  338. //
  339. // To test this, we're going to create a service with the capabilities option
  340. //
  341. // []string{"CAP_NET_RAW", "CAP_SYS_CHROOT"}
  342. //
  343. // We'll get the service's tasks to get the container ID, and then we'll
  344. // inspect the container. If the output of the container inspect contains the
  345. // capabilities option with the correct value, we can assume that the capabilities has been
  346. // plumbed correctly.
  347. func TestCreateServiceCapabilities(t *testing.T) {
  348. skip.If(
  349. t, versions.LessThan(testEnv.DaemonAPIVersion(), "1.41"),
  350. "setting service capabilities is unsupported before api v1.41",
  351. )
  352. ctx := setupTest(t)
  353. d := swarm.NewSwarm(ctx, t, testEnv)
  354. defer d.Stop(t)
  355. client := d.NewClientT(t)
  356. defer client.Close()
  357. // store the map we're going to be using everywhere.
  358. capAdd := []string{"CAP_SYS_CHROOT"}
  359. capDrop := []string{"CAP_NET_RAW"}
  360. // Create the service with the capabilities options
  361. var instances uint64 = 1
  362. serviceID := swarm.CreateService(ctx, t, d,
  363. swarm.ServiceWithCapabilities(capAdd, capDrop),
  364. )
  365. // wait for the service to converge to 1 running task as expected
  366. poll.WaitOn(t, swarm.RunningTasksCount(ctx, client, serviceID, instances))
  367. // we're going to check 3 things:
  368. //
  369. // 1. Does the container, when inspected, have the capabilities option set?
  370. // 2. Does the task have the capabilities in the spec?
  371. // 3. Does the service have the capabilities in the spec?
  372. //
  373. // if all 3 of these things are true, we know that the capabilities has been
  374. // plumbed correctly through the engine.
  375. //
  376. // We don't actually have to get inside the container and check its
  377. // logs or anything. If we see the capabilities set on the container inspect,
  378. // we know that the capabilities is plumbed correctly. everything below that
  379. // level has been tested elsewhere.
  380. // get all tasks of the service, so we can get the container
  381. tasks, err := client.TaskList(ctx, types.TaskListOptions{
  382. Filters: filters.NewArgs(filters.Arg("service", serviceID)),
  383. })
  384. assert.NilError(t, err)
  385. assert.Check(t, is.Equal(len(tasks), 1))
  386. // verify that the container has the capabilities option set
  387. ctnr, err := client.ContainerInspect(ctx, tasks[0].Status.ContainerStatus.ContainerID)
  388. assert.NilError(t, err)
  389. assert.DeepEqual(t, ctnr.HostConfig.CapAdd, strslice.StrSlice(capAdd))
  390. assert.DeepEqual(t, ctnr.HostConfig.CapDrop, strslice.StrSlice(capDrop))
  391. // verify that the task has the capabilities option set in the task object
  392. assert.DeepEqual(t, tasks[0].Spec.ContainerSpec.CapabilityAdd, capAdd)
  393. assert.DeepEqual(t, tasks[0].Spec.ContainerSpec.CapabilityDrop, capDrop)
  394. // verify that the service also has the capabilities set in the spec.
  395. service, _, err := client.ServiceInspectWithRaw(ctx, serviceID, types.ServiceInspectOptions{})
  396. assert.NilError(t, err)
  397. assert.DeepEqual(t, service.Spec.TaskTemplate.ContainerSpec.CapabilityAdd, capAdd)
  398. assert.DeepEqual(t, service.Spec.TaskTemplate.ContainerSpec.CapabilityDrop, capDrop)
  399. }