github.com/juju/juju@v0.0.0-20240327075706-a90865de2538/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  	stdcontext "context"
     8  	"io"
     9  	"sort"
    10  	"time"
    11  
    12  	"github.com/EvilSuperstars/go-cidrman"
    13  	"github.com/go-macaroon-bakery/macaroon-bakery/v3/bakery"
    14  	"github.com/juju/charm/v12"
    15  	"github.com/juju/clock"
    16  	"github.com/juju/collections/set"
    17  	"github.com/juju/errors"
    18  	"github.com/juju/names/v5"
    19  	"github.com/juju/worker/v3"
    20  	"github.com/juju/worker/v3/catacomb"
    21  	"gopkg.in/macaroon.v2"
    22  
    23  	"github.com/juju/juju/api"
    24  	"github.com/juju/juju/api/controller/firewaller"
    25  	"github.com/juju/juju/api/controller/remoterelations"
    26  	"github.com/juju/juju/core/instance"
    27  	"github.com/juju/juju/core/life"
    28  	"github.com/juju/juju/core/network"
    29  	"github.com/juju/juju/core/network/firewall"
    30  	"github.com/juju/juju/core/relation"
    31  	"github.com/juju/juju/core/watcher"
    32  	"github.com/juju/juju/environs"
    33  	"github.com/juju/juju/environs/config"
    34  	"github.com/juju/juju/environs/context"
    35  	"github.com/juju/juju/environs/instances"
    36  	"github.com/juju/juju/environs/models"
    37  	"github.com/juju/juju/rpc/params"
    38  	"github.com/juju/juju/worker/common"
    39  )
    40  
    41  // FirewallerAPI exposes functionality off the firewaller API facade to a worker.
    42  type FirewallerAPI interface {
    43  	WatchModelMachines() (watcher.StringsWatcher, error)
    44  	WatchOpenedPorts() (watcher.StringsWatcher, error)
    45  	WatchModelFirewallRules() (watcher.NotifyWatcher, error)
    46  	ModelFirewallRules() (firewall.IngressRules, error)
    47  	ModelConfig() (*config.Config, error)
    48  	Machine(tag names.MachineTag) (*firewaller.Machine, error)
    49  	Unit(tag names.UnitTag) (*firewaller.Unit, error)
    50  	Relation(tag names.RelationTag) (*firewaller.Relation, error)
    51  	WatchEgressAddressesForRelation(tag names.RelationTag) (watcher.StringsWatcher, error)
    52  	WatchIngressAddressesForRelation(tag names.RelationTag) (watcher.StringsWatcher, error)
    53  	ControllerAPIInfoForModel(modelUUID string) (*api.Info, error)
    54  	MacaroonForRelation(relationKey string) (*macaroon.Macaroon, error)
    55  	SetRelationStatus(relationKey string, status relation.Status, message string) error
    56  	AllSpaceInfos() (network.SpaceInfos, error)
    57  	WatchSubnets() (watcher.StringsWatcher, error)
    58  }
    59  
    60  // CrossModelFirewallerFacade exposes firewaller functionality on the
    61  // remote offering model to a worker.
    62  type CrossModelFirewallerFacade interface {
    63  	PublishIngressNetworkChange(params.IngressNetworksChangeEvent) error
    64  	WatchEgressAddressesForRelation(details params.RemoteEntityArg) (watcher.StringsWatcher, error)
    65  }
    66  
    67  // RemoteFirewallerAPICloser implements CrossModelFirewallerFacade
    68  // and adds a Close() method.
    69  type CrossModelFirewallerFacadeCloser interface {
    70  	io.Closer
    71  	CrossModelFirewallerFacade
    72  }
    73  
    74  // EnvironFirewaller defines methods to allow the worker to perform
    75  // firewall operations (open/close ports) on a Juju global firewall.
    76  type EnvironFirewaller interface {
    77  	environs.Firewaller
    78  }
    79  
    80  // EnvironModelFirewaller defines methods to allow the worker to
    81  // perform firewall operations (open/close port) on a Juju model firewall.
    82  type EnvironModelFirewaller interface {
    83  	models.ModelFirewaller
    84  }
    85  
    86  // EnvironInstances defines methods to allow the worker to perform
    87  // operations on instances in a Juju cloud environment.
    88  type EnvironInstances interface {
    89  	Instances(ctx context.ProviderCallContext, ids []instance.Id) ([]instances.Instance, error)
    90  }
    91  
    92  type newCrossModelFacadeFunc func(*api.Info) (CrossModelFirewallerFacadeCloser, error)
    93  
    94  // Config defines the operation of a Worker.
    95  type Config struct {
    96  	ModelUUID              string
    97  	Mode                   string
    98  	FirewallerAPI          FirewallerAPI
    99  	RemoteRelationsApi     *remoterelations.Client
   100  	EnvironFirewaller      EnvironFirewaller
   101  	EnvironModelFirewaller EnvironModelFirewaller
   102  	EnvironInstances       EnvironInstances
   103  	EnvironIPV6CIDRSupport bool
   104  
   105  	NewCrossModelFacadeFunc newCrossModelFacadeFunc
   106  
   107  	Clock  clock.Clock
   108  	Logger Logger
   109  
   110  	CredentialAPI common.CredentialAPI
   111  
   112  	// TODO: (jack-w-shaw) Drop these once we move tests to mocks based
   113  	// WatchMachineNotify is called when the Firewaller starts watching the
   114  	// machine with the given tag (manual machines aren't watched). This
   115  	// should only be used for testing.
   116  	WatchMachineNotify func(tag names.MachineTag)
   117  	// FlushModelNotify is called when the Firewaller flushes it's model.
   118  	// This should only be used for testing
   119  	FlushModelNotify func()
   120  }
   121  
   122  // Validate returns an error if cfg cannot drive a Worker.
   123  func (cfg Config) Validate() error {
   124  	if cfg.ModelUUID == "" {
   125  		return errors.NotValidf("empty model uuid")
   126  	}
   127  	if cfg.FirewallerAPI == nil {
   128  		return errors.NotValidf("nil Firewaller Facade")
   129  	}
   130  	if cfg.RemoteRelationsApi == nil {
   131  		return errors.NotValidf("nil RemoteRelations Facade")
   132  	}
   133  	if cfg.Mode == config.FwGlobal && cfg.EnvironFirewaller == nil {
   134  		return errors.NotValidf("nil EnvironFirewaller")
   135  	}
   136  	if cfg.EnvironInstances == nil {
   137  		return errors.NotValidf("nil EnvironInstances")
   138  	}
   139  	if cfg.NewCrossModelFacadeFunc == nil {
   140  		return errors.NotValidf("nil Cross Model Facade func")
   141  	}
   142  	if cfg.Logger == nil {
   143  		return errors.NotValidf("nil Logger")
   144  	}
   145  	if cfg.CredentialAPI == nil {
   146  		return errors.NotValidf("nil Credential Facade")
   147  	}
   148  	return nil
   149  }
   150  
   151  // Firewaller watches the state for port ranges opened or closed on
   152  // machines and reflects those changes onto the backing environment.
   153  // Uses Firewaller API V1.
   154  type Firewaller struct {
   155  	catacomb               catacomb.Catacomb
   156  	firewallerApi          FirewallerAPI
   157  	remoteRelationsApi     *remoterelations.Client
   158  	environFirewaller      EnvironFirewaller
   159  	environModelFirewaller EnvironModelFirewaller
   160  	environInstances       EnvironInstances
   161  
   162  	machinesWatcher      watcher.StringsWatcher
   163  	portsWatcher         watcher.StringsWatcher
   164  	subnetWatcher        watcher.StringsWatcher
   165  	modelFirewallWatcher watcher.NotifyWatcher
   166  	machineds            map[names.MachineTag]*machineData
   167  	unitsChange          chan *unitsChange
   168  	unitds               map[names.UnitTag]*unitData
   169  	applicationids       map[names.ApplicationTag]*applicationData
   170  	exposedChange        chan *exposedChange
   171  	spaceInfos           network.SpaceInfos
   172  	globalMode           bool
   173  	globalIngressRuleRef map[string]int // map of rule names to count of occurrences
   174  
   175  	// Set to true if the environment supports ingress rules containing
   176  	// IPV6 CIDRs.
   177  	envIPV6CIDRSupport bool
   178  
   179  	modelUUID                  string
   180  	newRemoteFirewallerAPIFunc newCrossModelFacadeFunc
   181  	remoteRelationsWatcher     watcher.StringsWatcher
   182  	localRelationsChange       chan *remoteRelationNetworkChange
   183  	relationIngress            map[names.RelationTag]*remoteRelationData
   184  	relationWorkerRunner       *worker.Runner
   185  	clk                        clock.Clock
   186  	logger                     Logger
   187  
   188  	cloudCallContextFunc common.CloudCallContextFunc
   189  
   190  	// Only used for testing
   191  	watchMachineNotify func(tag names.MachineTag)
   192  	flushModelNotify   func()
   193  }
   194  
   195  // NewFirewaller returns a new Firewaller.
   196  func NewFirewaller(cfg Config) (worker.Worker, error) {
   197  	if err := cfg.Validate(); err != nil {
   198  		return nil, errors.Trace(err)
   199  	}
   200  	clk := cfg.Clock
   201  	if clk == nil {
   202  		clk = clock.WallClock
   203  	}
   204  
   205  	fw := &Firewaller{
   206  		firewallerApi:              cfg.FirewallerAPI,
   207  		remoteRelationsApi:         cfg.RemoteRelationsApi,
   208  		environFirewaller:          cfg.EnvironFirewaller,
   209  		environModelFirewaller:     cfg.EnvironModelFirewaller,
   210  		environInstances:           cfg.EnvironInstances,
   211  		envIPV6CIDRSupport:         cfg.EnvironIPV6CIDRSupport,
   212  		newRemoteFirewallerAPIFunc: cfg.NewCrossModelFacadeFunc,
   213  		modelUUID:                  cfg.ModelUUID,
   214  		machineds:                  make(map[names.MachineTag]*machineData),
   215  		unitsChange:                make(chan *unitsChange),
   216  		unitds:                     make(map[names.UnitTag]*unitData),
   217  		applicationids:             make(map[names.ApplicationTag]*applicationData),
   218  		exposedChange:              make(chan *exposedChange),
   219  		relationIngress:            make(map[names.RelationTag]*remoteRelationData),
   220  		localRelationsChange:       make(chan *remoteRelationNetworkChange),
   221  		clk:                        clk,
   222  		logger:                     cfg.Logger,
   223  		relationWorkerRunner: worker.NewRunner(worker.RunnerParams{
   224  			Clock:  clk,
   225  			Logger: cfg.Logger,
   226  
   227  			// One of the remote relation workers failing should not
   228  			// prevent the others from running.
   229  			IsFatal: func(error) bool { return false },
   230  
   231  			// For any failures, try again in 1 minute.
   232  			RestartDelay: time.Minute,
   233  		}),
   234  		cloudCallContextFunc: common.NewCloudCallContextFunc(cfg.CredentialAPI),
   235  		watchMachineNotify:   cfg.WatchMachineNotify,
   236  		flushModelNotify:     cfg.FlushModelNotify,
   237  	}
   238  
   239  	switch cfg.Mode {
   240  	case config.FwInstance:
   241  	case config.FwGlobal:
   242  		fw.globalMode = true
   243  		fw.globalIngressRuleRef = make(map[string]int)
   244  	default:
   245  		return nil, errors.Errorf("invalid firewall-mode %q", cfg.Mode)
   246  	}
   247  
   248  	err := catacomb.Invoke(catacomb.Plan{
   249  		Site: &fw.catacomb,
   250  		Work: fw.loop,
   251  		Init: []worker.Worker{fw.relationWorkerRunner},
   252  	})
   253  	if err != nil {
   254  		return nil, errors.Trace(err)
   255  	}
   256  	return fw, nil
   257  }
   258  
   259  func (fw *Firewaller) setUp() error {
   260  	var err error
   261  	fw.machinesWatcher, err = fw.firewallerApi.WatchModelMachines()
   262  	if err != nil {
   263  		return errors.Trace(err)
   264  	}
   265  	if err := fw.catacomb.Add(fw.machinesWatcher); err != nil {
   266  		return errors.Trace(err)
   267  	}
   268  
   269  	fw.portsWatcher, err = fw.firewallerApi.WatchOpenedPorts()
   270  	if err != nil {
   271  		return errors.Annotatef(err, "failed to start ports watcher")
   272  	}
   273  	if err := fw.catacomb.Add(fw.portsWatcher); err != nil {
   274  		return errors.Trace(err)
   275  	}
   276  
   277  	fw.remoteRelationsWatcher, err = fw.remoteRelationsApi.WatchRemoteRelations()
   278  	if err != nil {
   279  		return errors.Trace(err)
   280  	}
   281  	if err := fw.catacomb.Add(fw.remoteRelationsWatcher); err != nil {
   282  		return errors.Trace(err)
   283  	}
   284  
   285  	fw.subnetWatcher, err = fw.firewallerApi.WatchSubnets()
   286  	if err != nil {
   287  		return errors.Annotatef(err, "failed to start subnet watcher")
   288  	}
   289  	if err := fw.catacomb.Add(fw.subnetWatcher); err != nil {
   290  		return errors.Trace(err)
   291  	}
   292  
   293  	if fw.environModelFirewaller != nil {
   294  		fw.modelFirewallWatcher, err = fw.firewallerApi.WatchModelFirewallRules()
   295  		if err != nil {
   296  			return errors.Annotatef(err, "failed to start subnet watcher")
   297  		}
   298  		if err := fw.catacomb.Add(fw.modelFirewallWatcher); err != nil {
   299  			return errors.Trace(err)
   300  		}
   301  	}
   302  
   303  	if fw.spaceInfos, err = fw.firewallerApi.AllSpaceInfos(); err != nil {
   304  		return errors.Trace(err)
   305  	}
   306  
   307  	fw.logger.Debugf("started watching opened port ranges for the model")
   308  	return nil
   309  }
   310  
   311  func (fw *Firewaller) loop() error {
   312  	if err := fw.setUp(); err != nil {
   313  		return errors.Trace(err)
   314  	}
   315  	var (
   316  		reconciled                    bool
   317  		modelGroupInitiallyConfigured bool
   318  	)
   319  	portsChange := fw.portsWatcher.Changes()
   320  
   321  	var modelFirewallChanges watcher.NotifyChannel
   322  	var ensureModelFirewalls <-chan time.Time
   323  	if fw.modelFirewallWatcher != nil {
   324  		modelFirewallChanges = fw.modelFirewallWatcher.Changes()
   325  	}
   326  
   327  	for {
   328  		select {
   329  		case <-fw.catacomb.Dying():
   330  			return fw.catacomb.ErrDying()
   331  		case <-ensureModelFirewalls:
   332  			err := fw.flushModel()
   333  			if errors.Is(err, errors.NotFound) {
   334  				ensureModelFirewalls = fw.clk.After(time.Second)
   335  			} else if err != nil {
   336  				return err
   337  			} else {
   338  				ensureModelFirewalls = nil
   339  			}
   340  		case _, ok := <-modelFirewallChanges:
   341  			if !ok {
   342  				return errors.New("model config watcher closed")
   343  			}
   344  			if ensureModelFirewalls == nil {
   345  				ensureModelFirewalls = fw.clk.After(0)
   346  			}
   347  		case change, ok := <-fw.machinesWatcher.Changes():
   348  			if !ok {
   349  				return errors.New("machines watcher closed")
   350  			}
   351  			for _, machineId := range change {
   352  				if err := fw.machineLifeChanged(names.NewMachineTag(machineId)); err != nil {
   353  					return err
   354  				}
   355  			}
   356  			if !reconciled {
   357  				reconciled = true
   358  				var err error
   359  				if fw.globalMode {
   360  					err = fw.reconcileGlobal()
   361  				} else {
   362  					err = fw.reconcileInstances()
   363  				}
   364  				if err != nil {
   365  					return errors.Trace(err)
   366  				}
   367  			}
   368  			// After first machine exists, make sure to trigger the model firewall flush.
   369  			if len(change) > 0 && !modelGroupInitiallyConfigured {
   370  				modelGroupInitiallyConfigured = true
   371  				if ensureModelFirewalls == nil {
   372  					ensureModelFirewalls = fw.clk.After(0)
   373  				}
   374  			}
   375  		case change, ok := <-portsChange:
   376  			if !ok {
   377  				return errors.New("ports watcher closed")
   378  			}
   379  			for _, portsGlobalKey := range change {
   380  				machineTag := names.NewMachineTag(portsGlobalKey)
   381  				if err := fw.openedPortsChanged(machineTag); err != nil {
   382  					return errors.Trace(err)
   383  				}
   384  			}
   385  		case change, ok := <-fw.remoteRelationsWatcher.Changes():
   386  			if !ok {
   387  				return errors.New("remote relations watcher closed")
   388  			}
   389  			for _, relationKey := range change {
   390  				if err := fw.relationLifeChanged(names.NewRelationTag(relationKey)); err != nil {
   391  					return err
   392  				}
   393  			}
   394  		case _, ok := <-fw.subnetWatcher.Changes():
   395  			if !ok {
   396  				return errors.New("subnet watcher closed")
   397  			}
   398  
   399  			if err := fw.subnetsChanged(); err != nil {
   400  				return errors.Trace(err)
   401  			}
   402  		case change := <-fw.localRelationsChange:
   403  			// We have a notification that the remote (consuming) model
   404  			// has changed egress networks so need to update the local
   405  			// model to allow those networks through the firewall.
   406  			if err := fw.relationIngressChanged(change); err != nil {
   407  				return errors.Trace(err)
   408  			}
   409  		case change := <-fw.unitsChange:
   410  			if err := fw.unitsChanged(change); err != nil {
   411  				return errors.Trace(err)
   412  			}
   413  		case change := <-fw.exposedChange:
   414  			change.applicationd.exposed = change.exposed
   415  			change.applicationd.exposedEndpoints = change.exposedEndpoints
   416  			var unitds []*unitData
   417  			for _, unitd := range change.applicationd.unitds {
   418  				unitds = append(unitds, unitd)
   419  			}
   420  			if err := fw.flushUnits(unitds); err != nil {
   421  				return errors.Annotate(err, "cannot change firewall ports")
   422  			}
   423  		}
   424  	}
   425  }
   426  
   427  func (fw *Firewaller) subnetsChanged() error {
   428  	// Refresh space topology
   429  	var err error
   430  	if fw.spaceInfos, err = fw.firewallerApi.AllSpaceInfos(); err != nil {
   431  		return errors.Trace(err)
   432  	}
   433  
   434  	// Select units for which the ingress rules must be refreshed. We only
   435  	// consider applications that expose endpoints to at least one space.
   436  	var unitds []*unitData
   437  	for _, appd := range fw.applicationids {
   438  		var exposedToSpaces bool
   439  		for _, exposeDetails := range appd.exposedEndpoints {
   440  			if len(exposeDetails.ExposeToSpaces) != 0 {
   441  				exposedToSpaces = true
   442  				break
   443  			}
   444  		}
   445  
   446  		if !exposedToSpaces {
   447  			continue // no need to re-eval ingress rules.
   448  		}
   449  
   450  		for _, unitd := range appd.unitds {
   451  			unitds = append(unitds, unitd)
   452  		}
   453  	}
   454  
   455  	if len(unitds) == 0 {
   456  		return nil // nothing to do
   457  	}
   458  
   459  	if err := fw.flushUnits(unitds); err != nil {
   460  		return errors.Annotate(err, "cannot update unit ingress rules")
   461  	}
   462  	return nil
   463  }
   464  
   465  func (fw *Firewaller) relationIngressChanged(change *remoteRelationNetworkChange) error {
   466  	fw.logger.Debugf("process remote relation ingress change for %v", change.relationTag)
   467  	relData, ok := fw.relationIngress[change.relationTag]
   468  	if ok {
   469  		relData.networks = change.networks
   470  		relData.ingressRequired = change.ingressRequired
   471  	}
   472  	appData, ok := fw.applicationids[change.localApplicationTag]
   473  	if !ok {
   474  		fw.logger.Debugf("ignoring unknown application: %v", change.localApplicationTag)
   475  		return nil
   476  	}
   477  	unitds := []*unitData{}
   478  	for _, unitd := range appData.unitds {
   479  		unitds = append(unitds, unitd)
   480  	}
   481  	if err := fw.flushUnits(unitds); err != nil {
   482  		return errors.Annotate(err, "cannot change firewall ports")
   483  	}
   484  	return nil
   485  }
   486  
   487  // startMachine creates a new data value for tracking details of the
   488  // machine and starts watching the machine for units added or removed.
   489  func (fw *Firewaller) startMachine(tag names.MachineTag) error {
   490  	machined := &machineData{
   491  		fw:     fw,
   492  		tag:    tag,
   493  		unitds: make(map[names.UnitTag]*unitData),
   494  	}
   495  	m, err := machined.machine()
   496  	if params.IsCodeNotFound(err) {
   497  		fw.logger.Debugf("not watching %q", tag)
   498  		return nil
   499  	} else if err != nil {
   500  		return errors.Annotate(err, "cannot watch machine units")
   501  	}
   502  	manual, err := m.IsManual()
   503  	if err != nil {
   504  		return errors.Trace(err)
   505  	}
   506  	if manual {
   507  		// Don't track manual machines, we can't change their ports.
   508  		fw.logger.Debugf("not watching manual %q", tag)
   509  		return nil
   510  	}
   511  	unitw, err := m.WatchUnits()
   512  	if err != nil {
   513  		return errors.Trace(err)
   514  	}
   515  	// XXX(fwereade): this is the best of a bunch of bad options. We've started
   516  	// the watch, so we're responsible for it; but we (probably?) need to do this
   517  	// little dance below to update the machined data on the fw loop goroutine,
   518  	// whence it's usually accessed, before we start the machined watchLoop
   519  	// below. That catacomb *should* be the only one responsible -- and it *is*
   520  	// responsible -- but having it in the main fw catacomb as well does no harm,
   521  	// and greatly simplifies the code below (which would otherwise have to
   522  	// manage unitw lifetime and errors manually).
   523  	if err := fw.catacomb.Add(unitw); err != nil {
   524  		return errors.Trace(err)
   525  	}
   526  	select {
   527  	case <-fw.catacomb.Dying():
   528  		return fw.catacomb.ErrDying()
   529  	case change, ok := <-unitw.Changes():
   530  		if !ok {
   531  			return errors.New("machine units watcher closed")
   532  		}
   533  		fw.machineds[tag] = machined
   534  		err = fw.unitsChanged(&unitsChange{machined, change})
   535  		if err != nil {
   536  			delete(fw.machineds, tag)
   537  			return errors.Annotatef(err, "cannot respond to units changes for %q, %q", tag, fw.modelUUID)
   538  		}
   539  	}
   540  
   541  	err = catacomb.Invoke(catacomb.Plan{
   542  		Site: &machined.catacomb,
   543  		Work: func() error {
   544  			return machined.watchLoop(unitw)
   545  		},
   546  	})
   547  	if err != nil {
   548  		delete(fw.machineds, tag)
   549  		return errors.Trace(err)
   550  	}
   551  
   552  	// register the machined with the firewaller's catacomb.
   553  	err = fw.catacomb.Add(machined)
   554  	if err != nil {
   555  		return errors.Trace(err)
   556  	}
   557  	fw.logger.Debugf("started watching %q", tag)
   558  	if fw.watchMachineNotify != nil {
   559  		fw.watchMachineNotify(tag)
   560  	}
   561  	return nil
   562  }
   563  
   564  // startUnit creates a new data value for tracking details of the unit
   565  // The provided machineTag must be the tag for the machine the unit was last
   566  // observed to be assigned to.
   567  func (fw *Firewaller) startUnit(unit *firewaller.Unit, machineTag names.MachineTag) error {
   568  	application, err := unit.Application()
   569  	if err != nil {
   570  		return err
   571  	}
   572  
   573  	applicationTag := application.Tag()
   574  	unitTag := unit.Tag()
   575  	unitd := &unitData{
   576  		fw:   fw,
   577  		unit: unit,
   578  		tag:  unitTag,
   579  	}
   580  	fw.unitds[unitTag] = unitd
   581  
   582  	unitd.machined = fw.machineds[machineTag]
   583  	unitd.machined.unitds[unitTag] = unitd
   584  	if fw.applicationids[applicationTag] == nil {
   585  		err := fw.startApplication(application)
   586  		if err != nil {
   587  			delete(fw.unitds, unitTag)
   588  			delete(unitd.machined.unitds, unitTag)
   589  			return err
   590  		}
   591  	}
   592  	unitd.applicationd = fw.applicationids[applicationTag]
   593  	unitd.applicationd.unitds[unitTag] = unitd
   594  
   595  	if err = fw.openedPortsChanged(machineTag); err != nil {
   596  		return errors.Trace(err)
   597  	}
   598  
   599  	return nil
   600  }
   601  
   602  // startApplication creates a new data value for tracking details of the
   603  // application and starts watching the application for exposure changes.
   604  func (fw *Firewaller) startApplication(app *firewaller.Application) error {
   605  	exposed, exposedEndpoints, err := app.ExposeInfo()
   606  	if err != nil {
   607  		return err
   608  	}
   609  	applicationd := &applicationData{
   610  		fw:               fw,
   611  		application:      app,
   612  		exposed:          exposed,
   613  		exposedEndpoints: exposedEndpoints,
   614  		unitds:           make(map[names.UnitTag]*unitData),
   615  	}
   616  	fw.applicationids[app.Tag()] = applicationd
   617  
   618  	err = catacomb.Invoke(catacomb.Plan{
   619  		Site: &applicationd.catacomb,
   620  		Work: func() error {
   621  			return applicationd.watchLoop(exposed, exposedEndpoints)
   622  		},
   623  	})
   624  	if err != nil {
   625  		return errors.Trace(err)
   626  	}
   627  	if err := fw.catacomb.Add(applicationd); err != nil {
   628  		return errors.Trace(err)
   629  	}
   630  	return nil
   631  }
   632  
   633  // reconcileGlobal compares the initially started watcher for machines,
   634  // units and applications with the opened and closed ports globally and
   635  // opens and closes the appropriate ports for the whole environment.
   636  func (fw *Firewaller) reconcileGlobal() error {
   637  	var machines []*machineData
   638  	for _, machined := range fw.machineds {
   639  		machines = append(machines, machined)
   640  	}
   641  	want, err := fw.gatherIngressRules(machines...)
   642  	if err != nil {
   643  		return err
   644  	}
   645  	ctx := stdcontext.Background()
   646  	initialPortRanges, err := fw.environFirewaller.IngressRules(fw.cloudCallContextFunc(ctx))
   647  	if err != nil {
   648  		return err
   649  	}
   650  
   651  	// Check which ports to open or to close.
   652  	toOpen, toClose := initialPortRanges.Diff(want)
   653  	if len(toOpen) > 0 {
   654  		fw.logger.Infof("opening global ports %v", toOpen)
   655  		if err := fw.environFirewaller.OpenPorts(fw.cloudCallContextFunc(ctx), toOpen); err != nil {
   656  			return errors.Annotatef(err, "failed to open global ports %v", toOpen)
   657  		}
   658  	}
   659  	if len(toClose) > 0 {
   660  		fw.logger.Infof("closing global ports %v", toClose)
   661  		if err := fw.environFirewaller.ClosePorts(fw.cloudCallContextFunc(ctx), toClose); err != nil {
   662  			return errors.Annotatef(err, "failed to close global ports %v", toClose)
   663  		}
   664  	}
   665  	return nil
   666  }
   667  
   668  // reconcileInstances compares the initially started watcher for machines,
   669  // units and applications with the opened and closed ports of the instances and
   670  // opens and closes the appropriate ports for each instance.
   671  func (fw *Firewaller) reconcileInstances() error {
   672  	for _, machined := range fw.machineds {
   673  		m, err := machined.machine()
   674  		if params.IsCodeNotFound(err) {
   675  			if err := fw.forgetMachine(machined); err != nil {
   676  				return err
   677  			}
   678  			continue
   679  		}
   680  		if err != nil {
   681  			return err
   682  		}
   683  		instanceId, err := m.InstanceId()
   684  		if errors.IsNotProvisioned(err) {
   685  			fw.logger.Errorf("Machine not yet provisioned: %v", err)
   686  			continue
   687  		}
   688  		if err != nil {
   689  			return err
   690  		}
   691  		ctx := stdcontext.Background()
   692  		envInstances, err := fw.environInstances.Instances(fw.cloudCallContextFunc(ctx), []instance.Id{instanceId})
   693  		if err == environs.ErrNoInstances {
   694  			return nil
   695  		}
   696  		if err != nil {
   697  			return err
   698  		}
   699  		machineId := machined.tag.Id()
   700  
   701  		fwInstance, ok := envInstances[0].(instances.InstanceFirewaller)
   702  		if !ok {
   703  			return nil
   704  		}
   705  
   706  		initialRules, err := fwInstance.IngressRules(fw.cloudCallContextFunc(ctx), machineId)
   707  		if err != nil {
   708  			return err
   709  		}
   710  
   711  		// Check which ports to open or to close.
   712  		toOpen, toClose := initialRules.Diff(machined.ingressRules)
   713  		if len(toOpen) > 0 {
   714  			fw.logger.Infof("opening instance port ranges %v for %q",
   715  				toOpen, machined.tag)
   716  			if err := fwInstance.OpenPorts(fw.cloudCallContextFunc(ctx), machineId, toOpen); err != nil {
   717  				// TODO(mue) Add local retry logic.
   718  				return errors.Annotatef(err, "failed to open instance ports %v for %q", toOpen, machined.tag)
   719  			}
   720  		}
   721  		if len(toClose) > 0 {
   722  			fw.logger.Infof("closing instance port ranges %v for %q",
   723  				toClose, machined.tag)
   724  			if err := fwInstance.ClosePorts(fw.cloudCallContextFunc(ctx), machineId, toClose); err != nil {
   725  				// TODO(mue) Add local retry logic.
   726  				return errors.Annotatef(err, "failed to close instance ports %v for %q", toOpen, machined.tag)
   727  			}
   728  		}
   729  	}
   730  	return nil
   731  }
   732  
   733  // unitsChanged responds to changes to the assigned units.
   734  func (fw *Firewaller) unitsChanged(change *unitsChange) error {
   735  	changed := []*unitData{}
   736  	for _, name := range change.units {
   737  		unitTag := names.NewUnitTag(name)
   738  		unit, err := fw.firewallerApi.Unit(unitTag)
   739  		if err != nil && !params.IsCodeNotFound(err) {
   740  			return err
   741  		}
   742  		var machineTag names.MachineTag
   743  		if unit != nil {
   744  			machineTag, err = unit.AssignedMachine()
   745  			if params.IsCodeNotFound(err) {
   746  				continue
   747  			} else if err != nil && !params.IsCodeNotAssigned(err) {
   748  				return err
   749  			}
   750  		}
   751  		if unitd, known := fw.unitds[unitTag]; known {
   752  			knownMachineTag := fw.unitds[unitTag].machined.tag
   753  			if unit == nil || unit.Life() == life.Dead || machineTag != knownMachineTag {
   754  				fw.forgetUnit(unitd)
   755  				changed = append(changed, unitd)
   756  				fw.logger.Debugf("stopped watching unit %s", name)
   757  			}
   758  		} else if unit != nil && unit.Life() != life.Dead && fw.machineds[machineTag] != nil {
   759  			err = fw.startUnit(unit, machineTag)
   760  			if params.IsCodeNotFound(err) {
   761  				continue
   762  			}
   763  			if err != nil {
   764  				return err
   765  			}
   766  			changed = append(changed, fw.unitds[unitTag])
   767  			fw.logger.Debugf("started watching %q", unitTag)
   768  		}
   769  	}
   770  	if err := fw.flushUnits(changed); err != nil {
   771  		return errors.Annotate(err, "cannot change firewall ports")
   772  	}
   773  	return nil
   774  }
   775  
   776  // openedPortsChanged handles port change notifications
   777  func (fw *Firewaller) openedPortsChanged(machineTag names.MachineTag) (err error) {
   778  	defer func() {
   779  		if params.IsCodeNotFound(err) {
   780  			err = nil
   781  		}
   782  	}()
   783  	machined, ok := fw.machineds[machineTag]
   784  	if !ok {
   785  		// It is common to receive a port change notification before
   786  		// registering the machine, so if a machine is not found in
   787  		// firewaller's list, just skip the change.  Look up will also
   788  		// fail if it's a manual machine.
   789  		fw.logger.Debugf("failed to lookup %q, skipping port change", machineTag)
   790  		return nil
   791  	}
   792  
   793  	m, err := machined.machine()
   794  	if err != nil {
   795  		return err
   796  	}
   797  
   798  	_, opendPortRangesByEndpoint, err := m.OpenedMachinePortRanges()
   799  	if err != nil {
   800  		return err
   801  	}
   802  
   803  	// Check for missing units and defer the handling of this change for
   804  	// the future.
   805  	for unitTag := range opendPortRangesByEndpoint {
   806  		if _, ok := machined.unitds[unitTag]; !ok {
   807  			// It is common to receive port change notification before
   808  			// registering a unit. Skip handling the port change - it will
   809  			// be handled when the unit is registered.
   810  			fw.logger.Debugf("failed to lookup %q, skipping port change", unitTag)
   811  			return nil
   812  		}
   813  	}
   814  
   815  	if equalGroupedPortRanges(machined.openedPortRangesByEndpoint, opendPortRangesByEndpoint) {
   816  		return nil // no change
   817  	}
   818  
   819  	machined.openedPortRangesByEndpoint = opendPortRangesByEndpoint
   820  	return fw.flushMachine(machined)
   821  }
   822  
   823  func equalGroupedPortRanges(a, b map[names.UnitTag]network.GroupedPortRanges) bool {
   824  	if len(a) != len(b) {
   825  		return false
   826  	}
   827  	for unitTag, portRangesA := range a {
   828  		portRangesB, exists := b[unitTag]
   829  		if !exists || !portRangesA.EqualTo(portRangesB) {
   830  			return false
   831  		}
   832  	}
   833  	return true
   834  }
   835  
   836  // flushUnits opens and closes ports for the passed unit data.
   837  func (fw *Firewaller) flushUnits(unitds []*unitData) error {
   838  	machineds := map[names.MachineTag]*machineData{}
   839  	for _, unitd := range unitds {
   840  		machineds[unitd.machined.tag] = unitd.machined
   841  	}
   842  	for _, machined := range machineds {
   843  		if err := fw.flushMachine(machined); err != nil {
   844  			return err
   845  		}
   846  	}
   847  	return nil
   848  }
   849  
   850  // flushMachine opens and closes ports for the passed machine.
   851  func (fw *Firewaller) flushMachine(machined *machineData) error {
   852  	want, err := fw.gatherIngressRules(machined)
   853  	if err != nil {
   854  		return errors.Trace(err)
   855  	}
   856  	toOpen, toClose := machined.ingressRules.Diff(want)
   857  	machined.ingressRules = want
   858  	if fw.globalMode {
   859  		return fw.flushGlobalPorts(toOpen, toClose)
   860  	}
   861  	return fw.flushInstancePorts(machined, toOpen, toClose)
   862  }
   863  
   864  // gatherIngressRules returns the ingress rules to open and close
   865  // for the specified machines.
   866  func (fw *Firewaller) gatherIngressRules(machines ...*machineData) (firewall.IngressRules, error) {
   867  	var want firewall.IngressRules
   868  	for _, machined := range machines {
   869  		for unitTag := range machined.openedPortRangesByEndpoint {
   870  			unitd, known := machined.unitds[unitTag]
   871  			if !known {
   872  				fw.logger.Debugf("no ingress rules for unknown %v on %v", unitTag, machined.tag)
   873  				continue
   874  			}
   875  
   876  			unitRules, err := fw.ingressRulesForMachineUnit(machined, unitd)
   877  			if err != nil {
   878  				return nil, errors.Trace(err)
   879  			}
   880  
   881  			want = append(want, unitRules...)
   882  		}
   883  	}
   884  	if err := want.Validate(); err != nil {
   885  		return nil, errors.Trace(err)
   886  	}
   887  
   888  	// Substrates that do not support IPV6 CIDRs will complain if we pass
   889  	// an IPV6 CIDR. To work around this issue, we filter out any IPV6
   890  	// CIDRs from the collected ingress rule list.
   891  	if !fw.envIPV6CIDRSupport {
   892  		want = want.RemoveCIDRsMatchingAddressType(network.IPv6Address)
   893  	}
   894  
   895  	return want, nil
   896  }
   897  
   898  func (fw *Firewaller) ingressRulesForMachineUnit(machine *machineData, unit *unitData) (firewall.IngressRules, error) {
   899  	unitPortRanges := machine.openedPortRangesByEndpoint[unit.tag]
   900  	if len(unitPortRanges) == 0 {
   901  		return nil, nil // no ports opened by the charm
   902  	}
   903  
   904  	var rules firewall.IngressRules
   905  	var err error
   906  	if unit.applicationd.exposed {
   907  		rules = fw.ingressRulesForExposedMachineUnit(machine, unit, unitPortRanges)
   908  	} else {
   909  		if rules, err = fw.ingressRulesForNonExposedMachineUnit(unit.applicationd.application.Tag(),
   910  			unitPortRanges); err != nil {
   911  			return nil, errors.Trace(err)
   912  		}
   913  	}
   914  
   915  	// De-dup and sort rules before returning them back.
   916  	rules = rules.UniqueRules()
   917  	sort.Slice(rules, func(i, j int) bool { return rules[i].LessThan(rules[j]) })
   918  	fw.logger.Debugf("ingress rules for %q: %v", unit.tag, rules)
   919  	return rules, nil
   920  }
   921  
   922  func (fw *Firewaller) ingressRulesForNonExposedMachineUnit(appTag names.ApplicationTag,
   923  	openUnitPortRanges network.GroupedPortRanges) (firewall.IngressRules, error) {
   924  	// Not exposed, so add any ingress rules required by remote relations.
   925  	srcCIDRs, err := fw.updateForRemoteRelationIngress(appTag)
   926  	if err != nil || len(srcCIDRs) == 0 {
   927  		return nil, errors.Trace(err)
   928  	}
   929  
   930  	var rules firewall.IngressRules
   931  	for _, portRange := range openUnitPortRanges.UniquePortRanges() {
   932  		rules = append(rules, firewall.NewIngressRule(portRange, srcCIDRs.Values()...))
   933  	}
   934  
   935  	return rules, nil
   936  }
   937  
   938  func (fw *Firewaller) ingressRulesForExposedMachineUnit(machine *machineData, unit *unitData,
   939  	openUnitPortRanges network.GroupedPortRanges) firewall.IngressRules {
   940  	var (
   941  		exposedEndpoints = unit.applicationd.exposedEndpoints
   942  		rules            firewall.IngressRules
   943  	)
   944  
   945  	for exposedEndpoint, exposeDetails := range exposedEndpoints {
   946  		// Collect the operator-provided CIDRs that should be able to
   947  		// access the port ranges opened for this endpoint; then resolve
   948  		// the CIDRs for the spaces specified in the expose details to
   949  		// construct the full source CIDR list for the generated rules.
   950  		srcCIDRs := set.NewStrings(exposeDetails.ExposeToCIDRs...)
   951  		for _, spaceID := range exposeDetails.ExposeToSpaces {
   952  			sp := fw.spaceInfos.GetByID(spaceID)
   953  			if sp == nil {
   954  				fw.logger.Warningf("exposed endpoint references unknown space ID %q", spaceID)
   955  				continue
   956  			}
   957  
   958  			if len(sp.Subnets) == 0 {
   959  				if exposedEndpoint == "" {
   960  					fw.logger.Warningf("all endpoints of application %q are exposed to space %q which contains no subnets",
   961  						unit.applicationd.application.Name(), sp.Name)
   962  				} else {
   963  					fw.logger.Warningf("endpoint %q application %q are exposed to space %q which contains no subnets",
   964  						exposedEndpoint, unit.applicationd.application.Name(), sp.Name)
   965  				}
   966  			}
   967  			for _, subnet := range sp.Subnets {
   968  				srcCIDRs.Add(subnet.CIDR)
   969  			}
   970  		}
   971  
   972  		if len(srcCIDRs) == 0 {
   973  			continue // no rules required
   974  		}
   975  
   976  		// If this is a named (i.e. not the wildcard) endpoint, look up
   977  		// the port ranges opened for *all* endpoints as well as for
   978  		// that endpoint name specifically, and create ingress rules.
   979  		if exposedEndpoint != "" {
   980  			for _, portRange := range openUnitPortRanges[exposedEndpoint] { // ports opened for this endpoint
   981  				rules = append(rules, firewall.NewIngressRule(portRange, srcCIDRs.Values()...))
   982  			}
   983  			for _, portRange := range openUnitPortRanges[""] { // ports opened for ALL endpoints
   984  				rules = append(rules, firewall.NewIngressRule(portRange, srcCIDRs.Values()...))
   985  			}
   986  			continue
   987  		}
   988  
   989  		// Create ingress rules for all endpoints except the ones that
   990  		// have their own dedicated entry in the exposed endpoints map.
   991  		for endpointName, portRanges := range openUnitPortRanges {
   992  			// This non-wildcard endpoint has an entry in the exposed
   993  			// endpoints list that override the global expose-all
   994  			// entry so we should skip it.
   995  			if _, hasExposeOverride := exposedEndpoints[endpointName]; hasExposeOverride && endpointName != "" {
   996  				continue
   997  			}
   998  
   999  			for _, portRange := range portRanges {
  1000  				rules = append(rules, firewall.NewIngressRule(portRange, srcCIDRs.Values()...))
  1001  			}
  1002  		}
  1003  	}
  1004  
  1005  	return rules
  1006  }
  1007  
  1008  // TODO(wallyworld) - consider making this configurable.
  1009  const maxAllowedCIDRS = 20
  1010  
  1011  func (fw *Firewaller) updateForRemoteRelationIngress(appTag names.ApplicationTag) (set.Strings, error) {
  1012  	fw.logger.Debugf("finding egress rules for %v", appTag)
  1013  	// Now create the rules for any remote relations of which the
  1014  	// unit's application is a part.
  1015  	cidrs := make(set.Strings)
  1016  	for _, data := range fw.relationIngress {
  1017  		if data.localApplicationTag != appTag {
  1018  			continue
  1019  		}
  1020  		if !data.ingressRequired {
  1021  			continue
  1022  		}
  1023  		for _, cidr := range data.networks.Values() {
  1024  			cidrs.Add(cidr)
  1025  		}
  1026  	}
  1027  	// If we have too many CIDRs to create a rule for, consolidate.
  1028  	// If a firewall rule with a whitelist of CIDRs has been set up,
  1029  	// use that, else open to the world.
  1030  	if cidrs.Size() > maxAllowedCIDRS {
  1031  		// First, try and merge the cidrs.
  1032  		merged, err := cidrman.MergeCIDRs(cidrs.Values())
  1033  		if err != nil {
  1034  			return nil, errors.Trace(err)
  1035  		}
  1036  		cidrs = set.NewStrings(merged...)
  1037  	}
  1038  
  1039  	// If there's still too many after merging, look for any firewall whitelist.
  1040  	if cidrs.Size() > maxAllowedCIDRS {
  1041  		cfg, err := fw.firewallerApi.ModelConfig()
  1042  		if err != nil {
  1043  			return nil, errors.Trace(err)
  1044  		}
  1045  		whitelistCidrs := cfg.SAASIngressAllow()
  1046  		cidrs = set.NewStrings(whitelistCidrs...)
  1047  	}
  1048  
  1049  	return cidrs, nil
  1050  }
  1051  
  1052  // flushGlobalPorts opens and closes global ports in the environment.
  1053  // It keeps a reference count for ports so that only 0-to-1 and 1-to-0 events
  1054  // modify the environment.
  1055  func (fw *Firewaller) flushGlobalPorts(rawOpen, rawClose firewall.IngressRules) error {
  1056  	// Filter which ports are really to open or close.
  1057  	var toOpen, toClose firewall.IngressRules
  1058  	for _, rule := range rawOpen {
  1059  		ruleName := rule.String()
  1060  		if fw.globalIngressRuleRef[ruleName] == 0 {
  1061  			toOpen = append(toOpen, rule)
  1062  		}
  1063  		fw.globalIngressRuleRef[ruleName]++
  1064  	}
  1065  	for _, rule := range rawClose {
  1066  		ruleName := rule.String()
  1067  		fw.globalIngressRuleRef[ruleName]--
  1068  		if fw.globalIngressRuleRef[ruleName] == 0 {
  1069  			toClose = append(toClose, rule)
  1070  			delete(fw.globalIngressRuleRef, ruleName)
  1071  		}
  1072  	}
  1073  	ctx := stdcontext.Background()
  1074  	// Open and close the ports.
  1075  	if len(toOpen) > 0 {
  1076  		toOpen.Sort()
  1077  		fw.logger.Infof("opening port ranges %v in environment", toOpen)
  1078  		if err := fw.environFirewaller.OpenPorts(fw.cloudCallContextFunc(ctx), toOpen); err != nil {
  1079  			// TODO(mue) Add local retry logic.
  1080  			return errors.Annotatef(err, "failed to open port ranges %v in environment", toOpen)
  1081  		}
  1082  	}
  1083  	if len(toClose) > 0 {
  1084  		toClose.Sort()
  1085  		fw.logger.Infof("closing port ranges %v in environment", toClose)
  1086  		if err := fw.environFirewaller.ClosePorts(fw.cloudCallContextFunc(ctx), toClose); err != nil {
  1087  			// TODO(mue) Add local retry logic.
  1088  			return errors.Annotatef(err, "failed to close port ranges %v in environment", toOpen)
  1089  		}
  1090  	}
  1091  	return nil
  1092  }
  1093  
  1094  func (fw *Firewaller) flushModel() error {
  1095  	if fw.environModelFirewaller == nil {
  1096  		return nil
  1097  	}
  1098  	want, err := fw.firewallerApi.ModelFirewallRules()
  1099  	if err != nil {
  1100  		return errors.Trace(err)
  1101  	}
  1102  
  1103  	ctx := stdcontext.Background()
  1104  	curr, err := fw.environModelFirewaller.ModelIngressRules(fw.cloudCallContextFunc(ctx))
  1105  	if err != nil {
  1106  		return errors.Trace(err)
  1107  	}
  1108  
  1109  	toOpen, toClose := curr.Diff(want)
  1110  	if len(toOpen) > 0 {
  1111  		toOpen.Sort()
  1112  		fw.logger.Infof("opening port ranges %v on model firewall", toOpen)
  1113  		if err := fw.environModelFirewaller.OpenModelPorts(fw.cloudCallContextFunc(ctx), toOpen); err != nil {
  1114  			// TODO(mue) Add local retry logic.
  1115  			return errors.Annotatef(err, "failed to open port ranges %v on model firewall", toOpen)
  1116  		}
  1117  	}
  1118  	if len(toClose) > 0 {
  1119  		toClose.Sort()
  1120  		fw.logger.Infof("closing port ranges %v on model firewall", toClose)
  1121  		if err := fw.environModelFirewaller.CloseModelPorts(fw.cloudCallContextFunc(ctx), toClose); err != nil {
  1122  			// TODO(mue) Add local retry logic.
  1123  			return errors.Annotatef(err, "failed to close port ranges %v on model firewall", toOpen)
  1124  		}
  1125  	}
  1126  	if fw.flushModelNotify != nil {
  1127  		fw.flushModelNotify()
  1128  	}
  1129  	return nil
  1130  }
  1131  
  1132  // flushInstancePorts opens and closes ports global on the machine.
  1133  func (fw *Firewaller) flushInstancePorts(machined *machineData, toOpen, toClose firewall.IngressRules) (err error) {
  1134  	defer func() {
  1135  		if params.IsCodeNotFound(err) {
  1136  			err = nil
  1137  		}
  1138  	}()
  1139  
  1140  	// If there's nothing to do, do nothing.
  1141  	// This is important because when a machine is first created,
  1142  	// it will have no instance id but also no open ports -
  1143  	// InstanceId will fail but we don't care.
  1144  	fw.logger.Debugf("flush instance ports: to open %v, to close %v", toOpen, toClose)
  1145  	if len(toOpen) == 0 && len(toClose) == 0 {
  1146  		return nil
  1147  	}
  1148  	m, err := machined.machine()
  1149  	if err != nil {
  1150  		return err
  1151  	}
  1152  	machineId := machined.tag.Id()
  1153  	instanceId, err := m.InstanceId()
  1154  	if errors.IsNotProvisioned(err) {
  1155  		// Not provisioned yet, so nothing to do for this instance
  1156  		return nil
  1157  	}
  1158  	if err != nil {
  1159  		return err
  1160  	}
  1161  	ctx := stdcontext.Background()
  1162  	envInstances, err := fw.environInstances.Instances(fw.cloudCallContextFunc(ctx), []instance.Id{instanceId})
  1163  	if err != nil {
  1164  		return err
  1165  	}
  1166  	fwInstance, ok := envInstances[0].(instances.InstanceFirewaller)
  1167  	if !ok {
  1168  		fw.logger.Infof("flushInstancePorts called on an instance of type %T which doesn't support firewall.",
  1169  			envInstances[0])
  1170  		return nil
  1171  	}
  1172  
  1173  	// Open and close the ports.
  1174  	if len(toOpen) > 0 {
  1175  		toOpen.Sort()
  1176  		if err := fwInstance.OpenPorts(fw.cloudCallContextFunc(ctx), machineId, toOpen); err != nil {
  1177  			// TODO(mue) Add local retry logic.
  1178  			return err
  1179  		}
  1180  		fw.logger.Infof("opened port ranges %v on %q", toOpen, machined.tag)
  1181  	}
  1182  	if len(toClose) > 0 {
  1183  		toClose.Sort()
  1184  		if err := fwInstance.ClosePorts(fw.cloudCallContextFunc(ctx), machineId, toClose); err != nil {
  1185  			// TODO(mue) Add local retry logic.
  1186  			return err
  1187  		}
  1188  		fw.logger.Infof("closed port ranges %v on %q", toClose, machined.tag)
  1189  	}
  1190  	return nil
  1191  }
  1192  
  1193  // machineLifeChanged starts watching new machines when the firewaller
  1194  // is starting, or when new machines come to life, and stops watching
  1195  // machines that are dying.
  1196  func (fw *Firewaller) machineLifeChanged(tag names.MachineTag) error {
  1197  	m, err := fw.firewallerApi.Machine(tag)
  1198  	found := !params.IsCodeNotFound(err)
  1199  	if found && err != nil {
  1200  		return err
  1201  	}
  1202  	dead := !found || m.Life() == life.Dead
  1203  	machined, known := fw.machineds[tag]
  1204  	if known && dead {
  1205  		return fw.forgetMachine(machined)
  1206  	}
  1207  	if !known && !dead {
  1208  		err := fw.startMachine(tag)
  1209  		if err != nil {
  1210  			return err
  1211  		}
  1212  	}
  1213  	return nil
  1214  }
  1215  
  1216  // forgetMachine cleans the machine data after the machine is removed.
  1217  func (fw *Firewaller) forgetMachine(machined *machineData) error {
  1218  	for _, unitd := range machined.unitds {
  1219  		fw.forgetUnit(unitd)
  1220  	}
  1221  	if err := fw.flushMachine(machined); err != nil {
  1222  		return errors.Trace(err)
  1223  	}
  1224  
  1225  	// Unusually, it's fine to ignore this error, because we know the machined
  1226  	// is being tracked in fw.catacomb. But we do still want to wait until the
  1227  	// watch loop has stopped before we nuke the last data and return.
  1228  	_ = worker.Stop(machined)
  1229  	delete(fw.machineds, machined.tag)
  1230  	fw.logger.Debugf("stopped watching %q", machined.tag)
  1231  	return nil
  1232  }
  1233  
  1234  // forgetUnit cleans the unit data after the unit is removed.
  1235  func (fw *Firewaller) forgetUnit(unitd *unitData) {
  1236  	applicationd := unitd.applicationd
  1237  	machined := unitd.machined
  1238  
  1239  	// If it's the last unit in the application, we'll need to stop the applicationd.
  1240  	stoppedApplication := false
  1241  	if len(applicationd.unitds) == 1 {
  1242  		if _, found := applicationd.unitds[unitd.tag]; found {
  1243  			// Unusually, it's fine to ignore this error, because we know the
  1244  			// applicationd is being tracked in fw.catacomb. But we do still want
  1245  			// to wait until the watch loop has stopped before we nuke the last
  1246  			// data and return.
  1247  			_ = worker.Stop(applicationd)
  1248  			stoppedApplication = true
  1249  		}
  1250  	}
  1251  
  1252  	// Clean up after stopping.
  1253  	delete(fw.unitds, unitd.tag)
  1254  	delete(machined.unitds, unitd.tag)
  1255  	delete(applicationd.unitds, unitd.tag)
  1256  	fw.logger.Debugf("stopped watching %q", unitd.tag)
  1257  	if stoppedApplication {
  1258  		applicationTag := applicationd.application.Tag()
  1259  		delete(fw.applicationids, applicationTag)
  1260  		fw.logger.Debugf("stopped watching %q", applicationTag)
  1261  	}
  1262  }
  1263  
  1264  // Kill is part of the worker.Worker interface.
  1265  func (fw *Firewaller) Kill() {
  1266  	fw.catacomb.Kill(nil)
  1267  }
  1268  
  1269  // Wait is part of the worker.Worker interface.
  1270  func (fw *Firewaller) Wait() error {
  1271  	return fw.catacomb.Wait()
  1272  }
  1273  
  1274  // unitsChange contains the changed units for one specific machine.
  1275  type unitsChange struct {
  1276  	machined *machineData
  1277  	units    []string
  1278  }
  1279  
  1280  // machineData holds machine details and watches units added or removed.
  1281  type machineData struct {
  1282  	catacomb     catacomb.Catacomb
  1283  	fw           *Firewaller
  1284  	tag          names.MachineTag
  1285  	unitds       map[names.UnitTag]*unitData
  1286  	ingressRules firewall.IngressRules
  1287  	// ports defined by units on this machine
  1288  	openedPortRangesByEndpoint map[names.UnitTag]network.GroupedPortRanges
  1289  }
  1290  
  1291  func (md *machineData) machine() (*firewaller.Machine, error) {
  1292  	return md.fw.firewallerApi.Machine(md.tag)
  1293  }
  1294  
  1295  // watchLoop watches the machine for units added or removed.
  1296  func (md *machineData) watchLoop(unitw watcher.StringsWatcher) error {
  1297  	if err := md.catacomb.Add(unitw); err != nil {
  1298  		return errors.Trace(err)
  1299  	}
  1300  	for {
  1301  		select {
  1302  		case <-md.catacomb.Dying():
  1303  			return md.catacomb.ErrDying()
  1304  		case change, ok := <-unitw.Changes():
  1305  			if !ok {
  1306  				return errors.New("machine units watcher closed")
  1307  			}
  1308  			select {
  1309  			case <-md.catacomb.Dying():
  1310  				return md.catacomb.ErrDying()
  1311  			case md.fw.unitsChange <- &unitsChange{md, change}:
  1312  			}
  1313  		}
  1314  	}
  1315  }
  1316  
  1317  // Kill is part of the worker.Worker interface.
  1318  func (md *machineData) Kill() {
  1319  	md.catacomb.Kill(nil)
  1320  }
  1321  
  1322  // Wait is part of the worker.Worker interface.
  1323  func (md *machineData) Wait() error {
  1324  	return md.catacomb.Wait()
  1325  }
  1326  
  1327  // unitData holds unit details.
  1328  type unitData struct {
  1329  	fw           *Firewaller
  1330  	tag          names.UnitTag
  1331  	unit         *firewaller.Unit
  1332  	applicationd *applicationData
  1333  	machined     *machineData
  1334  }
  1335  
  1336  // exposedChange contains the changed exposed flag for one specific application.
  1337  type exposedChange struct {
  1338  	applicationd     *applicationData
  1339  	exposed          bool
  1340  	exposedEndpoints map[string]params.ExposedEndpoint
  1341  }
  1342  
  1343  // applicationData holds application details and watches exposure changes.
  1344  type applicationData struct {
  1345  	catacomb         catacomb.Catacomb
  1346  	fw               *Firewaller
  1347  	application      *firewaller.Application
  1348  	exposed          bool
  1349  	exposedEndpoints map[string]params.ExposedEndpoint
  1350  	unitds           map[names.UnitTag]*unitData
  1351  }
  1352  
  1353  // watchLoop watches the application's exposed flag for changes.
  1354  func (ad *applicationData) watchLoop(curExposed bool, curExposedEndpoints map[string]params.ExposedEndpoint) error {
  1355  	appWatcher, err := ad.application.Watch()
  1356  	if err != nil {
  1357  		if params.IsCodeNotFound(err) {
  1358  			return nil
  1359  		}
  1360  		return errors.Trace(err)
  1361  	}
  1362  	if err := ad.catacomb.Add(appWatcher); err != nil {
  1363  		return errors.Trace(err)
  1364  	}
  1365  	for {
  1366  		select {
  1367  		case <-ad.catacomb.Dying():
  1368  			return ad.catacomb.ErrDying()
  1369  		case _, ok := <-appWatcher.Changes():
  1370  			if !ok {
  1371  				return errors.New("application watcher closed")
  1372  			}
  1373  			newExposed, newExposedEndpoints, err := ad.application.ExposeInfo()
  1374  			if err != nil {
  1375  				if errors.IsNotFound(err) {
  1376  					ad.fw.logger.Debugf("application(%q).IsExposed() returned NotFound: %v", ad.application.Name(), err)
  1377  					return nil
  1378  				}
  1379  				return errors.Trace(err)
  1380  			}
  1381  			if curExposed == newExposed && equalExposedEndpoints(curExposedEndpoints, newExposedEndpoints) {
  1382  				ad.fw.logger.Tracef("application(%q) expose settings unchanged: exposed: %v, exposedEndpoints: %v",
  1383  					ad.application.Name(), curExposed, curExposedEndpoints)
  1384  				continue
  1385  			}
  1386  			ad.fw.logger.Tracef("application(%q) expose settings changed: exposed: %v, exposedEndpoints: %v",
  1387  				ad.application.Name(), newExposed, newExposedEndpoints)
  1388  
  1389  			curExposed, curExposedEndpoints = newExposed, newExposedEndpoints
  1390  			select {
  1391  			case <-ad.catacomb.Dying():
  1392  				return ad.catacomb.ErrDying()
  1393  			case ad.fw.exposedChange <- &exposedChange{ad, newExposed, newExposedEndpoints}:
  1394  			}
  1395  		}
  1396  	}
  1397  }
  1398  
  1399  func equalExposedEndpoints(a, b map[string]params.ExposedEndpoint) bool {
  1400  	if len(a) != len(b) {
  1401  		return false
  1402  	}
  1403  
  1404  	for endpoint, exposeDetailsA := range a {
  1405  		exposeDetailsB, found := b[endpoint]
  1406  		if !found {
  1407  			return false
  1408  		}
  1409  
  1410  		if !equalStringSlices(exposeDetailsA.ExposeToSpaces, exposeDetailsB.ExposeToSpaces) ||
  1411  			!equalStringSlices(exposeDetailsA.ExposeToCIDRs, exposeDetailsB.ExposeToCIDRs) {
  1412  			return false
  1413  		}
  1414  
  1415  	}
  1416  
  1417  	return true
  1418  }
  1419  
  1420  func equalStringSlices(a, b []string) bool {
  1421  	if len(a) != len(b) {
  1422  		return false
  1423  	}
  1424  
  1425  	setA := set.NewStrings(a...)
  1426  	setB := set.NewStrings(b...)
  1427  	return setA.Difference(setB).IsEmpty()
  1428  }
  1429  
  1430  // Kill is part of the worker.Worker interface.
  1431  func (ad *applicationData) Kill() {
  1432  	ad.catacomb.Kill(nil)
  1433  }
  1434  
  1435  // Wait is part of the worker.Worker interface.
  1436  func (ad *applicationData) Wait() error {
  1437  	return ad.catacomb.Wait()
  1438  }
  1439  
  1440  // relationLifeChanged manages the workers to process ingress changes for
  1441  // the specified relation.
  1442  func (fw *Firewaller) relationLifeChanged(tag names.RelationTag) error {
  1443  	results, err := fw.remoteRelationsApi.Relations([]string{tag.Id()})
  1444  	if err != nil {
  1445  		return errors.Trace(err)
  1446  	}
  1447  	relErr := results[0].Error
  1448  	notfound := relErr != nil && params.IsCodeNotFound(relErr)
  1449  	if relErr != nil && !notfound {
  1450  		return err
  1451  	}
  1452  	rel := results[0].Result
  1453  
  1454  	gone := notfound || rel.Life == life.Dead || rel.Suspended
  1455  	data, known := fw.relationIngress[tag]
  1456  	if known && gone {
  1457  		fw.logger.Debugf("relation %v was known but has died or been suspended", tag.Id())
  1458  		// If relation is suspended, shut off ingress immediately.
  1459  		// Units will also eventually leave scope which would cause
  1460  		// ingress to be shut off, but best to do it up front.
  1461  		if rel != nil && rel.Suspended {
  1462  			change := &remoteRelationNetworkChange{
  1463  				relationTag:         tag,
  1464  				localApplicationTag: data.localApplicationTag,
  1465  				ingressRequired:     false,
  1466  			}
  1467  			if err := fw.relationIngressChanged(change); err != nil {
  1468  				return errors.Trace(err)
  1469  			}
  1470  		}
  1471  		return fw.forgetRelation(data)
  1472  	}
  1473  	if !known && !gone {
  1474  		err := fw.startRelation(rel, rel.Endpoint.Role)
  1475  		if err != nil {
  1476  			return err
  1477  		}
  1478  	}
  1479  	return nil
  1480  }
  1481  
  1482  type remoteRelationInfo struct {
  1483  	relationToken string
  1484  }
  1485  
  1486  type remoteRelationData struct {
  1487  	catacomb      catacomb.Catacomb
  1488  	fw            *Firewaller
  1489  	relationReady chan remoteRelationInfo
  1490  
  1491  	tag                 names.RelationTag
  1492  	localApplicationTag names.ApplicationTag
  1493  	relationToken       string
  1494  	remoteModelUUID     string
  1495  	endpointRole        charm.RelationRole
  1496  	isOffer             bool
  1497  
  1498  	crossModelFirewallerFacade CrossModelFirewallerFacadeCloser
  1499  
  1500  	// These values are updated when ingress information on the
  1501  	// relation changes in the model.
  1502  	ingressRequired bool
  1503  	networks        set.Strings
  1504  }
  1505  
  1506  // startRelation creates a new data value for tracking details of the
  1507  // relation and starts watching the related models for subnets added or removed.
  1508  func (fw *Firewaller) startRelation(rel *params.RemoteRelation, role charm.RelationRole) error {
  1509  	remoteApps, err := fw.remoteRelationsApi.RemoteApplications([]string{rel.RemoteApplicationName})
  1510  	if err != nil {
  1511  		return errors.Trace(err)
  1512  	}
  1513  	remoteAppResult := remoteApps[0]
  1514  	if remoteAppResult.Error != nil {
  1515  		return errors.Trace(err)
  1516  	}
  1517  
  1518  	tag := names.NewRelationTag(rel.Key)
  1519  	data := &remoteRelationData{
  1520  		fw:                  fw,
  1521  		tag:                 tag,
  1522  		remoteModelUUID:     rel.SourceModelUUID,
  1523  		localApplicationTag: names.NewApplicationTag(rel.ApplicationName),
  1524  		endpointRole:        role,
  1525  		relationReady:       make(chan remoteRelationInfo),
  1526  	}
  1527  
  1528  	// Start the worker which will watch the remote relation for things like new networks.
  1529  	// We use ReplaceWorker since the relation may have been removed and we are re-adding it.
  1530  	if err := fw.relationWorkerRunner.StartWorker(tag.Id(), func() (worker.Worker, error) {
  1531  		// This may be a restart after an api error, so ensure any previous
  1532  		// worker is killed and the catacomb is reset.
  1533  		data.Kill()
  1534  		data.catacomb = catacomb.Catacomb{}
  1535  		if err := catacomb.Invoke(catacomb.Plan{
  1536  			Site: &data.catacomb,
  1537  			Work: data.watchLoop,
  1538  		}); err != nil {
  1539  			return nil, errors.Trace(err)
  1540  		}
  1541  		return data, nil
  1542  	}); err != nil {
  1543  		return errors.Annotate(err, "error starting remote relation worker")
  1544  	}
  1545  	fw.relationIngress[tag] = data
  1546  
  1547  	data.isOffer = remoteAppResult.Result.IsConsumerProxy
  1548  	return fw.startRelationPoller(rel.Key, rel.RemoteApplicationName, data.relationReady)
  1549  }
  1550  
  1551  // watchLoop watches the relation for networks added or removed.
  1552  func (rd *remoteRelationData) watchLoop() error {
  1553  	defer func() {
  1554  		if rd.crossModelFirewallerFacade != nil {
  1555  			rd.crossModelFirewallerFacade.Close()
  1556  		}
  1557  	}()
  1558  
  1559  	// First, wait for relation to become ready.
  1560  	for rd.relationToken == "" {
  1561  		select {
  1562  		case <-rd.catacomb.Dying():
  1563  			return rd.catacomb.ErrDying()
  1564  		case remoteRelationInfo := <-rd.relationReady:
  1565  			rd.relationToken = remoteRelationInfo.relationToken
  1566  			rd.fw.logger.Debugf(
  1567  				"relation %v in model %v is ready",
  1568  				rd.relationToken, rd.remoteModelUUID)
  1569  		}
  1570  	}
  1571  
  1572  	if rd.endpointRole == charm.RoleRequirer {
  1573  		return rd.requirerEndpointLoop()
  1574  	}
  1575  	return rd.providerEndpointLoop()
  1576  }
  1577  
  1578  func (rd *remoteRelationData) requirerEndpointLoop() error {
  1579  	// If the requirer end of the relation is on the offering model,
  1580  	// there's nothing to do here because the provider end on the
  1581  	// consuming model will be watching for changes.
  1582  	// TODO(wallyworld) - this will change if we want to allow bidirectional traffic.
  1583  	if rd.isOffer {
  1584  		return nil
  1585  	}
  1586  
  1587  	rd.fw.logger.Debugf("starting requirer endpoint loop for %v on %v ", rd.tag.Id(), rd.localApplicationTag.Id())
  1588  	// Now watch for updates to egress addresses so we can inform the offering
  1589  	// model what firewall ingress to allow.
  1590  	egressAddressWatcher, err := rd.fw.firewallerApi.WatchEgressAddressesForRelation(rd.tag)
  1591  	if err != nil {
  1592  		if !params.IsCodeNotFound(err) && !params.IsCodeNotSupported(err) {
  1593  			return errors.Trace(err)
  1594  		}
  1595  		rd.fw.logger.Infof("no egress required for %v", rd.localApplicationTag)
  1596  		rd.ingressRequired = false
  1597  		return nil
  1598  	}
  1599  	if err := rd.catacomb.Add(egressAddressWatcher); err != nil {
  1600  		return errors.Trace(err)
  1601  	}
  1602  	for {
  1603  		select {
  1604  		case <-rd.catacomb.Dying():
  1605  			return rd.catacomb.ErrDying()
  1606  		case cidrs := <-egressAddressWatcher.Changes():
  1607  			rd.fw.logger.Debugf("relation egress addresses for %v changed in model %v: %v", rd.tag, rd.fw.modelUUID,
  1608  				cidrs)
  1609  			if err := rd.updateProviderModel(cidrs); err != nil {
  1610  				return errors.Trace(err)
  1611  			}
  1612  		}
  1613  	}
  1614  }
  1615  
  1616  func (rd *remoteRelationData) providerEndpointLoop() error {
  1617  	rd.fw.logger.Debugf("starting provider endpoint loop for %v on %v ", rd.tag.Id(), rd.localApplicationTag.Id())
  1618  	// Watch for ingress changes requested by the consuming model.
  1619  	ingressAddressWatcher, err := rd.ingressAddressWatcher()
  1620  	if err != nil {
  1621  		if !params.IsCodeNotFound(err) && !params.IsCodeNotSupported(err) {
  1622  			return errors.Trace(err)
  1623  		}
  1624  		rd.fw.logger.Infof("no ingress required for %v", rd.localApplicationTag)
  1625  		rd.ingressRequired = false
  1626  		return nil
  1627  	}
  1628  	if err := rd.catacomb.Add(ingressAddressWatcher); err != nil {
  1629  		return errors.Trace(err)
  1630  	}
  1631  	for {
  1632  		select {
  1633  		case <-rd.catacomb.Dying():
  1634  			return rd.catacomb.ErrDying()
  1635  		case cidrs := <-ingressAddressWatcher.Changes():
  1636  			rd.fw.logger.Debugf("relation ingress addresses for %v changed in model %v: %v", rd.tag, rd.fw.modelUUID,
  1637  				cidrs)
  1638  			if err := rd.updateIngressNetworks(cidrs); err != nil {
  1639  				return errors.Trace(err)
  1640  			}
  1641  		}
  1642  	}
  1643  }
  1644  
  1645  func (rd *remoteRelationData) ingressAddressWatcher() (watcher.StringsWatcher, error) {
  1646  	if rd.isOffer {
  1647  		// On the offering side we watch the local model for ingress changes
  1648  		// which will have been published from the consuming model.
  1649  		return rd.fw.firewallerApi.WatchIngressAddressesForRelation(rd.tag)
  1650  	} else {
  1651  		// On the consuming side, if this is the provider end of the relation,
  1652  		// we watch the remote model's egress changes to get our ingress changes.
  1653  		apiInfo, err := rd.fw.firewallerApi.ControllerAPIInfoForModel(rd.remoteModelUUID)
  1654  		if err != nil {
  1655  			return nil, errors.Annotatef(err, "cannot get api info for model %v", rd.remoteModelUUID)
  1656  		}
  1657  		rd.crossModelFirewallerFacade, err = rd.fw.newRemoteFirewallerAPIFunc(apiInfo)
  1658  		if err != nil {
  1659  			return nil, errors.Annotate(err, "cannot open facade to remote model to watch ingress addresses")
  1660  		}
  1661  
  1662  		mac, err := rd.fw.firewallerApi.MacaroonForRelation(rd.tag.Id())
  1663  		if err != nil {
  1664  			return nil, errors.Annotatef(err, "cannot get macaroon for %v", rd.tag.Id())
  1665  		}
  1666  		arg := params.RemoteEntityArg{
  1667  			Token:     rd.relationToken,
  1668  			Macaroons: macaroon.Slice{mac},
  1669  		}
  1670  		return rd.crossModelFirewallerFacade.WatchEgressAddressesForRelation(arg)
  1671  	}
  1672  }
  1673  
  1674  type remoteRelationNetworkChange struct {
  1675  	relationTag         names.RelationTag
  1676  	localApplicationTag names.ApplicationTag
  1677  	networks            set.Strings
  1678  	ingressRequired     bool
  1679  }
  1680  
  1681  // updateProviderModel gathers the ingress CIDRs for the relation and notifies
  1682  // that a change has occurred.
  1683  func (rd *remoteRelationData) updateProviderModel(cidrs []string) error {
  1684  	rd.fw.logger.Debugf("ingress cidrs for %v: %+v", rd.tag, cidrs)
  1685  	change := &remoteRelationNetworkChange{
  1686  		relationTag:         rd.tag,
  1687  		localApplicationTag: rd.localApplicationTag,
  1688  		networks:            set.NewStrings(cidrs...),
  1689  		ingressRequired:     len(cidrs) > 0,
  1690  	}
  1691  
  1692  	apiInfo, err := rd.fw.firewallerApi.ControllerAPIInfoForModel(rd.remoteModelUUID)
  1693  	if err != nil {
  1694  		return errors.Annotatef(err, "cannot get api info for model %v", rd.remoteModelUUID)
  1695  	}
  1696  	mac, err := rd.fw.firewallerApi.MacaroonForRelation(rd.tag.Id())
  1697  	if params.IsCodeNotFound(err) {
  1698  		// Relation has gone, nothing to do.
  1699  		return nil
  1700  	}
  1701  	if err != nil {
  1702  		return errors.Annotatef(err, "cannot get macaroon for %v", rd.tag.Id())
  1703  	}
  1704  	remoteModelAPI, err := rd.fw.newRemoteFirewallerAPIFunc(apiInfo)
  1705  	if err != nil {
  1706  		return errors.Annotate(err, "cannot open facade to remote model to publish network change")
  1707  	}
  1708  	defer remoteModelAPI.Close()
  1709  	event := params.IngressNetworksChangeEvent{
  1710  		RelationToken:   rd.relationToken,
  1711  		Networks:        change.networks.Values(),
  1712  		IngressRequired: change.ingressRequired,
  1713  		Macaroons:       macaroon.Slice{mac},
  1714  		BakeryVersion:   bakery.LatestVersion,
  1715  	}
  1716  	err = remoteModelAPI.PublishIngressNetworkChange(event)
  1717  	if errors.IsNotFound(err) {
  1718  		rd.fw.logger.Debugf("relation id not found publishing %+v", event)
  1719  		return nil
  1720  	}
  1721  
  1722  	// If the requested ingress is not permitted on the offering side,
  1723  	// mark the relation as in error. It's not an error that requires a
  1724  	// worker restart though.
  1725  	if params.IsCodeForbidden(err) {
  1726  		return rd.fw.firewallerApi.SetRelationStatus(rd.tag.Id(), relation.Error, err.Error())
  1727  	}
  1728  	return errors.Annotate(err, "cannot publish ingress network change")
  1729  }
  1730  
  1731  // updateIngressNetworks processes the changed ingress networks on the relation.
  1732  func (rd *remoteRelationData) updateIngressNetworks(cidrs []string) error {
  1733  	rd.fw.logger.Debugf("ingress cidrs for %v: %+v", rd.tag, cidrs)
  1734  	change := &remoteRelationNetworkChange{
  1735  		relationTag:         rd.tag,
  1736  		localApplicationTag: rd.localApplicationTag,
  1737  		networks:            set.NewStrings(cidrs...),
  1738  		ingressRequired:     len(cidrs) > 0,
  1739  	}
  1740  	select {
  1741  	case <-rd.catacomb.Dying():
  1742  		return rd.catacomb.ErrDying()
  1743  	case rd.fw.localRelationsChange <- change:
  1744  	}
  1745  	return nil
  1746  }
  1747  
  1748  // Kill is part of the worker.Worker interface.
  1749  func (rd *remoteRelationData) Kill() {
  1750  	rd.catacomb.Kill(nil)
  1751  }
  1752  
  1753  // Wait is part of the worker.Worker interface.
  1754  func (rd *remoteRelationData) Wait() error {
  1755  	return rd.catacomb.Wait()
  1756  }
  1757  
  1758  // forgetRelation cleans the relation data after the relation is removed.
  1759  func (fw *Firewaller) forgetRelation(data *remoteRelationData) error {
  1760  	fw.logger.Debugf("forget relation %v", data.tag.Id())
  1761  	delete(fw.relationIngress, data.tag)
  1762  	// There's not much we can do if there's an error stopping the remote
  1763  	// relation worker, so just log it.
  1764  	if err := fw.relationWorkerRunner.StopAndRemoveWorker(data.tag.Id(), fw.catacomb.Dying()); err != nil {
  1765  		fw.logger.Errorf("error stopping remote relation worker for %s: %v", data.tag, err)
  1766  	}
  1767  	fw.logger.Debugf("stopped watching %q", data.tag)
  1768  	return nil
  1769  }
  1770  
  1771  type remoteRelationPoller struct {
  1772  	catacomb       catacomb.Catacomb
  1773  	fw             *Firewaller
  1774  	relationTag    names.RelationTag
  1775  	applicationTag names.ApplicationTag
  1776  	relationReady  chan remoteRelationInfo
  1777  }
  1778  
  1779  // startRelationPoller creates a new worker which waits until a remote
  1780  // relation is registered in both models.
  1781  func (fw *Firewaller) startRelationPoller(relationKey, remoteAppName string,
  1782  	relationReady chan remoteRelationInfo) error {
  1783  	poller := &remoteRelationPoller{
  1784  		fw:             fw,
  1785  		relationTag:    names.NewRelationTag(relationKey),
  1786  		applicationTag: names.NewApplicationTag(remoteAppName),
  1787  		relationReady:  relationReady,
  1788  	}
  1789  
  1790  	err := catacomb.Invoke(catacomb.Plan{
  1791  		Site: &poller.catacomb,
  1792  		Work: poller.pollLoop,
  1793  	})
  1794  	if err != nil {
  1795  		return errors.Trace(err)
  1796  	}
  1797  
  1798  	// register poller with the firewaller's catacomb.
  1799  	return fw.catacomb.Add(poller)
  1800  }
  1801  
  1802  // pollLoop waits for a remote relation to be registered.
  1803  // It does this by waiting for the relation and app tokens to be created.
  1804  func (p *remoteRelationPoller) pollLoop() error {
  1805  	p.fw.logger.Debugf("polling for relation %v on %v to be ready", p.relationTag, p.applicationTag)
  1806  	for {
  1807  		select {
  1808  		case <-p.catacomb.Dying():
  1809  			return p.catacomb.ErrDying()
  1810  		case <-p.fw.clk.After(3 * time.Second):
  1811  			// Relation is exported with the consuming model UUID.
  1812  			relToken, err := p.fw.remoteRelationsApi.GetToken(p.relationTag)
  1813  			if err != nil {
  1814  				continue
  1815  			}
  1816  			p.fw.logger.Debugf("token %v for relation id: %v in model %v", relToken, p.relationTag.Id(), p.fw.modelUUID)
  1817  			relationInfo := remoteRelationInfo{
  1818  				relationToken: relToken,
  1819  			}
  1820  			select {
  1821  			case <-p.catacomb.Dying():
  1822  				return p.catacomb.ErrDying()
  1823  			case p.relationReady <- relationInfo:
  1824  			}
  1825  			return nil
  1826  		}
  1827  	}
  1828  }
  1829  
  1830  // Kill is part of the worker.Worker interface.
  1831  func (p *remoteRelationPoller) Kill() {
  1832  	p.catacomb.Kill(nil)
  1833  }
  1834  
  1835  // Wait is part of the worker.Worker interface.
  1836  func (p *remoteRelationPoller) Wait() error {
  1837  	return p.catacomb.Wait()
  1838  }