~nskaggs/+junk/xenial-test

« back to all changes in this revision

Viewing changes to src/github.com/juju/juju/apiserver/common/networkingcommon/types.go

  • Committer: Nicholas Skaggs
  • Date: 2016-10-24 20:56:05 UTC
  • Revision ID: nicholas.skaggs@canonical.com-20161024205605-z8lta0uvuhtxwzwl
Initi with beta15

Show diffs side-by-side

added added

removed removed

Lines of Context:
 
1
// Copyright 2015 Canonical Ltd.
 
2
// Licensed under the AGPLv3, see LICENCE file for details.
 
3
 
 
4
package networkingcommon
 
5
 
 
6
import (
 
7
        "encoding/json"
 
8
        "net"
 
9
        "regexp"
 
10
        "sort"
 
11
        "strings"
 
12
 
 
13
        "github.com/juju/errors"
 
14
        "github.com/juju/utils/set"
 
15
        "gopkg.in/juju/names.v2"
 
16
 
 
17
        "github.com/juju/juju/apiserver/params"
 
18
        "github.com/juju/juju/cloudconfig/instancecfg"
 
19
        "github.com/juju/juju/environs"
 
20
        "github.com/juju/juju/network"
 
21
        providercommon "github.com/juju/juju/provider/common"
 
22
        "github.com/juju/juju/state"
 
23
)
 
24
 
 
25
// BackingSubnet defines the methods supported by a Subnet entity
 
26
// stored persistently.
 
27
//
 
28
// TODO(dimitern): Once the state backing is implemented, remove this
 
29
// and just use *state.Subnet.
 
30
type BackingSubnet interface {
 
31
        CIDR() string
 
32
        VLANTag() int
 
33
        ProviderId() network.Id
 
34
        AvailabilityZones() []string
 
35
        Status() string
 
36
        SpaceName() string
 
37
        Life() params.Life
 
38
}
 
39
 
 
40
// BackingSubnetInfo describes a single subnet to be added in the
 
41
// backing store.
 
42
//
 
43
// TODO(dimitern): Replace state.SubnetInfo with this and remove
 
44
// BackingSubnetInfo, once the rest of state backing methods and the
 
45
// following pre-reqs are done:
 
46
// * subnetDoc.AvailabilityZone becomes subnetDoc.AvailabilityZones,
 
47
//   adding an upgrade step to migrate existing non empty zones on
 
48
//   subnet docs. Also change state.Subnet.AvailabilityZone to
 
49
// * add subnetDoc.SpaceName - no upgrade step needed, as it will only
 
50
//   be used for new space-aware subnets.
 
51
// * Subnets need a reference count to calculate Status.
 
52
// * ensure EC2 and MAAS providers accept empty IDs as Subnets() args
 
53
//   and return all subnets, including the AvailabilityZones (for EC2;
 
54
//   empty for MAAS as zones are orthogonal to networks).
 
55
type BackingSubnetInfo struct {
 
56
        // ProviderId is a provider-specific network id. This may be empty.
 
57
        ProviderId network.Id
 
58
 
 
59
        // CIDR of the network, in 123.45.67.89/24 format.
 
60
        CIDR string
 
61
 
 
62
        // VLANTag needs to be between 1 and 4094 for VLANs and 0 for normal
 
63
        // networks. It's defined by IEEE 802.1Q standard.
 
64
        VLANTag int
 
65
 
 
66
        // AvailabilityZones describes which availability zone(s) this
 
67
        // subnet is in. It can be empty if the provider does not support
 
68
        // availability zones.
 
69
        AvailabilityZones []string
 
70
 
 
71
        // SpaceName holds the juju network space this subnet is
 
72
        // associated with. Can be empty if not supported.
 
73
        SpaceName string
 
74
 
 
75
        // Status holds the status of the subnet. Normally this will be
 
76
        // calculated from the reference count and Life of a subnet.
 
77
        Status string
 
78
 
 
79
        // Live holds the life of the subnet
 
80
        Life params.Life
 
81
}
 
82
 
 
83
// BackingSpace defines the methods supported by a Space entity stored
 
84
// persistently.
 
85
type BackingSpace interface {
 
86
        // Name returns the space name.
 
87
        Name() string
 
88
 
 
89
        // Subnets returns the subnets in the space
 
90
        Subnets() ([]BackingSubnet, error)
 
91
 
 
92
        // ProviderId returns the network ID of the provider
 
93
        ProviderId() network.Id
 
94
 
 
95
        // Zones returns a list of availability zone(s) that this
 
96
        // space is in. It can be empty if the provider does not support
 
97
        // availability zones.
 
98
        Zones() []string
 
99
 
 
100
        // Life returns the lifecycle state of the space
 
101
        Life() params.Life
 
102
}
 
103
 
 
104
// Backing defines the methods needed by the API facade to store and
 
105
// retrieve information from the underlying persistency layer (state
 
106
// DB).
 
107
type NetworkBacking interface {
 
108
        environs.EnvironConfigGetter
 
109
 
 
110
        // AvailabilityZones returns all cached availability zones (i.e.
 
111
        // not from the provider, but in state).
 
112
        AvailabilityZones() ([]providercommon.AvailabilityZone, error)
 
113
 
 
114
        // SetAvailabilityZones replaces the cached list of availability
 
115
        // zones with the given zones.
 
116
        SetAvailabilityZones([]providercommon.AvailabilityZone) error
 
117
 
 
118
        // AddSpace creates a space
 
119
        AddSpace(Name string, ProviderId network.Id, Subnets []string, Public bool) error
 
120
 
 
121
        // AllSpaces returns all known Juju network spaces.
 
122
        AllSpaces() ([]BackingSpace, error)
 
123
 
 
124
        // AddSubnet creates a backing subnet for an existing subnet.
 
125
        AddSubnet(BackingSubnetInfo) (BackingSubnet, error)
 
126
 
 
127
        // AllSubnets returns all backing subnets.
 
128
        AllSubnets() ([]BackingSubnet, error)
 
129
}
 
130
 
 
131
func BackingSubnetToParamsSubnet(subnet BackingSubnet) params.Subnet {
 
132
        cidr := subnet.CIDR()
 
133
        vlantag := subnet.VLANTag()
 
134
        providerid := subnet.ProviderId()
 
135
        zones := subnet.AvailabilityZones()
 
136
        status := subnet.Status()
 
137
        var spaceTag names.SpaceTag
 
138
        if subnet.SpaceName() != "" {
 
139
                spaceTag = names.NewSpaceTag(subnet.SpaceName())
 
140
        }
 
141
 
 
142
        return params.Subnet{
 
143
                CIDR:       cidr,
 
144
                VLANTag:    vlantag,
 
145
                ProviderId: string(providerid),
 
146
                Zones:      zones,
 
147
                Status:     status,
 
148
                SpaceTag:   spaceTag.String(),
 
149
                Life:       subnet.Life(),
 
150
        }
 
151
}
 
152
 
 
153
type byMACThenCIDRThenIndexThenName []params.NetworkConfig
 
154
 
 
155
func (c byMACThenCIDRThenIndexThenName) Len() int {
 
156
        return len(c)
 
157
}
 
158
 
 
159
func (c byMACThenCIDRThenIndexThenName) Swap(i, j int) {
 
160
        orgI, orgJ := c[i], c[j]
 
161
        c[j], c[i] = orgI, orgJ
 
162
}
 
163
 
 
164
func (c byMACThenCIDRThenIndexThenName) Less(i, j int) bool {
 
165
        if c[i].MACAddress == c[j].MACAddress {
 
166
                // Same MACAddress means related interfaces.
 
167
                if c[i].CIDR == "" || c[j].CIDR == "" {
 
168
                        // Empty CIDRs go at the bottom, otherwise order by InterfaceName.
 
169
                        return c[i].CIDR != "" || c[i].InterfaceName < c[j].InterfaceName
 
170
                }
 
171
                if c[i].DeviceIndex == c[j].DeviceIndex {
 
172
                        if c[i].InterfaceName == c[j].InterfaceName {
 
173
                                // Sort addresses of the same interface.
 
174
                                return c[i].CIDR < c[j].CIDR || c[i].Address < c[j].Address
 
175
                        }
 
176
                        // Prefer shorter names (e.g. parents) with equal DeviceIndex.
 
177
                        return c[i].InterfaceName < c[j].InterfaceName
 
178
                }
 
179
                // When both CIDR and DeviceIndex are non-empty, order by DeviceIndex
 
180
                return c[i].DeviceIndex < c[j].DeviceIndex
 
181
        }
 
182
        // Group by MACAddress.
 
183
        return c[i].MACAddress < c[j].MACAddress
 
184
}
 
185
 
 
186
// SortNetworkConfigsByParents returns the given input sorted, such that any
 
187
// child interfaces appear after their parents.
 
188
func SortNetworkConfigsByParents(input []params.NetworkConfig) []params.NetworkConfig {
 
189
        sortedInputCopy := CopyNetworkConfigs(input)
 
190
        sort.Stable(byMACThenCIDRThenIndexThenName(sortedInputCopy))
 
191
        return sortedInputCopy
 
192
}
 
193
 
 
194
type byInterfaceName []params.NetworkConfig
 
195
 
 
196
func (c byInterfaceName) Len() int {
 
197
        return len(c)
 
198
}
 
199
 
 
200
func (c byInterfaceName) Swap(i, j int) {
 
201
        orgI, orgJ := c[i], c[j]
 
202
        c[j], c[i] = orgI, orgJ
 
203
}
 
204
 
 
205
func (c byInterfaceName) Less(i, j int) bool {
 
206
        return c[i].InterfaceName < c[j].InterfaceName
 
207
}
 
208
 
 
209
// SortNetworkConfigsByInterfaceName returns the given input sorted by
 
210
// InterfaceName.
 
211
func SortNetworkConfigsByInterfaceName(input []params.NetworkConfig) []params.NetworkConfig {
 
212
        sortedInputCopy := CopyNetworkConfigs(input)
 
213
        sort.Stable(byInterfaceName(sortedInputCopy))
 
214
        return sortedInputCopy
 
215
}
 
216
 
 
217
// NetworkConfigsToIndentedJSON returns the given input as an indented JSON
 
218
// string.
 
219
func NetworkConfigsToIndentedJSON(input []params.NetworkConfig) (string, error) {
 
220
        jsonBytes, err := json.MarshalIndent(input, "", "  ")
 
221
        if err != nil {
 
222
                return "", err
 
223
        }
 
224
        return string(jsonBytes), nil
 
225
}
 
226
 
 
227
// CopyNetworkConfigs returns a copy of the given input
 
228
func CopyNetworkConfigs(input []params.NetworkConfig) []params.NetworkConfig {
 
229
        return append([]params.NetworkConfig(nil), input...)
 
230
}
 
231
 
 
232
// NetworkConfigFromInterfaceInfo converts a slice of network.InterfaceInfo into
 
233
// the equivalent params.NetworkConfig slice.
 
234
func NetworkConfigFromInterfaceInfo(interfaceInfos []network.InterfaceInfo) []params.NetworkConfig {
 
235
        result := make([]params.NetworkConfig, len(interfaceInfos))
 
236
        for i, v := range interfaceInfos {
 
237
                var dnsServers []string
 
238
                for _, nameserver := range v.DNSServers {
 
239
                        dnsServers = append(dnsServers, nameserver.Value)
 
240
                }
 
241
                result[i] = params.NetworkConfig{
 
242
                        DeviceIndex:         v.DeviceIndex,
 
243
                        MACAddress:          v.MACAddress,
 
244
                        CIDR:                v.CIDR,
 
245
                        MTU:                 v.MTU,
 
246
                        ProviderId:          string(v.ProviderId),
 
247
                        ProviderSubnetId:    string(v.ProviderSubnetId),
 
248
                        ProviderSpaceId:     string(v.ProviderSpaceId),
 
249
                        ProviderVLANId:      string(v.ProviderVLANId),
 
250
                        ProviderAddressId:   string(v.ProviderAddressId),
 
251
                        VLANTag:             v.VLANTag,
 
252
                        InterfaceName:       v.InterfaceName,
 
253
                        ParentInterfaceName: v.ParentInterfaceName,
 
254
                        InterfaceType:       string(v.InterfaceType),
 
255
                        Disabled:            v.Disabled,
 
256
                        NoAutoStart:         v.NoAutoStart,
 
257
                        ConfigType:          string(v.ConfigType),
 
258
                        Address:             v.Address.Value,
 
259
                        DNSServers:          dnsServers,
 
260
                        DNSSearchDomains:    v.DNSSearchDomains,
 
261
                        GatewayAddress:      v.GatewayAddress.Value,
 
262
                }
 
263
        }
 
264
        return result
 
265
}
 
266
 
 
267
// NetworkConfigsToStateArgs splits the given networkConfig into a slice of
 
268
// state.LinkLayerDeviceArgs and a slice of state.LinkLayerDeviceAddress. The
 
269
// input is expected to come from MergeProviderAndObservedNetworkConfigs and to
 
270
// be sorted.
 
271
func NetworkConfigsToStateArgs(networkConfig []params.NetworkConfig) (
 
272
        []state.LinkLayerDeviceArgs,
 
273
        []state.LinkLayerDeviceAddress,
 
274
) {
 
275
        var devicesArgs []state.LinkLayerDeviceArgs
 
276
        var devicesAddrs []state.LinkLayerDeviceAddress
 
277
 
 
278
        logger.Tracef("transforming network config to state args: %+v", networkConfig)
 
279
        seenDeviceNames := set.NewStrings()
 
280
        for _, netConfig := range networkConfig {
 
281
                logger.Tracef("transforming device %q", netConfig.InterfaceName)
 
282
                if !seenDeviceNames.Contains(netConfig.InterfaceName) {
 
283
                        // First time we see this, add it to devicesArgs.
 
284
                        seenDeviceNames.Add(netConfig.InterfaceName)
 
285
                        var mtu uint
 
286
                        if netConfig.MTU >= 0 {
 
287
                                mtu = uint(netConfig.MTU)
 
288
                        }
 
289
                        args := state.LinkLayerDeviceArgs{
 
290
                                Name:        netConfig.InterfaceName,
 
291
                                MTU:         mtu,
 
292
                                ProviderID:  network.Id(netConfig.ProviderId),
 
293
                                Type:        state.LinkLayerDeviceType(netConfig.InterfaceType),
 
294
                                MACAddress:  netConfig.MACAddress,
 
295
                                IsAutoStart: !netConfig.NoAutoStart,
 
296
                                IsUp:        !netConfig.Disabled,
 
297
                                ParentName:  netConfig.ParentInterfaceName,
 
298
                        }
 
299
                        logger.Tracef("state device args for device: %+v", args)
 
300
                        devicesArgs = append(devicesArgs, args)
 
301
                }
 
302
 
 
303
                if netConfig.CIDR == "" || netConfig.Address == "" {
 
304
                        logger.Tracef(
 
305
                                "skipping empty CIDR %q and/or Address %q of %q",
 
306
                                netConfig.CIDR, netConfig.Address, netConfig.InterfaceName,
 
307
                        )
 
308
                        continue
 
309
                }
 
310
                _, ipNet, err := net.ParseCIDR(netConfig.CIDR)
 
311
                if err != nil {
 
312
                        logger.Warningf("FIXME: ignoring unexpected CIDR format %q: %v", netConfig.CIDR, err)
 
313
                        continue
 
314
                }
 
315
                ipAddr := net.ParseIP(netConfig.Address)
 
316
                if ipAddr == nil {
 
317
                        logger.Warningf("FIXME: ignoring unexpected Address format %q", netConfig.Address)
 
318
                        continue
 
319
                }
 
320
                ipNet.IP = ipAddr
 
321
                cidrAddress := ipNet.String()
 
322
 
 
323
                var derivedConfigMethod state.AddressConfigMethod
 
324
                switch method := state.AddressConfigMethod(netConfig.ConfigType); method {
 
325
                case state.StaticAddress, state.DynamicAddress,
 
326
                        state.LoopbackAddress, state.ManualAddress:
 
327
                        derivedConfigMethod = method
 
328
                case "dhcp": // awkward special case
 
329
                        derivedConfigMethod = state.DynamicAddress
 
330
                default:
 
331
                        derivedConfigMethod = state.StaticAddress
 
332
                }
 
333
 
 
334
                addr := state.LinkLayerDeviceAddress{
 
335
                        DeviceName:       netConfig.InterfaceName,
 
336
                        ProviderID:       network.Id(netConfig.ProviderAddressId),
 
337
                        ConfigMethod:     derivedConfigMethod,
 
338
                        CIDRAddress:      cidrAddress,
 
339
                        DNSServers:       netConfig.DNSServers,
 
340
                        DNSSearchDomains: netConfig.DNSSearchDomains,
 
341
                        GatewayAddress:   netConfig.GatewayAddress,
 
342
                }
 
343
                logger.Tracef("state address args for device: %+v", addr)
 
344
                devicesAddrs = append(devicesAddrs, addr)
 
345
        }
 
346
        logger.Tracef("seen devices: %+v", seenDeviceNames.SortedValues())
 
347
        logger.Tracef("network config transformed to state args:\n%+v\n%+v", devicesArgs, devicesAddrs)
 
348
        return devicesArgs, devicesAddrs
 
349
}
 
350
 
 
351
// NetworkingEnvironFromModelConfig constructs and returns
 
352
// environs.NetworkingEnviron using the given configGetter. Returns an error
 
353
// satisfying errors.IsNotSupported() if the model config does not support
 
354
// networking features.
 
355
func NetworkingEnvironFromModelConfig(configGetter environs.EnvironConfigGetter) (environs.NetworkingEnviron, error) {
 
356
        modelConfig, err := configGetter.ModelConfig()
 
357
        if err != nil {
 
358
                return nil, errors.Annotate(err, "failed to get model config")
 
359
        }
 
360
        if modelConfig.Type() == "dummy" {
 
361
                return nil, errors.NotSupportedf("dummy provider network config")
 
362
        }
 
363
        env, err := environs.GetEnviron(configGetter, environs.New)
 
364
        if err != nil {
 
365
                return nil, errors.Annotate(err, "failed to construct a model from config")
 
366
        }
 
367
        netEnviron, supported := environs.SupportsNetworking(env)
 
368
        if !supported {
 
369
                // " not supported" will be appended to the message below.
 
370
                return nil, errors.NotSupportedf("model %q networking", modelConfig.Name())
 
371
        }
 
372
        return netEnviron, nil
 
373
}
 
374
 
 
375
var vlanInterfaceNameRegex = regexp.MustCompile(`^.+\.[0-9]{1,4}[^0-9]?$`)
 
376
 
 
377
var (
 
378
        netInterfaces  = net.Interfaces
 
379
        interfaceAddrs = (*net.Interface).Addrs
 
380
)
 
381
 
 
382
// GetObservedNetworkConfig discovers what network interfaces exist on the
 
383
// machine, and returns that as a sorted slice of params.NetworkConfig to later
 
384
// update the state network config we have about the machine.
 
385
func GetObservedNetworkConfig() ([]params.NetworkConfig, error) {
 
386
        logger.Tracef("discovering observed machine network config...")
 
387
 
 
388
        interfaces, err := netInterfaces()
 
389
        if err != nil {
 
390
                return nil, errors.Annotate(err, "cannot get network interfaces")
 
391
        }
 
392
 
 
393
        var observedConfig []params.NetworkConfig
 
394
        for _, nic := range interfaces {
 
395
                isUp := nic.Flags&net.FlagUp > 0
 
396
 
 
397
                derivedType := network.EthernetInterface
 
398
                derivedConfigType := ""
 
399
                if nic.Flags&net.FlagLoopback > 0 {
 
400
                        derivedType = network.LoopbackInterface
 
401
                        derivedConfigType = string(network.ConfigLoopback)
 
402
                } else if vlanInterfaceNameRegex.MatchString(nic.Name) {
 
403
                        derivedType = network.VLAN_8021QInterface
 
404
                }
 
405
 
 
406
                nicConfig := params.NetworkConfig{
 
407
                        DeviceIndex:   nic.Index,
 
408
                        MACAddress:    nic.HardwareAddr.String(),
 
409
                        ConfigType:    derivedConfigType,
 
410
                        MTU:           nic.MTU,
 
411
                        InterfaceName: nic.Name,
 
412
                        InterfaceType: string(derivedType),
 
413
                        NoAutoStart:   !isUp,
 
414
                        Disabled:      !isUp,
 
415
                }
 
416
 
 
417
                addrs, err := interfaceAddrs(&nic)
 
418
                if err != nil {
 
419
                        return nil, errors.Annotatef(err, "cannot get interface %q addresses", nic.Name)
 
420
                }
 
421
 
 
422
                if len(addrs) == 0 {
 
423
                        observedConfig = append(observedConfig, nicConfig)
 
424
                        logger.Infof("no addresses observed on interface %q", nic.Name)
 
425
                        continue
 
426
                }
 
427
 
 
428
                for _, addr := range addrs {
 
429
                        cidrAddress := addr.String()
 
430
                        if cidrAddress == "" {
 
431
                                continue
 
432
                        }
 
433
                        ip, ipNet, err := net.ParseCIDR(cidrAddress)
 
434
                        if err != nil {
 
435
                                logger.Warningf("cannot parse interface %q address %q as CIDR: %v", nic.Name, cidrAddress, err)
 
436
                                if ip := net.ParseIP(cidrAddress); ip == nil {
 
437
                                        return nil, errors.Errorf("cannot parse interface %q IP address %q", nic.Name, cidrAddress)
 
438
                                } else {
 
439
                                        ipNet = &net.IPNet{}
 
440
                                }
 
441
                                ipNet.IP = ip
 
442
                                ipNet.Mask = net.IPv4Mask(255, 255, 255, 0)
 
443
                                logger.Infof("assuming interface %q has observed address %q", nic.Name, ipNet.String())
 
444
                        }
 
445
                        if ip.To4() == nil {
 
446
                                logger.Debugf("skipping observed IPv6 address %q on %q: not fully supported yet", ip, nic.Name)
 
447
                                continue
 
448
                        }
 
449
 
 
450
                        nicConfigCopy := nicConfig
 
451
                        nicConfigCopy.CIDR = ipNet.String()
 
452
                        nicConfigCopy.Address = ip.String()
 
453
 
 
454
                        // TODO(dimitern): Add DNS servers, search domains, and gateway
 
455
                        // later.
 
456
 
 
457
                        observedConfig = append(observedConfig, nicConfigCopy)
 
458
                }
 
459
        }
 
460
        sortedConfig := SortNetworkConfigsByParents(observedConfig)
 
461
 
 
462
        logger.Tracef("about to update network config with observed: %+v", sortedConfig)
 
463
        return sortedConfig, nil
 
464
}
 
465
 
 
466
// MergeProviderAndObservedNetworkConfigs returns the effective, sorted, network
 
467
// configs after merging providerConfig with observedConfig.
 
468
func MergeProviderAndObservedNetworkConfigs(providerConfigs, observedConfigs []params.NetworkConfig) ([]params.NetworkConfig, error) {
 
469
        providerConfigsByName := make(map[string][]params.NetworkConfig)
 
470
        sortedProviderConfigs := SortNetworkConfigsByParents(providerConfigs)
 
471
        for _, config := range sortedProviderConfigs {
 
472
                name := config.InterfaceName
 
473
                providerConfigsByName[name] = append(providerConfigsByName[name], config)
 
474
        }
 
475
 
 
476
        jsonProviderConfig, err := NetworkConfigsToIndentedJSON(sortedProviderConfigs)
 
477
        if err != nil {
 
478
                return nil, errors.Annotatef(err, "cannot serialize provider config %#v as JSON", sortedProviderConfigs)
 
479
        }
 
480
        logger.Debugf("provider network config of machine:\n%s", jsonProviderConfig)
 
481
 
 
482
        sortedObservedConfigs := SortNetworkConfigsByParents(observedConfigs)
 
483
        jsonObservedConfig, err := NetworkConfigsToIndentedJSON(sortedObservedConfigs)
 
484
        if err != nil {
 
485
                return nil, errors.Annotatef(err, "cannot serialize observed config %#v as JSON", sortedObservedConfigs)
 
486
        }
 
487
        logger.Debugf("observed network config of machine:\n%s", jsonObservedConfig)
 
488
 
 
489
        var mergedConfigs []params.NetworkConfig
 
490
        for _, config := range sortedObservedConfigs {
 
491
                name := config.InterfaceName
 
492
                logger.Tracef("merging observed config for device %q: %+v", name, config)
 
493
                if strings.HasPrefix(name, instancecfg.DefaultBridgePrefix) {
 
494
                        logger.Tracef("found potential juju bridge %q in observed config", name)
 
495
                        unprefixedName := strings.TrimPrefix(name, instancecfg.DefaultBridgePrefix)
 
496
                        underlyingConfigs, underlyingKnownByProvider := providerConfigsByName[unprefixedName]
 
497
                        logger.Tracef("device %q underlying %q has provider config: %+v", name, unprefixedName, underlyingConfigs)
 
498
                        if underlyingKnownByProvider {
 
499
                                // This config is for a bridge created by Juju and not known by
 
500
                                // the provider. The bridge is configured to adopt the address
 
501
                                // allocated to the underlying interface, which is known by the
 
502
                                // provider. However, since the same underlying interface can
 
503
                                // have multiple addresses, we need to match the adopted
 
504
                                // bridgeConfig to the correct address.
 
505
 
 
506
                                var underlyingConfig params.NetworkConfig
 
507
                                for i, underlying := range underlyingConfigs {
 
508
                                        if underlying.Address == config.Address {
 
509
                                                logger.Tracef("replacing undelying config %+v", underlying)
 
510
                                                // Remove what we found before changing it below.
 
511
                                                underlyingConfig = underlying
 
512
                                                underlyingConfigs = append(underlyingConfigs[:i], underlyingConfigs[i+1:]...)
 
513
                                                break
 
514
                                        }
 
515
                                }
 
516
                                logger.Tracef("underlying provider config after update: %+v", underlyingConfigs)
 
517
 
 
518
                                bridgeConfig := config
 
519
                                bridgeConfig.InterfaceType = string(network.BridgeInterface)
 
520
                                bridgeConfig.ConfigType = underlyingConfig.ConfigType
 
521
                                bridgeConfig.VLANTag = underlyingConfig.VLANTag
 
522
                                bridgeConfig.ProviderId = "" // Juju-created bridges never have a ProviderID
 
523
                                bridgeConfig.ProviderSpaceId = underlyingConfig.ProviderSpaceId
 
524
                                bridgeConfig.ProviderVLANId = underlyingConfig.ProviderVLANId
 
525
                                bridgeConfig.ProviderSubnetId = underlyingConfig.ProviderSubnetId
 
526
                                bridgeConfig.ProviderAddressId = underlyingConfig.ProviderAddressId
 
527
                                if underlyingParent := underlyingConfig.ParentInterfaceName; underlyingParent != "" {
 
528
                                        bridgeConfig.ParentInterfaceName = instancecfg.DefaultBridgePrefix + underlyingParent
 
529
                                }
 
530
 
 
531
                                underlyingConfig.ConfigType = string(network.ConfigManual)
 
532
                                underlyingConfig.ParentInterfaceName = name
 
533
                                underlyingConfig.ProviderAddressId = ""
 
534
                                underlyingConfig.CIDR = ""
 
535
                                underlyingConfig.Address = ""
 
536
 
 
537
                                underlyingConfigs = append(underlyingConfigs, underlyingConfig)
 
538
                                providerConfigsByName[unprefixedName] = underlyingConfigs
 
539
                                logger.Tracef("updated provider network config by name: %+v", providerConfigsByName)
 
540
 
 
541
                                mergedConfigs = append(mergedConfigs, bridgeConfig)
 
542
                                continue
 
543
                        }
 
544
                }
 
545
 
 
546
                knownProviderConfigs, knownByProvider := providerConfigsByName[name]
 
547
                if !knownByProvider {
 
548
                        // Not known by the provider and not a Juju-created bridge, so just
 
549
                        // use the observed config for it.
 
550
                        logger.Tracef("device %q not known to provider - adding only observed config: %+v", name, config)
 
551
                        mergedConfigs = append(mergedConfigs, config)
 
552
                        continue
 
553
                }
 
554
                logger.Tracef("device %q has known provider network config: %+v", name, knownProviderConfigs)
 
555
 
 
556
                for _, providerConfig := range knownProviderConfigs {
 
557
                        if providerConfig.Address == config.Address {
 
558
                                logger.Tracef(
 
559
                                        "device %q has observed address %q, index %d, and MTU %q; overriding index %d and MTU %d from provider config",
 
560
                                        name, config.Address, config.DeviceIndex, config.MTU, providerConfig.DeviceIndex, providerConfig.MTU,
 
561
                                )
 
562
                                // Prefer observed device indices and MTU values as more up-to-date.
 
563
                                providerConfig.DeviceIndex = config.DeviceIndex
 
564
                                providerConfig.MTU = config.MTU
 
565
 
 
566
                                mergedConfigs = append(mergedConfigs, providerConfig)
 
567
                                break
 
568
                        }
 
569
                }
 
570
        }
 
571
 
 
572
        sortedMergedConfigs := SortNetworkConfigsByParents(mergedConfigs)
 
573
 
 
574
        jsonMergedConfig, err := NetworkConfigsToIndentedJSON(sortedMergedConfigs)
 
575
        if err != nil {
 
576
                errors.Annotatef(err, "cannot serialize merged config %#v as JSON", sortedMergedConfigs)
 
577
        }
 
578
        logger.Debugf("combined machine network config:\n%s", jsonMergedConfig)
 
579
 
 
580
        return mergedConfigs, nil
 
581
}