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