update_test.go 14 KB

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