update_test.go 14 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469
  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-cmd", "cmd1"}, {"no-healthcheck", "true"}},
  265. err: "--no-healthcheck conflicts with --health-* options",
  266. },
  267. {
  268. flags: [][2]string{{"health-interval", "10m"}, {"no-healthcheck", "true"}},
  269. err: "--no-healthcheck conflicts with --health-* options",
  270. },
  271. {
  272. flags: [][2]string{{"health-timeout", "1m"}, {"no-healthcheck", "true"}},
  273. err: "--no-healthcheck conflicts with --health-* options",
  274. },
  275. }
  276. for i, c := range testCases {
  277. flags := newUpdateCommand(nil).Flags()
  278. for _, flag := range c.flags {
  279. flags.Set(flag[0], flag[1])
  280. }
  281. cspec := &swarm.ContainerSpec{
  282. Healthcheck: c.initial,
  283. }
  284. err := updateHealthcheck(flags, cspec)
  285. if c.err != "" {
  286. assert.Error(t, err, c.err)
  287. } else {
  288. assert.NilError(t, err)
  289. if !reflect.DeepEqual(cspec.Healthcheck, c.expected) {
  290. t.Errorf("incorrect result for test %d, expected health config:\n\t%#v\ngot:\n\t%#v", i, c.expected, cspec.Healthcheck)
  291. }
  292. }
  293. }
  294. }
  295. func TestUpdateHosts(t *testing.T) {
  296. flags := newUpdateCommand(nil).Flags()
  297. flags.Set("host-add", "example.net:2.2.2.2")
  298. flags.Set("host-add", "ipv6.net:2001:db8:abc8::1")
  299. // remove with ipv6 should work
  300. flags.Set("host-rm", "example.net:2001:db8:abc8::1")
  301. // just hostname should work as well
  302. flags.Set("host-rm", "example.net")
  303. // bad format error
  304. assert.Error(t, flags.Set("host-add", "$example.com$"), "bad format for add-host:")
  305. hosts := []string{"1.2.3.4 example.com", "4.3.2.1 example.org", "2001:db8:abc8::1 example.net"}
  306. updateHosts(flags, &hosts)
  307. assert.Equal(t, len(hosts), 3)
  308. assert.Equal(t, hosts[0], "1.2.3.4 example.com")
  309. assert.Equal(t, hosts[1], "2001:db8:abc8::1 ipv6.net")
  310. assert.Equal(t, hosts[2], "4.3.2.1 example.org")
  311. }
  312. func TestUpdatePortsRmWithProtocol(t *testing.T) {
  313. flags := newUpdateCommand(nil).Flags()
  314. flags.Set("publish-add", "8081:81")
  315. flags.Set("publish-add", "8082:82")
  316. flags.Set("publish-rm", "80")
  317. flags.Set("publish-rm", "81/tcp")
  318. flags.Set("publish-rm", "82/udp")
  319. portConfigs := []swarm.PortConfig{
  320. {
  321. TargetPort: 80,
  322. PublishedPort: 8080,
  323. Protocol: swarm.PortConfigProtocolTCP,
  324. PublishMode: swarm.PortConfigPublishModeIngress,
  325. },
  326. }
  327. err := updatePorts(flags, &portConfigs)
  328. assert.Equal(t, err, nil)
  329. assert.Equal(t, len(portConfigs), 2)
  330. assert.Equal(t, portConfigs[0].TargetPort, uint32(81))
  331. assert.Equal(t, portConfigs[1].TargetPort, uint32(82))
  332. }
  333. type secretAPIClientMock struct {
  334. listResult []swarm.Secret
  335. }
  336. func (s secretAPIClientMock) SecretList(ctx context.Context, options types.SecretListOptions) ([]swarm.Secret, error) {
  337. return s.listResult, nil
  338. }
  339. func (s secretAPIClientMock) SecretCreate(ctx context.Context, secret swarm.SecretSpec) (types.SecretCreateResponse, error) {
  340. return types.SecretCreateResponse{}, nil
  341. }
  342. func (s secretAPIClientMock) SecretRemove(ctx context.Context, id string) error {
  343. return nil
  344. }
  345. func (s secretAPIClientMock) SecretInspectWithRaw(ctx context.Context, name string) (swarm.Secret, []byte, error) {
  346. return swarm.Secret{}, []byte{}, nil
  347. }
  348. func (s secretAPIClientMock) SecretUpdate(ctx context.Context, id string, version swarm.Version, secret swarm.SecretSpec) error {
  349. return nil
  350. }
  351. // TestUpdateSecretUpdateInPlace tests the ability to update the "target" of an secret with "docker service update"
  352. // by combining "--secret-rm" and "--secret-add" for the same secret.
  353. func TestUpdateSecretUpdateInPlace(t *testing.T) {
  354. apiClient := secretAPIClientMock{
  355. listResult: []swarm.Secret{
  356. {
  357. ID: "tn9qiblgnuuut11eufquw5dev",
  358. Spec: swarm.SecretSpec{Annotations: swarm.Annotations{Name: "foo"}},
  359. },
  360. },
  361. }
  362. flags := newUpdateCommand(nil).Flags()
  363. flags.Set("secret-add", "source=foo,target=foo2")
  364. flags.Set("secret-rm", "foo")
  365. secrets := []*swarm.SecretReference{
  366. {
  367. File: &swarm.SecretReferenceFileTarget{
  368. Name: "foo",
  369. UID: "0",
  370. GID: "0",
  371. Mode: 292,
  372. },
  373. SecretID: "tn9qiblgnuuut11eufquw5dev",
  374. SecretName: "foo",
  375. },
  376. }
  377. updatedSecrets, err := getUpdatedSecrets(apiClient, flags, secrets)
  378. assert.Equal(t, err, nil)
  379. assert.Equal(t, len(updatedSecrets), 1)
  380. assert.Equal(t, updatedSecrets[0].SecretID, "tn9qiblgnuuut11eufquw5dev")
  381. assert.Equal(t, updatedSecrets[0].SecretName, "foo")
  382. assert.Equal(t, updatedSecrets[0].File.Name, "foo2")
  383. }
  384. func TestUpdateReadOnly(t *testing.T) {
  385. spec := &swarm.ServiceSpec{}
  386. cspec := &spec.TaskTemplate.ContainerSpec
  387. // Update with --read-only=true, changed to true
  388. flags := newUpdateCommand(nil).Flags()
  389. flags.Set("read-only", "true")
  390. updateService(flags, spec)
  391. assert.Equal(t, cspec.ReadOnly, true)
  392. // Update without --read-only, no change
  393. flags = newUpdateCommand(nil).Flags()
  394. updateService(flags, spec)
  395. assert.Equal(t, cspec.ReadOnly, true)
  396. // Update with --read-only=false, changed to false
  397. flags = newUpdateCommand(nil).Flags()
  398. flags.Set("read-only", "false")
  399. updateService(flags, spec)
  400. assert.Equal(t, cspec.ReadOnly, false)
  401. }