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