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 }