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