github.com/axw/juju@v0.0.0-20161005053422-4bd6544d08d4/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  	"gopkg.in/juju/names.v2"
    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  	applicationids  map[names.ApplicationTag]*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  		applicationids: make(map[names.ApplicationTag]*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  
   253  	// register the machined with the firewaller's catacomb.
   254  	return fw.catacomb.Add(machined)
   255  }
   256  
   257  // startUnit creates a new data value for tracking details of the unit
   258  // The provided machineTag must be the tag for the machine the unit was last
   259  // observed to be assigned to.
   260  func (fw *Firewaller) startUnit(unit *firewaller.Unit, machineTag names.MachineTag) error {
   261  	application, err := unit.Application()
   262  	if err != nil {
   263  		return err
   264  	}
   265  	applicationTag := application.Tag()
   266  	unitTag := unit.Tag()
   267  	if err != nil {
   268  		return err
   269  	}
   270  	unitd := &unitData{
   271  		fw:   fw,
   272  		unit: unit,
   273  		tag:  unitTag,
   274  	}
   275  	fw.unitds[unitTag] = unitd
   276  
   277  	unitd.machined = fw.machineds[machineTag]
   278  	unitd.machined.unitds[unitTag] = unitd
   279  	if fw.applicationids[applicationTag] == nil {
   280  		err := fw.startService(application)
   281  		if err != nil {
   282  			delete(fw.unitds, unitTag)
   283  			return err
   284  		}
   285  	}
   286  	unitd.serviced = fw.applicationids[applicationTag]
   287  	unitd.serviced.unitds[unitTag] = unitd
   288  
   289  	m, err := unitd.machined.machine()
   290  	if err != nil {
   291  		return err
   292  	}
   293  
   294  	// check if the machine has ports open on any subnets
   295  	subnetTags, err := m.ActiveSubnets()
   296  	if err != nil {
   297  		return errors.Annotatef(err, "failed getting %q active subnets", machineTag)
   298  	}
   299  	for _, subnetTag := range subnetTags {
   300  		err := fw.openedPortsChanged(machineTag, subnetTag)
   301  		if err != nil {
   302  			return err
   303  		}
   304  	}
   305  
   306  	return nil
   307  }
   308  
   309  // startService creates a new data value for tracking details of the
   310  // service and starts watching the service for exposure changes.
   311  func (fw *Firewaller) startService(service *firewaller.Application) error {
   312  	exposed, err := service.IsExposed()
   313  	if err != nil {
   314  		return err
   315  	}
   316  	serviced := &serviceData{
   317  		fw:          fw,
   318  		application: service,
   319  		exposed:     exposed,
   320  		unitds:      make(map[names.UnitTag]*unitData),
   321  	}
   322  	err = catacomb.Invoke(catacomb.Plan{
   323  		Site: &serviced.catacomb,
   324  		Work: func() error {
   325  			return serviced.watchLoop(exposed)
   326  		},
   327  	})
   328  	if err != nil {
   329  		return errors.Trace(err)
   330  	}
   331  	if err := fw.catacomb.Add(serviced); err != nil {
   332  		return errors.Trace(err)
   333  	}
   334  	fw.applicationids[service.Tag()] = serviced
   335  	return nil
   336  }
   337  
   338  // reconcileGlobal compares the initially started watcher for machines,
   339  // units and services with the opened and closed ports globally and
   340  // opens and closes the appropriate ports for the whole environment.
   341  func (fw *Firewaller) reconcileGlobal() error {
   342  	initialPortRanges, err := fw.environ.Ports()
   343  	if err != nil {
   344  		return err
   345  	}
   346  	collector := make(map[network.PortRange]bool)
   347  	for _, machined := range fw.machineds {
   348  		for portRange, unitTag := range machined.definedPorts {
   349  			unitd, known := machined.unitds[unitTag]
   350  			if !known {
   351  				delete(machined.unitds, unitTag)
   352  				continue
   353  			}
   354  			if unitd.serviced.exposed {
   355  				collector[portRange] = true
   356  			}
   357  		}
   358  	}
   359  	wantedPorts := []network.PortRange{}
   360  	for port := range collector {
   361  		wantedPorts = append(wantedPorts, port)
   362  	}
   363  	// Check which ports to open or to close.
   364  	toOpen := diffRanges(wantedPorts, initialPortRanges)
   365  	toClose := diffRanges(initialPortRanges, wantedPorts)
   366  	if len(toOpen) > 0 {
   367  		logger.Infof("opening global ports %v", toOpen)
   368  		if err := fw.environ.OpenPorts(toOpen); err != nil {
   369  			return err
   370  		}
   371  		network.SortPortRanges(toOpen)
   372  	}
   373  	if len(toClose) > 0 {
   374  		logger.Infof("closing global ports %v", toClose)
   375  		if err := fw.environ.ClosePorts(toClose); err != nil {
   376  			return err
   377  		}
   378  		network.SortPortRanges(toClose)
   379  	}
   380  	return nil
   381  }
   382  
   383  // reconcileInstances compares the initially started watcher for machines,
   384  // units and services with the opened and closed ports of the instances and
   385  // opens and closes the appropriate ports for each instance.
   386  func (fw *Firewaller) reconcileInstances() error {
   387  	for _, machined := range fw.machineds {
   388  		m, err := machined.machine()
   389  		if params.IsCodeNotFound(err) {
   390  			if err := fw.forgetMachine(machined); err != nil {
   391  				return err
   392  			}
   393  			continue
   394  		}
   395  		if err != nil {
   396  			return err
   397  		}
   398  		instanceId, err := m.InstanceId()
   399  		if errors.IsNotProvisioned(err) {
   400  			logger.Errorf("Machine not yet provisioned: %v", err)
   401  			continue
   402  		}
   403  		if err != nil {
   404  			return err
   405  		}
   406  		instances, err := fw.environ.Instances([]instance.Id{instanceId})
   407  		if err == environs.ErrNoInstances {
   408  			return nil
   409  		}
   410  		if err != nil {
   411  			return err
   412  		}
   413  		machineId := machined.tag.Id()
   414  		initialPortRanges, err := instances[0].Ports(machineId)
   415  		if err != nil {
   416  			return err
   417  		}
   418  
   419  		// Check which ports to open or to close.
   420  		toOpen := diffRanges(machined.openedPorts, initialPortRanges)
   421  		toClose := diffRanges(initialPortRanges, machined.openedPorts)
   422  		if len(toOpen) > 0 {
   423  			logger.Infof("opening instance port ranges %v for %q",
   424  				toOpen, machined.tag)
   425  			if err := instances[0].OpenPorts(machineId, toOpen); err != nil {
   426  				// TODO(mue) Add local retry logic.
   427  				return err
   428  			}
   429  			network.SortPortRanges(toOpen)
   430  		}
   431  		if len(toClose) > 0 {
   432  			logger.Infof("closing instance port ranges %v for %q",
   433  				toClose, machined.tag)
   434  			if err := instances[0].ClosePorts(machineId, toClose); err != nil {
   435  				// TODO(mue) Add local retry logic.
   436  				return err
   437  			}
   438  			network.SortPortRanges(toClose)
   439  		}
   440  	}
   441  	return nil
   442  }
   443  
   444  // unitsChanged responds to changes to the assigned units.
   445  func (fw *Firewaller) unitsChanged(change *unitsChange) error {
   446  	changed := []*unitData{}
   447  	for _, name := range change.units {
   448  		unitTag := names.NewUnitTag(name)
   449  		unit, err := fw.st.Unit(unitTag)
   450  		if err != nil && !params.IsCodeNotFound(err) {
   451  			return err
   452  		}
   453  		var machineTag names.MachineTag
   454  		if unit != nil {
   455  			machineTag, err = unit.AssignedMachine()
   456  			if params.IsCodeNotFound(err) {
   457  				continue
   458  			} else if err != nil && !params.IsCodeNotAssigned(err) {
   459  				return err
   460  			}
   461  		}
   462  		if unitd, known := fw.unitds[unitTag]; known {
   463  			knownMachineTag := fw.unitds[unitTag].machined.tag
   464  			if unit == nil || unit.Life() == params.Dead || machineTag != knownMachineTag {
   465  				fw.forgetUnit(unitd)
   466  				changed = append(changed, unitd)
   467  				logger.Debugf("stopped watching unit %s", name)
   468  			}
   469  			// TODO(dfc) fw.machineds should be map[names.Tag]
   470  		} else if unit != nil && unit.Life() != params.Dead && fw.machineds[machineTag] != nil {
   471  			err = fw.startUnit(unit, machineTag)
   472  			if err != nil {
   473  				return err
   474  			}
   475  			changed = append(changed, fw.unitds[unitTag])
   476  			logger.Debugf("started watching %q", unitTag)
   477  		}
   478  	}
   479  	if err := fw.flushUnits(changed); err != nil {
   480  		return errors.Annotate(err, "cannot change firewall ports")
   481  	}
   482  	return nil
   483  }
   484  
   485  // openedPortsChanged handles port change notifications
   486  func (fw *Firewaller) openedPortsChanged(machineTag names.MachineTag, subnetTag names.SubnetTag) error {
   487  
   488  	machined, ok := fw.machineds[machineTag]
   489  	if !ok {
   490  		// It is common to receive a port change notification before
   491  		// registering the machine, so if a machine is not found in
   492  		// firewaller's list, just skip the change.
   493  		logger.Errorf("failed to lookup %q, skipping port change", machineTag)
   494  		return nil
   495  	}
   496  
   497  	m, err := machined.machine()
   498  	if err != nil {
   499  		return err
   500  	}
   501  
   502  	ports, err := m.OpenedPorts(subnetTag)
   503  	if err != nil {
   504  		return err
   505  	}
   506  
   507  	newPortRanges := make(map[network.PortRange]names.UnitTag)
   508  	for portRange, unitTag := range ports {
   509  		unitd, ok := machined.unitds[unitTag]
   510  		if !ok {
   511  			// It is common to receive port change notification before
   512  			// registering a unit. Skip handling the port change - it will
   513  			// be handled when the unit is registered.
   514  			logger.Errorf("failed to lookup %q, skipping port change", unitTag)
   515  			return nil
   516  		}
   517  		newPortRanges[portRange] = unitd.tag
   518  	}
   519  
   520  	if !portMapsEqual(machined.definedPorts, newPortRanges) {
   521  		machined.definedPorts = newPortRanges
   522  		return fw.flushMachine(machined)
   523  	}
   524  	return nil
   525  }
   526  
   527  func portMapsEqual(a, b map[network.PortRange]names.UnitTag) bool {
   528  	if len(a) != len(b) {
   529  		return false
   530  	}
   531  	for key, valueA := range a {
   532  		valueB, exists := b[key]
   533  		if !exists {
   534  			return false
   535  		}
   536  		if valueA != valueB {
   537  			return false
   538  		}
   539  	}
   540  	return true
   541  }
   542  
   543  // flushUnits opens and closes ports for the passed unit data.
   544  func (fw *Firewaller) flushUnits(unitds []*unitData) error {
   545  	machineds := map[names.MachineTag]*machineData{}
   546  	for _, unitd := range unitds {
   547  		machineds[unitd.machined.tag] = unitd.machined
   548  	}
   549  	for _, machined := range machineds {
   550  		if err := fw.flushMachine(machined); err != nil {
   551  			return err
   552  		}
   553  	}
   554  	return nil
   555  }
   556  
   557  // flushMachine opens and closes ports for the passed machine.
   558  func (fw *Firewaller) flushMachine(machined *machineData) error {
   559  	// Gather ports to open and close.
   560  	want := []network.PortRange{}
   561  	for portRange, unitTag := range machined.definedPorts {
   562  		unitd, known := machined.unitds[unitTag]
   563  		if !known {
   564  			delete(machined.unitds, unitTag)
   565  			continue
   566  		}
   567  		if unitd.serviced.exposed {
   568  			want = append(want, portRange)
   569  		}
   570  	}
   571  	toOpen := diffRanges(want, machined.openedPorts)
   572  	toClose := diffRanges(machined.openedPorts, want)
   573  	machined.openedPorts = want
   574  	if fw.globalMode {
   575  		return fw.flushGlobalPorts(toOpen, toClose)
   576  	}
   577  	return fw.flushInstancePorts(machined, toOpen, toClose)
   578  }
   579  
   580  // flushGlobalPorts opens and closes global ports in the environment.
   581  // It keeps a reference count for ports so that only 0-to-1 and 1-to-0 events
   582  // modify the environment.
   583  func (fw *Firewaller) flushGlobalPorts(rawOpen, rawClose []network.PortRange) error {
   584  	// Filter which ports are really to open or close.
   585  	var toOpen, toClose []network.PortRange
   586  	for _, portRange := range rawOpen {
   587  		if fw.globalPortRef[portRange] == 0 {
   588  			toOpen = append(toOpen, portRange)
   589  		}
   590  		fw.globalPortRef[portRange]++
   591  	}
   592  	for _, portRange := range rawClose {
   593  		fw.globalPortRef[portRange]--
   594  		if fw.globalPortRef[portRange] == 0 {
   595  			toClose = append(toClose, portRange)
   596  			delete(fw.globalPortRef, portRange)
   597  		}
   598  	}
   599  	// Open and close the ports.
   600  	if len(toOpen) > 0 {
   601  		if err := fw.environ.OpenPorts(toOpen); err != nil {
   602  			// TODO(mue) Add local retry logic.
   603  			return err
   604  		}
   605  		network.SortPortRanges(toOpen)
   606  		logger.Infof("opened port ranges %v in environment", toOpen)
   607  	}
   608  	if len(toClose) > 0 {
   609  		if err := fw.environ.ClosePorts(toClose); err != nil {
   610  			// TODO(mue) Add local retry logic.
   611  			return err
   612  		}
   613  		network.SortPortRanges(toClose)
   614  		logger.Infof("closed port ranges %v in environment", toClose)
   615  	}
   616  	return nil
   617  }
   618  
   619  // flushInstancePorts opens and closes ports global on the machine.
   620  func (fw *Firewaller) flushInstancePorts(machined *machineData, toOpen, toClose []network.PortRange) error {
   621  	// If there's nothing to do, do nothing.
   622  	// This is important because when a machine is first created,
   623  	// it will have no instance id but also no open ports -
   624  	// InstanceId will fail but we don't care.
   625  	if len(toOpen) == 0 && len(toClose) == 0 {
   626  		return nil
   627  	}
   628  	m, err := machined.machine()
   629  	if params.IsCodeNotFound(err) {
   630  		return nil
   631  	}
   632  	if err != nil {
   633  		return err
   634  	}
   635  	machineId := machined.tag.Id()
   636  	instanceId, err := m.InstanceId()
   637  	if err != nil {
   638  		return err
   639  	}
   640  	instances, err := fw.environ.Instances([]instance.Id{instanceId})
   641  	if err != nil {
   642  		return err
   643  	}
   644  	// Open and close the ports.
   645  	if len(toOpen) > 0 {
   646  		if err := instances[0].OpenPorts(machineId, toOpen); err != nil {
   647  			// TODO(mue) Add local retry logic.
   648  			return err
   649  		}
   650  		network.SortPortRanges(toOpen)
   651  		logger.Infof("opened port ranges %v on %q", toOpen, machined.tag)
   652  	}
   653  	if len(toClose) > 0 {
   654  		if err := instances[0].ClosePorts(machineId, toClose); err != nil {
   655  			// TODO(mue) Add local retry logic.
   656  			return err
   657  		}
   658  		network.SortPortRanges(toClose)
   659  		logger.Infof("closed port ranges %v on %q", toClose, machined.tag)
   660  	}
   661  	return nil
   662  }
   663  
   664  // machineLifeChanged starts watching new machines when the firewaller
   665  // is starting, or when new machines come to life, and stops watching
   666  // machines that are dying.
   667  func (fw *Firewaller) machineLifeChanged(tag names.MachineTag) error {
   668  	m, err := fw.st.Machine(tag)
   669  	found := !params.IsCodeNotFound(err)
   670  	if found && err != nil {
   671  		return err
   672  	}
   673  	dead := !found || m.Life() == params.Dead
   674  	machined, known := fw.machineds[tag]
   675  	if known && dead {
   676  		return fw.forgetMachine(machined)
   677  	}
   678  	if !known && !dead {
   679  		err = fw.startMachine(tag)
   680  		if err != nil {
   681  			return err
   682  		}
   683  		logger.Debugf("started watching %q", tag)
   684  	}
   685  	return nil
   686  }
   687  
   688  // forgetMachine cleans the machine data after the machine is removed.
   689  func (fw *Firewaller) forgetMachine(machined *machineData) error {
   690  	for _, unitd := range machined.unitds {
   691  		fw.forgetUnit(unitd)
   692  	}
   693  	if err := fw.flushMachine(machined); err != nil {
   694  		return errors.Trace(err)
   695  	}
   696  
   697  	// Unusually, it's fine to ignore this error, because we know the machined
   698  	// is being tracked in fw.catacomb. But we do still want to wait until the
   699  	// watch loop has stopped before we nuke the last data and return.
   700  	worker.Stop(machined)
   701  	delete(fw.machineds, machined.tag)
   702  	logger.Debugf("stopped watching %q", machined.tag)
   703  	return nil
   704  }
   705  
   706  // forgetUnit cleans the unit data after the unit is removed.
   707  func (fw *Firewaller) forgetUnit(unitd *unitData) {
   708  	serviced := unitd.serviced
   709  	machined := unitd.machined
   710  
   711  	// If it's the last unit in the service, we'll need to stop the serviced.
   712  	stoppedService := false
   713  	if len(serviced.unitds) == 1 {
   714  		if _, found := serviced.unitds[unitd.tag]; found {
   715  			// Unusually, it's fine to ignore this error, because we know the
   716  			// serviced is being tracked in fw.catacomb. But we do still want
   717  			// to wait until the watch loop has stopped before we nuke the last
   718  			// data and return.
   719  			worker.Stop(serviced)
   720  			stoppedService = true
   721  		}
   722  	}
   723  
   724  	// Clean up after stopping.
   725  	delete(fw.unitds, unitd.tag)
   726  	delete(machined.unitds, unitd.tag)
   727  	delete(serviced.unitds, unitd.tag)
   728  	logger.Debugf("stopped watching %q", unitd.tag)
   729  	if stoppedService {
   730  		applicationTag := serviced.application.Tag()
   731  		delete(fw.applicationids, applicationTag)
   732  		logger.Debugf("stopped watching %q", applicationTag)
   733  	}
   734  }
   735  
   736  // Kill is part of the worker.Worker interface.
   737  func (fw *Firewaller) Kill() {
   738  	fw.catacomb.Kill(nil)
   739  }
   740  
   741  // Wait is part of the worker.Worker interface.
   742  func (fw *Firewaller) Wait() error {
   743  	return fw.catacomb.Wait()
   744  }
   745  
   746  // unitsChange contains the changed units for one specific machine.
   747  type unitsChange struct {
   748  	machined *machineData
   749  	units    []string
   750  }
   751  
   752  // machineData holds machine details and watches units added or removed.
   753  type machineData struct {
   754  	catacomb    catacomb.Catacomb
   755  	fw          *Firewaller
   756  	tag         names.MachineTag
   757  	unitds      map[names.UnitTag]*unitData
   758  	openedPorts []network.PortRange
   759  	// ports defined by units on this machine
   760  	definedPorts map[network.PortRange]names.UnitTag
   761  }
   762  
   763  func (md *machineData) machine() (*firewaller.Machine, error) {
   764  	return md.fw.st.Machine(md.tag)
   765  }
   766  
   767  // watchLoop watches the machine for units added or removed.
   768  func (md *machineData) watchLoop(unitw watcher.StringsWatcher) error {
   769  	if err := md.catacomb.Add(unitw); err != nil {
   770  		return errors.Trace(err)
   771  	}
   772  	for {
   773  		select {
   774  		case <-md.catacomb.Dying():
   775  			return md.catacomb.ErrDying()
   776  		case change, ok := <-unitw.Changes():
   777  			if !ok {
   778  				return errors.New("machine units watcher closed")
   779  			}
   780  			select {
   781  			case md.fw.unitsChange <- &unitsChange{md, change}:
   782  			case <-md.catacomb.Dying():
   783  				return md.catacomb.ErrDying()
   784  			}
   785  		}
   786  	}
   787  }
   788  
   789  // Kill is part of the worker.Worker interface.
   790  func (md *machineData) Kill() {
   791  	md.catacomb.Kill(nil)
   792  }
   793  
   794  // Wait is part of the worker.Worker interface.
   795  func (md *machineData) Wait() error {
   796  	return md.catacomb.Wait()
   797  }
   798  
   799  // unitData holds unit details.
   800  type unitData struct {
   801  	fw       *Firewaller
   802  	tag      names.UnitTag
   803  	unit     *firewaller.Unit
   804  	serviced *serviceData
   805  	machined *machineData
   806  }
   807  
   808  // exposedChange contains the changed exposed flag for one specific service.
   809  type exposedChange struct {
   810  	serviced *serviceData
   811  	exposed  bool
   812  }
   813  
   814  // serviceData holds service details and watches exposure changes.
   815  type serviceData struct {
   816  	catacomb    catacomb.Catacomb
   817  	fw          *Firewaller
   818  	application *firewaller.Application
   819  	exposed     bool
   820  	unitds      map[names.UnitTag]*unitData
   821  }
   822  
   823  // watchLoop watches the service's exposed flag for changes.
   824  func (sd *serviceData) watchLoop(exposed bool) error {
   825  	serviceWatcher, err := sd.application.Watch()
   826  	if err != nil {
   827  		return errors.Trace(err)
   828  	}
   829  	if err := sd.catacomb.Add(serviceWatcher); err != nil {
   830  		return errors.Trace(err)
   831  	}
   832  	for {
   833  		select {
   834  		case <-sd.catacomb.Dying():
   835  			return sd.catacomb.ErrDying()
   836  		case _, ok := <-serviceWatcher.Changes():
   837  			if !ok {
   838  				return errors.New("service watcher closed")
   839  			}
   840  			if err := sd.application.Refresh(); err != nil {
   841  				if !params.IsCodeNotFound(err) {
   842  					return errors.Trace(err)
   843  				}
   844  				return nil
   845  			}
   846  			change, err := sd.application.IsExposed()
   847  			if err != nil {
   848  				return errors.Trace(err)
   849  			}
   850  			if change == exposed {
   851  				continue
   852  			}
   853  
   854  			exposed = change
   855  			select {
   856  			case sd.fw.exposedChange <- &exposedChange{sd, change}:
   857  			case <-sd.catacomb.Dying():
   858  				return sd.catacomb.ErrDying()
   859  			}
   860  		}
   861  	}
   862  }
   863  
   864  // Kill is part of the worker.Worker interface.
   865  func (sd *serviceData) Kill() {
   866  	sd.catacomb.Kill(nil)
   867  }
   868  
   869  // Wait is part of the worker.Worker interface.
   870  func (sd *serviceData) Wait() error {
   871  	return sd.catacomb.Wait()
   872  }
   873  
   874  // diffRanges returns all the port rangess that exist in A but not B.
   875  func diffRanges(A, B []network.PortRange) (missing []network.PortRange) {
   876  next:
   877  	for _, a := range A {
   878  		for _, b := range B {
   879  			if a == b {
   880  				continue next
   881  			}
   882  		}
   883  		missing = append(missing, a)
   884  	}
   885  	return
   886  }
   887  
   888  // parsePortsKey parses a ports document global key coming from the ports
   889  // watcher (e.g. "42:0.1.2.0/24") and returns the machine and subnet tags from
   890  // its components (in the last example "machine-42" and "subnet-0.1.2.0/24").
   891  func parsePortsKey(change string) (machineTag names.MachineTag, subnetTag names.SubnetTag, err error) {
   892  	defer errors.DeferredAnnotatef(&err, "invalid ports change %q", change)
   893  
   894  	parts := strings.SplitN(change, ":", 2)
   895  	if len(parts) != 2 {
   896  		return names.MachineTag{}, names.SubnetTag{}, errors.Errorf("unexpected format")
   897  	}
   898  	machineID, subnetID := parts[0], parts[1]
   899  
   900  	machineTag = names.NewMachineTag(machineID)
   901  	if subnetID != "" {
   902  		subnetTag = names.NewSubnetTag(subnetID)
   903  	}
   904  	return machineTag, subnetTag, nil
   905  }