github.com/juju/juju@v0.0.0-20240430160146-1752b71fcf00/network/containerizer/bridgepolicy.go (about)

     1  // Copyright 2017 Canonical Ltd.
     2  // Licensed under the AGPLv3, see LICENCE file for details.
     3  
     4  package containerizer
     5  
     6  import (
     7  	"fmt"
     8  	"hash/crc32"
     9  	"sort"
    10  	"strings"
    11  
    12  	"github.com/juju/collections/set"
    13  	"github.com/juju/errors"
    14  	"github.com/juju/loggo"
    15  
    16  	"github.com/juju/juju/core/instance"
    17  	corenetwork "github.com/juju/juju/core/network"
    18  	"github.com/juju/juju/environs"
    19  	"github.com/juju/juju/network"
    20  	"github.com/juju/juju/state"
    21  )
    22  
    23  var logger = loggo.GetLogger("juju.network.containerizer")
    24  
    25  var skippedDeviceNames = set.NewStrings(
    26  	network.DefaultLXDBridge,
    27  	network.DefaultKVMBridge,
    28  )
    29  
    30  // namedNICsBySpace is a type alias for a map of link-layer devices
    31  // keyed by name, keyed in turn by the space they are in.
    32  type namedNICsBySpace = map[string]map[string]LinkLayerDevice
    33  
    34  // BridgePolicy defines functionality that helps us create and define bridges
    35  // for guests inside a host machine, along with the creation of network
    36  // devices on those bridges for the containers to use.
    37  type BridgePolicy struct {
    38  	// spaces is a slice of SpaceInfos.
    39  	spaces corenetwork.SpaceInfos
    40  
    41  	// netBondReconfigureDelay is how much of a delay to inject if we see that
    42  	// one of the devices being bridged is a BondDevice. This exists because of
    43  	// https://bugs.launchpad.net/juju/+bug/1657579
    44  	netBondReconfigureDelay int
    45  
    46  	// containerNetworkingMethod defines the way containers are networked.
    47  	// It's one of:
    48  	//  - fan
    49  	//  - provider
    50  	//  - local
    51  	containerNetworkingMethod string
    52  }
    53  
    54  // NewBridgePolicy returns a new BridgePolicy for the input environ config
    55  // getter and state indirection.
    56  func NewBridgePolicy(cfgGetter environs.ConfigGetter, st SpaceBacking) (*BridgePolicy, error) {
    57  	cfg := cfgGetter.Config()
    58  
    59  	spaces, err := st.AllSpaceInfos()
    60  	if err != nil {
    61  		return nil, errors.Annotate(err, "getting space infos")
    62  	}
    63  
    64  	return &BridgePolicy{
    65  		spaces:                    spaces,
    66  		netBondReconfigureDelay:   cfg.NetBondReconfigureDelay(),
    67  		containerNetworkingMethod: cfg.ContainerNetworkingMethod(),
    68  	}, nil
    69  }
    70  
    71  // FindMissingBridgesForContainer looks at the spaces that the container should
    72  // have access to, and returns any host devices need to be bridged for use as
    73  // the container network.
    74  // This will return an Error if the container requires a space that the host
    75  // machine cannot provide.
    76  func (p *BridgePolicy) FindMissingBridgesForContainer(
    77  	host Machine, guest Container,
    78  ) ([]network.DeviceToBridge, int, error) {
    79  	guestSpaceInfos, devicesPerSpace, err := p.findSpacesAndDevicesForContainer(host, guest)
    80  	if err != nil {
    81  		return nil, 0, errors.Trace(err)
    82  	}
    83  	logger.Debugf("FindMissingBridgesForContainer(%q) spaces %s devices %v",
    84  		guest.Id(), guestSpaceInfos, formatDeviceMap(devicesPerSpace))
    85  
    86  	spacesFound := make(corenetwork.SpaceInfos, 0)
    87  	fanSpacesFound := make(corenetwork.SpaceInfos, 0)
    88  	for spaceID, devices := range devicesPerSpace {
    89  		for _, device := range devices {
    90  			if device.Type() == corenetwork.BridgeDevice {
    91  				if p.containerNetworkingMethod != "local" && skippedDeviceNames.Contains(device.Name()) {
    92  					continue
    93  				}
    94  				if strings.HasPrefix(device.Name(), "fan-") {
    95  					addInfo := p.spaces.GetByID(spaceID)
    96  					fanSpacesFound = append(fanSpacesFound, *addInfo)
    97  				} else {
    98  					addInfo := p.spaces.GetByID(spaceID)
    99  					spacesFound = append(spacesFound, *addInfo)
   100  				}
   101  			}
   102  		}
   103  	}
   104  
   105  	notFound := guestSpaceInfos.Minus(spacesFound)
   106  	fanNotFound := guestSpaceInfos.Minus(fanSpacesFound)
   107  
   108  	if p.containerNetworkingMethod == "fan" {
   109  		if len(fanNotFound) == 0 {
   110  			// Nothing to do; just return success.
   111  			return nil, 0, nil
   112  		}
   113  		return nil, 0, errors.Errorf("host machine %q has no available FAN devices in space(s) %s",
   114  			host.Id(), fanNotFound)
   115  	}
   116  
   117  	if len(notFound) == 0 {
   118  		// Nothing to do; just return success.
   119  		return nil, 0, nil
   120  	}
   121  
   122  	hostDeviceNamesToBridge := make([]string, 0)
   123  	reconfigureDelay := 0
   124  	hostDeviceByName := make(map[string]LinkLayerDevice, 0)
   125  	for _, spaceInfo := range notFound {
   126  		hostDeviceNames := make([]string, 0)
   127  		for _, hostDevice := range devicesPerSpace[spaceInfo.ID] {
   128  			possible, err := possibleBridgeTarget(hostDevice)
   129  			if err != nil {
   130  				return nil, 0, err
   131  			}
   132  			if !possible {
   133  				continue
   134  			}
   135  			hostDeviceNames = append(hostDeviceNames, hostDevice.Name())
   136  			hostDeviceByName[hostDevice.Name()] = hostDevice
   137  			spacesFound = append(spacesFound, spaceInfo)
   138  		}
   139  		if len(hostDeviceNames) > 0 {
   140  			if spaceInfo.ID == corenetwork.AlphaSpaceId {
   141  				// When we are bridging unknown space devices, we bridge all
   142  				// of them. Both because this is a fallback, and because we
   143  				// don't know what the exact spaces are going to be.
   144  				for _, deviceName := range hostDeviceNames {
   145  					hostDeviceNamesToBridge = append(hostDeviceNamesToBridge, deviceName)
   146  					if hostDeviceByName[deviceName].Type() == corenetwork.BondDevice {
   147  						if reconfigureDelay < p.netBondReconfigureDelay {
   148  							reconfigureDelay = p.netBondReconfigureDelay
   149  						}
   150  					}
   151  				}
   152  			} else {
   153  				// This should already be sorted from
   154  				// LinkLayerDevicesForSpaces but sorting to be sure we stably
   155  				// pick the host device
   156  				hostDeviceNames = network.NaturallySortDeviceNames(hostDeviceNames...)
   157  				hostDeviceNamesToBridge = append(hostDeviceNamesToBridge, hostDeviceNames[0])
   158  				if hostDeviceByName[hostDeviceNames[0]].Type() == corenetwork.BondDevice {
   159  					if reconfigureDelay < p.netBondReconfigureDelay {
   160  						reconfigureDelay = p.netBondReconfigureDelay
   161  					}
   162  				}
   163  			}
   164  		}
   165  	}
   166  	notFound = notFound.Minus(spacesFound)
   167  	if len(notFound) != 0 {
   168  		hostSpaces, err := host.AllSpaces()
   169  		if err != nil {
   170  			// log it, but we're returning another error right now
   171  			logger.Warningf("got error looking for spaces for host machine %q: %v",
   172  				host.Id(), err)
   173  		}
   174  		notFoundNames := notFound.String()
   175  		logger.Warningf("container %q wants spaces %s, but host machine %q has %s missing %s",
   176  			guest.Id(), guestSpaceInfos,
   177  			host.Id(), p.spaceNamesForPrinting(hostSpaces), notFoundNames)
   178  		return nil, 0, errors.Errorf("host machine %q has no available device in space(s) %s",
   179  			host.Id(), notFoundNames)
   180  	}
   181  
   182  	hostToBridge := make([]network.DeviceToBridge, 0, len(hostDeviceNamesToBridge))
   183  	for _, hostName := range network.NaturallySortDeviceNames(hostDeviceNamesToBridge...) {
   184  		hostToBridge = append(hostToBridge, network.DeviceToBridge{
   185  			DeviceName: hostName,
   186  			BridgeName: BridgeNameForDevice(hostName),
   187  			MACAddress: hostDeviceByName[hostName].MACAddress(),
   188  		})
   189  	}
   190  	return hostToBridge, reconfigureDelay, nil
   191  }
   192  
   193  // findSpacesAndDevicesForContainer looks up what spaces the container wants
   194  // to be in, and what spaces the host machine is already in, and tries to
   195  // find the devices on the host that are useful for the container.
   196  func (p *BridgePolicy) findSpacesAndDevicesForContainer(
   197  	host Machine, guest Container,
   198  ) (corenetwork.SpaceInfos, map[string][]LinkLayerDevice, error) {
   199  	containerSpaces, err := p.determineContainerSpaces(host, guest)
   200  	if err != nil {
   201  		return nil, nil, errors.Trace(err)
   202  	}
   203  	devicesPerSpace, err := linkLayerDevicesForSpaces(host, containerSpaces)
   204  	if err != nil {
   205  		logger.Errorf("findSpacesAndDevicesForContainer(%q) got error looking for host spaces: %v",
   206  			guest.Id(), err)
   207  		return nil, nil, errors.Trace(err)
   208  	}
   209  
   210  	// OVS bridges expose one of the internal ports as a device with the
   211  	// same name as the bridge. These special interfaces are not detected
   212  	// as bridge devices but rather appear as regular NICs. If the configured
   213  	// networking method is "provider", we need to patch the type of these
   214  	// devices so they appear as bridges to allow the bridge policy logic
   215  	// to make use of them.
   216  	if p.containerNetworkingMethod == "provider" {
   217  		for spaceID, devsInSpace := range devicesPerSpace {
   218  			for devIdx, dev := range devsInSpace {
   219  				if dev.VirtualPortType() != corenetwork.OvsPort {
   220  					continue
   221  				}
   222  
   223  				devicesPerSpace[spaceID][devIdx] = ovsBridgeDevice{
   224  					wrappedDev: dev,
   225  				}
   226  			}
   227  		}
   228  	}
   229  
   230  	return containerSpaces, devicesPerSpace, nil
   231  }
   232  
   233  // linkLayerDevicesForSpaces takes a list of SpaceInfos, and returns
   234  // the devices on this machine that are in those spaces that we feel
   235  // would be useful for containers to know about.  (eg, if there is a
   236  // host device that has been bridged, we return the bridge, rather
   237  // than the underlying device, but if we have only the host device,
   238  // we return that.)
   239  // Note that devices like 'lxdbr0' that are bridges that might not be
   240  // externally accessible may be returned if the default space is
   241  // listed as one of the desired spaces.
   242  func linkLayerDevicesForSpaces(host Machine, spaces corenetwork.SpaceInfos) (map[string][]LinkLayerDevice, error) {
   243  	deviceByName, err := linkLayerDevicesByName(host)
   244  	if err != nil {
   245  		return nil, errors.Trace(err)
   246  	}
   247  
   248  	addresses, err := host.AllDeviceAddresses()
   249  	if err != nil {
   250  		return nil, errors.Trace(err)
   251  	}
   252  
   253  	// Iterate all addresses and key them by the address device name.
   254  	addressByDeviceName := make(map[string]Address)
   255  	for _, addr := range addresses {
   256  		addressByDeviceName[addr.DeviceName()] = addr
   257  	}
   258  
   259  	// Iterate the devices by name, lookup the associated spaces, and
   260  	// gather the devices.
   261  	spaceToDevices := make(namedNICsBySpace, 0)
   262  	for _, device := range deviceByName {
   263  		addr, ok := addressByDeviceName[device.Name()]
   264  		if !ok {
   265  			logger.Infof("device %q has no addresses, ignoring", device.Name())
   266  			continue
   267  		}
   268  
   269  		// Loopback devices are not considered part of the empty space.
   270  		if device.Type() == corenetwork.LoopbackDevice {
   271  			continue
   272  		}
   273  
   274  		spaceID := corenetwork.AlphaSpaceId
   275  
   276  		subnet, err := addr.Subnet()
   277  		if err != nil {
   278  			if !errors.IsNotFound(err) {
   279  				// We don't understand the error, so error out for now
   280  				return nil, errors.Trace(err)
   281  			}
   282  		} else {
   283  			spaceID = subnet.SpaceID()
   284  		}
   285  		spaceToDevices = includeDevice(spaceToDevices, spaceID, device)
   286  	}
   287  
   288  	result := make(map[string][]LinkLayerDevice, len(spaceToDevices))
   289  	for spaceID, deviceMap := range spaceToDevices {
   290  		if !spaces.ContainsID(spaceID) {
   291  			continue
   292  		}
   293  		result[spaceID] = deviceMapToSortedList(deviceMap)
   294  	}
   295  	return result, nil
   296  }
   297  
   298  func linkLayerDevicesByName(host Machine) (map[string]LinkLayerDevice, error) {
   299  	devices, err := host.AllLinkLayerDevices()
   300  	if err != nil {
   301  		return nil, errors.Trace(err)
   302  	}
   303  	deviceByName := make(map[string]LinkLayerDevice, len(devices))
   304  	for _, dev := range devices {
   305  		deviceByName[dev.Name()] = dev
   306  	}
   307  	return deviceByName, nil
   308  }
   309  
   310  func includeDevice(spaceToDevices namedNICsBySpace, spaceID string, device LinkLayerDevice) namedNICsBySpace {
   311  	spaceInfo, ok := spaceToDevices[spaceID]
   312  	if !ok {
   313  		spaceInfo = make(map[string]LinkLayerDevice)
   314  		spaceToDevices[spaceID] = spaceInfo
   315  	}
   316  	spaceInfo[device.Name()] = device
   317  	return spaceToDevices
   318  }
   319  
   320  // deviceMapToSortedList takes a map from device name to LinkLayerDevice
   321  // object, and returns the list of LinkLayerDevice object using
   322  // NaturallySortDeviceNames
   323  func deviceMapToSortedList(deviceMap map[string]LinkLayerDevice) []LinkLayerDevice {
   324  	names := make([]string, 0, len(deviceMap))
   325  	for name := range deviceMap {
   326  		// name must == device.Name()
   327  		names = append(names, name)
   328  	}
   329  	sortedNames := network.NaturallySortDeviceNames(names...)
   330  	result := make([]LinkLayerDevice, len(sortedNames))
   331  	for i, name := range sortedNames {
   332  		result[i] = deviceMap[name]
   333  	}
   334  	return result
   335  }
   336  
   337  // determineContainerSpaces tries to use the direct information about a
   338  // container to find what spaces it should be in, and then falls back to what
   339  // we know about the host machine.
   340  func (p *BridgePolicy) determineContainerSpaces(
   341  	host Machine, guest Container,
   342  ) (corenetwork.SpaceInfos, error) {
   343  	// Gather any *positive* space constraints for the guest.
   344  	cons, err := guest.Constraints()
   345  	if err != nil {
   346  		return nil, errors.Trace(err)
   347  	}
   348  
   349  	spaces := make(corenetwork.SpaceInfos, 0)
   350  	// Constraints have been left in space name form,
   351  	// as they are human-readable and can be changed.
   352  	for _, spaceName := range cons.IncludeSpaces() {
   353  		if space := p.spaces.GetByName(spaceName); space != nil {
   354  			spaces = append(spaces, *space)
   355  		}
   356  	}
   357  
   358  	logger.Debugf("for container %q, found desired spaces: %s", guest.Id(), spaces)
   359  
   360  	if len(spaces) == 0 {
   361  		// We have determined that the container doesn't have any useful
   362  		// constraints set on it. So lets see if we can come up with
   363  		// something useful.
   364  		spaces, err = p.inferContainerSpaces(host, guest.Id())
   365  		if err != nil {
   366  			return nil, errors.Trace(err)
   367  		}
   368  	}
   369  
   370  	return spaces, nil
   371  }
   372  
   373  // spaceNamesForPrinting return a sorted, comma delimited, string containing
   374  // the space names for each of the given space ids.
   375  func (p *BridgePolicy) spaceNamesForPrinting(ids set.Strings) string {
   376  	if ids.Size() == 0 {
   377  		return "<none>"
   378  	}
   379  	names := set.NewStrings()
   380  	for _, id := range ids.Values() {
   381  		if info := p.spaces.GetByID(id); info != nil {
   382  			names.Add(fmt.Sprintf("%q", info.Name))
   383  		} else {
   384  			// fallback, in case we do not have a name for the given
   385  			// id.
   386  			names.Add(fmt.Sprintf("%q", id))
   387  		}
   388  	}
   389  	return strings.Join(names.SortedValues(), ", ")
   390  }
   391  
   392  // inferContainerSpaces tries to find a valid space for the container to be in.
   393  // This should only be used when the container itself doesn't have any valid
   394  // constraints on what spaces it should be in.
   395  // If containerNetworkingMethod is 'local' we fall back to the default space
   396  // and use lxdbr0.
   397  // If this machine is in a single space, then that space is used.
   398  // If the machine is in multiple spaces, we return an error with the possible
   399  // spaces that the user can use to constrain connectivity.
   400  func (p *BridgePolicy) inferContainerSpaces(host Machine, containerId string) (corenetwork.SpaceInfos, error) {
   401  	if p.containerNetworkingMethod == "local" {
   402  		alphaInfo := p.spaces.GetByID(corenetwork.AlphaSpaceId)
   403  		return corenetwork.SpaceInfos{*alphaInfo}, nil
   404  	}
   405  
   406  	hostSpaces, err := host.AllSpaces()
   407  	if err != nil {
   408  		return nil, errors.Trace(err)
   409  	}
   410  	namesHostSpaces := p.spaceNamesForPrinting(hostSpaces)
   411  	logger.Debugf("container %q not qualified to a space, host machine %q is using spaces %s",
   412  		containerId, host.Id(), namesHostSpaces)
   413  
   414  	if len(hostSpaces) == 1 {
   415  		hostInfo := p.spaces.GetByID(hostSpaces.Values()[0])
   416  		return corenetwork.SpaceInfos{*hostInfo}, nil
   417  	}
   418  	if len(hostSpaces) == 0 {
   419  		logger.Debugf("container has no desired spaces, " +
   420  			"and host has no known spaces, triggering fallback " +
   421  			"to bridge all devices")
   422  		alphaInfo := p.spaces.GetByID(corenetwork.AlphaSpaceId)
   423  		return corenetwork.SpaceInfos{*alphaInfo}, nil
   424  	}
   425  	return nil, errors.Errorf("no obvious space for container %q, host machine has spaces: %s",
   426  		containerId, namesHostSpaces)
   427  }
   428  
   429  func possibleBridgeTarget(dev LinkLayerDevice) (bool, error) {
   430  	// LoopbackDevices can never be bridged
   431  	if dev.Type() == corenetwork.LoopbackDevice || dev.Type() == corenetwork.BridgeDevice {
   432  		return false, nil
   433  	}
   434  	// Devices that have no parent entry are direct host devices that can be
   435  	// bridged.
   436  	if dev.ParentName() == "" {
   437  		return true, nil
   438  	}
   439  	// TODO(jam): 2016-12-22 This feels dirty, but it falls out of how we are
   440  	// currently modeling VLAN objects.  see bug https://pad.lv/1652049
   441  	if dev.Type() != corenetwork.VLAN8021QDevice {
   442  		// Only VLAN8021QDevice have parents that still allow us to
   443  		// bridge them.
   444  		// When anything else has a parent set, it shouldn't be used.
   445  		return false, nil
   446  	}
   447  	parentDevice, err := dev.ParentDevice()
   448  	if err != nil {
   449  		// If we got an error here, we have some sort of
   450  		// database inconsistency error.
   451  		return false, err
   452  	}
   453  	if parentDevice.Type() == corenetwork.EthernetDevice || parentDevice.Type() == corenetwork.BondDevice {
   454  		// A plain VLAN device with a direct parent
   455  		// of its underlying ethernet device.
   456  		return true, nil
   457  	}
   458  	return false, nil
   459  }
   460  
   461  // BridgeNameForDevice returns a name to use for a new device that bridges the
   462  // device with the input name. The policy is to:
   463  //  1. Add br- to device name (to keep current behaviour),
   464  //     if it does not fit in 15 characters then:
   465  //  2. Add b- to device name, if it doesn't fit in 15 characters then:
   466  //
   467  // 3a. For devices starting in 'en' remove 'en' and add 'b-'
   468  // 3b. For all other devices
   469  //
   470  //		'b-' + 6-char hash of name + '-' + last 6 chars of name
   471  //	 4. If using the device name directly always replace '.' with '-'
   472  //	    to make sure that bridges from VLANs won't break
   473  func BridgeNameForDevice(device string) string {
   474  	device = strings.Replace(device, ".", "-", -1)
   475  	switch {
   476  	case len(device) < 13:
   477  		return fmt.Sprintf("br-%s", device)
   478  	case len(device) == 13:
   479  		return fmt.Sprintf("b-%s", device)
   480  	case device[:2] == "en":
   481  		return fmt.Sprintf("b-%s", device[2:])
   482  	default:
   483  		hash := crc32.Checksum([]byte(device), crc32.IEEETable) & 0xffffff
   484  		return fmt.Sprintf("b-%0.6x-%s", hash, device[len(device)-6:])
   485  	}
   486  }
   487  
   488  // PopulateContainerLinkLayerDevices generates and returns link-layer devices
   489  // for the input guest, setting each device to be a child of the corresponding
   490  // bridge on the host machine.
   491  // It also records when one of the desired spaces is available on the host
   492  // machine, but not currently bridged.
   493  func (p *BridgePolicy) PopulateContainerLinkLayerDevices(
   494  	host Machine, guest Container, askProviderForAddress bool,
   495  ) (corenetwork.InterfaceInfos, error) {
   496  	guestSpaces, devicesPerSpace, err := p.findSpacesAndDevicesForContainer(host, guest)
   497  	if err != nil {
   498  		return nil, errors.Trace(err)
   499  	}
   500  	logger.Debugf("for container %q, found host devices spaces: %s", guest.Id(), formatDeviceMap(devicesPerSpace))
   501  	spacesFound := make(corenetwork.SpaceInfos, 0)
   502  	devicesByName := make(map[string]LinkLayerDevice)
   503  	bridgeDeviceNames := make([]string, 0)
   504  
   505  	for spaceID, hostDevices := range devicesPerSpace {
   506  		for _, hostDevice := range hostDevices {
   507  			isFan := strings.HasPrefix(hostDevice.Name(), "fan-")
   508  			if !(isFan == (p.containerNetworkingMethod == "fan")) {
   509  				// This is not a suitable bridge for
   510  				// our container networking method.
   511  				continue
   512  			}
   513  
   514  			name := hostDevice.Name()
   515  			if hostDevice.Type() == corenetwork.BridgeDevice && !skippedDeviceNames.Contains(name) {
   516  				devicesByName[name] = hostDevice
   517  				bridgeDeviceNames = append(bridgeDeviceNames, name)
   518  				spaceInfo := p.spaces.GetByID(spaceID)
   519  				spacesFound = append(spacesFound, *spaceInfo)
   520  			}
   521  		}
   522  	}
   523  
   524  	missingSpaces := guestSpaces.Minus(spacesFound)
   525  
   526  	// Check if we are missing the default space and can fill it in with a local bridge
   527  	if len(missingSpaces) == 1 &&
   528  		missingSpaces.ContainsID(corenetwork.AlphaSpaceId) &&
   529  		p.containerNetworkingMethod == "local" {
   530  
   531  		localBridgeName := network.DefaultLXDBridge
   532  		if guest.ContainerType() == instance.KVM {
   533  			localBridgeName = network.DefaultKVMBridge
   534  		}
   535  
   536  		for _, hostDevice := range devicesPerSpace[corenetwork.AlphaSpaceId] {
   537  			name := hostDevice.Name()
   538  			if hostDevice.Type() == corenetwork.BridgeDevice && name == localBridgeName {
   539  				alphaInfo := p.spaces.GetByID(corenetwork.AlphaSpaceId)
   540  				missingSpaces = missingSpaces.Minus(corenetwork.SpaceInfos{*alphaInfo})
   541  				devicesByName[name] = hostDevice
   542  				bridgeDeviceNames = append(bridgeDeviceNames, name)
   543  				spacesFound = append(spacesFound, *alphaInfo)
   544  			}
   545  		}
   546  	}
   547  
   548  	if len(missingSpaces) > 0 && len(bridgeDeviceNames) == 0 {
   549  		missingSpacesNames := missingSpaces.String()
   550  		logger.Warningf("container %q wants spaces %s could not find host %q bridges for %s, found bridges %s",
   551  			guest.Id(), guestSpaces,
   552  			host.Id(), missingSpacesNames, bridgeDeviceNames)
   553  		return nil, errors.Errorf("unable to find host bridge for space(s) %s for container %q",
   554  			missingSpacesNames, guest.Id())
   555  	}
   556  
   557  	sortedBridgeDeviceNames := network.NaturallySortDeviceNames(bridgeDeviceNames...)
   558  	logger.Debugf("for container %q using host machine %q bridge devices: %s",
   559  		guest.Id(), host.Id(), network.QuoteSpaces(sortedBridgeDeviceNames))
   560  
   561  	interfaces := make(corenetwork.InterfaceInfos, len(bridgeDeviceNames))
   562  
   563  	for i, hostBridgeName := range sortedBridgeDeviceNames {
   564  		hostBridge := devicesByName[hostBridgeName]
   565  		newDevice, err := hostBridge.EthernetDeviceForBridge(fmt.Sprintf("eth%d", i), askProviderForAddress)
   566  		if err != nil {
   567  			return nil, errors.Trace(err)
   568  		}
   569  		interfaces[i] = newDevice
   570  	}
   571  
   572  	logger.Debugf("prepared container %q network config: %+v", guest.Id(), interfaces)
   573  	return interfaces, nil
   574  }
   575  
   576  func formatDeviceMap(spacesToDevices map[string][]LinkLayerDevice) string {
   577  	spaceIDs := make([]string, len(spacesToDevices))
   578  	i := 0
   579  	for spaceID := range spacesToDevices {
   580  		spaceIDs[i] = spaceID
   581  		i++
   582  	}
   583  	sort.Strings(spaceIDs)
   584  	var out []string
   585  	for _, id := range spaceIDs {
   586  		start := fmt.Sprintf("%q:[", id)
   587  		devices := spacesToDevices[id]
   588  		deviceNames := make([]string, len(devices))
   589  		for i, dev := range devices {
   590  			deviceNames[i] = dev.Name()
   591  		}
   592  		deviceNames = network.NaturallySortDeviceNames(deviceNames...)
   593  		quotedNames := make([]string, len(deviceNames))
   594  		for i, name := range deviceNames {
   595  			quotedNames[i] = fmt.Sprintf("%q", name)
   596  		}
   597  		out = append(out, start+strings.Join(quotedNames, ",")+"]")
   598  	}
   599  	return "map{" + strings.Join(out, ", ") + "}"
   600  }
   601  
   602  // ovsBridgeDevice wraps a LinkLayerDevice and overrides its reported type to
   603  // BridgeDevice.
   604  type ovsBridgeDevice struct {
   605  	wrappedDev LinkLayerDevice
   606  }
   607  
   608  // Type ensures the wrapped device's type is always reported as bridge.
   609  func (dev ovsBridgeDevice) Type() corenetwork.LinkLayerDeviceType { return corenetwork.BridgeDevice }
   610  func (dev ovsBridgeDevice) Name() string                          { return dev.wrappedDev.Name() }
   611  func (dev ovsBridgeDevice) MACAddress() string                    { return dev.wrappedDev.MACAddress() }
   612  func (dev ovsBridgeDevice) ParentName() string                    { return dev.wrappedDev.ParentName() }
   613  func (dev ovsBridgeDevice) ParentDevice() (LinkLayerDevice, error) {
   614  	return dev.wrappedDev.ParentDevice()
   615  }
   616  func (dev ovsBridgeDevice) Addresses() ([]*state.Address, error) { return dev.wrappedDev.Addresses() }
   617  func (dev ovsBridgeDevice) MTU() uint                            { return dev.wrappedDev.MTU() }
   618  func (dev ovsBridgeDevice) IsUp() bool                           { return dev.wrappedDev.IsUp() }
   619  func (dev ovsBridgeDevice) IsAutoStart() bool                    { return dev.wrappedDev.IsAutoStart() }
   620  func (dev ovsBridgeDevice) EthernetDeviceForBridge(
   621  	name string, askForProviderAddress bool,
   622  ) (corenetwork.InterfaceInfo, error) {
   623  	return dev.wrappedDev.EthernetDeviceForBridge(name, askForProviderAddress)
   624  }
   625  func (dev ovsBridgeDevice) VirtualPortType() corenetwork.VirtualPortType {
   626  	return dev.wrappedDev.VirtualPortType()
   627  }