update_test.go 15 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496
  1. package service
  2. import (
  3. "reflect"
  4. "sort"
  5. "testing"
  6. "time"
  7. "github.com/docker/docker/api/types"
  8. "github.com/docker/docker/api/types/container"
  9. mounttypes "github.com/docker/docker/api/types/mount"
  10. "github.com/docker/docker/api/types/swarm"
  11. "github.com/docker/docker/pkg/testutil/assert"
  12. "golang.org/x/net/context"
  13. )
  14. func TestUpdateServiceArgs(t *testing.T) {
  15. flags := newUpdateCommand(nil).Flags()
  16. flags.Set("args", "the \"new args\"")
  17. spec := &swarm.ServiceSpec{}
  18. cspec := &spec.TaskTemplate.ContainerSpec
  19. cspec.Args = []string{"old", "args"}
  20. updateService(flags, spec)
  21. assert.EqualStringSlice(t, cspec.Args, []string{"the", "new args"})
  22. }
  23. func TestUpdateLabels(t *testing.T) {
  24. flags := newUpdateCommand(nil).Flags()
  25. flags.Set("label-add", "toadd=newlabel")
  26. flags.Set("label-rm", "toremove")
  27. labels := map[string]string{
  28. "toremove": "thelabeltoremove",
  29. "tokeep": "value",
  30. }
  31. updateLabels(flags, &labels)
  32. assert.Equal(t, len(labels), 2)
  33. assert.Equal(t, labels["tokeep"], "value")
  34. assert.Equal(t, labels["toadd"], "newlabel")
  35. }
  36. func TestUpdateLabelsRemoveALabelThatDoesNotExist(t *testing.T) {
  37. flags := newUpdateCommand(nil).Flags()
  38. flags.Set("label-rm", "dne")
  39. labels := map[string]string{"foo": "theoldlabel"}
  40. updateLabels(flags, &labels)
  41. assert.Equal(t, len(labels), 1)
  42. }
  43. func TestUpdatePlacementConstraints(t *testing.T) {
  44. flags := newUpdateCommand(nil).Flags()
  45. flags.Set("constraint-add", "node=toadd")
  46. flags.Set("constraint-rm", "node!=toremove")
  47. placement := &swarm.Placement{
  48. Constraints: []string{"node!=toremove", "container=tokeep"},
  49. }
  50. updatePlacementConstraints(flags, placement)
  51. assert.Equal(t, len(placement.Constraints), 2)
  52. assert.Equal(t, placement.Constraints[0], "container=tokeep")
  53. assert.Equal(t, placement.Constraints[1], "node=toadd")
  54. }
  55. func TestUpdatePlacementPrefs(t *testing.T) {
  56. flags := newUpdateCommand(nil).Flags()
  57. flags.Set("placement-pref-add", "spread=node.labels.dc")
  58. flags.Set("placement-pref-rm", "spread=node.labels.rack")
  59. placement := &swarm.Placement{
  60. Preferences: []swarm.PlacementPreference{
  61. {
  62. Spread: &swarm.SpreadOver{
  63. SpreadDescriptor: "node.labels.rack",
  64. },
  65. },
  66. {
  67. Spread: &swarm.SpreadOver{
  68. SpreadDescriptor: "node.labels.row",
  69. },
  70. },
  71. },
  72. }
  73. updatePlacementPreferences(flags, placement)
  74. assert.Equal(t, len(placement.Preferences), 2)
  75. assert.Equal(t, placement.Preferences[0].Spread.SpreadDescriptor, "node.labels.row")
  76. assert.Equal(t, placement.Preferences[1].Spread.SpreadDescriptor, "node.labels.dc")
  77. }
  78. func TestUpdateEnvironment(t *testing.T) {
  79. flags := newUpdateCommand(nil).Flags()
  80. flags.Set("env-add", "toadd=newenv")
  81. flags.Set("env-rm", "toremove")
  82. envs := []string{"toremove=theenvtoremove", "tokeep=value"}
  83. updateEnvironment(flags, &envs)
  84. assert.Equal(t, len(envs), 2)
  85. // Order has been removed in updateEnvironment (map)
  86. sort.Strings(envs)
  87. assert.Equal(t, envs[0], "toadd=newenv")
  88. assert.Equal(t, envs[1], "tokeep=value")
  89. }
  90. func TestUpdateEnvironmentWithDuplicateValues(t *testing.T) {
  91. flags := newUpdateCommand(nil).Flags()
  92. flags.Set("env-add", "foo=newenv")
  93. flags.Set("env-add", "foo=dupe")
  94. flags.Set("env-rm", "foo")
  95. envs := []string{"foo=value"}
  96. updateEnvironment(flags, &envs)
  97. assert.Equal(t, len(envs), 0)
  98. }
  99. func TestUpdateEnvironmentWithDuplicateKeys(t *testing.T) {
  100. // Test case for #25404
  101. flags := newUpdateCommand(nil).Flags()
  102. flags.Set("env-add", "A=b")
  103. envs := []string{"A=c"}
  104. updateEnvironment(flags, &envs)
  105. assert.Equal(t, len(envs), 1)
  106. assert.Equal(t, envs[0], "A=b")
  107. }
  108. func TestUpdateGroups(t *testing.T) {
  109. flags := newUpdateCommand(nil).Flags()
  110. flags.Set("group-add", "wheel")
  111. flags.Set("group-add", "docker")
  112. flags.Set("group-rm", "root")
  113. flags.Set("group-add", "foo")
  114. flags.Set("group-rm", "docker")
  115. groups := []string{"bar", "root"}
  116. updateGroups(flags, &groups)
  117. assert.Equal(t, len(groups), 3)
  118. assert.Equal(t, groups[0], "bar")
  119. assert.Equal(t, groups[1], "foo")
  120. assert.Equal(t, groups[2], "wheel")
  121. }
  122. func TestUpdateDNSConfig(t *testing.T) {
  123. flags := newUpdateCommand(nil).Flags()
  124. // IPv4, with duplicates
  125. flags.Set("dns-add", "1.1.1.1")
  126. flags.Set("dns-add", "1.1.1.1")
  127. flags.Set("dns-add", "2.2.2.2")
  128. flags.Set("dns-rm", "3.3.3.3")
  129. flags.Set("dns-rm", "2.2.2.2")
  130. // IPv6
  131. flags.Set("dns-add", "2001:db8:abc8::1")
  132. // Invalid dns record
  133. assert.Error(t, flags.Set("dns-add", "x.y.z.w"), "x.y.z.w is not an ip address")
  134. // domains with duplicates
  135. flags.Set("dns-search-add", "example.com")
  136. flags.Set("dns-search-add", "example.com")
  137. flags.Set("dns-search-add", "example.org")
  138. flags.Set("dns-search-rm", "example.org")
  139. // Invalid dns search domain
  140. assert.Error(t, flags.Set("dns-search-add", "example$com"), "example$com is not a valid domain")
  141. flags.Set("dns-option-add", "ndots:9")
  142. flags.Set("dns-option-rm", "timeout:3")
  143. config := &swarm.DNSConfig{
  144. Nameservers: []string{"3.3.3.3", "5.5.5.5"},
  145. Search: []string{"localdomain"},
  146. Options: []string{"timeout:3"},
  147. }
  148. updateDNSConfig(flags, &config)
  149. assert.Equal(t, len(config.Nameservers), 3)
  150. assert.Equal(t, config.Nameservers[0], "1.1.1.1")
  151. assert.Equal(t, config.Nameservers[1], "2001:db8:abc8::1")
  152. assert.Equal(t, config.Nameservers[2], "5.5.5.5")
  153. assert.Equal(t, len(config.Search), 2)
  154. assert.Equal(t, config.Search[0], "example.com")
  155. assert.Equal(t, config.Search[1], "localdomain")
  156. assert.Equal(t, len(config.Options), 1)
  157. assert.Equal(t, config.Options[0], "ndots:9")
  158. }
  159. func TestUpdateMounts(t *testing.T) {
  160. flags := newUpdateCommand(nil).Flags()
  161. flags.Set("mount-add", "type=volume,source=vol2,target=/toadd")
  162. flags.Set("mount-rm", "/toremove")
  163. mounts := []mounttypes.Mount{
  164. {Target: "/toremove", Source: "vol1", Type: mounttypes.TypeBind},
  165. {Target: "/tokeep", Source: "vol3", Type: mounttypes.TypeBind},
  166. }
  167. updateMounts(flags, &mounts)
  168. assert.Equal(t, len(mounts), 2)
  169. assert.Equal(t, mounts[0].Target, "/toadd")
  170. assert.Equal(t, mounts[1].Target, "/tokeep")
  171. }
  172. func TestUpdateMountsWithDuplicateMounts(t *testing.T) {
  173. flags := newUpdateCommand(nil).Flags()
  174. flags.Set("mount-add", "type=volume,source=vol4,target=/toadd")
  175. mounts := []mounttypes.Mount{
  176. {Target: "/tokeep1", Source: "vol1", Type: mounttypes.TypeBind},
  177. {Target: "/toadd", Source: "vol2", Type: mounttypes.TypeBind},
  178. {Target: "/tokeep2", Source: "vol3", Type: mounttypes.TypeBind},
  179. }
  180. updateMounts(flags, &mounts)
  181. assert.Equal(t, len(mounts), 3)
  182. assert.Equal(t, mounts[0].Target, "/tokeep1")
  183. assert.Equal(t, mounts[1].Target, "/tokeep2")
  184. assert.Equal(t, mounts[2].Target, "/toadd")
  185. }
  186. func TestUpdatePorts(t *testing.T) {
  187. flags := newUpdateCommand(nil).Flags()
  188. flags.Set("publish-add", "1000:1000")
  189. flags.Set("publish-rm", "333/udp")
  190. portConfigs := []swarm.PortConfig{
  191. {TargetPort: 333, Protocol: swarm.PortConfigProtocolUDP},
  192. {TargetPort: 555},
  193. }
  194. err := updatePorts(flags, &portConfigs)
  195. assert.Equal(t, err, nil)
  196. assert.Equal(t, len(portConfigs), 2)
  197. // Do a sort to have the order (might have changed by map)
  198. targetPorts := []int{int(portConfigs[0].TargetPort), int(portConfigs[1].TargetPort)}
  199. sort.Ints(targetPorts)
  200. assert.Equal(t, targetPorts[0], 555)
  201. assert.Equal(t, targetPorts[1], 1000)
  202. }
  203. func TestUpdatePortsDuplicate(t *testing.T) {
  204. // Test case for #25375
  205. flags := newUpdateCommand(nil).Flags()
  206. flags.Set("publish-add", "80:80")
  207. portConfigs := []swarm.PortConfig{
  208. {
  209. TargetPort: 80,
  210. PublishedPort: 80,
  211. Protocol: swarm.PortConfigProtocolTCP,
  212. PublishMode: swarm.PortConfigPublishModeIngress,
  213. },
  214. }
  215. err := updatePorts(flags, &portConfigs)
  216. assert.Equal(t, err, nil)
  217. assert.Equal(t, len(portConfigs), 1)
  218. assert.Equal(t, portConfigs[0].TargetPort, uint32(80))
  219. }
  220. func TestUpdateHealthcheckTable(t *testing.T) {
  221. type test struct {
  222. flags [][2]string
  223. initial *container.HealthConfig
  224. expected *container.HealthConfig
  225. err string
  226. }
  227. testCases := []test{
  228. {
  229. flags: [][2]string{{"no-healthcheck", "true"}},
  230. initial: &container.HealthConfig{Test: []string{"CMD-SHELL", "cmd1"}, Retries: 10},
  231. expected: &container.HealthConfig{Test: []string{"NONE"}},
  232. },
  233. {
  234. flags: [][2]string{{"health-cmd", "cmd1"}},
  235. initial: &container.HealthConfig{Test: []string{"NONE"}},
  236. expected: &container.HealthConfig{Test: []string{"CMD-SHELL", "cmd1"}},
  237. },
  238. {
  239. flags: [][2]string{{"health-retries", "10"}},
  240. initial: &container.HealthConfig{Test: []string{"NONE"}},
  241. expected: &container.HealthConfig{Retries: 10},
  242. },
  243. {
  244. flags: [][2]string{{"health-retries", "10"}},
  245. initial: &container.HealthConfig{Test: []string{"CMD", "cmd1"}},
  246. expected: &container.HealthConfig{Test: []string{"CMD", "cmd1"}, Retries: 10},
  247. },
  248. {
  249. flags: [][2]string{{"health-interval", "1m"}},
  250. initial: &container.HealthConfig{Test: []string{"CMD", "cmd1"}},
  251. expected: &container.HealthConfig{Test: []string{"CMD", "cmd1"}, Interval: time.Minute},
  252. },
  253. {
  254. flags: [][2]string{{"health-cmd", ""}},
  255. initial: &container.HealthConfig{Test: []string{"CMD", "cmd1"}, Retries: 10},
  256. expected: &container.HealthConfig{Retries: 10},
  257. },
  258. {
  259. flags: [][2]string{{"health-retries", "0"}},
  260. initial: &container.HealthConfig{Test: []string{"CMD", "cmd1"}, Retries: 10},
  261. expected: &container.HealthConfig{Test: []string{"CMD", "cmd1"}},
  262. },
  263. {
  264. flags: [][2]string{{"health-start-period", "1m"}},
  265. initial: &container.HealthConfig{Test: []string{"CMD", "cmd1"}},
  266. expected: &container.HealthConfig{Test: []string{"CMD", "cmd1"}, StartPeriod: time.Minute},
  267. },
  268. {
  269. flags: [][2]string{{"health-cmd", "cmd1"}, {"no-healthcheck", "true"}},
  270. err: "--no-healthcheck conflicts with --health-* options",
  271. },
  272. {
  273. flags: [][2]string{{"health-interval", "10m"}, {"no-healthcheck", "true"}},
  274. err: "--no-healthcheck conflicts with --health-* options",
  275. },
  276. {
  277. flags: [][2]string{{"health-timeout", "1m"}, {"no-healthcheck", "true"}},
  278. err: "--no-healthcheck conflicts with --health-* options",
  279. },
  280. }
  281. for i, c := range testCases {
  282. flags := newUpdateCommand(nil).Flags()
  283. for _, flag := range c.flags {
  284. flags.Set(flag[0], flag[1])
  285. }
  286. cspec := &swarm.ContainerSpec{
  287. Healthcheck: c.initial,
  288. }
  289. err := updateHealthcheck(flags, cspec)
  290. if c.err != "" {
  291. assert.Error(t, err, c.err)
  292. } else {
  293. assert.NilError(t, err)
  294. if !reflect.DeepEqual(cspec.Healthcheck, c.expected) {
  295. t.Errorf("incorrect result for test %d, expected health config:\n\t%#v\ngot:\n\t%#v", i, c.expected, cspec.Healthcheck)
  296. }
  297. }
  298. }
  299. }
  300. func TestUpdateHosts(t *testing.T) {
  301. flags := newUpdateCommand(nil).Flags()
  302. flags.Set("host-add", "example.net:2.2.2.2")
  303. flags.Set("host-add", "ipv6.net:2001:db8:abc8::1")
  304. // remove with ipv6 should work
  305. flags.Set("host-rm", "example.net:2001:db8:abc8::1")
  306. // just hostname should work as well
  307. flags.Set("host-rm", "example.net")
  308. // bad format error
  309. assert.Error(t, flags.Set("host-add", "$example.com$"), "bad format for add-host:")
  310. hosts := []string{"1.2.3.4 example.com", "4.3.2.1 example.org", "2001:db8:abc8::1 example.net"}
  311. updateHosts(flags, &hosts)
  312. assert.Equal(t, len(hosts), 3)
  313. assert.Equal(t, hosts[0], "1.2.3.4 example.com")
  314. assert.Equal(t, hosts[1], "2001:db8:abc8::1 ipv6.net")
  315. assert.Equal(t, hosts[2], "4.3.2.1 example.org")
  316. }
  317. func TestUpdatePortsRmWithProtocol(t *testing.T) {
  318. flags := newUpdateCommand(nil).Flags()
  319. flags.Set("publish-add", "8081:81")
  320. flags.Set("publish-add", "8082:82")
  321. flags.Set("publish-rm", "80")
  322. flags.Set("publish-rm", "81/tcp")
  323. flags.Set("publish-rm", "82/udp")
  324. portConfigs := []swarm.PortConfig{
  325. {
  326. TargetPort: 80,
  327. PublishedPort: 8080,
  328. Protocol: swarm.PortConfigProtocolTCP,
  329. PublishMode: swarm.PortConfigPublishModeIngress,
  330. },
  331. }
  332. err := updatePorts(flags, &portConfigs)
  333. assert.Equal(t, err, nil)
  334. assert.Equal(t, len(portConfigs), 2)
  335. assert.Equal(t, portConfigs[0].TargetPort, uint32(81))
  336. assert.Equal(t, portConfigs[1].TargetPort, uint32(82))
  337. }
  338. type secretAPIClientMock struct {
  339. listResult []swarm.Secret
  340. }
  341. func (s secretAPIClientMock) SecretList(ctx context.Context, options types.SecretListOptions) ([]swarm.Secret, error) {
  342. return s.listResult, nil
  343. }
  344. func (s secretAPIClientMock) SecretCreate(ctx context.Context, secret swarm.SecretSpec) (types.SecretCreateResponse, error) {
  345. return types.SecretCreateResponse{}, nil
  346. }
  347. func (s secretAPIClientMock) SecretRemove(ctx context.Context, id string) error {
  348. return nil
  349. }
  350. func (s secretAPIClientMock) SecretInspectWithRaw(ctx context.Context, name string) (swarm.Secret, []byte, error) {
  351. return swarm.Secret{}, []byte{}, nil
  352. }
  353. func (s secretAPIClientMock) SecretUpdate(ctx context.Context, id string, version swarm.Version, secret swarm.SecretSpec) error {
  354. return nil
  355. }
  356. // TestUpdateSecretUpdateInPlace tests the ability to update the "target" of an secret with "docker service update"
  357. // by combining "--secret-rm" and "--secret-add" for the same secret.
  358. func TestUpdateSecretUpdateInPlace(t *testing.T) {
  359. apiClient := secretAPIClientMock{
  360. listResult: []swarm.Secret{
  361. {
  362. ID: "tn9qiblgnuuut11eufquw5dev",
  363. Spec: swarm.SecretSpec{Annotations: swarm.Annotations{Name: "foo"}},
  364. },
  365. },
  366. }
  367. flags := newUpdateCommand(nil).Flags()
  368. flags.Set("secret-add", "source=foo,target=foo2")
  369. flags.Set("secret-rm", "foo")
  370. secrets := []*swarm.SecretReference{
  371. {
  372. File: &swarm.SecretReferenceFileTarget{
  373. Name: "foo",
  374. UID: "0",
  375. GID: "0",
  376. Mode: 292,
  377. },
  378. SecretID: "tn9qiblgnuuut11eufquw5dev",
  379. SecretName: "foo",
  380. },
  381. }
  382. updatedSecrets, err := getUpdatedSecrets(apiClient, flags, secrets)
  383. assert.Equal(t, err, nil)
  384. assert.Equal(t, len(updatedSecrets), 1)
  385. assert.Equal(t, updatedSecrets[0].SecretID, "tn9qiblgnuuut11eufquw5dev")
  386. assert.Equal(t, updatedSecrets[0].SecretName, "foo")
  387. assert.Equal(t, updatedSecrets[0].File.Name, "foo2")
  388. }
  389. func TestUpdateReadOnly(t *testing.T) {
  390. spec := &swarm.ServiceSpec{}
  391. cspec := &spec.TaskTemplate.ContainerSpec
  392. // Update with --read-only=true, changed to true
  393. flags := newUpdateCommand(nil).Flags()
  394. flags.Set("read-only", "true")
  395. updateService(flags, spec)
  396. assert.Equal(t, cspec.ReadOnly, true)
  397. // Update without --read-only, no change
  398. flags = newUpdateCommand(nil).Flags()
  399. updateService(flags, spec)
  400. assert.Equal(t, cspec.ReadOnly, true)
  401. // Update with --read-only=false, changed to false
  402. flags = newUpdateCommand(nil).Flags()
  403. flags.Set("read-only", "false")
  404. updateService(flags, spec)
  405. assert.Equal(t, cspec.ReadOnly, false)
  406. }
  407. func TestUpdateStopSignal(t *testing.T) {
  408. spec := &swarm.ServiceSpec{}
  409. cspec := &spec.TaskTemplate.ContainerSpec
  410. // Update with --stop-signal=SIGUSR1
  411. flags := newUpdateCommand(nil).Flags()
  412. flags.Set("stop-signal", "SIGUSR1")
  413. updateService(flags, spec)
  414. assert.Equal(t, cspec.StopSignal, "SIGUSR1")
  415. // Update without --stop-signal, no change
  416. flags = newUpdateCommand(nil).Flags()
  417. updateService(flags, spec)
  418. assert.Equal(t, cspec.StopSignal, "SIGUSR1")
  419. // Update with --stop-signal=SIGWINCH
  420. flags = newUpdateCommand(nil).Flags()
  421. flags.Set("stop-signal", "SIGWINCH")
  422. updateService(flags, spec)
  423. assert.Equal(t, cspec.StopSignal, "SIGWINCH")
  424. }