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  }