github.com/niedbalski/juju@v0.0.0-20190215020005-8ff100488e47/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  	"github.com/juju/juju/network"
    18  
    19  	// Used for some constants and things like LinkLayerDevice[Args]
    20  	"github.com/juju/juju/state"
    21  )
    22  
    23  var logger = loggo.GetLogger("juju.network.containerizer")
    24  
    25  // BridgePolicy defines functionality that helps us create and define bridges
    26  // for guests inside of a host machine, along with the creation of network
    27  // devices on those bridges for the containers to use.
    28  // Ideally BridgePolicy would be defined outside of the 'state' package as it
    29  // doesn't deal directly with DB content, but not quite enough of State is exposed
    30  type BridgePolicy struct {
    31  	// NetBondReconfigureDelay is how much of a delay to inject if we see that
    32  	// one of the devices being bridged is a BondDevice. This exists because of
    33  	// https://bugs.launchpad.net/juju/+bug/1657579
    34  	NetBondReconfigureDelay int
    35  	// ContainerNetworkingMethod defines the way containers are networked.
    36  	// It's one of:
    37  	//  - fan
    38  	//  - provider
    39  	//  - local
    40  	ContainerNetworkingMethod string
    41  }
    42  
    43  // inferContainerSpaces tries to find a valid space for the container to be
    44  // on. This should only be used when the container itself doesn't have any
    45  // valid constraints on what spaces it should be in.
    46  // If ContainerNetworkingMethod is 'local' we fall back to "" and use lxdbr0.
    47  // If this machine is in a single space, then that space is used. Else, if
    48  // the machine has the default space, then that space is used.
    49  // If neither of those conditions is true, then we return an error.
    50  func (p *BridgePolicy) inferContainerSpaces(m Machine, containerId, defaultSpaceName string) (set.Strings, error) {
    51  	if p.ContainerNetworkingMethod == "local" {
    52  		return set.NewStrings(""), nil
    53  	}
    54  	hostSpaces, err := m.AllSpaces()
    55  	if err != nil {
    56  		return nil, errors.Trace(err)
    57  	}
    58  	logger.Debugf("container %q not qualified to a space, host machine %q is using spaces %s",
    59  		containerId, m.Id(), network.QuoteSpaceSet(hostSpaces))
    60  	if len(hostSpaces) == 1 {
    61  		return hostSpaces, nil
    62  	}
    63  	if defaultSpaceName != "" && hostSpaces.Contains(defaultSpaceName) {
    64  		return set.NewStrings(defaultSpaceName), nil
    65  	}
    66  	if len(hostSpaces) == 0 {
    67  		logger.Debugf("container has no desired spaces, " +
    68  			"and host has no known spaces, triggering fallback " +
    69  			"to bridge all devices")
    70  		return set.NewStrings(""), nil
    71  	}
    72  	return nil, errors.Errorf("no obvious space for container %q, host machine has spaces: %s",
    73  		containerId, network.QuoteSpaceSet(hostSpaces))
    74  }
    75  
    76  // determineContainerSpaces tries to use the direct information about a
    77  // container to find what spaces it should be in, and then falls back to what
    78  // we know about the host machine.
    79  func (p *BridgePolicy) determineContainerSpaces(m Machine, containerMachine Container, defaultSpaceName string) (set.Strings, error) {
    80  	containerSpaces, err := containerMachine.DesiredSpaces()
    81  	if err != nil {
    82  		return nil, errors.Trace(err)
    83  	}
    84  	logger.Debugf("for container %q, found desired spaces: %s",
    85  		containerMachine.Id(), network.QuoteSpaceSet(containerSpaces))
    86  	if len(containerSpaces) == 0 {
    87  		// We have determined that the container doesn't have any useful
    88  		// constraints set on it. So lets see if we can come up with
    89  		// something useful.
    90  		containerSpaces, err = p.inferContainerSpaces(m, containerMachine.Id(), defaultSpaceName)
    91  		if err != nil {
    92  			return nil, errors.Trace(err)
    93  		}
    94  	}
    95  	return containerSpaces, nil
    96  }
    97  
    98  // findSpacesAndDevicesForContainer looks up what spaces the container wants
    99  // to be in, and what spaces the host machine is already in, and tries to
   100  // find the devices on the host that are useful for the container.
   101  func (p *BridgePolicy) findSpacesAndDevicesForContainer(m Machine, containerMachine Container) (set.Strings, map[string][]LinkLayerDevice, error) {
   102  	containerSpaces, err := p.determineContainerSpaces(m, containerMachine, "")
   103  	if err != nil {
   104  		return nil, nil, errors.Trace(err)
   105  	}
   106  	devicesPerSpace, err := m.LinkLayerDevicesForSpaces(containerSpaces.Values())
   107  	if err != nil {
   108  		logger.Errorf("findSpacesAndDevicesForContainer(%q) got error looking for host spaces: %v",
   109  			containerMachine.Id(), err)
   110  		return nil, nil, errors.Trace(err)
   111  	}
   112  	return containerSpaces, devicesPerSpace, nil
   113  }
   114  
   115  func possibleBridgeTarget(dev LinkLayerDevice) (bool, error) {
   116  	// LoopbackDevices can never be bridged
   117  	if dev.Type() == state.LoopbackDevice || dev.Type() == state.BridgeDevice {
   118  		return false, nil
   119  	}
   120  	// Devices that have no parent entry are direct host devices that can be
   121  	// bridged.
   122  	if dev.ParentName() == "" {
   123  		return true, nil
   124  	}
   125  	// TODO(jam): 2016-12-22 This feels dirty, but it falls out of how we are
   126  	// currently modeling VLAN objects.  see bug https://pad.lv/1652049
   127  	if dev.Type() != state.VLAN_8021QDevice {
   128  		// Only state.VLAN_8021QDevice have parents that still allow us to bridge
   129  		// them. When anything else has a parent set, it shouldn't be used
   130  		return false, nil
   131  	}
   132  	parentDevice, err := dev.ParentDevice()
   133  	if err != nil {
   134  		// If we got an error here, we have some sort of
   135  		// database inconsistency error.
   136  		return false, err
   137  	}
   138  	if parentDevice.Type() == state.EthernetDevice || parentDevice.Type() == state.BondDevice {
   139  		// A plain VLAN device with a direct parent of its underlying
   140  		// ethernet device
   141  		return true, nil
   142  	}
   143  	return false, nil
   144  }
   145  
   146  func formatDeviceMap(spacesToDevices map[string][]LinkLayerDevice) string {
   147  	spaceNames := make([]string, len(spacesToDevices))
   148  	i := 0
   149  	for spaceName := range spacesToDevices {
   150  		spaceNames[i] = spaceName
   151  		i++
   152  	}
   153  	sort.Strings(spaceNames)
   154  	out := []string{}
   155  	for _, name := range spaceNames {
   156  		start := fmt.Sprintf("%q:[", name)
   157  		devices := spacesToDevices[name]
   158  		deviceNames := make([]string, len(devices))
   159  		for i, dev := range devices {
   160  			deviceNames[i] = dev.Name()
   161  		}
   162  		deviceNames = network.NaturallySortDeviceNames(deviceNames...)
   163  		quotedNames := make([]string, len(deviceNames))
   164  		for i, name := range deviceNames {
   165  			quotedNames[i] = fmt.Sprintf("%q", name)
   166  		}
   167  		out = append(out, start+strings.Join(quotedNames, ",")+"]")
   168  	}
   169  	return "map{" + strings.Join(out, ", ") + "}"
   170  }
   171  
   172  var skippedDeviceNames = set.NewStrings(
   173  	network.DefaultLXCBridge,
   174  	network.DefaultLXDBridge,
   175  	network.DefaultKVMBridge,
   176  )
   177  
   178  // The general policy is to:
   179  // 1. Add br- to device name (to keep current behaviour), if it doesn fit in 15 characters then:
   180  // 2. Add b- to device name, if it doesn't fit in 15 characters then:
   181  // 3a. For devices starting in 'en' remove 'en' and add 'b-'
   182  // 3b. For all other devices 'b-' + 6-char hash of name + '-' + last 6 chars of name
   183  // 4. If using the device name directly always replace '.' with '-', to make sure that bridges from VLANs won't break
   184  func BridgeNameForDevice(device string) string {
   185  	device = strings.Replace(device, ".", "-", -1)
   186  	switch {
   187  	case len(device) < 13:
   188  		return fmt.Sprintf("br-%s", device)
   189  	case len(device) == 13:
   190  		return fmt.Sprintf("b-%s", device)
   191  	case device[:2] == "en":
   192  		return fmt.Sprintf("b-%s", device[2:])
   193  	default:
   194  		hash := crc32.Checksum([]byte(device), crc32.IEEETable) & 0xffffff
   195  		return fmt.Sprintf("b-%0.6x-%s", hash, device[len(device)-6:])
   196  	}
   197  }
   198  
   199  // FindMissingBridgesForContainer looks at the spaces that the container
   200  // wants to be in, and sees if there are any host devices that should be
   201  // bridged.
   202  // This will return an Error if the container wants a space that the host
   203  // machine cannot provide.
   204  func (b *BridgePolicy) FindMissingBridgesForContainer(m Machine, containerMachine Container) ([]network.DeviceToBridge, int, error) {
   205  	reconfigureDelay := 0
   206  	containerSpaces, devicesPerSpace, err := b.findSpacesAndDevicesForContainer(m, containerMachine)
   207  	hostDeviceByName := make(map[string]LinkLayerDevice, 0)
   208  	if err != nil {
   209  		return nil, 0, errors.Trace(err)
   210  	}
   211  	logger.Debugf("FindMissingBridgesForContainer(%q) spaces %s devices %v",
   212  		containerMachine.Id(), network.QuoteSpaceSet(containerSpaces),
   213  		formatDeviceMap(devicesPerSpace))
   214  	spacesFound := set.NewStrings()
   215  	fanSpacesFound := set.NewStrings()
   216  	for spaceName, devices := range devicesPerSpace {
   217  		for _, device := range devices {
   218  			if device.Type() == state.BridgeDevice {
   219  				if b.ContainerNetworkingMethod != "local" && skippedDeviceNames.Contains(device.Name()) {
   220  					continue
   221  				}
   222  				if strings.HasPrefix(device.Name(), "fan-") {
   223  					fanSpacesFound.Add(spaceName)
   224  				} else {
   225  					spacesFound.Add(spaceName)
   226  				}
   227  			}
   228  		}
   229  	}
   230  	notFound := containerSpaces.Difference(spacesFound)
   231  	fanNotFound := containerSpaces.Difference(fanSpacesFound)
   232  	if b.ContainerNetworkingMethod == "fan" {
   233  		if fanNotFound.IsEmpty() {
   234  			// Nothing to do, just return success
   235  			return nil, 0, nil
   236  		} else {
   237  			return nil, 0, errors.Errorf("host machine %q has no available FAN devices in space(s) %s",
   238  				m.Id(), network.QuoteSpaceSet(fanNotFound))
   239  		}
   240  	} else {
   241  		if notFound.IsEmpty() {
   242  			// Nothing to do, just return success
   243  			return nil, 0, nil
   244  		}
   245  	}
   246  	hostDeviceNamesToBridge := make([]string, 0)
   247  	for _, spaceName := range notFound.Values() {
   248  		hostDeviceNames := make([]string, 0)
   249  		for _, hostDevice := range devicesPerSpace[spaceName] {
   250  			possible, err := possibleBridgeTarget(hostDevice)
   251  			if err != nil {
   252  				return nil, 0, err
   253  			}
   254  			if !possible {
   255  				continue
   256  			}
   257  			hostDeviceNames = append(hostDeviceNames, hostDevice.Name())
   258  			hostDeviceByName[hostDevice.Name()] = hostDevice
   259  			spacesFound.Add(spaceName)
   260  		}
   261  		if len(hostDeviceNames) > 0 {
   262  			if spaceName == "" {
   263  				// When we are bridging unknown space devices, we bridge all
   264  				// of them. Both because this is a fallback, and because we
   265  				// don't know what the exact spaces are going to be.
   266  				for _, deviceName := range hostDeviceNames {
   267  					hostDeviceNamesToBridge = append(hostDeviceNamesToBridge, deviceName)
   268  					if hostDeviceByName[deviceName].Type() == state.BondDevice {
   269  						if reconfigureDelay < b.NetBondReconfigureDelay {
   270  							reconfigureDelay = b.NetBondReconfigureDelay
   271  						}
   272  					}
   273  				}
   274  			} else {
   275  				// This should already be sorted from
   276  				// LinkLayerDevicesForSpaces but sorting to be sure we stably
   277  				// pick the host device
   278  				hostDeviceNames = network.NaturallySortDeviceNames(hostDeviceNames...)
   279  				hostDeviceNamesToBridge = append(hostDeviceNamesToBridge, hostDeviceNames[0])
   280  				if hostDeviceByName[hostDeviceNames[0]].Type() == state.BondDevice {
   281  					if reconfigureDelay < b.NetBondReconfigureDelay {
   282  						reconfigureDelay = b.NetBondReconfigureDelay
   283  					}
   284  				}
   285  			}
   286  		}
   287  	}
   288  	notFound = notFound.Difference(spacesFound)
   289  	if !notFound.IsEmpty() {
   290  		hostSpaces, err := m.AllSpaces()
   291  		if err != nil {
   292  			// log it, but we're returning another error right now
   293  			logger.Warningf("got error looking for spaces for host machine %q: %v",
   294  				m.Id(), err)
   295  		}
   296  		logger.Warningf("container %q wants spaces %s, but host machine %q has %s missing %s",
   297  			containerMachine.Id(), network.QuoteSpaceSet(containerSpaces),
   298  			m.Id(), network.QuoteSpaceSet(hostSpaces), network.QuoteSpaceSet(notFound))
   299  		return nil, 0, errors.Errorf("host machine %q has no available device in space(s) %s",
   300  			m.Id(), network.QuoteSpaceSet(notFound))
   301  	}
   302  
   303  	hostToBridge := make([]network.DeviceToBridge, 0, len(hostDeviceNamesToBridge))
   304  	for _, hostName := range network.NaturallySortDeviceNames(hostDeviceNamesToBridge...) {
   305  		hostToBridge = append(hostToBridge, network.DeviceToBridge{
   306  			DeviceName: hostName,
   307  			BridgeName: BridgeNameForDevice(hostName),
   308  			MACAddress: hostDeviceByName[hostName].MACAddress(),
   309  		})
   310  	}
   311  	return hostToBridge, reconfigureDelay, nil
   312  }
   313  
   314  // PopulateContainerLinkLayerDevices sets the link-layer devices of the given
   315  // containerMachine, setting each device linked to the corresponding
   316  // BridgeDevice of the host machine. It also records when one of the
   317  // desired spaces is available on the host machine, but not currently
   318  // bridged.
   319  func (p *BridgePolicy) PopulateContainerLinkLayerDevices(m Machine, containerMachine Container) error {
   320  	// TODO(jam): 20017-01-31 This doesn't quite feel right that we would be
   321  	// defining devices that 'will' exist in the container, but don't exist
   322  	// yet. If anything, this feels more like "Provider" level devices, because
   323  	// it is defining the devices from the outside, not the inside.
   324  	containerSpaces, devicesPerSpace, err := p.findSpacesAndDevicesForContainer(m, containerMachine)
   325  	if err != nil {
   326  		return errors.Trace(err)
   327  	}
   328  	logger.Debugf("for container %q, found host devices spaces: %s",
   329  		containerMachine.Id(), formatDeviceMap(devicesPerSpace))
   330  
   331  	localBridgeForType := map[instance.ContainerType]string{
   332  		instance.LXD: network.DefaultLXDBridge,
   333  		instance.KVM: network.DefaultKVMBridge,
   334  	}
   335  	spacesFound := set.NewStrings()
   336  	devicesByName := make(map[string]LinkLayerDevice)
   337  	bridgeDeviceNames := make([]string, 0)
   338  
   339  	for spaceName, hostDevices := range devicesPerSpace {
   340  		for _, hostDevice := range hostDevices {
   341  			isFan := strings.HasPrefix(hostDevice.Name(), "fan-")
   342  			wantThisDevice := isFan == (p.ContainerNetworkingMethod == "fan")
   343  			deviceType, name := hostDevice.Type(), hostDevice.Name()
   344  			if wantThisDevice && deviceType == state.BridgeDevice && !skippedDeviceNames.Contains(name) {
   345  				devicesByName[name] = hostDevice
   346  				bridgeDeviceNames = append(bridgeDeviceNames, name)
   347  				spacesFound.Add(spaceName)
   348  			}
   349  		}
   350  	}
   351  	missingSpace := containerSpaces.Difference(spacesFound)
   352  
   353  	// Check if we are missing "" and can fill it in with a local bridge
   354  	if len(missingSpace) == 1 && missingSpace.Contains("") && p.ContainerNetworkingMethod == "local" {
   355  		localBridgeName := localBridgeForType[containerMachine.ContainerType()]
   356  		for _, hostDevice := range devicesPerSpace[""] {
   357  			name := hostDevice.Name()
   358  			if hostDevice.Type() == state.BridgeDevice && name == localBridgeName {
   359  				missingSpace.Remove("")
   360  				devicesByName[name] = hostDevice
   361  				bridgeDeviceNames = append(bridgeDeviceNames, name)
   362  				spacesFound.Add("")
   363  			}
   364  		}
   365  	}
   366  	if len(missingSpace) > 0 {
   367  		logger.Warningf("container %q wants spaces %s could not find host %q bridges for %s, found bridges %s",
   368  			containerMachine.Id(), network.QuoteSpaceSet(containerSpaces),
   369  			m.Id(), network.QuoteSpaceSet(missingSpace), bridgeDeviceNames)
   370  		return errors.Errorf("unable to find host bridge for space(s) %s for container %q",
   371  			network.QuoteSpaceSet(missingSpace), containerMachine.Id())
   372  	}
   373  
   374  	sortedBridgeDeviceNames := network.NaturallySortDeviceNames(bridgeDeviceNames...)
   375  	logger.Debugf("for container %q using host machine %q bridge devices: %s",
   376  		containerMachine.Id(), m.Id(), network.QuoteSpaces(sortedBridgeDeviceNames))
   377  	containerDevicesArgs := make([]state.LinkLayerDeviceArgs, len(bridgeDeviceNames))
   378  
   379  	for i, hostBridgeName := range sortedBridgeDeviceNames {
   380  		hostBridge := devicesByName[hostBridgeName]
   381  		newLLD, err := hostBridge.EthernetDeviceForBridge(fmt.Sprintf("eth%d", i))
   382  		if err != nil {
   383  			return errors.Trace(err)
   384  		}
   385  		containerDevicesArgs[i] = newLLD
   386  	}
   387  	logger.Debugf("prepared container %q network config: %+v", containerMachine.Id(), containerDevicesArgs)
   388  
   389  	if err := containerMachine.SetLinkLayerDevices(containerDevicesArgs...); err != nil {
   390  		return errors.Trace(err)
   391  	}
   392  
   393  	logger.Debugf("container %q network config set", containerMachine.Id())
   394  	return nil
   395  }