github.com/juju/juju@v0.0.0-20240430160146-1752b71fcf00/apiserver/common/firewall/egressaddresswatcher.go (about)

     1  // Copyright 2017 Canonical Ltd.
     2  // Licensed under the AGPLv3, see LICENCE file for details.
     3  
     4  package firewall
     5  
     6  import (
     7  	"github.com/juju/collections/set"
     8  	"github.com/juju/errors"
     9  	"github.com/juju/worker/v3"
    10  	"github.com/juju/worker/v3/catacomb"
    11  
    12  	"github.com/juju/juju/core/network"
    13  	"github.com/juju/juju/core/watcher"
    14  )
    15  
    16  // EgressAddressWatcher reports changes to addresses
    17  // for local units in a given relation.
    18  // Each event contains the entire set of addresses which
    19  // are required for ingress on the remote side of the relation.
    20  type EgressAddressWatcher struct {
    21  	catacomb catacomb.Catacomb
    22  
    23  	backend State
    24  	appName string
    25  	rel     Relation
    26  
    27  	out chan []string
    28  
    29  	// Channel for machineAddressWatchers to report individual machine
    30  	// updates.
    31  	addressChanges chan string
    32  
    33  	// A map of machine id to machine data.
    34  	machines map[string]*machineData
    35  
    36  	// A map of machine id by unit name - this is needed because we
    37  	// might not be able to retrieve the machine name when a unit
    38  	// leaves scope if it's been completely removed by the time we look.
    39  	unitToMachine map[string]string
    40  
    41  	// A map of known unit addresses, keyed on unit name.
    42  	known map[string]string
    43  
    44  	// A set of known egress cidrs for the model.
    45  	knownModelEgress set.Strings
    46  
    47  	// A set of known egress cidrs for the relation.
    48  	knownRelationEgress set.Strings
    49  }
    50  
    51  // machineData holds the information we track at the machine level.
    52  type machineData struct {
    53  	units  set.Strings
    54  	worker *machineAddressWorker
    55  }
    56  
    57  // NewEgressAddressWatcher creates an EgressAddressWatcher.
    58  func NewEgressAddressWatcher(backend State, rel Relation, appName string) (*EgressAddressWatcher, error) {
    59  	w := &EgressAddressWatcher{
    60  		backend:          backend,
    61  		appName:          appName,
    62  		rel:              rel,
    63  		known:            make(map[string]string),
    64  		out:              make(chan []string),
    65  		addressChanges:   make(chan string),
    66  		machines:         make(map[string]*machineData),
    67  		unitToMachine:    make(map[string]string),
    68  		knownModelEgress: set.NewStrings(),
    69  	}
    70  	err := catacomb.Invoke(catacomb.Plan{
    71  		Site: &w.catacomb,
    72  		Work: w.loop,
    73  	})
    74  	return w, err
    75  }
    76  
    77  func (w *EgressAddressWatcher) loop() error {
    78  	defer close(w.out)
    79  
    80  	ruw, err := w.rel.WatchUnits(w.appName)
    81  	if errors.IsNotFound(err) {
    82  		return nil
    83  	}
    84  	if err != nil {
    85  		return errors.Trace(err)
    86  	}
    87  	if err := w.catacomb.Add(ruw); err != nil {
    88  		return errors.Trace(err)
    89  	}
    90  
    91  	// TODO(wallyworld) - we just want to watch for egress
    92  	// address changes but right now can only watch for
    93  	// any model config change.
    94  	mw := w.backend.WatchForModelConfigChanges()
    95  	if err := w.catacomb.Add(mw); err != nil {
    96  		return errors.Trace(err)
    97  	}
    98  
    99  	rw := w.rel.WatchRelationEgressNetworks()
   100  	if err := w.catacomb.Add(rw); err != nil {
   101  		return errors.Trace(err)
   102  	}
   103  
   104  	var (
   105  		changed       bool
   106  		sentInitial   bool
   107  		out           chan<- []string
   108  		addresses     set.Strings
   109  		lastAddresses set.Strings
   110  		addressesCIDR []string
   111  	)
   112  
   113  	// Wait for each of the watchers started above to
   114  	// send an initial change before sending any changes
   115  	// from this watcher.
   116  	var haveInitialRelationUnits bool
   117  	var haveInitialRelationEgressNetworks bool
   118  	var haveInitialModelConfig bool
   119  
   120  	for {
   121  		var ready bool
   122  		if !sentInitial {
   123  			ready = haveInitialRelationUnits && haveInitialRelationEgressNetworks && haveInitialModelConfig
   124  		}
   125  		if ready || changed {
   126  			addresses = nil
   127  			if len(w.known) > 0 {
   128  				// Egress CIDRs, if configured, override unit
   129  				// machine addresses. Relation CIDRs take
   130  				// precedence over those specified in model
   131  				// config.
   132  				addresses = set.NewStrings(w.knownRelationEgress.Values()...)
   133  				if addresses.Size() == 0 {
   134  					addresses = set.NewStrings(w.knownModelEgress.Values()...)
   135  				}
   136  				if addresses.Size() == 0 {
   137  					// No user configured egress so just use the unit addresses.
   138  					for _, addr := range w.known {
   139  						addresses.Add(addr)
   140  					}
   141  				}
   142  			}
   143  			changed = false
   144  			if !setEquals(addresses, lastAddresses) {
   145  				addressesCIDR = network.SubnetsForAddresses(addresses.Values())
   146  				ready = ready || sentInitial
   147  			}
   148  		}
   149  		if ready {
   150  			out = w.out
   151  		}
   152  
   153  		select {
   154  		case <-w.catacomb.Dying():
   155  			return w.catacomb.ErrDying()
   156  
   157  		case out <- addressesCIDR:
   158  			sentInitial = true
   159  			lastAddresses = addresses
   160  			out = nil
   161  
   162  		case _, ok := <-mw.Changes():
   163  			if !ok {
   164  				return w.catacomb.ErrDying()
   165  			}
   166  			cfg, err := w.backend.ModelConfig()
   167  			if err != nil {
   168  				return err
   169  			}
   170  			haveInitialModelConfig = true
   171  			egress := set.NewStrings(cfg.EgressSubnets()...)
   172  			if !setEquals(egress, w.knownModelEgress) {
   173  				logger.Debugf(
   174  					"model config egress subnets changed to %s (was %s)",
   175  					egress.SortedValues(),
   176  					w.knownModelEgress.SortedValues(),
   177  				)
   178  				changed = true
   179  				w.knownModelEgress = egress
   180  			}
   181  
   182  		case changes, ok := <-rw.Changes():
   183  			if !ok {
   184  				return w.catacomb.ErrDying()
   185  			}
   186  			haveInitialRelationEgressNetworks = true
   187  			egress := set.NewStrings(changes...)
   188  			if !setEquals(egress, w.knownRelationEgress) {
   189  				logger.Debugf(
   190  					"relation egress subnets changed to %s (was %s)",
   191  					egress.SortedValues(),
   192  					w.knownRelationEgress.SortedValues(),
   193  				)
   194  				changed = true
   195  				w.knownRelationEgress = egress
   196  			}
   197  
   198  		case c, ok := <-ruw.Changes():
   199  			if !ok {
   200  				return w.catacomb.ErrDying()
   201  			}
   202  			// A unit has entered or left scope.
   203  			// Get the new set of addresses resulting from that
   204  			// change, and if different to what we know, send the change.
   205  			haveInitialRelationUnits = true
   206  			addressesChanged, err := w.processUnitChanges(c)
   207  			if err != nil {
   208  				return err
   209  			}
   210  			changed = changed || addressesChanged
   211  
   212  		case machineId, ok := <-w.addressChanges:
   213  			if !ok {
   214  				continue
   215  			}
   216  			addressesChanged, err := w.processMachineAddresses(machineId)
   217  			if err != nil {
   218  				return errors.Trace(err)
   219  			}
   220  			changed = changed || addressesChanged
   221  		}
   222  	}
   223  }
   224  
   225  func (w *EgressAddressWatcher) unitAddress(unit Unit) (string, bool, error) {
   226  	addr, err := unit.PublicAddress()
   227  	if errors.IsNotAssigned(err) {
   228  		logger.Debugf("unit %s is not assigned to a machine, can't get address", unit.Name())
   229  		return "", false, nil
   230  	}
   231  	if network.IsNoAddressError(err) {
   232  		logger.Debugf("unit %s has no public address", unit.Name())
   233  		return "", false, nil
   234  	}
   235  	if err != nil {
   236  		return "", false, err
   237  	}
   238  	logger.Debugf("unit %q has public address %q", unit.Name(), addr.Value)
   239  	return addr.Value, true, nil
   240  }
   241  
   242  func (w *EgressAddressWatcher) processUnitChanges(c watcher.RelationUnitsChange) (bool, error) {
   243  	changed := false
   244  	for name := range c.Changed {
   245  
   246  		u, err := w.backend.Unit(name)
   247  		if errors.IsNotFound(err) {
   248  			continue
   249  		}
   250  		if err != nil {
   251  			return false, err
   252  		}
   253  
   254  		if err := w.trackUnit(u); err != nil {
   255  			return false, errors.Trace(err)
   256  		}
   257  
   258  		// We need to know whether to look at the public or cloud local address.
   259  		// For now, we'll use the public address and later if needed use a watcher
   260  		// parameter to look at the cloud local address.
   261  		addr, ok, err := w.unitAddress(u)
   262  		if err != nil {
   263  			return false, err
   264  		}
   265  		if !ok {
   266  			continue
   267  		}
   268  		if w.known[name] != addr {
   269  			w.known[name] = addr
   270  			changed = true
   271  		}
   272  	}
   273  	for _, name := range c.Departed {
   274  		if err := w.untrackUnit(name); err != nil {
   275  			return false, errors.Trace(err)
   276  		}
   277  		// If the unit is departing and we have seen its address,
   278  		// remove the address.
   279  		address, ok := w.known[name]
   280  		if !ok {
   281  			continue
   282  		}
   283  		delete(w.known, name)
   284  
   285  		// See if the address is still used by another unit.
   286  		inUse := false
   287  		for unit, addr := range w.known {
   288  			if name != unit && addr == address {
   289  				inUse = true
   290  				break
   291  			}
   292  		}
   293  		if !inUse {
   294  			changed = true
   295  		}
   296  	}
   297  	return changed, nil
   298  }
   299  
   300  func (w *EgressAddressWatcher) trackUnit(unit Unit) error {
   301  	machine, err := w.assignedMachine(unit)
   302  	if errors.IsNotAssigned(err) {
   303  		logger.Errorf("unit %q entered scope without a machine assigned - addresses will not be tracked", unit)
   304  		return nil
   305  	}
   306  	if err != nil {
   307  		return errors.Trace(err)
   308  	}
   309  
   310  	w.unitToMachine[unit.Name()] = machine.Id()
   311  	mData, ok := w.machines[machine.Id()]
   312  	if ok {
   313  		// We're already watching the machine, just add this unit.
   314  		mData.units.Add(unit.Name())
   315  		return nil
   316  	}
   317  
   318  	addressWorker, err := newMachineAddressWorker(machine, w.addressChanges)
   319  	if err != nil {
   320  		return errors.Trace(err)
   321  	}
   322  	w.machines[machine.Id()] = &machineData{
   323  		units:  set.NewStrings(unit.Name()),
   324  		worker: addressWorker,
   325  	}
   326  	err = w.catacomb.Add(addressWorker)
   327  	if err != nil {
   328  		return errors.Trace(err)
   329  	}
   330  	return nil
   331  }
   332  
   333  func (w *EgressAddressWatcher) untrackUnit(unitName string) error {
   334  	machineId, ok := w.unitToMachine[unitName]
   335  	if !ok {
   336  		logger.Errorf("missing machine id for unit %q", unitName)
   337  		return nil
   338  	}
   339  	delete(w.unitToMachine, unitName)
   340  
   341  	mData, ok := w.machines[machineId]
   342  	if !ok {
   343  		logger.Debugf("missing machine data for machine %q (hosting unit %q)", machineId, unitName)
   344  		return nil
   345  	}
   346  	mData.units.Remove(unitName)
   347  	if mData.units.Size() > 0 {
   348  		// No need to stop the watcher - there are still units on the
   349  		// machine.
   350  		return nil
   351  	}
   352  
   353  	err := worker.Stop(mData.worker)
   354  	if err != nil {
   355  		return errors.Trace(err)
   356  	}
   357  	delete(w.machines, machineId)
   358  	return nil
   359  }
   360  
   361  func (w *EgressAddressWatcher) assignedMachine(unit Unit) (Machine, error) {
   362  	machineId, err := unit.AssignedMachineId()
   363  	if err != nil {
   364  		return nil, errors.Trace(err)
   365  	}
   366  	machine, err := w.backend.Machine(machineId)
   367  	if err != nil {
   368  		return nil, errors.Trace(err)
   369  	}
   370  	return machine, nil
   371  }
   372  
   373  func (w *EgressAddressWatcher) processMachineAddresses(machineId string) (changed bool, err error) {
   374  	mData, ok := w.machines[machineId]
   375  	if !ok {
   376  		return false, errors.Errorf("missing machineData for machine %q", machineId)
   377  	}
   378  	for unitName := range mData.units {
   379  		unit, err := w.backend.Unit(unitName)
   380  		if errors.IsNotFound(err) {
   381  			continue
   382  		}
   383  		if err != nil {
   384  			return false, errors.Trace(err)
   385  		}
   386  		address, _, err := w.unitAddress(unit)
   387  		if err != nil {
   388  			return false, errors.Trace(err)
   389  		}
   390  		existingAddress := w.known[unitName]
   391  		if existingAddress != address {
   392  			w.known[unitName] = address
   393  			changed = true
   394  		}
   395  	}
   396  	return changed, nil
   397  }
   398  
   399  // Changes returns the event channel for this watcher.
   400  func (w *EgressAddressWatcher) Changes() <-chan []string {
   401  	return w.out
   402  }
   403  
   404  // Kill asks the watcher to stop without waiting for it do so.
   405  func (w *EgressAddressWatcher) Kill() {
   406  	w.catacomb.Kill(nil)
   407  }
   408  
   409  // Wait waits for the watcher to die and returns any
   410  // error encountered when it was running.
   411  func (w *EgressAddressWatcher) Wait() error {
   412  	return w.catacomb.Wait()
   413  }
   414  
   415  // Stop kills the watcher, then waits for it to die.
   416  func (w *EgressAddressWatcher) Stop() error {
   417  	w.Kill()
   418  	return w.Wait()
   419  }
   420  
   421  // Err returns any error encountered while the watcher
   422  // has been running.
   423  func (w *EgressAddressWatcher) Err() error {
   424  	return w.catacomb.Err()
   425  }
   426  
   427  func newMachineAddressWorker(machine Machine, out chan<- string) (*machineAddressWorker, error) {
   428  	w := &machineAddressWorker{
   429  		machine: machine,
   430  		out:     out,
   431  	}
   432  	err := catacomb.Invoke(catacomb.Plan{
   433  		Site: &w.catacomb,
   434  		Work: w.loop,
   435  	})
   436  	return w, errors.Trace(err)
   437  }
   438  
   439  // machineAddressWorker watches for machine address changes and
   440  // notifies the dest channel when it sees them.
   441  type machineAddressWorker struct {
   442  	catacomb catacomb.Catacomb
   443  	machine  Machine
   444  	out      chan<- string
   445  }
   446  
   447  func (w *machineAddressWorker) loop() error {
   448  	aw := w.machine.WatchAddresses()
   449  	if err := w.catacomb.Add(aw); err != nil {
   450  		return errors.Trace(err)
   451  	}
   452  	machineId := w.machine.Id()
   453  	var out chan<- string
   454  	for {
   455  		select {
   456  		case <-w.catacomb.Dying():
   457  			return w.catacomb.ErrDying()
   458  		case <-aw.Changes():
   459  			out = w.out
   460  		case out <- machineId:
   461  			out = nil
   462  		}
   463  	}
   464  }
   465  
   466  func (w *machineAddressWorker) Kill() {
   467  	w.catacomb.Kill(nil)
   468  }
   469  
   470  func (w *machineAddressWorker) Wait() error {
   471  	return w.catacomb.Wait()
   472  }
   473  
   474  func setEquals(a, b set.Strings) bool {
   475  	if a.Size() != b.Size() {
   476  		return false
   477  	}
   478  	return a.Intersection(b).Size() == a.Size()
   479  }