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