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