launchpad.net/~rogpeppe/juju-core/500-errgo-fix@v0.0.0-20140213181702-000000002356/worker/firewaller/firewaller.go (about) 1 // Copyright 2012, 2013 Canonical Ltd. 2 // Licensed under the AGPLv3, see LICENCE file for details. 3 4 package firewaller 5 6 import ( 7 "github.com/loggo/loggo" 8 "launchpad.net/errgo/errors" 9 "launchpad.net/tomb" 10 11 "launchpad.net/juju-core/environs" 12 "launchpad.net/juju-core/environs/config" 13 "launchpad.net/juju-core/instance" 14 "launchpad.net/juju-core/names" 15 apifirewaller "launchpad.net/juju-core/state/api/firewaller" 16 "launchpad.net/juju-core/state/api/params" 17 apiwatcher "launchpad.net/juju-core/state/api/watcher" 18 "launchpad.net/juju-core/state/watcher" 19 "launchpad.net/juju-core/worker" 20 ) 21 22 var logger = loggo.GetLogger("juju.worker.firewaller") 23 24 var mask = errors.Mask 25 26 // Firewaller watches the state for ports opened or closed 27 // and reflects those changes onto the backing environment. 28 type Firewaller struct { 29 tomb tomb.Tomb 30 st *apifirewaller.State 31 environ environs.Environ 32 environWatcher apiwatcher.NotifyWatcher 33 machinesWatcher apiwatcher.StringsWatcher 34 machineds map[string]*machineData 35 unitsChange chan *unitsChange 36 unitds map[string]*unitData 37 portsChange chan *portsChange 38 serviceds map[string]*serviceData 39 exposedChange chan *exposedChange 40 globalMode bool 41 globalPortRef map[instance.Port]int 42 } 43 44 // NewFirewaller returns a new Firewaller. 45 func NewFirewaller(st *apifirewaller.State) (*Firewaller, error) { 46 environWatcher, err := st.WatchForEnvironConfigChanges() 47 if err != nil { 48 return nil, mask(err) 49 } 50 machinesWatcher, err := st.WatchEnvironMachines() 51 if err != nil { 52 return nil, mask(err) 53 } 54 fw := &Firewaller{ 55 st: st, 56 environWatcher: environWatcher, 57 machinesWatcher: machinesWatcher, 58 machineds: make(map[string]*machineData), 59 unitsChange: make(chan *unitsChange), 60 unitds: make(map[string]*unitData), 61 portsChange: make(chan *portsChange), 62 serviceds: make(map[string]*serviceData), 63 exposedChange: make(chan *exposedChange), 64 } 65 go func() { 66 defer fw.tomb.Done() 67 fw.tomb.Kill(fw.loop()) 68 }() 69 return fw, nil 70 } 71 72 func (fw *Firewaller) loop() error { 73 defer fw.stopWatchers() 74 75 var err error 76 var reconciled bool 77 78 fw.environ, err = worker.WaitForEnviron(fw.environWatcher, fw.st, fw.tomb.Dying()) 79 if err != nil { 80 return mask(err, errors.Is(tomb.ErrDying)) 81 } 82 if fw.environ.Config().FirewallMode() == config.FwGlobal { 83 fw.globalMode = true 84 fw.globalPortRef = make(map[instance.Port]int) 85 } 86 for { 87 select { 88 case <-fw.tomb.Dying(): 89 return tomb.ErrDying 90 case _, ok := <-fw.environWatcher.Changes(): 91 if !ok { 92 return watcher.MustErr(fw.environWatcher) 93 } 94 config, err := fw.st.EnvironConfig() 95 if err != nil { 96 return mask(err) 97 } 98 if err := fw.environ.SetConfig(config); err != nil { 99 logger.Errorf("loaded invalid environment configuration: %v", err) 100 } 101 case change, ok := <-fw.machinesWatcher.Changes(): 102 if !ok { 103 return watcher.MustErr(fw.machinesWatcher) 104 } 105 for _, machineId := range change { 106 fw.machineLifeChanged(names.MachineTag(machineId)) 107 } 108 if !reconciled { 109 reconciled = true 110 var err error 111 if fw.globalMode { 112 err = fw.reconcileGlobal() 113 } else { 114 err = fw.reconcileInstances() 115 } 116 if err != nil { 117 return mask(err) 118 } 119 } 120 case change := <-fw.unitsChange: 121 if err := fw.unitsChanged(change); err != nil { 122 return mask(err) 123 } 124 case change := <-fw.portsChange: 125 change.unitd.ports = change.ports 126 if err := fw.flushUnits([]*unitData{change.unitd}); err != nil { 127 return errors.NoteMask(err, "cannot change firewall ports") 128 } 129 case change := <-fw.exposedChange: 130 change.serviced.exposed = change.exposed 131 unitds := []*unitData{} 132 for _, unitd := range change.serviced.unitds { 133 unitds = append(unitds, unitd) 134 } 135 if err := fw.flushUnits(unitds); err != nil { 136 return errors.NoteMask(err, "cannot change firewall ports") 137 } 138 } 139 } 140 } 141 142 func killTomb(t *tomb.Tomb, err error) { 143 if errors.Cause(err) == tomb.ErrDying { 144 return 145 } 146 t.Kill(err) 147 } 148 149 // stop a watcher with logging of a possible error. 150 func stop(what string, stopper watcher.Stopper) { 151 if err := stopper.Stop(); err != nil { 152 logger.Errorf("error stopping %s: %v", what, err) 153 } 154 } 155 156 // startMachine creates a new data value for tracking details of the 157 // machine and starts watching the machine for units added or removed. 158 func (fw *Firewaller) startMachine(tag string) error { 159 machined := &machineData{ 160 fw: fw, 161 tag: tag, 162 unitds: make(map[string]*unitData), 163 ports: make([]instance.Port, 0), 164 } 165 m, err := machined.machine() 166 if params.IsCodeNotFound(err) { 167 return nil 168 } else if err != nil { 169 return errors.NoteMask(err, "cannot watch machine units") 170 } 171 unitw, err := m.WatchUnits() 172 if err != nil { 173 return mask(err) 174 } 175 select { 176 case <-fw.tomb.Dying(): 177 stop("units watcher", unitw) 178 return tomb.ErrDying 179 case change, ok := <-unitw.Changes(): 180 if !ok { 181 stop("units watcher", unitw) 182 return watcher.MustErr(unitw) 183 } 184 fw.machineds[tag] = machined 185 err = fw.unitsChanged(&unitsChange{machined, change}) 186 if err != nil { 187 stop("units watcher", unitw) 188 delete(fw.machineds, tag) 189 return errors.Notef(err, "cannot respond to units changes for %q", tag) 190 } 191 } 192 go machined.watchLoop(unitw) 193 return nil 194 } 195 196 // startUnit creates a new data value for tracking details of the unit 197 // and starts watching the unit for port changes. The provided 198 // machineTag must be the tag for the machine the unit was last 199 // observed to be assigned to. 200 func (fw *Firewaller) startUnit(unit *apifirewaller.Unit, machineTag string) error { 201 service, err := unit.Service() 202 if err != nil { 203 return mask(err) 204 } 205 serviceName := service.Name() 206 unitName := unit.Name() 207 openedPorts, err := unit.OpenedPorts() 208 if err != nil { 209 return mask(err) 210 } 211 unitd := &unitData{ 212 fw: fw, 213 unit: unit, 214 ports: openedPorts, 215 } 216 fw.unitds[unitName] = unitd 217 218 unitd.machined = fw.machineds[machineTag] 219 unitd.machined.unitds[unitName] = unitd 220 if fw.serviceds[serviceName] == nil { 221 err := fw.startService(service) 222 if err != nil { 223 delete(fw.unitds, unitName) 224 return err 225 } 226 } 227 unitd.serviced = fw.serviceds[serviceName] 228 unitd.serviced.unitds[unitName] = unitd 229 230 ports := make([]instance.Port, len(unitd.ports)) 231 copy(ports, unitd.ports) 232 233 go unitd.watchLoop(ports) 234 return nil 235 } 236 237 // startService creates a new data value for tracking details of the 238 // service and starts watching the service for exposure changes. 239 func (fw *Firewaller) startService(service *apifirewaller.Service) error { 240 exposed, err := service.IsExposed() 241 if err != nil { 242 return mask(err) 243 } 244 serviced := &serviceData{ 245 fw: fw, 246 service: service, 247 exposed: exposed, 248 unitds: make(map[string]*unitData), 249 } 250 fw.serviceds[service.Name()] = serviced 251 go serviced.watchLoop(serviced.exposed) 252 return nil 253 } 254 255 // reconcileGlobal compares the initially started watcher for machines, 256 // units and services with the opened and closed ports globally and 257 // opens and closes the appropriate ports for the whole environment. 258 func (fw *Firewaller) reconcileGlobal() error { 259 initialPorts, err := fw.environ.Ports() 260 if err != nil { 261 return mask(err) 262 } 263 collector := make(map[instance.Port]bool) 264 for _, unitd := range fw.unitds { 265 if unitd.serviced.exposed { 266 for _, port := range unitd.ports { 267 collector[port] = true 268 } 269 } 270 } 271 wantedPorts := []instance.Port{} 272 for port := range collector { 273 wantedPorts = append(wantedPorts, port) 274 } 275 // Check which ports to open or to close. 276 toOpen := Diff(wantedPorts, initialPorts) 277 toClose := Diff(initialPorts, wantedPorts) 278 if len(toOpen) > 0 { 279 logger.Infof("opening global ports %v", toOpen) 280 if err := fw.environ.OpenPorts(toOpen); err != nil { 281 return mask(err) 282 } 283 instance.SortPorts(toOpen) 284 } 285 if len(toClose) > 0 { 286 logger.Infof("closing global ports %v", toClose) 287 if err := fw.environ.ClosePorts(toClose); err != nil { 288 return mask(err) 289 } 290 instance.SortPorts(toClose) 291 } 292 return nil 293 } 294 295 // reconcileInstances compares the initially started watcher for machines, 296 // units and services with the opened and closed ports of the instances and 297 // opens and closes the appropriate ports for each instance. 298 func (fw *Firewaller) reconcileInstances() error { 299 for _, machined := range fw.machineds { 300 m, err := machined.machine() 301 if params.IsCodeNotFound(err) { 302 if err := fw.forgetMachine(machined); err != nil { 303 return mask(err) 304 } 305 continue 306 } else if err != nil { 307 return mask(err) 308 } 309 instanceId, err := m.InstanceId() 310 if err != nil { 311 return mask(err) 312 } 313 instances, err := fw.environ.Instances([]instance.Id{instanceId}) 314 if errors.Cause(err) == environs.ErrNoInstances { 315 return nil 316 } else if err != nil { 317 return mask(err) 318 } 319 _, machineId, err := names.ParseTag(machined.tag, names.MachineTagKind) 320 if err != nil { 321 return mask(err) 322 } 323 initialPorts, err := instances[0].Ports(machineId) 324 if err != nil { 325 return mask(err) 326 } 327 328 // Check which ports to open or to close. 329 toOpen := Diff(machined.ports, initialPorts) 330 toClose := Diff(initialPorts, machined.ports) 331 if len(toOpen) > 0 { 332 logger.Infof("opening instance ports %v for %q", 333 toOpen, machined.tag) 334 if err := instances[0].OpenPorts(machineId, toOpen); err != nil { 335 // TODO(mue) Add local retry logic. 336 return mask(err) 337 } 338 instance.SortPorts(toOpen) 339 } 340 if len(toClose) > 0 { 341 logger.Infof("closing instance ports %v for %q", 342 toClose, machined.tag) 343 if err := instances[0].ClosePorts(machineId, toClose); err != nil { 344 // TODO(mue) Add local retry logic. 345 return mask(err) 346 } 347 instance.SortPorts(toClose) 348 } 349 } 350 return nil 351 } 352 353 // unitsChanged responds to changes to the assigned units. 354 func (fw *Firewaller) unitsChanged(change *unitsChange) error { 355 changed := []*unitData{} 356 for _, name := range change.units { 357 unit, err := fw.st.Unit(names.UnitTag(name)) 358 if err != nil && !params.IsCodeNotFound(err) { 359 return err 360 } 361 var machineTag string 362 if unit != nil { 363 machineTag, err = unit.AssignedMachine() 364 if params.IsCodeNotFound(err) { 365 continue 366 } else if err != nil && !params.IsCodeNotAssigned(err) { 367 return err 368 } 369 } 370 if unitd, known := fw.unitds[name]; known { 371 knownMachineTag := fw.unitds[name].machined.tag 372 if unit == nil || unit.Life() == params.Dead || machineTag != knownMachineTag { 373 fw.forgetUnit(unitd) 374 changed = append(changed, unitd) 375 logger.Debugf("stopped watching unit %s", name) 376 } 377 } else if unit != nil && unit.Life() != params.Dead && fw.machineds[machineTag] != nil { 378 err = fw.startUnit(unit, machineTag) 379 if err != nil { 380 return mask(err) 381 } 382 changed = append(changed, fw.unitds[name]) 383 logger.Debugf("started watching unit %s", name) 384 } 385 } 386 if err := fw.flushUnits(changed); err != nil { 387 return errors.NoteMask(err, "cannot change firewall ports") 388 } 389 return nil 390 } 391 392 // flushUnits opens and closes ports for the passed unit data. 393 func (fw *Firewaller) flushUnits(unitds []*unitData) error { 394 machineds := map[string]*machineData{} 395 for _, unitd := range unitds { 396 machineds[unitd.machined.tag] = unitd.machined 397 } 398 for _, machined := range machineds { 399 if err := fw.flushMachine(machined); err != nil { 400 return mask(err) 401 } 402 } 403 return nil 404 } 405 406 // flushMachine opens and closes ports for the passed machine. 407 func (fw *Firewaller) flushMachine(machined *machineData) error { 408 // Gather ports to open and close. 409 ports := map[instance.Port]bool{} 410 for _, unitd := range machined.unitds { 411 if unitd.serviced.exposed { 412 for _, port := range unitd.ports { 413 ports[port] = true 414 } 415 } 416 } 417 want := []instance.Port{} 418 for port := range ports { 419 want = append(want, port) 420 } 421 toOpen := Diff(want, machined.ports) 422 toClose := Diff(machined.ports, want) 423 machined.ports = want 424 if fw.globalMode { 425 return fw.flushGlobalPorts(toOpen, toClose) 426 } 427 return fw.flushInstancePorts(machined, toOpen, toClose) 428 } 429 430 // flushGlobalPorts opens and closes global ports in the environment. 431 // It keeps a reference count for ports so that only 0-to-1 and 1-to-0 events 432 // modify the environment. 433 func (fw *Firewaller) flushGlobalPorts(rawOpen, rawClose []instance.Port) error { 434 // Filter which ports are really to open or close. 435 var toOpen, toClose []instance.Port 436 for _, port := range rawOpen { 437 if fw.globalPortRef[port] == 0 { 438 toOpen = append(toOpen, port) 439 } 440 fw.globalPortRef[port]++ 441 } 442 for _, port := range rawClose { 443 fw.globalPortRef[port]-- 444 if fw.globalPortRef[port] == 0 { 445 toClose = append(toClose, port) 446 delete(fw.globalPortRef, port) 447 } 448 } 449 // Open and close the ports. 450 if len(toOpen) > 0 { 451 if err := fw.environ.OpenPorts(toOpen); err != nil { 452 // TODO(mue) Add local retry logic. 453 return mask(err) 454 } 455 instance.SortPorts(toOpen) 456 logger.Infof("opened ports %v in environment", toOpen) 457 } 458 if len(toClose) > 0 { 459 if err := fw.environ.ClosePorts(toClose); err != nil { 460 // TODO(mue) Add local retry logic. 461 return mask(err) 462 } 463 instance.SortPorts(toClose) 464 logger.Infof("closed ports %v in environment", toClose) 465 } 466 return nil 467 } 468 469 // flushGlobalPorts opens and closes ports global on the machine. 470 func (fw *Firewaller) flushInstancePorts(machined *machineData, toOpen, toClose []instance.Port) error { 471 // If there's nothing to do, do nothing. 472 // This is important because when a machine is first created, 473 // it will have no instance id but also no open ports - 474 // InstanceId will fail but we don't care. 475 if len(toOpen) == 0 && len(toClose) == 0 { 476 return nil 477 } 478 m, err := machined.machine() 479 if params.IsCodeNotFound(err) { 480 return nil 481 } 482 if err != nil { 483 return mask(err) 484 } 485 _, machineId, err := names.ParseTag(machined.tag, names.MachineTagKind) 486 if err != nil { 487 return mask(err) 488 } 489 instanceId, err := m.InstanceId() 490 if err != nil { 491 return mask(err) 492 } 493 instances, err := fw.environ.Instances([]instance.Id{instanceId}) 494 if err != nil { 495 return mask(err) 496 } 497 498 // Open and close the ports. 499 if len(toOpen) > 0 { 500 if err := instances[0].OpenPorts(machineId, toOpen); err != nil { 501 // TODO(mue) Add local retry logic. 502 return mask(err) 503 } 504 instance.SortPorts(toOpen) 505 logger.Infof("opened ports %v on %q", toOpen, machined.tag) 506 } 507 if len(toClose) > 0 { 508 if err := instances[0].ClosePorts(machineId, toClose); err != nil { 509 // TODO(mue) Add local retry logic. 510 return mask(err) 511 } 512 instance.SortPorts(toClose) 513 logger.Infof("closed ports %v on %q", toClose, machined.tag) 514 } 515 return nil 516 } 517 518 // machineLifeChanged starts watching new machines when the firewaller 519 // is starting, or when new machines come to life, and stops watching 520 // machines that are dying. 521 func (fw *Firewaller) machineLifeChanged(tag string) error { 522 m, err := fw.st.Machine(tag) 523 found := !params.IsCodeNotFound(err) 524 if found && err != nil { 525 return err 526 } 527 dead := !found || m.Life() == params.Dead 528 machined, known := fw.machineds[tag] 529 if known && dead { 530 return fw.forgetMachine(machined) 531 } 532 if !known && !dead { 533 err = fw.startMachine(tag) 534 if err != nil { 535 return mask(err) 536 } 537 logger.Debugf("started watching %q", tag) 538 } 539 return nil 540 } 541 542 // forgetMachine cleans the machine data after the machine is removed. 543 func (fw *Firewaller) forgetMachine(machined *machineData) error { 544 for _, unitd := range machined.unitds { 545 fw.forgetUnit(unitd) 546 } 547 if err := fw.flushMachine(machined); err != nil { 548 return mask(err) 549 } 550 delete(fw.machineds, machined.tag) 551 if err := machined.Stop(); err != nil { 552 return mask(err) 553 } 554 logger.Debugf("stopped watching %q", machined.tag) 555 return nil 556 } 557 558 // forgetUnit cleans the unit data after the unit is removed. 559 func (fw *Firewaller) forgetUnit(unitd *unitData) { 560 name := unitd.unit.Name() 561 serviced := unitd.serviced 562 machined := unitd.machined 563 if err := unitd.Stop(); err != nil { 564 logger.Errorf("unit watcher %q returned error when stopping: %v", name, err) 565 } 566 // Clean up after stopping. 567 delete(fw.unitds, name) 568 delete(machined.unitds, name) 569 delete(serviced.unitds, name) 570 if len(serviced.unitds) == 0 { 571 // Stop service data after all units are removed. 572 if err := serviced.Stop(); err != nil { 573 logger.Errorf("service watcher %q returned error when stopping: %v", serviced.service.Name(), err) 574 } 575 delete(fw.serviceds, serviced.service.Name()) 576 } 577 } 578 579 // stopWatchers stops all the firewaller's watchers. 580 func (fw *Firewaller) stopWatchers() { 581 watcher.Stop(fw.environWatcher, &fw.tomb) 582 watcher.Stop(fw.machinesWatcher, &fw.tomb) 583 for _, unitd := range fw.unitds { 584 watcher.Stop(unitd, &fw.tomb) 585 } 586 for _, serviced := range fw.serviceds { 587 watcher.Stop(serviced, &fw.tomb) 588 } 589 for _, machined := range fw.machineds { 590 watcher.Stop(machined, &fw.tomb) 591 } 592 } 593 594 // Err returns the reason why the firewaller has stopped or tomb.ErrStillAlive 595 // when it is still alive. 596 func (fw *Firewaller) Err() (reason error) { 597 return fw.tomb.Err() 598 } 599 600 // Kill implements worker.Worker.Kill. 601 func (fw *Firewaller) Kill() { 602 fw.tomb.Kill(nil) 603 } 604 605 // Wait implements worker.Worker.Wait. 606 func (fw *Firewaller) Wait() error { 607 return fw.tomb.Wait() 608 } 609 610 // Stop stops the Firewaller and returns any error encountered while stopping. 611 func (fw *Firewaller) Stop() error { 612 fw.tomb.Kill(nil) 613 return fw.tomb.Wait() 614 } 615 616 // unitsChange contains the changed units for one specific machine. 617 type unitsChange struct { 618 machined *machineData 619 units []string 620 } 621 622 // machineData holds machine details and watches units added or removed. 623 type machineData struct { 624 tomb tomb.Tomb 625 fw *Firewaller 626 tag string 627 unitds map[string]*unitData 628 ports []instance.Port 629 } 630 631 func (md *machineData) machine() (*apifirewaller.Machine, error) { 632 return md.fw.st.Machine(md.tag) 633 } 634 635 // watchLoop watches the machine for units added or removed. 636 func (md *machineData) watchLoop(unitw apiwatcher.StringsWatcher) { 637 defer md.tomb.Done() 638 defer watcher.Stop(unitw, &md.tomb) 639 for { 640 select { 641 case <-md.tomb.Dying(): 642 return 643 case change, ok := <-unitw.Changes(): 644 if !ok { 645 _, err := md.machine() 646 if !params.IsCodeNotFound(err) { 647 killTomb(&md.fw.tomb, watcher.MustErr(unitw)) 648 } 649 return 650 } 651 select { 652 case md.fw.unitsChange <- &unitsChange{md, change}: 653 case <-md.tomb.Dying(): 654 return 655 } 656 } 657 } 658 } 659 660 // stopWatch stops the machine watching. 661 func (md *machineData) Stop() error { 662 md.tomb.Kill(nil) 663 return md.tomb.Wait() 664 } 665 666 // portsChange contains the changed ports for one specific unit. 667 type portsChange struct { 668 unitd *unitData 669 ports []instance.Port 670 } 671 672 // unitData holds unit details and watches port changes. 673 type unitData struct { 674 tomb tomb.Tomb 675 fw *Firewaller 676 unit *apifirewaller.Unit 677 serviced *serviceData 678 machined *machineData 679 ports []instance.Port 680 } 681 682 // watchLoop watches the unit for port changes. 683 func (ud *unitData) watchLoop(latestPorts []instance.Port) { 684 defer ud.tomb.Done() 685 w, err := ud.unit.Watch() 686 if err != nil { 687 ud.fw.tomb.Kill(err) 688 return 689 } 690 defer watcher.Stop(w, &ud.tomb) 691 for { 692 select { 693 case <-ud.tomb.Dying(): 694 return 695 case _, ok := <-w.Changes(): 696 if !ok { 697 ud.fw.tomb.Kill(watcher.MustErr(w)) 698 return 699 } 700 if err := ud.unit.Refresh(); err != nil { 701 if !params.IsCodeNotFound(err) { 702 ud.fw.tomb.Kill(err) 703 } 704 return 705 } 706 change, err := ud.unit.OpenedPorts() 707 if err != nil { 708 ud.fw.tomb.Kill(err) 709 return 710 } 711 if samePorts(change, latestPorts) { 712 continue 713 } 714 latestPorts = append(latestPorts[:0], change...) 715 select { 716 case ud.fw.portsChange <- &portsChange{ud, change}: 717 case <-ud.tomb.Dying(): 718 return 719 } 720 } 721 } 722 } 723 724 // samePorts returns whether old and new contain the same set of ports. 725 // Both old and new must be sorted. 726 func samePorts(old, new []instance.Port) bool { 727 if len(old) != len(new) { 728 return false 729 } 730 for i, p := range old { 731 if new[i] != p { 732 return false 733 } 734 } 735 return true 736 } 737 738 // Stop stops the unit watching. 739 func (ud *unitData) Stop() error { 740 ud.tomb.Kill(nil) 741 return ud.tomb.Wait() 742 } 743 744 // exposedChange contains the changed exposed flag for one specific service. 745 type exposedChange struct { 746 serviced *serviceData 747 exposed bool 748 } 749 750 // serviceData holds service details and watches exposure changes. 751 type serviceData struct { 752 tomb tomb.Tomb 753 fw *Firewaller 754 service *apifirewaller.Service 755 exposed bool 756 unitds map[string]*unitData 757 } 758 759 // watchLoop watches the service's exposed flag for changes. 760 func (sd *serviceData) watchLoop(exposed bool) { 761 defer sd.tomb.Done() 762 w, err := sd.service.Watch() 763 if err != nil { 764 sd.fw.tomb.Kill(err) 765 return 766 } 767 defer watcher.Stop(w, &sd.tomb) 768 for { 769 select { 770 case <-sd.tomb.Dying(): 771 return 772 case _, ok := <-w.Changes(): 773 if !ok { 774 sd.fw.tomb.Kill(watcher.MustErr(w)) 775 return 776 } 777 if err := sd.service.Refresh(); err != nil { 778 if !params.IsCodeNotFound(err) { 779 sd.fw.tomb.Kill(err) 780 } 781 return 782 } 783 change, err := sd.service.IsExposed() 784 if err != nil { 785 sd.fw.tomb.Kill(err) 786 return 787 } 788 if change == exposed { 789 continue 790 } 791 exposed = change 792 select { 793 case sd.fw.exposedChange <- &exposedChange{sd, change}: 794 case <-sd.tomb.Dying(): 795 return 796 } 797 } 798 } 799 } 800 801 // Stop stops the service watching. 802 func (sd *serviceData) Stop() error { 803 sd.tomb.Kill(nil) 804 return sd.tomb.Wait() 805 } 806 807 // Diff returns all the ports that exist in A but not B. 808 func Diff(A, B []instance.Port) (missing []instance.Port) { 809 next: 810 for _, a := range A { 811 for _, b := range B { 812 if a == b { 813 continue next 814 } 815 } 816 missing = append(missing, a) 817 } 818 return 819 }