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