github.com/juju/juju@v0.0.0-20240430160146-1752b71fcf00/apiserver/facades/controller/instancepoller/merge.go (about)

     1  // Copyright 2020 Canonical Ltd.
     2  // Licensed under the AGPLv3, see LICENCE file for details.
     3  
     4  package instancepoller
     5  
     6  import (
     7  	"strings"
     8  
     9  	"github.com/juju/collections/set"
    10  	"github.com/juju/errors"
    11  	"github.com/juju/mgo/v3/txn"
    12  	jujutxn "github.com/juju/txn/v3"
    13  
    14  	"github.com/juju/juju/apiserver/common/networkingcommon"
    15  	"github.com/juju/juju/core/network"
    16  	"github.com/juju/juju/state"
    17  )
    18  
    19  // mergeMachineLinkLayerOp is a model operation used to merge incoming
    20  // provider-sourced network configuration with existing data for a single
    21  // machine/host/container.
    22  type mergeMachineLinkLayerOp struct {
    23  	*networkingcommon.MachineLinkLayerOp
    24  
    25  	// namelessHWAddrs stores the hardware addresses of
    26  	// incoming devices that have no accompanying name.
    27  	namelessHWAddrs set.Strings
    28  
    29  	// providerIDs is used for observing ID usage for incoming devices.
    30  	// We consult it to ensure that the same provider ID is not being
    31  	// used for multiple NICs.
    32  	providerIDs map[network.Id]string
    33  }
    34  
    35  func newMergeMachineLinkLayerOp(
    36  	machine networkingcommon.LinkLayerMachine, incoming network.InterfaceInfos,
    37  ) *mergeMachineLinkLayerOp {
    38  	return &mergeMachineLinkLayerOp{
    39  		MachineLinkLayerOp: networkingcommon.NewMachineLinkLayerOp("provider", machine, incoming),
    40  		namelessHWAddrs:    set.NewStrings(),
    41  	}
    42  }
    43  
    44  // Build (state.ModelOperation) returns the transaction operations used to
    45  // merge incoming provider link-layer data with that in state.
    46  func (o *mergeMachineLinkLayerOp) Build(attempt int) ([]txn.Op, error) {
    47  	o.ClearProcessed()
    48  	o.providerIDs = make(map[network.Id]string)
    49  
    50  	if err := o.PopulateExistingDevices(); err != nil {
    51  		return nil, errors.Trace(err)
    52  	}
    53  
    54  	// If the machine agent has not yet populated any link-layer devices,
    55  	// then we do nothing here. We have already set addresses directly on the
    56  	// machine document, so the incoming provider-sourced addresses are usable.
    57  	// For now we ensure that the instance-poller only adds device information
    58  	// that the machine agent is unaware of.
    59  	if len(o.ExistingDevices()) == 0 {
    60  		return nil, jujutxn.ErrNoOperations
    61  	}
    62  
    63  	if attempt == 0 {
    64  		o.normaliseIncoming()
    65  	}
    66  
    67  	if err := o.PopulateExistingAddresses(); err != nil {
    68  		return nil, errors.Trace(err)
    69  	}
    70  
    71  	var ops []txn.Op
    72  	for _, existingDev := range o.ExistingDevices() {
    73  		devOps, err := o.processExistingDevice(existingDev)
    74  		if err != nil {
    75  			return nil, errors.Trace(err)
    76  		}
    77  		ops = append(ops, devOps...)
    78  	}
    79  
    80  	o.processNewDevices()
    81  
    82  	if len(ops) > 0 {
    83  		return append([]txn.Op{o.AssertAliveOp()}, ops...), nil
    84  	}
    85  	return ops, nil
    86  }
    87  
    88  // normaliseIncoming is intended accommodate providers such as EC2
    89  // that know device hardware addresses, but not device names.
    90  // We populate names on the incoming data based on
    91  // matching existing devices by hardware address.
    92  // If we locate multiple existing devices with the hardware address,
    93  // such as will be the case for bridged NICs, fallback through the
    94  // following options.
    95  //   - If there is a device that already has a provider ID, use that name.
    96  //   - If the devices are of different types, choose an ethernet device over
    97  //     a bridge (as observed for MAAS).
    98  func (o *mergeMachineLinkLayerOp) normaliseIncoming() {
    99  	incoming := o.Incoming()
   100  
   101  	// If the incoming devices have names, no action is required
   102  	// (assuming all or none here per current known provider implementations
   103  	// of `NetworkInterfaces`)
   104  	if len(incoming) > 0 && incoming[0].InterfaceName != "" {
   105  		return
   106  	}
   107  
   108  	// First get the best device per hardware address.
   109  	devByHWAddr := make(map[string]networkingcommon.LinkLayerDevice)
   110  	for _, dev := range o.ExistingDevices() {
   111  		hwAddr := dev.MACAddress()
   112  
   113  		// If this is the first one we've seen, select it.
   114  		current, ok := devByHWAddr[hwAddr]
   115  		if !ok {
   116  			devByHWAddr[hwAddr] = dev
   117  			continue
   118  		}
   119  
   120  		// If we have a matching device that already has a provider ID,
   121  		// I.e. it was previously matched to the hardware address,
   122  		// make sure the same one is resolved thereafter.
   123  		if current.ProviderID() != "" {
   124  			continue
   125  		}
   126  
   127  		// Otherwise choose a physical NIC over other device types.
   128  		if dev.Type() == network.EthernetDevice {
   129  			devByHWAddr[hwAddr] = dev
   130  		}
   131  	}
   132  
   133  	// Now set the names.
   134  	for i, dev := range incoming {
   135  		if existing, ok := devByHWAddr[dev.MACAddress]; ok && dev.InterfaceName == "" {
   136  			o.namelessHWAddrs.Add(dev.MACAddress)
   137  			incoming[i].InterfaceName = existing.Name()
   138  		}
   139  	}
   140  }
   141  
   142  func (o *mergeMachineLinkLayerOp) processExistingDevice(dev networkingcommon.LinkLayerDevice) ([]txn.Op, error) {
   143  	incomingDev := o.MatchingIncoming(dev)
   144  
   145  	var ops []txn.Op
   146  	var err error
   147  
   148  	// If this device was not observed by the provider *and* it is identified
   149  	// by both name and hardware address, ensure that responsibility for the
   150  	// addresses is relinquished to the machine agent.
   151  	if incomingDev == nil {
   152  		// If this device matches an incoming hardware address that we gave a
   153  		// surrogate name to, do not relinquish it.
   154  		if o.namelessHWAddrs.Contains(dev.MACAddress()) {
   155  			return nil, nil
   156  		}
   157  
   158  		ops, err = o.opsForDeviceOriginRelinquishment(dev)
   159  		return ops, errors.Trace(err)
   160  	}
   161  
   162  	// Log a warning if we are changing a provider ID that is already set.
   163  	providerID := dev.ProviderID()
   164  	if providerID != "" && providerID != incomingDev.ProviderId {
   165  		logger.Warningf(
   166  			"changing provider ID for device %q from %q to %q",
   167  			dev.Name(), providerID, incomingDev.ProviderId,
   168  		)
   169  	}
   170  
   171  	// Check that the incoming data is not using a provider ID for more
   172  	// than one device. This is not verified by transaction assertions.
   173  	if incomingDev.ProviderId != "" {
   174  		if usedBy, ok := o.providerIDs[incomingDev.ProviderId]; ok {
   175  			return nil, errors.Errorf(
   176  				"unable to set provider ID %q for multiple devices: %q, %q",
   177  				incomingDev.ProviderId, usedBy, dev.Name(),
   178  			)
   179  		}
   180  
   181  		o.providerIDs[incomingDev.ProviderId] = dev.Name()
   182  	}
   183  
   184  	ops, err = dev.SetProviderIDOps(incomingDev.ProviderId)
   185  	if err != nil {
   186  		if !state.IsProviderIDNotUniqueError(err) {
   187  			return nil, errors.Trace(err)
   188  		}
   189  
   190  		// If this provider ID is already assigned, log a warning and continue.
   191  		// If the ID is moving from one device to another for whatever reason,
   192  		// It will be eventually consistent. E.g. removed from the old device
   193  		// on this pass and added to the new device on the next.
   194  		logger.Warningf(
   195  			"not setting provider ID for device %q to %q; it is assigned to another device",
   196  			dev.Name(), incomingDev.ProviderId,
   197  		)
   198  	}
   199  
   200  	// Collect normalised addresses for the incoming device.
   201  	// TODO (manadart 2020-07-15): We also need to set shadow addresses.
   202  	// These are sent where appropriate by the provider,
   203  	// but we do not yet process them.
   204  	incomingAddrs := o.MatchingIncomingAddrs(dev.Name())
   205  
   206  	for _, addr := range o.DeviceAddresses(dev) {
   207  		addrOps, err := o.processExistingDeviceAddress(dev, addr, incomingAddrs)
   208  		if err != nil {
   209  			return nil, errors.Trace(err)
   210  		}
   211  		ops = append(ops, addrOps...)
   212  	}
   213  
   214  	// TODO (manadart 2020-07-15): Process (log) new addresses on the device.
   215  
   216  	o.MarkDevProcessed(dev.Name())
   217  	return ops, nil
   218  }
   219  
   220  // opsForDeviceOriginRelinquishment returns transaction operations required to
   221  // ensure that a device has no provider ID and that the origin for all
   222  // addresses on the device is relinquished to the machine.
   223  func (o *mergeMachineLinkLayerOp) opsForDeviceOriginRelinquishment(
   224  	dev networkingcommon.LinkLayerDevice,
   225  ) ([]txn.Op, error) {
   226  	ops, err := dev.SetProviderIDOps("")
   227  	if err != nil {
   228  		return nil, errors.Trace(err)
   229  	}
   230  
   231  	for _, addr := range o.DeviceAddresses(dev) {
   232  		ops = append(ops, addr.SetOriginOps(network.OriginMachine)...)
   233  	}
   234  
   235  	return ops, nil
   236  }
   237  
   238  func (o *mergeMachineLinkLayerOp) processExistingDeviceAddress(
   239  	dev networkingcommon.LinkLayerDevice,
   240  	addr networkingcommon.LinkLayerAddress,
   241  	incomingAddrs []state.LinkLayerDeviceAddress,
   242  ) ([]txn.Op, error) {
   243  	addrValue := addr.Value()
   244  	name := dev.Name()
   245  
   246  	// If one of the incoming addresses matches the existing one,
   247  	// return ops for setting the incoming provider IDs.
   248  	for _, incomingAddr := range incomingAddrs {
   249  		if strings.HasPrefix(incomingAddr.CIDRAddress, addrValue) {
   250  			if o.IsAddrProcessed(name, addrValue) {
   251  				continue
   252  			}
   253  
   254  			ops, err := addr.SetProviderIDOps(incomingAddr.ProviderID)
   255  			if err != nil {
   256  				return nil, errors.Trace(err)
   257  			}
   258  
   259  			o.MarkAddrProcessed(name, addrValue)
   260  
   261  			return append(ops, addr.SetProviderNetIDsOps(
   262  				incomingAddr.ProviderNetworkID, incomingAddr.ProviderSubnetID)...), nil
   263  		}
   264  	}
   265  
   266  	// Otherwise relinquish responsibility for this device to the machiner.
   267  	return addr.SetOriginOps(network.OriginMachine), nil
   268  }
   269  
   270  // processNewDevices handles incoming devices that did not match any we already
   271  // have in state.
   272  // TODO (manadart 2020-06-12): It should be unlikely for the provider to be
   273  // aware of devices that the machiner knows nothing about.
   274  // At the time of writing we preserve existing behaviour and do not add them.
   275  // Log for now and consider adding such devices in the future.
   276  func (o *mergeMachineLinkLayerOp) processNewDevices() {
   277  	for _, dev := range o.Incoming() {
   278  		if !o.IsDevProcessed(dev) {
   279  			logger.Debugf(
   280  				"ignoring unrecognised device %q (%s) with addresses %v",
   281  				dev.InterfaceName, dev.MACAddress, dev.Addresses,
   282  			)
   283  		}
   284  	}
   285  }