1
// Copyright 2015 Canonical Ltd.
2
// Licensed under the AGPLv3, see LICENCE file for details.
4
package networkingcommon
13
"github.com/juju/errors"
14
"github.com/juju/utils/set"
15
"gopkg.in/juju/names.v2"
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"
25
// BackingSubnet defines the methods supported by a Subnet entity
26
// stored persistently.
28
// TODO(dimitern): Once the state backing is implemented, remove this
29
// and just use *state.Subnet.
30
type BackingSubnet interface {
33
ProviderId() network.Id
34
AvailabilityZones() []string
40
// BackingSubnetInfo describes a single subnet to be added in the
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.
59
// CIDR of the network, in 123.45.67.89/24 format.
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.
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
71
// SpaceName holds the juju network space this subnet is
72
// associated with. Can be empty if not supported.
75
// Status holds the status of the subnet. Normally this will be
76
// calculated from the reference count and Life of a subnet.
79
// Live holds the life of the subnet
83
// BackingSpace defines the methods supported by a Space entity stored
85
type BackingSpace interface {
86
// Name returns the space name.
89
// Subnets returns the subnets in the space
90
Subnets() ([]BackingSubnet, error)
92
// ProviderId returns the network ID of the provider
93
ProviderId() network.Id
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.
100
// Life returns the lifecycle state of the space
104
// Backing defines the methods needed by the API facade to store and
105
// retrieve information from the underlying persistency layer (state
107
type NetworkBacking interface {
108
environs.EnvironConfigGetter
110
// AvailabilityZones returns all cached availability zones (i.e.
111
// not from the provider, but in state).
112
AvailabilityZones() ([]providercommon.AvailabilityZone, error)
114
// SetAvailabilityZones replaces the cached list of availability
115
// zones with the given zones.
116
SetAvailabilityZones([]providercommon.AvailabilityZone) error
118
// AddSpace creates a space
119
AddSpace(Name string, ProviderId network.Id, Subnets []string, Public bool) error
121
// AllSpaces returns all known Juju network spaces.
122
AllSpaces() ([]BackingSpace, error)
124
// AddSubnet creates a backing subnet for an existing subnet.
125
AddSubnet(BackingSubnetInfo) (BackingSubnet, error)
127
// AllSubnets returns all backing subnets.
128
AllSubnets() ([]BackingSubnet, error)
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())
142
return params.Subnet{
145
ProviderId: string(providerid),
148
SpaceTag: spaceTag.String(),
153
type byMACThenCIDRThenIndexThenName []params.NetworkConfig
155
func (c byMACThenCIDRThenIndexThenName) Len() int {
159
func (c byMACThenCIDRThenIndexThenName) Swap(i, j int) {
160
orgI, orgJ := c[i], c[j]
161
c[j], c[i] = orgI, orgJ
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
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
176
// Prefer shorter names (e.g. parents) with equal DeviceIndex.
177
return c[i].InterfaceName < c[j].InterfaceName
179
// When both CIDR and DeviceIndex are non-empty, order by DeviceIndex
180
return c[i].DeviceIndex < c[j].DeviceIndex
182
// Group by MACAddress.
183
return c[i].MACAddress < c[j].MACAddress
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
194
type byInterfaceName []params.NetworkConfig
196
func (c byInterfaceName) Len() int {
200
func (c byInterfaceName) Swap(i, j int) {
201
orgI, orgJ := c[i], c[j]
202
c[j], c[i] = orgI, orgJ
205
func (c byInterfaceName) Less(i, j int) bool {
206
return c[i].InterfaceName < c[j].InterfaceName
209
// SortNetworkConfigsByInterfaceName returns the given input sorted by
211
func SortNetworkConfigsByInterfaceName(input []params.NetworkConfig) []params.NetworkConfig {
212
sortedInputCopy := CopyNetworkConfigs(input)
213
sort.Stable(byInterfaceName(sortedInputCopy))
214
return sortedInputCopy
217
// NetworkConfigsToIndentedJSON returns the given input as an indented JSON
219
func NetworkConfigsToIndentedJSON(input []params.NetworkConfig) (string, error) {
220
jsonBytes, err := json.MarshalIndent(input, "", " ")
224
return string(jsonBytes), nil
227
// CopyNetworkConfigs returns a copy of the given input
228
func CopyNetworkConfigs(input []params.NetworkConfig) []params.NetworkConfig {
229
return append([]params.NetworkConfig(nil), input...)
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)
241
result[i] = params.NetworkConfig{
242
DeviceIndex: v.DeviceIndex,
243
MACAddress: v.MACAddress,
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),
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,
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
271
func NetworkConfigsToStateArgs(networkConfig []params.NetworkConfig) (
272
[]state.LinkLayerDeviceArgs,
273
[]state.LinkLayerDeviceAddress,
275
var devicesArgs []state.LinkLayerDeviceArgs
276
var devicesAddrs []state.LinkLayerDeviceAddress
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)
286
if netConfig.MTU >= 0 {
287
mtu = uint(netConfig.MTU)
289
args := state.LinkLayerDeviceArgs{
290
Name: netConfig.InterfaceName,
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,
299
logger.Tracef("state device args for device: %+v", args)
300
devicesArgs = append(devicesArgs, args)
303
if netConfig.CIDR == "" || netConfig.Address == "" {
305
"skipping empty CIDR %q and/or Address %q of %q",
306
netConfig.CIDR, netConfig.Address, netConfig.InterfaceName,
310
_, ipNet, err := net.ParseCIDR(netConfig.CIDR)
312
logger.Warningf("FIXME: ignoring unexpected CIDR format %q: %v", netConfig.CIDR, err)
315
ipAddr := net.ParseIP(netConfig.Address)
317
logger.Warningf("FIXME: ignoring unexpected Address format %q", netConfig.Address)
321
cidrAddress := ipNet.String()
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
331
derivedConfigMethod = state.StaticAddress
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,
343
logger.Tracef("state address args for device: %+v", addr)
344
devicesAddrs = append(devicesAddrs, addr)
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
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()
358
return nil, errors.Annotate(err, "failed to get model config")
360
if modelConfig.Type() == "dummy" {
361
return nil, errors.NotSupportedf("dummy provider network config")
363
env, err := environs.GetEnviron(configGetter, environs.New)
365
return nil, errors.Annotate(err, "failed to construct a model from config")
367
netEnviron, supported := environs.SupportsNetworking(env)
369
// " not supported" will be appended to the message below.
370
return nil, errors.NotSupportedf("model %q networking", modelConfig.Name())
372
return netEnviron, nil
375
var vlanInterfaceNameRegex = regexp.MustCompile(`^.+\.[0-9]{1,4}[^0-9]?$`)
378
netInterfaces = net.Interfaces
379
interfaceAddrs = (*net.Interface).Addrs
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...")
388
interfaces, err := netInterfaces()
390
return nil, errors.Annotate(err, "cannot get network interfaces")
393
var observedConfig []params.NetworkConfig
394
for _, nic := range interfaces {
395
isUp := nic.Flags&net.FlagUp > 0
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
406
nicConfig := params.NetworkConfig{
407
DeviceIndex: nic.Index,
408
MACAddress: nic.HardwareAddr.String(),
409
ConfigType: derivedConfigType,
411
InterfaceName: nic.Name,
412
InterfaceType: string(derivedType),
417
addrs, err := interfaceAddrs(&nic)
419
return nil, errors.Annotatef(err, "cannot get interface %q addresses", nic.Name)
423
observedConfig = append(observedConfig, nicConfig)
424
logger.Infof("no addresses observed on interface %q", nic.Name)
428
for _, addr := range addrs {
429
cidrAddress := addr.String()
430
if cidrAddress == "" {
433
ip, ipNet, err := net.ParseCIDR(cidrAddress)
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)
442
ipNet.Mask = net.IPv4Mask(255, 255, 255, 0)
443
logger.Infof("assuming interface %q has observed address %q", nic.Name, ipNet.String())
446
logger.Debugf("skipping observed IPv6 address %q on %q: not fully supported yet", ip, nic.Name)
450
nicConfigCopy := nicConfig
451
nicConfigCopy.CIDR = ipNet.String()
452
nicConfigCopy.Address = ip.String()
454
// TODO(dimitern): Add DNS servers, search domains, and gateway
457
observedConfig = append(observedConfig, nicConfigCopy)
460
sortedConfig := SortNetworkConfigsByParents(observedConfig)
462
logger.Tracef("about to update network config with observed: %+v", sortedConfig)
463
return sortedConfig, nil
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)
476
jsonProviderConfig, err := NetworkConfigsToIndentedJSON(sortedProviderConfigs)
478
return nil, errors.Annotatef(err, "cannot serialize provider config %#v as JSON", sortedProviderConfigs)
480
logger.Debugf("provider network config of machine:\n%s", jsonProviderConfig)
482
sortedObservedConfigs := SortNetworkConfigsByParents(observedConfigs)
483
jsonObservedConfig, err := NetworkConfigsToIndentedJSON(sortedObservedConfigs)
485
return nil, errors.Annotatef(err, "cannot serialize observed config %#v as JSON", sortedObservedConfigs)
487
logger.Debugf("observed network config of machine:\n%s", jsonObservedConfig)
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.
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:]...)
516
logger.Tracef("underlying provider config after update: %+v", underlyingConfigs)
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
531
underlyingConfig.ConfigType = string(network.ConfigManual)
532
underlyingConfig.ParentInterfaceName = name
533
underlyingConfig.ProviderAddressId = ""
534
underlyingConfig.CIDR = ""
535
underlyingConfig.Address = ""
537
underlyingConfigs = append(underlyingConfigs, underlyingConfig)
538
providerConfigsByName[unprefixedName] = underlyingConfigs
539
logger.Tracef("updated provider network config by name: %+v", providerConfigsByName)
541
mergedConfigs = append(mergedConfigs, bridgeConfig)
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)
554
logger.Tracef("device %q has known provider network config: %+v", name, knownProviderConfigs)
556
for _, providerConfig := range knownProviderConfigs {
557
if providerConfig.Address == config.Address {
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,
562
// Prefer observed device indices and MTU values as more up-to-date.
563
providerConfig.DeviceIndex = config.DeviceIndex
564
providerConfig.MTU = config.MTU
566
mergedConfigs = append(mergedConfigs, providerConfig)
572
sortedMergedConfigs := SortNetworkConfigsByParents(mergedConfigs)
574
jsonMergedConfig, err := NetworkConfigsToIndentedJSON(sortedMergedConfigs)
576
errors.Annotatef(err, "cannot serialize merged config %#v as JSON", sortedMergedConfigs)
578
logger.Debugf("combined machine network config:\n%s", jsonMergedConfig)
580
return mergedConfigs, nil