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  }