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