github.com/kim0/docker@v0.6.2-0.20161130212042-4addda3f07e7/daemon/daemon_test.go (about) 1 package daemon 2 3 import ( 4 "io/ioutil" 5 "os" 6 "path/filepath" 7 "reflect" 8 "testing" 9 "time" 10 11 containertypes "github.com/docker/docker/api/types/container" 12 "github.com/docker/docker/container" 13 "github.com/docker/docker/pkg/discovery" 14 _ "github.com/docker/docker/pkg/discovery/memory" 15 "github.com/docker/docker/pkg/registrar" 16 "github.com/docker/docker/pkg/truncindex" 17 "github.com/docker/docker/registry" 18 "github.com/docker/docker/volume" 19 volumedrivers "github.com/docker/docker/volume/drivers" 20 "github.com/docker/docker/volume/local" 21 "github.com/docker/docker/volume/store" 22 "github.com/docker/go-connections/nat" 23 ) 24 25 // 26 // https://github.com/docker/docker/issues/8069 27 // 28 29 func TestGetContainer(t *testing.T) { 30 c1 := &container.Container{ 31 CommonContainer: container.CommonContainer{ 32 ID: "5a4ff6a163ad4533d22d69a2b8960bf7fafdcba06e72d2febdba229008b0bf57", 33 Name: "tender_bardeen", 34 }, 35 } 36 37 c2 := &container.Container{ 38 CommonContainer: container.CommonContainer{ 39 ID: "3cdbd1aa394fd68559fd1441d6eff2ab7c1e6363582c82febfaa8045df3bd8de", 40 Name: "drunk_hawking", 41 }, 42 } 43 44 c3 := &container.Container{ 45 CommonContainer: container.CommonContainer{ 46 ID: "3cdbd1aa394fd68559fd1441d6eff2abfafdcba06e72d2febdba229008b0bf57", 47 Name: "3cdbd1aa", 48 }, 49 } 50 51 c4 := &container.Container{ 52 CommonContainer: container.CommonContainer{ 53 ID: "75fb0b800922abdbef2d27e60abcdfaf7fb0698b2a96d22d3354da361a6ff4a5", 54 Name: "5a4ff6a163ad4533d22d69a2b8960bf7fafdcba06e72d2febdba229008b0bf57", 55 }, 56 } 57 58 c5 := &container.Container{ 59 CommonContainer: container.CommonContainer{ 60 ID: "d22d69a2b8960bf7fafdcba06e72d2febdba960bf7fafdcba06e72d2f9008b060b", 61 Name: "d22d69a2b896", 62 }, 63 } 64 65 store := container.NewMemoryStore() 66 store.Add(c1.ID, c1) 67 store.Add(c2.ID, c2) 68 store.Add(c3.ID, c3) 69 store.Add(c4.ID, c4) 70 store.Add(c5.ID, c5) 71 72 index := truncindex.NewTruncIndex([]string{}) 73 index.Add(c1.ID) 74 index.Add(c2.ID) 75 index.Add(c3.ID) 76 index.Add(c4.ID) 77 index.Add(c5.ID) 78 79 daemon := &Daemon{ 80 containers: store, 81 idIndex: index, 82 nameIndex: registrar.NewRegistrar(), 83 } 84 85 daemon.reserveName(c1.ID, c1.Name) 86 daemon.reserveName(c2.ID, c2.Name) 87 daemon.reserveName(c3.ID, c3.Name) 88 daemon.reserveName(c4.ID, c4.Name) 89 daemon.reserveName(c5.ID, c5.Name) 90 91 if container, _ := daemon.GetContainer("3cdbd1aa394fd68559fd1441d6eff2ab7c1e6363582c82febfaa8045df3bd8de"); container != c2 { 92 t.Fatal("Should explicitly match full container IDs") 93 } 94 95 if container, _ := daemon.GetContainer("75fb0b8009"); container != c4 { 96 t.Fatal("Should match a partial ID") 97 } 98 99 if container, _ := daemon.GetContainer("drunk_hawking"); container != c2 { 100 t.Fatal("Should match a full name") 101 } 102 103 // c3.Name is a partial match for both c3.ID and c2.ID 104 if c, _ := daemon.GetContainer("3cdbd1aa"); c != c3 { 105 t.Fatal("Should match a full name even though it collides with another container's ID") 106 } 107 108 if container, _ := daemon.GetContainer("d22d69a2b896"); container != c5 { 109 t.Fatal("Should match a container where the provided prefix is an exact match to the its name, and is also a prefix for its ID") 110 } 111 112 if _, err := daemon.GetContainer("3cdbd1"); err == nil { 113 t.Fatal("Should return an error when provided a prefix that partially matches multiple container ID's") 114 } 115 116 if _, err := daemon.GetContainer("nothing"); err == nil { 117 t.Fatal("Should return an error when provided a prefix that is neither a name or a partial match to an ID") 118 } 119 } 120 121 func initDaemonWithVolumeStore(tmp string) (*Daemon, error) { 122 var err error 123 daemon := &Daemon{ 124 repository: tmp, 125 root: tmp, 126 } 127 daemon.volumes, err = store.New(tmp) 128 if err != nil { 129 return nil, err 130 } 131 132 volumesDriver, err := local.New(tmp, 0, 0) 133 if err != nil { 134 return nil, err 135 } 136 volumedrivers.Register(volumesDriver, volumesDriver.Name()) 137 138 return daemon, nil 139 } 140 141 func TestValidContainerNames(t *testing.T) { 142 invalidNames := []string{"-rm", "&sdfsfd", "safd%sd"} 143 validNames := []string{"word-word", "word_word", "1weoid"} 144 145 for _, name := range invalidNames { 146 if validContainerNamePattern.MatchString(name) { 147 t.Fatalf("%q is not a valid container name and was returned as valid.", name) 148 } 149 } 150 151 for _, name := range validNames { 152 if !validContainerNamePattern.MatchString(name) { 153 t.Fatalf("%q is a valid container name and was returned as invalid.", name) 154 } 155 } 156 } 157 158 func TestContainerInitDNS(t *testing.T) { 159 tmp, err := ioutil.TempDir("", "docker-container-test-") 160 if err != nil { 161 t.Fatal(err) 162 } 163 defer os.RemoveAll(tmp) 164 165 containerID := "d59df5276e7b219d510fe70565e0404bc06350e0d4b43fe961f22f339980170e" 166 containerPath := filepath.Join(tmp, containerID) 167 if err := os.MkdirAll(containerPath, 0755); err != nil { 168 t.Fatal(err) 169 } 170 171 config := `{"State":{"Running":true,"Paused":false,"Restarting":false,"OOMKilled":false,"Dead":false,"Pid":2464,"ExitCode":0, 172 "Error":"","StartedAt":"2015-05-26T16:48:53.869308965Z","FinishedAt":"0001-01-01T00:00:00Z"}, 173 "ID":"d59df5276e7b219d510fe70565e0404bc06350e0d4b43fe961f22f339980170e","Created":"2015-05-26T16:48:53.7987917Z","Path":"top", 174 "Args":[],"Config":{"Hostname":"d59df5276e7b","Domainname":"","User":"","Memory":0,"MemorySwap":0,"CpuShares":0,"Cpuset":"", 175 "AttachStdin":false,"AttachStdout":false,"AttachStderr":false,"PortSpecs":null,"ExposedPorts":null,"Tty":true,"OpenStdin":true, 176 "StdinOnce":false,"Env":null,"Cmd":["top"],"Image":"ubuntu:latest","Volumes":null,"WorkingDir":"","Entrypoint":null, 177 "NetworkDisabled":false,"MacAddress":"","OnBuild":null,"Labels":{}},"Image":"07f8e8c5e66084bef8f848877857537ffe1c47edd01a93af27e7161672ad0e95", 178 "NetworkSettings":{"IPAddress":"172.17.0.1","IPPrefixLen":16,"MacAddress":"02:42:ac:11:00:01","LinkLocalIPv6Address":"fe80::42:acff:fe11:1", 179 "LinkLocalIPv6PrefixLen":64,"GlobalIPv6Address":"","GlobalIPv6PrefixLen":0,"Gateway":"172.17.42.1","IPv6Gateway":"","Bridge":"docker0","Ports":{}}, 180 "ResolvConfPath":"/var/lib/docker/containers/d59df5276e7b219d510fe70565e0404bc06350e0d4b43fe961f22f339980170e/resolv.conf", 181 "HostnamePath":"/var/lib/docker/containers/d59df5276e7b219d510fe70565e0404bc06350e0d4b43fe961f22f339980170e/hostname", 182 "HostsPath":"/var/lib/docker/containers/d59df5276e7b219d510fe70565e0404bc06350e0d4b43fe961f22f339980170e/hosts", 183 "LogPath":"/var/lib/docker/containers/d59df5276e7b219d510fe70565e0404bc06350e0d4b43fe961f22f339980170e/d59df5276e7b219d510fe70565e0404bc06350e0d4b43fe961f22f339980170e-json.log", 184 "Name":"/ubuntu","Driver":"aufs","MountLabel":"","ProcessLabel":"","AppArmorProfile":"","RestartCount":0, 185 "UpdateDns":false,"Volumes":{},"VolumesRW":{},"AppliedVolumesFrom":null}` 186 187 // Container struct only used to retrieve path to config file 188 container := &container.Container{CommonContainer: container.CommonContainer{Root: containerPath}} 189 configPath, err := container.ConfigPath() 190 if err != nil { 191 t.Fatal(err) 192 } 193 if err = ioutil.WriteFile(configPath, []byte(config), 0644); err != nil { 194 t.Fatal(err) 195 } 196 197 hostConfig := `{"Binds":[],"ContainerIDFile":"","Memory":0,"MemorySwap":0,"CpuShares":0,"CpusetCpus":"", 198 "Privileged":false,"PortBindings":{},"Links":null,"PublishAllPorts":false,"Dns":null,"DnsOptions":null,"DnsSearch":null,"ExtraHosts":null,"VolumesFrom":null, 199 "Devices":[],"NetworkMode":"bridge","IpcMode":"","PidMode":"","CapAdd":null,"CapDrop":null,"RestartPolicy":{"Name":"no","MaximumRetryCount":0}, 200 "SecurityOpt":null,"ReadonlyRootfs":false,"Ulimits":null,"LogConfig":{"Type":"","Config":null},"CgroupParent":""}` 201 202 hostConfigPath, err := container.HostConfigPath() 203 if err != nil { 204 t.Fatal(err) 205 } 206 if err = ioutil.WriteFile(hostConfigPath, []byte(hostConfig), 0644); err != nil { 207 t.Fatal(err) 208 } 209 210 daemon, err := initDaemonWithVolumeStore(tmp) 211 if err != nil { 212 t.Fatal(err) 213 } 214 defer volumedrivers.Unregister(volume.DefaultDriverName) 215 216 c, err := daemon.load(containerID) 217 if err != nil { 218 t.Fatal(err) 219 } 220 221 if c.HostConfig.DNS == nil { 222 t.Fatal("Expected container DNS to not be nil") 223 } 224 225 if c.HostConfig.DNSSearch == nil { 226 t.Fatal("Expected container DNSSearch to not be nil") 227 } 228 229 if c.HostConfig.DNSOptions == nil { 230 t.Fatal("Expected container DNSOptions to not be nil") 231 } 232 } 233 234 func newPortNoError(proto, port string) nat.Port { 235 p, _ := nat.NewPort(proto, port) 236 return p 237 } 238 239 func TestMerge(t *testing.T) { 240 volumesImage := make(map[string]struct{}) 241 volumesImage["/test1"] = struct{}{} 242 volumesImage["/test2"] = struct{}{} 243 portsImage := make(nat.PortSet) 244 portsImage[newPortNoError("tcp", "1111")] = struct{}{} 245 portsImage[newPortNoError("tcp", "2222")] = struct{}{} 246 configImage := &containertypes.Config{ 247 ExposedPorts: portsImage, 248 Env: []string{"VAR1=1", "VAR2=2"}, 249 Volumes: volumesImage, 250 } 251 252 portsUser := make(nat.PortSet) 253 portsUser[newPortNoError("tcp", "2222")] = struct{}{} 254 portsUser[newPortNoError("tcp", "3333")] = struct{}{} 255 volumesUser := make(map[string]struct{}) 256 volumesUser["/test3"] = struct{}{} 257 configUser := &containertypes.Config{ 258 ExposedPorts: portsUser, 259 Env: []string{"VAR2=3", "VAR3=3"}, 260 Volumes: volumesUser, 261 } 262 263 if err := merge(configUser, configImage); err != nil { 264 t.Error(err) 265 } 266 267 if len(configUser.ExposedPorts) != 3 { 268 t.Fatalf("Expected 3 ExposedPorts, 1111, 2222 and 3333, found %d", len(configUser.ExposedPorts)) 269 } 270 for portSpecs := range configUser.ExposedPorts { 271 if portSpecs.Port() != "1111" && portSpecs.Port() != "2222" && portSpecs.Port() != "3333" { 272 t.Fatalf("Expected 1111 or 2222 or 3333, found %s", portSpecs) 273 } 274 } 275 if len(configUser.Env) != 3 { 276 t.Fatalf("Expected 3 env var, VAR1=1, VAR2=3 and VAR3=3, found %d", len(configUser.Env)) 277 } 278 for _, env := range configUser.Env { 279 if env != "VAR1=1" && env != "VAR2=3" && env != "VAR3=3" { 280 t.Fatalf("Expected VAR1=1 or VAR2=3 or VAR3=3, found %s", env) 281 } 282 } 283 284 if len(configUser.Volumes) != 3 { 285 t.Fatalf("Expected 3 volumes, /test1, /test2 and /test3, found %d", len(configUser.Volumes)) 286 } 287 for v := range configUser.Volumes { 288 if v != "/test1" && v != "/test2" && v != "/test3" { 289 t.Fatalf("Expected /test1 or /test2 or /test3, found %s", v) 290 } 291 } 292 293 ports, _, err := nat.ParsePortSpecs([]string{"0000"}) 294 if err != nil { 295 t.Error(err) 296 } 297 configImage2 := &containertypes.Config{ 298 ExposedPorts: ports, 299 } 300 301 if err := merge(configUser, configImage2); err != nil { 302 t.Error(err) 303 } 304 305 if len(configUser.ExposedPorts) != 4 { 306 t.Fatalf("Expected 4 ExposedPorts, 0000, 1111, 2222 and 3333, found %d", len(configUser.ExposedPorts)) 307 } 308 for portSpecs := range configUser.ExposedPorts { 309 if portSpecs.Port() != "0" && portSpecs.Port() != "1111" && portSpecs.Port() != "2222" && portSpecs.Port() != "3333" { 310 t.Fatalf("Expected %q or %q or %q or %q, found %s", 0, 1111, 2222, 3333, portSpecs) 311 } 312 } 313 } 314 315 func TestDaemonReloadLabels(t *testing.T) { 316 daemon := &Daemon{} 317 daemon.configStore = &Config{ 318 CommonConfig: CommonConfig{ 319 Labels: []string{"foo:bar"}, 320 }, 321 } 322 323 valuesSets := make(map[string]interface{}) 324 valuesSets["labels"] = "foo:baz" 325 newConfig := &Config{ 326 CommonConfig: CommonConfig{ 327 Labels: []string{"foo:baz"}, 328 valuesSet: valuesSets, 329 }, 330 } 331 332 if err := daemon.Reload(newConfig); err != nil { 333 t.Fatal(err) 334 } 335 336 label := daemon.configStore.Labels[0] 337 if label != "foo:baz" { 338 t.Fatalf("Expected daemon label `foo:baz`, got %s", label) 339 } 340 } 341 342 func TestDaemonReloadInsecureRegistries(t *testing.T) { 343 daemon := &Daemon{} 344 // initialize daemon with existing insecure registries: "127.0.0.0/8", "10.10.1.11:5000", "10.10.1.22:5000" 345 daemon.RegistryService = registry.NewService(registry.ServiceOptions{ 346 InsecureRegistries: []string{ 347 "127.0.0.0/8", 348 "10.10.1.11:5000", 349 "10.10.1.22:5000", // this will be removed when reloading 350 "docker1.com", 351 "docker2.com", // this will be removed when reloading 352 }, 353 }) 354 355 daemon.configStore = &Config{} 356 357 insecureRegistries := []string{ 358 "127.0.0.0/8", // this will be kept 359 "10.10.1.11:5000", // this will be kept 360 "10.10.1.33:5000", // this will be newly added 361 "docker1.com", // this will be kept 362 "docker3.com", // this will be newly added 363 } 364 365 valuesSets := make(map[string]interface{}) 366 valuesSets["insecure-registries"] = insecureRegistries 367 368 newConfig := &Config{ 369 CommonConfig: CommonConfig{ 370 ServiceOptions: registry.ServiceOptions{ 371 InsecureRegistries: insecureRegistries, 372 }, 373 valuesSet: valuesSets, 374 }, 375 } 376 377 if err := daemon.Reload(newConfig); err != nil { 378 t.Fatal(err) 379 } 380 381 // After Reload, daemon.RegistryService will be changed which is useful 382 // for registry communication in daemon. 383 registries := daemon.RegistryService.ServiceConfig() 384 385 // After Reload(), newConfig has come to registries.InsecureRegistryCIDRs and registries.IndexConfigs in daemon. 386 // Then collect registries.InsecureRegistryCIDRs in dataMap. 387 // When collecting, we need to convert CIDRS into string as a key, 388 // while the times of key appears as value. 389 dataMap := map[string]int{} 390 for _, value := range registries.InsecureRegistryCIDRs { 391 if _, ok := dataMap[value.String()]; !ok { 392 dataMap[value.String()] = 1 393 } else { 394 dataMap[value.String()]++ 395 } 396 } 397 398 for _, value := range registries.IndexConfigs { 399 if _, ok := dataMap[value.Name]; !ok { 400 dataMap[value.Name] = 1 401 } else { 402 dataMap[value.Name]++ 403 } 404 } 405 406 // Finally compare dataMap with the original insecureRegistries. 407 // Each value in insecureRegistries should appear in daemon's insecure registries, 408 // and each can only appear exactly ONCE. 409 for _, r := range insecureRegistries { 410 if value, ok := dataMap[r]; !ok { 411 t.Fatalf("Expected daemon insecure registry %s, got none", r) 412 } else if value != 1 { 413 t.Fatalf("Expected only 1 daemon insecure registry %s, got %d", r, value) 414 } 415 } 416 417 // assert if "10.10.1.22:5000" is removed when reloading 418 if value, ok := dataMap["10.10.1.22:5000"]; ok { 419 t.Fatalf("Expected no insecure registry of 10.10.1.22:5000, got %d", value) 420 } 421 422 // assert if "docker2.com" is removed when reloading 423 if value, ok := dataMap["docker2.com"]; ok { 424 t.Fatalf("Expected no insecure registry of docker2.com, got %d", value) 425 } 426 } 427 428 func TestDaemonReloadNotAffectOthers(t *testing.T) { 429 daemon := &Daemon{} 430 daemon.configStore = &Config{ 431 CommonConfig: CommonConfig{ 432 Labels: []string{"foo:bar"}, 433 Debug: true, 434 }, 435 } 436 437 valuesSets := make(map[string]interface{}) 438 valuesSets["labels"] = "foo:baz" 439 newConfig := &Config{ 440 CommonConfig: CommonConfig{ 441 Labels: []string{"foo:baz"}, 442 valuesSet: valuesSets, 443 }, 444 } 445 446 if err := daemon.Reload(newConfig); err != nil { 447 t.Fatal(err) 448 } 449 450 label := daemon.configStore.Labels[0] 451 if label != "foo:baz" { 452 t.Fatalf("Expected daemon label `foo:baz`, got %s", label) 453 } 454 debug := daemon.configStore.Debug 455 if !debug { 456 t.Fatalf("Expected debug 'enabled', got 'disabled'") 457 } 458 } 459 460 func TestDaemonDiscoveryReload(t *testing.T) { 461 daemon := &Daemon{} 462 daemon.configStore = &Config{ 463 CommonConfig: CommonConfig{ 464 ClusterStore: "memory://127.0.0.1", 465 ClusterAdvertise: "127.0.0.1:3333", 466 }, 467 } 468 469 if err := daemon.initDiscovery(daemon.configStore); err != nil { 470 t.Fatal(err) 471 } 472 473 expected := discovery.Entries{ 474 &discovery.Entry{Host: "127.0.0.1", Port: "3333"}, 475 } 476 477 select { 478 case <-time.After(10 * time.Second): 479 t.Fatal("timeout waiting for discovery") 480 case <-daemon.discoveryWatcher.ReadyCh(): 481 } 482 483 stopCh := make(chan struct{}) 484 defer close(stopCh) 485 ch, errCh := daemon.discoveryWatcher.Watch(stopCh) 486 487 select { 488 case <-time.After(1 * time.Second): 489 t.Fatal("failed to get discovery advertisements in time") 490 case e := <-ch: 491 if !reflect.DeepEqual(e, expected) { 492 t.Fatalf("expected %v, got %v\n", expected, e) 493 } 494 case e := <-errCh: 495 t.Fatal(e) 496 } 497 498 valuesSets := make(map[string]interface{}) 499 valuesSets["cluster-store"] = "memory://127.0.0.1:2222" 500 valuesSets["cluster-advertise"] = "127.0.0.1:5555" 501 newConfig := &Config{ 502 CommonConfig: CommonConfig{ 503 ClusterStore: "memory://127.0.0.1:2222", 504 ClusterAdvertise: "127.0.0.1:5555", 505 valuesSet: valuesSets, 506 }, 507 } 508 509 expected = discovery.Entries{ 510 &discovery.Entry{Host: "127.0.0.1", Port: "5555"}, 511 } 512 513 if err := daemon.Reload(newConfig); err != nil { 514 t.Fatal(err) 515 } 516 517 select { 518 case <-time.After(10 * time.Second): 519 t.Fatal("timeout waiting for discovery") 520 case <-daemon.discoveryWatcher.ReadyCh(): 521 } 522 523 ch, errCh = daemon.discoveryWatcher.Watch(stopCh) 524 525 select { 526 case <-time.After(1 * time.Second): 527 t.Fatal("failed to get discovery advertisements in time") 528 case e := <-ch: 529 if !reflect.DeepEqual(e, expected) { 530 t.Fatalf("expected %v, got %v\n", expected, e) 531 } 532 case e := <-errCh: 533 t.Fatal(e) 534 } 535 } 536 537 func TestDaemonDiscoveryReloadFromEmptyDiscovery(t *testing.T) { 538 daemon := &Daemon{} 539 daemon.configStore = &Config{} 540 541 valuesSet := make(map[string]interface{}) 542 valuesSet["cluster-store"] = "memory://127.0.0.1:2222" 543 valuesSet["cluster-advertise"] = "127.0.0.1:5555" 544 newConfig := &Config{ 545 CommonConfig: CommonConfig{ 546 ClusterStore: "memory://127.0.0.1:2222", 547 ClusterAdvertise: "127.0.0.1:5555", 548 valuesSet: valuesSet, 549 }, 550 } 551 552 expected := discovery.Entries{ 553 &discovery.Entry{Host: "127.0.0.1", Port: "5555"}, 554 } 555 556 if err := daemon.Reload(newConfig); err != nil { 557 t.Fatal(err) 558 } 559 560 select { 561 case <-time.After(10 * time.Second): 562 t.Fatal("timeout waiting for discovery") 563 case <-daemon.discoveryWatcher.ReadyCh(): 564 } 565 566 stopCh := make(chan struct{}) 567 defer close(stopCh) 568 ch, errCh := daemon.discoveryWatcher.Watch(stopCh) 569 570 select { 571 case <-time.After(1 * time.Second): 572 t.Fatal("failed to get discovery advertisements in time") 573 case e := <-ch: 574 if !reflect.DeepEqual(e, expected) { 575 t.Fatalf("expected %v, got %v\n", expected, e) 576 } 577 case e := <-errCh: 578 t.Fatal(e) 579 } 580 } 581 582 func TestDaemonDiscoveryReloadOnlyClusterAdvertise(t *testing.T) { 583 daemon := &Daemon{} 584 daemon.configStore = &Config{ 585 CommonConfig: CommonConfig{ 586 ClusterStore: "memory://127.0.0.1", 587 }, 588 } 589 valuesSets := make(map[string]interface{}) 590 valuesSets["cluster-advertise"] = "127.0.0.1:5555" 591 newConfig := &Config{ 592 CommonConfig: CommonConfig{ 593 ClusterAdvertise: "127.0.0.1:5555", 594 valuesSet: valuesSets, 595 }, 596 } 597 expected := discovery.Entries{ 598 &discovery.Entry{Host: "127.0.0.1", Port: "5555"}, 599 } 600 601 if err := daemon.Reload(newConfig); err != nil { 602 t.Fatal(err) 603 } 604 605 select { 606 case <-daemon.discoveryWatcher.ReadyCh(): 607 case <-time.After(10 * time.Second): 608 t.Fatal("Timeout waiting for discovery") 609 } 610 stopCh := make(chan struct{}) 611 defer close(stopCh) 612 ch, errCh := daemon.discoveryWatcher.Watch(stopCh) 613 614 select { 615 case <-time.After(1 * time.Second): 616 t.Fatal("failed to get discovery advertisements in time") 617 case e := <-ch: 618 if !reflect.DeepEqual(e, expected) { 619 t.Fatalf("expected %v, got %v\n", expected, e) 620 } 621 case e := <-errCh: 622 t.Fatal(e) 623 } 624 625 }