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  }