github.com/cloud-green/juju@v0.0.0-20151002100041-a00291338d3d/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 errors.IsNotProvisioned(err) {
   347  			logger.Warningf("Machine not yet provisioned: %v", err)
   348  			continue
   349  		}
   350  		if err != nil {
   351  			return err
   352  		}
   353  		instances, err := fw.environ.Instances([]instance.Id{instanceId})
   354  		if err == environs.ErrNoInstances {
   355  			return nil
   356  		} else if err != nil {
   357  			return err
   358  		}
   359  		machineId := machined.tag.Id()
   360  		initialPortRanges, err := instances[0].Ports(machineId)
   361  		if err != nil {
   362  			return err
   363  		}
   364  
   365  		// Check which ports to open or to close.
   366  		toOpen := diffRanges(machined.openedPorts, initialPortRanges)
   367  		toClose := diffRanges(initialPortRanges, machined.openedPorts)
   368  		if len(toOpen) > 0 {
   369  			logger.Infof("opening instance port ranges %v for %q",
   370  				toOpen, machined.tag)
   371  			if err := instances[0].OpenPorts(machineId, toOpen); err != nil {
   372  				// TODO(mue) Add local retry logic.
   373  				return err
   374  			}
   375  			network.SortPortRanges(toOpen)
   376  		}
   377  		if len(toClose) > 0 {
   378  			logger.Infof("closing instance port ranges %v for %q",
   379  				toClose, machined.tag)
   380  			if err := instances[0].ClosePorts(machineId, toClose); err != nil {
   381  				// TODO(mue) Add local retry logic.
   382  				return err
   383  			}
   384  			network.SortPortRanges(toClose)
   385  		}
   386  	}
   387  	return nil
   388  }
   389  
   390  // unitsChanged responds to changes to the assigned units.
   391  func (fw *Firewaller) unitsChanged(change *unitsChange) error {
   392  	changed := []*unitData{}
   393  	for _, name := range change.units {
   394  		unitTag := names.NewUnitTag(name)
   395  		unit, err := fw.st.Unit(unitTag)
   396  		if err != nil && !params.IsCodeNotFound(err) {
   397  			return err
   398  		}
   399  		var machineTag names.MachineTag
   400  		if unit != nil {
   401  			machineTag, err = unit.AssignedMachine()
   402  			if params.IsCodeNotFound(err) {
   403  				continue
   404  			} else if err != nil && !params.IsCodeNotAssigned(err) {
   405  				return err
   406  			}
   407  		}
   408  		if unitd, known := fw.unitds[unitTag]; known {
   409  			knownMachineTag := fw.unitds[unitTag].machined.tag
   410  			if unit == nil || unit.Life() == params.Dead || machineTag != knownMachineTag {
   411  				fw.forgetUnit(unitd)
   412  				changed = append(changed, unitd)
   413  				logger.Debugf("stopped watching unit %s", name)
   414  			}
   415  			// TODO(dfc) fw.machineds should be map[names.Tag]
   416  		} else if unit != nil && unit.Life() != params.Dead && fw.machineds[machineTag] != nil {
   417  			err = fw.startUnit(unit, machineTag)
   418  			if err != nil {
   419  				return err
   420  			}
   421  			changed = append(changed, fw.unitds[unitTag])
   422  			logger.Debugf("started watching %q", unitTag)
   423  		}
   424  	}
   425  	if err := fw.flushUnits(changed); err != nil {
   426  		return errors.Annotate(err, "cannot change firewall ports")
   427  	}
   428  	return nil
   429  }
   430  
   431  // openedPortsChanged handles port change notifications
   432  func (fw *Firewaller) openedPortsChanged(machineTag names.MachineTag, networkTag names.NetworkTag) error {
   433  
   434  	machined, ok := fw.machineds[machineTag]
   435  	if !ok {
   436  		// It is common to receive a port change notification before
   437  		// registering the machine, so if a machine is not found in
   438  		// firewaller's list, just skip the change.
   439  		logger.Errorf("failed to lookup %q, skipping port change", machineTag)
   440  		return nil
   441  	}
   442  
   443  	m, err := machined.machine()
   444  	if err != nil {
   445  		return err
   446  	}
   447  
   448  	ports, err := m.OpenedPorts(networkTag)
   449  	if err != nil {
   450  		return err
   451  	}
   452  
   453  	newPortRanges := make(map[network.PortRange]names.UnitTag)
   454  	for portRange, unitTag := range ports {
   455  		unitd, ok := machined.unitds[unitTag]
   456  		if !ok {
   457  			// It is common to receive port change notification before
   458  			// registering a unit. Skip handling the port change - it will
   459  			// be handled when the unit is registered.
   460  			logger.Errorf("failed to lookup %q, skipping port change", unitTag)
   461  			return nil
   462  		}
   463  		newPortRanges[portRange] = unitd.tag
   464  	}
   465  
   466  	if !portMapsEqual(machined.definedPorts, newPortRanges) {
   467  		machined.definedPorts = newPortRanges
   468  		return fw.flushMachine(machined)
   469  	}
   470  	return nil
   471  }
   472  
   473  func portMapsEqual(a, b map[network.PortRange]names.UnitTag) bool {
   474  	if len(a) != len(b) {
   475  		return false
   476  	}
   477  	for key, valueA := range a {
   478  		valueB, exists := b[key]
   479  		if !exists {
   480  			return false
   481  		}
   482  		if valueA != valueB {
   483  			return false
   484  		}
   485  	}
   486  	return true
   487  }
   488  
   489  // flushUnits opens and closes ports for the passed unit data.
   490  func (fw *Firewaller) flushUnits(unitds []*unitData) error {
   491  	machineds := map[names.MachineTag]*machineData{}
   492  	for _, unitd := range unitds {
   493  		machineds[unitd.machined.tag] = unitd.machined
   494  	}
   495  	for _, machined := range machineds {
   496  		if err := fw.flushMachine(machined); err != nil {
   497  			return err
   498  		}
   499  	}
   500  	return nil
   501  }
   502  
   503  // flushMachine opens and closes ports for the passed machine.
   504  func (fw *Firewaller) flushMachine(machined *machineData) error {
   505  	// Gather ports to open and close.
   506  	want := []network.PortRange{}
   507  	for portRange, unitTag := range machined.definedPorts {
   508  		unitd, known := machined.unitds[unitTag]
   509  		if !known {
   510  			delete(machined.unitds, unitTag)
   511  			continue
   512  		}
   513  		if unitd.serviced.exposed {
   514  			want = append(want, portRange)
   515  		}
   516  	}
   517  	toOpen := diffRanges(want, machined.openedPorts)
   518  	toClose := diffRanges(machined.openedPorts, want)
   519  	machined.openedPorts = want
   520  	if fw.globalMode {
   521  		return fw.flushGlobalPorts(toOpen, toClose)
   522  	}
   523  	return fw.flushInstancePorts(machined, toOpen, toClose)
   524  }
   525  
   526  // flushGlobalPorts opens and closes global ports in the environment.
   527  // It keeps a reference count for ports so that only 0-to-1 and 1-to-0 events
   528  // modify the environment.
   529  func (fw *Firewaller) flushGlobalPorts(rawOpen, rawClose []network.PortRange) error {
   530  	// Filter which ports are really to open or close.
   531  	var toOpen, toClose []network.PortRange
   532  	for _, portRange := range rawOpen {
   533  		if fw.globalPortRef[portRange] == 0 {
   534  			toOpen = append(toOpen, portRange)
   535  		}
   536  		fw.globalPortRef[portRange]++
   537  	}
   538  	for _, portRange := range rawClose {
   539  		fw.globalPortRef[portRange]--
   540  		if fw.globalPortRef[portRange] == 0 {
   541  			toClose = append(toClose, portRange)
   542  			delete(fw.globalPortRef, portRange)
   543  		}
   544  	}
   545  	// Open and close the ports.
   546  	if len(toOpen) > 0 {
   547  		if err := fw.environ.OpenPorts(toOpen); err != nil {
   548  			// TODO(mue) Add local retry logic.
   549  			return err
   550  		}
   551  		network.SortPortRanges(toOpen)
   552  		logger.Infof("opened port ranges %v in environment", toOpen)
   553  	}
   554  	if len(toClose) > 0 {
   555  		if err := fw.environ.ClosePorts(toClose); err != nil {
   556  			// TODO(mue) Add local retry logic.
   557  			return err
   558  		}
   559  		network.SortPortRanges(toClose)
   560  		logger.Infof("closed port ranges %v in environment", toClose)
   561  	}
   562  	return nil
   563  }
   564  
   565  // flushInstancePorts opens and closes ports global on the machine.
   566  func (fw *Firewaller) flushInstancePorts(machined *machineData, toOpen, toClose []network.PortRange) error {
   567  	// If there's nothing to do, do nothing.
   568  	// This is important because when a machine is first created,
   569  	// it will have no instance id but also no open ports -
   570  	// InstanceId will fail but we don't care.
   571  	if len(toOpen) == 0 && len(toClose) == 0 {
   572  		return nil
   573  	}
   574  	m, err := machined.machine()
   575  	if params.IsCodeNotFound(err) {
   576  		return nil
   577  	}
   578  	if err != nil {
   579  		return err
   580  	}
   581  	machineId := machined.tag.Id()
   582  	instanceId, err := m.InstanceId()
   583  	if err != nil {
   584  		return err
   585  	}
   586  	instances, err := fw.environ.Instances([]instance.Id{instanceId})
   587  	if err != nil {
   588  		return err
   589  	}
   590  	// Open and close the ports.
   591  	if len(toOpen) > 0 {
   592  		if err := instances[0].OpenPorts(machineId, toOpen); err != nil {
   593  			// TODO(mue) Add local retry logic.
   594  			return err
   595  		}
   596  		network.SortPortRanges(toOpen)
   597  		logger.Infof("opened port ranges %v on %q", toOpen, machined.tag)
   598  	}
   599  	if len(toClose) > 0 {
   600  		if err := instances[0].ClosePorts(machineId, toClose); err != nil {
   601  			// TODO(mue) Add local retry logic.
   602  			return err
   603  		}
   604  		network.SortPortRanges(toClose)
   605  		logger.Infof("closed port ranges %v on %q", toClose, machined.tag)
   606  	}
   607  	return nil
   608  }
   609  
   610  // machineLifeChanged starts watching new machines when the firewaller
   611  // is starting, or when new machines come to life, and stops watching
   612  // machines that are dying.
   613  func (fw *Firewaller) machineLifeChanged(tag names.MachineTag) error {
   614  	m, err := fw.st.Machine(tag)
   615  	found := !params.IsCodeNotFound(err)
   616  	if found && err != nil {
   617  		return err
   618  	}
   619  	dead := !found || m.Life() == params.Dead
   620  	machined, known := fw.machineds[tag]
   621  	if known && dead {
   622  		return fw.forgetMachine(machined)
   623  	}
   624  	if !known && !dead {
   625  		err = fw.startMachine(tag)
   626  		if err != nil {
   627  			return err
   628  		}
   629  		logger.Debugf("started watching %q", tag)
   630  	}
   631  	return nil
   632  }
   633  
   634  // forgetMachine cleans the machine data after the machine is removed.
   635  func (fw *Firewaller) forgetMachine(machined *machineData) error {
   636  	for _, unitd := range machined.unitds {
   637  		fw.forgetUnit(unitd)
   638  	}
   639  	if err := fw.flushMachine(machined); err != nil {
   640  		return err
   641  	}
   642  	delete(fw.machineds, machined.tag)
   643  	if err := machined.Stop(); err != nil {
   644  		return err
   645  	}
   646  	logger.Debugf("stopped watching %q", machined.tag)
   647  	return nil
   648  }
   649  
   650  // forgetUnit cleans the unit data after the unit is removed.
   651  func (fw *Firewaller) forgetUnit(unitd *unitData) {
   652  	serviced := unitd.serviced
   653  	machined := unitd.machined
   654  
   655  	// Clean up after stopping.
   656  	delete(fw.unitds, unitd.tag)
   657  	delete(machined.unitds, unitd.tag)
   658  	delete(serviced.unitds, unitd.tag)
   659  	if len(serviced.unitds) == 0 {
   660  		// Stop service data after all units are removed.
   661  		if err := serviced.Stop(); err != nil {
   662  			logger.Errorf("service watcher %q returned error when stopping: %v", serviced.service.Name(), err)
   663  		}
   664  		delete(fw.serviceds, serviced.service.Tag())
   665  	}
   666  }
   667  
   668  // stopWatchers stops all the firewaller's watchers.
   669  func (fw *Firewaller) stopWatchers() {
   670  	if fw.environWatcher != nil {
   671  		watcher.Stop(fw.environWatcher, &fw.tomb)
   672  	}
   673  	if fw.machinesWatcher != nil {
   674  		watcher.Stop(fw.machinesWatcher, &fw.tomb)
   675  	}
   676  	if fw.portsWatcher != nil {
   677  		watcher.Stop(fw.portsWatcher, &fw.tomb)
   678  	}
   679  	for _, serviced := range fw.serviceds {
   680  		if serviced != nil {
   681  			watcher.Stop(serviced, &fw.tomb)
   682  		}
   683  	}
   684  	for _, machined := range fw.machineds {
   685  		if machined != nil {
   686  			watcher.Stop(machined, &fw.tomb)
   687  		}
   688  	}
   689  }
   690  
   691  // Err returns the reason why the firewaller has stopped or tomb.ErrStillAlive
   692  // when it is still alive.
   693  func (fw *Firewaller) Err() (reason error) {
   694  	return fw.tomb.Err()
   695  }
   696  
   697  // Kill implements worker.Worker.Kill.
   698  func (fw *Firewaller) Kill() {
   699  	fw.tomb.Kill(nil)
   700  }
   701  
   702  // Wait implements worker.Worker.Wait.
   703  func (fw *Firewaller) Wait() error {
   704  	return fw.tomb.Wait()
   705  }
   706  
   707  // unitsChange contains the changed units for one specific machine.
   708  type unitsChange struct {
   709  	machined *machineData
   710  	units    []string
   711  }
   712  
   713  // machineData holds machine details and watches units added or removed.
   714  type machineData struct {
   715  	tomb        tomb.Tomb
   716  	fw          *Firewaller
   717  	tag         names.MachineTag
   718  	unitds      map[names.UnitTag]*unitData
   719  	openedPorts []network.PortRange
   720  	// ports defined by units on this machine
   721  	definedPorts map[network.PortRange]names.UnitTag
   722  }
   723  
   724  func (md *machineData) machine() (*apifirewaller.Machine, error) {
   725  	return md.fw.st.Machine(md.tag)
   726  }
   727  
   728  // watchLoop watches the machine for units added or removed.
   729  func (md *machineData) watchLoop(unitw apiwatcher.StringsWatcher) {
   730  	defer md.tomb.Done()
   731  	defer watcher.Stop(unitw, &md.tomb)
   732  	for {
   733  		select {
   734  		case <-md.tomb.Dying():
   735  			return
   736  		case change, ok := <-unitw.Changes():
   737  			if !ok {
   738  				_, err := md.machine()
   739  				if !params.IsCodeNotFound(err) {
   740  					md.fw.tomb.Kill(watcher.EnsureErr(unitw))
   741  				}
   742  				return
   743  			}
   744  			select {
   745  			case md.fw.unitsChange <- &unitsChange{md, change}:
   746  			case <-md.tomb.Dying():
   747  				return
   748  			}
   749  		}
   750  	}
   751  }
   752  
   753  // Stop stops the machine watching.
   754  func (md *machineData) Stop() error {
   755  	md.tomb.Kill(nil)
   756  	return md.tomb.Wait()
   757  }
   758  
   759  // unitData holds unit details and watches port changes.
   760  type unitData struct {
   761  	tomb     tomb.Tomb
   762  	fw       *Firewaller
   763  	tag      names.UnitTag
   764  	unit     *apifirewaller.Unit
   765  	serviced *serviceData
   766  	machined *machineData
   767  }
   768  
   769  // exposedChange contains the changed exposed flag for one specific service.
   770  type exposedChange struct {
   771  	serviced *serviceData
   772  	exposed  bool
   773  }
   774  
   775  // serviceData holds service details and watches exposure changes.
   776  type serviceData struct {
   777  	tomb    tomb.Tomb
   778  	fw      *Firewaller
   779  	service *apifirewaller.Service
   780  	exposed bool
   781  	unitds  map[names.UnitTag]*unitData
   782  }
   783  
   784  // watchLoop watches the service's exposed flag for changes.
   785  func (sd *serviceData) watchLoop(exposed bool) {
   786  	defer sd.tomb.Done()
   787  	w, err := sd.service.Watch()
   788  	if err != nil {
   789  		sd.fw.tomb.Kill(err)
   790  		return
   791  	}
   792  	defer watcher.Stop(w, &sd.tomb)
   793  	for {
   794  		select {
   795  		case <-sd.tomb.Dying():
   796  			return
   797  		case _, ok := <-w.Changes():
   798  			if !ok {
   799  				sd.fw.tomb.Kill(watcher.EnsureErr(w))
   800  				return
   801  			}
   802  			if err := sd.service.Refresh(); err != nil {
   803  				if !params.IsCodeNotFound(err) {
   804  					sd.fw.tomb.Kill(err)
   805  				}
   806  				return
   807  			}
   808  			change, err := sd.service.IsExposed()
   809  			if err != nil {
   810  				sd.fw.tomb.Kill(err)
   811  				return
   812  			}
   813  			if change == exposed {
   814  				continue
   815  			}
   816  			exposed = change
   817  			select {
   818  			case sd.fw.exposedChange <- &exposedChange{sd, change}:
   819  			case <-sd.tomb.Dying():
   820  				return
   821  			}
   822  		}
   823  	}
   824  }
   825  
   826  // Stop stops the service watching.
   827  func (sd *serviceData) Stop() error {
   828  	sd.tomb.Kill(nil)
   829  	return sd.tomb.Wait()
   830  }
   831  
   832  // diffRanges returns all the port rangess that exist in A but not B.
   833  func diffRanges(A, B []network.PortRange) (missing []network.PortRange) {
   834  next:
   835  	for _, a := range A {
   836  		for _, b := range B {
   837  			if a == b {
   838  				continue next
   839  			}
   840  		}
   841  		missing = append(missing, a)
   842  	}
   843  	return
   844  }
   845  
   846  // parsePortsKey parses a ports document global key coming from the
   847  // ports watcher (e.g. "42:juju-public") and returns the machine and
   848  // network tags from its components (in the last example "machine-42"
   849  // and "network-juju-public").
   850  func parsePortsKey(change string) (machineTag names.MachineTag, networkTag names.NetworkTag, err error) {
   851  	defer errors.DeferredAnnotatef(&err, "invalid ports change %q", change)
   852  
   853  	parts := strings.SplitN(change, ":", 2)
   854  	if len(parts) != 2 {
   855  		return names.MachineTag{}, names.NetworkTag{}, errors.Errorf("unexpected format")
   856  	}
   857  	machineId, networkName := parts[0], parts[1]
   858  	return names.NewMachineTag(machineId), names.NewNetworkTag(networkName), nil
   859  }