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

     1  // Copyright 2017 Canonical Ltd.
     2  // Licensed under the AGPLv3, see LICENCE file for details.
     3  
     4  package containerizer_test
     5  
     6  import (
     7  	"fmt"
     8  	"strconv"
     9  
    10  	jc "github.com/juju/testing/checkers"
    11  	"github.com/juju/utils/v3"
    12  	gc "gopkg.in/check.v1"
    13  
    14  	"github.com/juju/juju/core/constraints"
    15  	"github.com/juju/juju/core/instance"
    16  	corenetwork "github.com/juju/juju/core/network"
    17  	"github.com/juju/juju/environs/config"
    18  	"github.com/juju/juju/network"
    19  	"github.com/juju/juju/network/containerizer"
    20  	"github.com/juju/juju/state"
    21  	statetesting "github.com/juju/juju/state/testing"
    22  )
    23  
    24  // bridgePolicyStateSuite includes tests that are backed by Mongo.
    25  type bridgePolicyStateSuite struct {
    26  	statetesting.StateSuite
    27  
    28  	machine          containerizer.Machine
    29  	containerMachine containerizer.Container
    30  }
    31  
    32  var _ = gc.Suite(&bridgePolicyStateSuite{})
    33  
    34  func (s *bridgePolicyStateSuite) SetUpTest(c *gc.C) {
    35  	s.StateSuite.SetUpTest(c)
    36  
    37  	var err error
    38  	m, err := s.State.AddMachine(state.UbuntuBase("12.10"), state.JobHostUnits)
    39  	c.Assert(err, jc.ErrorIsNil)
    40  	s.machine = containerizer.NewMachine(m)
    41  }
    42  
    43  func (s *bridgePolicyStateSuite) addContainerMachine(c *gc.C) {
    44  	// Add a container machine with s.machine as its host.
    45  	containerTemplate := state.MachineTemplate{
    46  		Base: state.UbuntuBase("12.10"),
    47  		Jobs: []state.MachineJob{state.JobHostUnits},
    48  	}
    49  	container, err := s.State.AddMachineInsideMachine(containerTemplate, s.machine.Id(), instance.LXD)
    50  	c.Assert(err, jc.ErrorIsNil)
    51  	s.containerMachine = containerizer.NewMachine(container)
    52  }
    53  
    54  func (s *bridgePolicyStateSuite) assertNoDevicesOnMachine(c *gc.C, machine containerizer.Container) {
    55  	s.assertAllLinkLayerDevicesOnMachineMatchCount(c, machine, 0)
    56  }
    57  
    58  func (s *bridgePolicyStateSuite) assertAllLinkLayerDevicesOnMachineMatchCount(
    59  	c *gc.C, machine containerizer.Container, expectedCount int,
    60  ) {
    61  	results, err := machine.AllLinkLayerDevices()
    62  	c.Assert(err, jc.ErrorIsNil)
    63  	c.Check(results, gc.HasLen, expectedCount)
    64  }
    65  
    66  func (s *bridgePolicyStateSuite) createSpaceAndSubnet(c *gc.C, spaceName, CIDR string) {
    67  	space, err := s.State.AddSpace(spaceName, corenetwork.Id(spaceName), nil, true)
    68  	c.Assert(err, jc.ErrorIsNil)
    69  	_, err = s.State.AddSubnet(corenetwork.SubnetInfo{
    70  		CIDR:    CIDR,
    71  		SpaceID: space.Id(),
    72  	})
    73  	c.Assert(err, jc.ErrorIsNil)
    74  }
    75  
    76  // setupTwoSpaces creates a 'somespace' and a 'dmz' space, each with a single
    77  // registered subnet. 10.0.0.0/24 for 'somespace', and '10.10.0.0/24' for 'dmz'
    78  func (s *bridgePolicyStateSuite) setupTwoSpaces(c *gc.C) {
    79  	s.createSpaceAndSubnet(c, "somespace", "10.0.0.0/24")
    80  	s.createSpaceAndSubnet(c, "dmz", "10.10.0.0/24")
    81  }
    82  
    83  func (s *bridgePolicyStateSuite) createNICWithIP(c *gc.C, machine containerizer.Machine, deviceName, cidrAddress string) {
    84  	s.createNICWithIPAndPortType(c, machine, deviceName, cidrAddress, corenetwork.NonVirtualPort)
    85  }
    86  
    87  func (s *bridgePolicyStateSuite) createNICWithIPAndPortType(c *gc.C, machine containerizer.Machine, deviceName, cidrAddress string, portType corenetwork.VirtualPortType) {
    88  	err := machine.SetLinkLayerDevices(
    89  		state.LinkLayerDeviceArgs{
    90  			Name:            deviceName,
    91  			Type:            corenetwork.EthernetDevice,
    92  			ParentName:      "",
    93  			IsUp:            true,
    94  			VirtualPortType: portType,
    95  		},
    96  	)
    97  	c.Assert(err, jc.ErrorIsNil)
    98  	err = machine.SetDevicesAddresses(
    99  		state.LinkLayerDeviceAddress{
   100  			DeviceName:   deviceName,
   101  			CIDRAddress:  cidrAddress,
   102  			ConfigMethod: corenetwork.ConfigStatic,
   103  		},
   104  	)
   105  	c.Assert(err, jc.ErrorIsNil)
   106  }
   107  
   108  func (s *bridgePolicyStateSuite) createBridgeWithIP(c *gc.C, machine containerizer.Machine, bridgeName, cidrAddress string) {
   109  	err := machine.SetLinkLayerDevices(
   110  		state.LinkLayerDeviceArgs{
   111  			Name:       bridgeName,
   112  			Type:       corenetwork.BridgeDevice,
   113  			ParentName: "",
   114  			IsUp:       true,
   115  		},
   116  	)
   117  	c.Assert(err, jc.ErrorIsNil)
   118  	err = machine.SetDevicesAddresses(
   119  		state.LinkLayerDeviceAddress{
   120  			DeviceName:   bridgeName,
   121  			CIDRAddress:  cidrAddress,
   122  			ConfigMethod: corenetwork.ConfigStatic,
   123  		},
   124  	)
   125  	c.Assert(err, jc.ErrorIsNil)
   126  }
   127  
   128  // createNICAndBridgeWithIP creates a network interface and a bridge on the
   129  // machine, and assigns the requested CIDRAddress to the bridge.
   130  func (s *bridgePolicyStateSuite) createNICAndBridgeWithIP(c *gc.C, machine containerizer.Machine, deviceName, bridgeName, cidrAddress string) {
   131  	s.createBridgeWithIP(c, machine, bridgeName, cidrAddress)
   132  	err := machine.SetLinkLayerDevices(
   133  		state.LinkLayerDeviceArgs{
   134  			Name:       deviceName,
   135  			Type:       corenetwork.EthernetDevice,
   136  			ParentName: bridgeName,
   137  			IsUp:       true,
   138  		},
   139  	)
   140  	c.Assert(err, jc.ErrorIsNil)
   141  }
   142  
   143  func (s *bridgePolicyStateSuite) setupMachineInTwoSpaces(c *gc.C) {
   144  	s.setupTwoSpaces(c)
   145  	s.createNICAndBridgeWithIP(c, s.machine, "ens33", "br-ens33", "10.0.0.20/24")
   146  	s.createNICAndBridgeWithIP(c, s.machine, "ens0p10", "br-ens0p10", "10.10.0.20/24")
   147  }
   148  
   149  func (s *bridgePolicyStateSuite) createLoopbackNIC(c *gc.C, machine containerizer.Machine) {
   150  	err := machine.SetLinkLayerDevices(
   151  		state.LinkLayerDeviceArgs{
   152  			Name:       "lo",
   153  			Type:       corenetwork.LoopbackDevice,
   154  			ParentName: "",
   155  			IsUp:       true,
   156  		},
   157  	)
   158  	c.Assert(err, jc.ErrorIsNil)
   159  	err = machine.SetDevicesAddresses(
   160  		state.LinkLayerDeviceAddress{
   161  			DeviceName:   "lo",
   162  			CIDRAddress:  "127.0.0.1/24",
   163  			ConfigMethod: corenetwork.ConfigStatic,
   164  		},
   165  	)
   166  	c.Assert(err, jc.ErrorIsNil)
   167  }
   168  
   169  // createAllDefaultDevices creates the loopback, lxcbr0, lxdbr0, and virbr0 devices
   170  func (s *bridgePolicyStateSuite) createAllDefaultDevices(c *gc.C, machine containerizer.Machine) {
   171  	// loopback
   172  	s.createLoopbackNIC(c, machine)
   173  	// container.DefaultLxdBridge
   174  	s.createBridgeWithIP(c, machine, "lxdbr0", "10.0.4.1/24")
   175  	// container.DefaultKvmBridge
   176  	s.createBridgeWithIP(c, machine, "virbr0", "192.168.124.1/24")
   177  }
   178  
   179  func (s *bridgePolicyStateSuite) TestPopulateContainerLinkLayerDevicesWithProviderNetworkingAndOvsBridge(c *gc.C) {
   180  	s.createNICWithIP(c, s.machine, "ens3", "172.12.0.10/24")
   181  	// OVS bridges appear as regular nics; however, juju detects them by
   182  	// ovs-vsctl and sets their virtual port type to corenetwork.OvsPort
   183  	s.createNICWithIPAndPortType(c, s.machine, "ovsbr0", "172.12.0.10/24", corenetwork.OvsPort)
   184  	s.createAllDefaultDevices(c, s.machine)
   185  
   186  	s.addContainerMachine(c)
   187  	s.assertNoDevicesOnMachine(c, s.containerMachine)
   188  
   189  	// When using "provider" as the container networking method, the bridge
   190  	// policy code will treat ovs devices as bridges.
   191  	bridgePolicy, err := containerizer.NewBridgePolicy(cfg(c, 13, "provider"), s.State)
   192  	c.Assert(err, jc.ErrorIsNil)
   193  
   194  	info, err := bridgePolicy.PopulateContainerLinkLayerDevices(s.machine, s.containerMachine, false)
   195  	c.Assert(err, jc.ErrorIsNil)
   196  	c.Assert(info, gc.HasLen, 1)
   197  	c.Assert(info[0].ParentInterfaceName, gc.Equals, "ovsbr0", gc.Commentf("expected container device parent to be the OVS bridge"))
   198  }
   199  
   200  func (s *bridgePolicyStateSuite) TestPopulateContainerLinkLayerDevicesWithLocalNetworkingAndOvsBridge(c *gc.C) {
   201  	s.createNICWithIP(c, s.machine, "ens3", "172.12.0.10/24")
   202  	// OVS bridges appear as regular nics; however, juju detects them by
   203  	// ovs-vsctl and sets their virtual port type to corenetwork.OvsPort
   204  	s.createNICWithIPAndPortType(c, s.machine, "ovsbr0", "172.12.0.10/24", corenetwork.OvsPort)
   205  	s.createAllDefaultDevices(c, s.machine)
   206  
   207  	s.addContainerMachine(c)
   208  	s.assertNoDevicesOnMachine(c, s.containerMachine)
   209  
   210  	// When using "local" as the container networking method, the bridge
   211  	// policy code will treat ovs devices as regular NICs.
   212  	bridgePolicy, err := containerizer.NewBridgePolicy(cfg(c, 13, "local"), s.State)
   213  	c.Assert(err, jc.ErrorIsNil)
   214  
   215  	info, err := bridgePolicy.PopulateContainerLinkLayerDevices(s.machine, s.containerMachine, false)
   216  	c.Assert(err, jc.ErrorIsNil)
   217  	c.Assert(info, gc.HasLen, 1)
   218  	c.Assert(info[0].ParentInterfaceName, gc.Equals, "lxdbr0", gc.Commentf("expected container device parent to be the default lxd bridge as the container networking method is 'local'"))
   219  }
   220  
   221  func (s *bridgePolicyStateSuite) TestPopulateContainerLinkLayerDevicesCorrectlyPaired(c *gc.C) {
   222  	// The device names chosen and the order are very explicit. We
   223  	// need to ensure that we have a list that does not sort well
   224  	// alphabetically. This is because SetParentLinkLayerDevices()
   225  	// uses a natural sort ordering and we want to verify the
   226  	// pairing between the container's NIC name and its parent in
   227  	// the host machine during this unit test.
   228  
   229  	devicesArgs := []state.LinkLayerDeviceArgs{
   230  		{
   231  			Name: "br-eth10",
   232  			Type: corenetwork.BridgeDevice,
   233  		},
   234  		{
   235  			Name: "br-eth1",
   236  			Type: corenetwork.BridgeDevice,
   237  		},
   238  		{
   239  			Name: "br-eth10-100",
   240  			Type: corenetwork.BridgeDevice,
   241  		},
   242  		{
   243  			Name: "br-eth2",
   244  			Type: corenetwork.BridgeDevice,
   245  		},
   246  		{
   247  			Name: "br-eth0",
   248  			Type: corenetwork.BridgeDevice,
   249  		},
   250  		{
   251  			Name: "br-eth4",
   252  			Type: corenetwork.BridgeDevice,
   253  		},
   254  		{
   255  			Name: "br-eth3",
   256  			Type: corenetwork.BridgeDevice,
   257  		},
   258  	}
   259  	// Put each of those bridges into a different subnet that is part
   260  	// of the same space.
   261  	space, err := s.State.AddSpace("somespace", "somespace", nil, true)
   262  	c.Assert(err, jc.ErrorIsNil)
   263  
   264  	devAddresses := make([]state.LinkLayerDeviceAddress, len(devicesArgs))
   265  	for i, devArg := range devicesArgs {
   266  		subnet := i*10 + 1
   267  		subnetCIDR := fmt.Sprintf("10.%d.0.0/24", subnet)
   268  		_, err = s.State.AddSubnet(corenetwork.SubnetInfo{
   269  			CIDR:    subnetCIDR,
   270  			SpaceID: space.Id(),
   271  		})
   272  		c.Assert(err, jc.ErrorIsNil)
   273  		devAddresses[i] = state.LinkLayerDeviceAddress{
   274  			DeviceName:   devArg.Name,
   275  			CIDRAddress:  fmt.Sprintf("10.%d.0.10/24", subnet),
   276  			ConfigMethod: corenetwork.ConfigStatic,
   277  		}
   278  	}
   279  
   280  	expectedParents := []string{
   281  		"br-eth0",
   282  		"br-eth1",
   283  		"br-eth2",
   284  		"br-eth3",
   285  		"br-eth4",
   286  		"br-eth10",
   287  		"br-eth10-100",
   288  	}
   289  
   290  	err = s.machine.SetLinkLayerDevices(devicesArgs[:]...)
   291  	c.Assert(err, jc.ErrorIsNil)
   292  	err = s.machine.SetDevicesAddresses(devAddresses...)
   293  	c.Assert(err, jc.ErrorIsNil)
   294  	s.addContainerMachine(c)
   295  	s.assertNoDevicesOnMachine(c, s.containerMachine)
   296  	err = s.containerMachine.SetConstraints(constraints.Value{
   297  		Spaces: &[]string{"somespace"},
   298  	})
   299  	c.Assert(err, jc.ErrorIsNil)
   300  
   301  	bridgePolicy, err := containerizer.NewBridgePolicy(cfg(c, 13, "provider"), s.State)
   302  	c.Assert(err, jc.ErrorIsNil)
   303  
   304  	info, err := bridgePolicy.PopulateContainerLinkLayerDevices(s.machine, s.containerMachine, false)
   305  	c.Assert(err, jc.ErrorIsNil)
   306  	c.Assert(info, gc.HasLen, len(devicesArgs))
   307  	for i, dev := range info {
   308  		c.Check(dev.InterfaceName, gc.Equals, "eth"+strconv.Itoa(i))
   309  		c.Check(dev.InterfaceType, gc.Equals, corenetwork.EthernetDevice)
   310  		c.Check(dev.MTU, gc.Equals, 0) // inherited from the parent device.
   311  		c.Check(dev.MACAddress, gc.Matches, "00:16:3e(:[0-9a-f]{2}){3}")
   312  		c.Check(dev.Disabled, jc.IsFalse)
   313  		c.Check(dev.NoAutoStart, jc.IsFalse)
   314  		c.Check(dev.ParentInterfaceName, gc.Equals, expectedParents[i])
   315  	}
   316  }
   317  
   318  func (s *bridgePolicyStateSuite) TestPopulateContainerLinkLayerDevicesConstraintsBindOnlyOne(c *gc.C) {
   319  	s.setupMachineInTwoSpaces(c)
   320  	s.addContainerMachine(c)
   321  	s.assertNoDevicesOnMachine(c, s.containerMachine)
   322  	err := s.containerMachine.SetConstraints(constraints.Value{
   323  		Spaces: &[]string{"dmz"},
   324  	})
   325  	c.Assert(err, jc.ErrorIsNil)
   326  
   327  	bridgePolicy, err := containerizer.NewBridgePolicy(cfg(c, 13, "provider"), s.State)
   328  	c.Assert(err, jc.ErrorIsNil)
   329  
   330  	info, err := bridgePolicy.PopulateContainerLinkLayerDevices(s.machine, s.containerMachine, false)
   331  	c.Assert(err, jc.ErrorIsNil)
   332  	c.Assert(info, gc.HasLen, 1)
   333  	dev := info[0]
   334  	c.Check(dev.InterfaceName, gc.Equals, "eth0")
   335  	c.Check(dev.InterfaceType, gc.Equals, corenetwork.EthernetDevice)
   336  	c.Check(dev.MTU, gc.Equals, 0) // inherited from the parent device.
   337  	c.Check(dev.MACAddress, gc.Matches, "00:16:3e(:[0-9a-f]{2}){3}")
   338  	c.Check(dev.Disabled, jc.IsFalse)
   339  	c.Check(dev.NoAutoStart, jc.IsFalse)
   340  	// br-ens0p10 on the host machine is in space dmz, while br-ens33 is in space somespace
   341  	c.Check(dev.ParentInterfaceName, gc.Equals, "br-ens0p10")
   342  }
   343  
   344  func (s *bridgePolicyStateSuite) TestPopulateContainerLinkLayerDevicesHostOneSpace(c *gc.C) {
   345  	s.setupTwoSpaces(c)
   346  	// Is put into the 'somespace' space
   347  	s.createNICAndBridgeWithIP(c, s.machine, "eth0", "br-eth0", "10.0.0.20/24")
   348  	// We change the machine to be in 'dmz' instead of 'somespace', but it is
   349  	// still in a single space. Adding a container to a machine that is in a
   350  	// single space puts that container into the same space.
   351  	err := s.machine.RemoveAllAddresses()
   352  	c.Assert(err, jc.ErrorIsNil)
   353  	err = s.machine.SetDevicesAddresses(
   354  		state.LinkLayerDeviceAddress{
   355  			DeviceName: "br-eth0",
   356  			// In the DMZ subnet
   357  			CIDRAddress:  "10.10.0.20/24",
   358  			ConfigMethod: corenetwork.ConfigStatic,
   359  		},
   360  	)
   361  	c.Assert(err, jc.ErrorIsNil)
   362  	s.addContainerMachine(c)
   363  	s.assertNoDevicesOnMachine(c, s.containerMachine)
   364  
   365  	bridgePolicy, err := containerizer.NewBridgePolicy(cfg(c, 13, "provider"), s.State)
   366  	c.Assert(err, jc.ErrorIsNil)
   367  
   368  	info, err := bridgePolicy.PopulateContainerLinkLayerDevices(s.machine, s.containerMachine, false)
   369  	c.Assert(err, jc.ErrorIsNil)
   370  	c.Assert(info, gc.HasLen, 1)
   371  	dev := info[0]
   372  	c.Check(dev.InterfaceName, gc.Equals, "eth0")
   373  	c.Check(dev.InterfaceType, gc.Equals, corenetwork.EthernetDevice)
   374  	c.Check(dev.MTU, gc.Equals, 0) // inherited from the parent device.
   375  	c.Check(dev.MACAddress, gc.Matches, "00:16:3e(:[0-9a-f]{2}){3}")
   376  	c.Check(dev.Disabled, jc.IsFalse)
   377  	c.Check(dev.NoAutoStart, jc.IsFalse)
   378  	c.Check(dev.ParentInterfaceName, gc.Equals, "br-eth0")
   379  }
   380  
   381  func (s *bridgePolicyStateSuite) TestPopulateContainerLinkLayerDevicesDefaultSpace(c *gc.C) {
   382  	// TODO(jam): 2016-12-28 Eventually we probably want to have a
   383  	// model-config level default-space, but for now, 'default' should not be
   384  	// special.
   385  	// The host machine is in both 'default' and 'dmz', and the container is
   386  	// not requested to be in any particular space. But because we have
   387  	// access to the 'default' space, we go ahead and use that for the
   388  	// container.
   389  	s.setupMachineInTwoSpaces(c)
   390  	s.addContainerMachine(c)
   391  	s.assertNoDevicesOnMachine(c, s.containerMachine)
   392  
   393  	bridgePolicy, err := containerizer.NewBridgePolicy(cfg(c, 13, "provider"), s.State)
   394  	c.Assert(err, jc.ErrorIsNil)
   395  
   396  	_, err = bridgePolicy.PopulateContainerLinkLayerDevices(s.machine, s.containerMachine, false)
   397  	c.Assert(err, gc.ErrorMatches, "no obvious space for container.*")
   398  }
   399  
   400  func (s *bridgePolicyStateSuite) TestPopulateContainerLinkLayerDevicesNoValidSpace(c *gc.C) {
   401  	// The host machine will be in 2 spaces, but neither one is 'somespace',
   402  	// thus we are unable to find a valid space to put the container in.
   403  	s.setupTwoSpaces(c)
   404  	// Is put into the 'dmz' space
   405  	s.createNICAndBridgeWithIP(c, s.machine, "eth0", "br-eth0", "10.10.0.20/24")
   406  	// Second bridge is in the 'db' space
   407  	s.createSpaceAndSubnet(c, "db", "10.20.0.0/24")
   408  	s.createNICAndBridgeWithIP(c, s.machine, "ens4", "br-ens4", "10.20.0.10/24")
   409  	s.addContainerMachine(c)
   410  	s.assertNoDevicesOnMachine(c, s.containerMachine)
   411  
   412  	bridgePolicy, err := containerizer.NewBridgePolicy(cfg(c, 13, "provider"), s.State)
   413  	c.Assert(err, jc.ErrorIsNil)
   414  
   415  	_, err = bridgePolicy.PopulateContainerLinkLayerDevices(s.machine, s.containerMachine, false)
   416  	c.Assert(err, gc.ErrorMatches, `no obvious space for container "0/lxd/0", host machine has spaces: .*`)
   417  }
   418  
   419  func (s *bridgePolicyStateSuite) TestPopulateContainerLinkLayerDevicesMismatchConstraints(c *gc.C) {
   420  	// Machine is in 'somespace' but container wants to be in 'dmz'
   421  	s.setupTwoSpaces(c)
   422  	// Is put into the 'somespace' space
   423  	s.createNICAndBridgeWithIP(c, s.machine, "eth0", "br-eth0", "10.0.0.20/24")
   424  	s.addContainerMachine(c)
   425  	s.assertNoDevicesOnMachine(c, s.containerMachine)
   426  	err := s.containerMachine.SetConstraints(constraints.Value{
   427  		Spaces: &[]string{"dmz"},
   428  	})
   429  	c.Assert(err, jc.ErrorIsNil)
   430  
   431  	bridgePolicy, err := containerizer.NewBridgePolicy(cfg(c, 13, "provider"), s.State)
   432  	c.Assert(err, jc.ErrorIsNil)
   433  
   434  	_, err = bridgePolicy.PopulateContainerLinkLayerDevices(s.machine, s.containerMachine, false)
   435  	c.Assert(err, gc.NotNil)
   436  	c.Assert(err.Error(), gc.Equals, `unable to find host bridge for space(s) "dmz" for container "0/lxd/0"`)
   437  }
   438  
   439  func (s *bridgePolicyStateSuite) TestPopulateContainerLinkLayerDevicesMissingBridge(c *gc.C) {
   440  	// Machine is in 'somespace' and 'dmz' but doesn't have a bridge for 'dmz'
   441  	s.setupTwoSpaces(c)
   442  	s.createNICAndBridgeWithIP(c, s.machine, "eth0", "br-eth0", "10.0.0.20/24")
   443  	s.createNICWithIP(c, s.machine, "ens5", "10.20.0.10/24")
   444  	s.addContainerMachine(c)
   445  	s.assertNoDevicesOnMachine(c, s.containerMachine)
   446  	err := s.containerMachine.SetConstraints(constraints.Value{
   447  		Spaces: &[]string{"dmz"},
   448  	})
   449  	c.Assert(err, jc.ErrorIsNil)
   450  
   451  	bridgePolicy, err := containerizer.NewBridgePolicy(cfg(c, 13, "provider"), s.State)
   452  	c.Assert(err, jc.ErrorIsNil)
   453  
   454  	_, err = bridgePolicy.PopulateContainerLinkLayerDevices(s.machine, s.containerMachine, false)
   455  	c.Assert(err, gc.NotNil)
   456  	c.Assert(err.Error(), gc.Equals, `unable to find host bridge for space(s) "dmz" for container "0/lxd/0"`)
   457  }
   458  
   459  func (s *bridgePolicyStateSuite) TestPopulateContainerLinkLayerDevicesNoDefaultNoConstraints(c *gc.C) {
   460  	// The host machine will be in 2 spaces, but neither one is 'somespace',
   461  	// thus we are unable to find a valid space to put the container in.
   462  	s.setupTwoSpaces(c)
   463  	// In 'dmz'
   464  	s.createNICAndBridgeWithIP(c, s.machine, "eth0", "br-eth0", "10.10.0.20/24")
   465  	// Second bridge is in the 'db' space
   466  	s.createSpaceAndSubnet(c, "db", "10.20.0.0/24")
   467  	s.createNICAndBridgeWithIP(c, s.machine, "ens4", "br-ens4", "10.20.0.10/24")
   468  	s.addContainerMachine(c)
   469  	s.assertNoDevicesOnMachine(c, s.containerMachine)
   470  
   471  	bridgePolicy, err := containerizer.NewBridgePolicy(cfg(c, 13, "provider"), s.State)
   472  	c.Assert(err, jc.ErrorIsNil)
   473  
   474  	_, err = bridgePolicy.PopulateContainerLinkLayerDevices(s.machine, s.containerMachine, false)
   475  	c.Assert(err, gc.ErrorMatches, `no obvious space for container "0/lxd/0", host machine has spaces: .*`)
   476  }
   477  
   478  func (s *bridgePolicyStateSuite) TestPopulateContainerLinkLayerDevicesTwoDevicesOneBridged(c *gc.C) {
   479  	// The host machine has 2 devices in one space, but only one is bridged.
   480  	// We'll only use the one that is bridged, and not complain about the other.
   481  	s.setupTwoSpaces(c)
   482  	// In 'somespace'
   483  	s.createNICWithIP(c, s.machine, "eth0", "10.0.0.20/24")
   484  	s.createNICAndBridgeWithIP(c, s.machine, "eth1", "br-eth1", "10.0.0.21/24")
   485  	s.addContainerMachine(c)
   486  	err := s.containerMachine.SetConstraints(constraints.Value{
   487  		Spaces: &[]string{"somespace"},
   488  	})
   489  	c.Assert(err, jc.ErrorIsNil)
   490  	s.assertNoDevicesOnMachine(c, s.containerMachine)
   491  
   492  	bridgePolicy, err := containerizer.NewBridgePolicy(cfg(c, 13, "provider"), s.State)
   493  	c.Assert(err, jc.ErrorIsNil)
   494  
   495  	info, err := bridgePolicy.PopulateContainerLinkLayerDevices(s.machine, s.containerMachine, false)
   496  	c.Assert(err, jc.ErrorIsNil)
   497  	c.Assert(info, gc.HasLen, 1)
   498  	dev := info[0]
   499  	c.Check(dev.InterfaceName, gc.Equals, "eth0")
   500  	c.Check(dev.InterfaceType, gc.Equals, corenetwork.EthernetDevice)
   501  	c.Check(dev.MTU, gc.Equals, 0) // inherited from the parent device.
   502  	c.Check(dev.MACAddress, gc.Matches, "00:16:3e(:[0-9a-f]{2}){3}")
   503  	c.Check(dev.Disabled, jc.IsFalse)
   504  	c.Check(dev.NoAutoStart, jc.IsFalse)
   505  	// br-eth1 is a valid bridge in the 'somespace' space
   506  	c.Check(dev.ParentInterfaceName, gc.Equals, "br-eth1")
   507  }
   508  
   509  func (s *bridgePolicyStateSuite) TestPopulateContainerLinkLayerDevicesTwoBridgedSameSpace(c *gc.C) {
   510  	// The host machine has 2 devices and both are bridged into the desired space
   511  	// We'll use both
   512  	s.setupTwoSpaces(c)
   513  	// In 'somespace'
   514  	s.createNICAndBridgeWithIP(c, s.machine, "ens33", "br-ens33", "10.0.0.20/24")
   515  	s.createNICAndBridgeWithIP(c, s.machine, "ens44", "br-ens44", "10.0.0.21/24")
   516  	s.addContainerMachine(c)
   517  	err := s.containerMachine.SetConstraints(constraints.Value{
   518  		Spaces: &[]string{"somespace"},
   519  	})
   520  	c.Assert(err, jc.ErrorIsNil)
   521  	s.assertNoDevicesOnMachine(c, s.containerMachine)
   522  
   523  	bridgePolicy, err := containerizer.NewBridgePolicy(cfg(c, 13, "provider"), s.State)
   524  	c.Assert(err, jc.ErrorIsNil)
   525  
   526  	info, err := bridgePolicy.PopulateContainerLinkLayerDevices(s.machine, s.containerMachine, false)
   527  	c.Assert(err, jc.ErrorIsNil)
   528  	c.Assert(info, gc.HasLen, 2)
   529  	dev := info[0]
   530  	c.Check(dev.InterfaceName, gc.Equals, "eth0")
   531  	// br-ens33 and br-ens44 are both bridges in the 'somespace' space
   532  	c.Check(dev.ParentInterfaceName, gc.Equals, "br-ens33")
   533  	dev = info[1]
   534  	c.Check(dev.InterfaceName, gc.Equals, "eth1")
   535  	// br-ens33 and br-ens44 are both bridges in the 'somespace' space
   536  	c.Check(dev.ParentInterfaceName, gc.Equals, "br-ens44")
   537  }
   538  
   539  func (s *bridgePolicyStateSuite) TestPopulateContainerLinkLayerDevicesTwoBridgesNotInSpaces(c *gc.C) {
   540  	// The host machine has 2 network devices and 2 bridges, but none of them
   541  	// are in a known space. The container also has no requested space.
   542  	// In that case, we will use all of the unknown bridges for container
   543  	// devices.
   544  	s.setupTwoSpaces(c)
   545  	s.createNICAndBridgeWithIP(c, s.machine, "ens3", "br-ens3", "172.12.1.10/24")
   546  	s.createNICAndBridgeWithIP(c, s.machine, "ens4", "br-ens4", "192.168.3.4/24")
   547  	s.createAllDefaultDevices(c, s.machine)
   548  	s.addContainerMachine(c)
   549  	s.assertNoDevicesOnMachine(c, s.containerMachine)
   550  
   551  	bridgePolicy, err := containerizer.NewBridgePolicy(cfg(c, 13, "provider"), s.State)
   552  	c.Assert(err, jc.ErrorIsNil)
   553  
   554  	info, err := bridgePolicy.PopulateContainerLinkLayerDevices(s.machine, s.containerMachine, false)
   555  	c.Assert(err, jc.ErrorIsNil)
   556  	c.Assert(info, gc.HasLen, 2)
   557  	dev := info[0]
   558  	c.Check(dev.InterfaceName, gc.Equals, "eth0")
   559  	// br-ens33 and br-ens44 are both bridges in the 'somespace' space
   560  	c.Check(dev.ParentInterfaceName, gc.Equals, "br-ens3")
   561  	dev = info[1]
   562  	c.Check(dev.InterfaceName, gc.Equals, "eth1")
   563  	// br-ens33 and br-ens44 are both bridges in the 'somespace' space
   564  	c.Check(dev.ParentInterfaceName, gc.Equals, "br-ens4")
   565  }
   566  
   567  func (s *bridgePolicyStateSuite) TestPopulateContainerLinkLayerDevicesNoLocal(c *gc.C) {
   568  	// The host machine has 1 network device and only local bridges, but none of them
   569  	// are in a known space. The container also has no requested space.
   570  	s.setupTwoSpaces(c)
   571  	s.createNICWithIP(c, s.machine, "ens3", "172.12.1.10/24")
   572  	s.createAllDefaultDevices(c, s.machine)
   573  	s.addContainerMachine(c)
   574  	s.assertNoDevicesOnMachine(c, s.containerMachine)
   575  
   576  	bridgePolicy, err := containerizer.NewBridgePolicy(cfg(c, 13, "provider"), s.State)
   577  	c.Assert(err, jc.ErrorIsNil)
   578  
   579  	_, err = bridgePolicy.PopulateContainerLinkLayerDevices(s.machine, s.containerMachine, false)
   580  	c.Assert(err, gc.NotNil)
   581  	c.Assert(err.Error(), gc.Equals, `unable to find host bridge for space(s) "alpha" for container "0/lxd/0"`)
   582  }
   583  
   584  func (s *bridgePolicyStateSuite) TestPopulateContainerLinkLayerDevicesUseLocal(c *gc.C) {
   585  	// The host machine has 1 network device and only local bridges, but none of them
   586  	// are in a known space. The container also has no requested space.
   587  	s.setupTwoSpaces(c)
   588  	s.createNICWithIP(c, s.machine, "ens3", "172.12.1.10/24")
   589  	s.createAllDefaultDevices(c, s.machine)
   590  	s.addContainerMachine(c)
   591  	s.assertNoDevicesOnMachine(c, s.containerMachine)
   592  
   593  	bridgePolicy, err := containerizer.NewBridgePolicy(cfg(c, 13, "local"), s.State)
   594  	c.Assert(err, jc.ErrorIsNil)
   595  
   596  	info, err := bridgePolicy.PopulateContainerLinkLayerDevices(s.machine, s.containerMachine, false)
   597  	c.Assert(err, jc.ErrorIsNil)
   598  	c.Assert(info, gc.HasLen, 1)
   599  	dev := info[0]
   600  	c.Check(dev.InterfaceName, gc.Equals, "eth0")
   601  	c.Check(dev.ParentInterfaceName, gc.Equals, "lxdbr0")
   602  }
   603  
   604  func (s *bridgePolicyStateSuite) TestFindMissingBridgesForContainerNoneMissing(c *gc.C) {
   605  	s.setupTwoSpaces(c)
   606  	s.createNICAndBridgeWithIP(c, s.machine, "eth0", "br-eth0", "10.0.0.20/24")
   607  	s.addContainerMachine(c)
   608  	err := s.containerMachine.SetConstraints(constraints.Value{
   609  		Spaces: &[]string{"somespace"},
   610  	})
   611  	c.Assert(err, jc.ErrorIsNil)
   612  
   613  	bridgePolicy, err := containerizer.NewBridgePolicy(cfg(c, 13, "provider"), s.State)
   614  	c.Assert(err, jc.ErrorIsNil)
   615  
   616  	missing, reconfigureDelay, err := bridgePolicy.FindMissingBridgesForContainer(s.machine, s.containerMachine)
   617  	c.Assert(err, jc.ErrorIsNil)
   618  	c.Check(missing, jc.DeepEquals, []network.DeviceToBridge{})
   619  	c.Check(reconfigureDelay, gc.Equals, 0)
   620  }
   621  
   622  func (s *bridgePolicyStateSuite) TestFindMissingBridgesForContainerDefaultUnbridged(c *gc.C) {
   623  	s.setupTwoSpaces(c)
   624  	s.createNICWithIP(c, s.machine, "eth0", "10.0.0.20/24")
   625  	s.addContainerMachine(c)
   626  	err := s.containerMachine.SetConstraints(constraints.Value{
   627  		Spaces: &[]string{"somespace"},
   628  	})
   629  	c.Assert(err, jc.ErrorIsNil)
   630  
   631  	bridgePolicy, err := containerizer.NewBridgePolicy(cfg(c, 13, "provider"), s.State)
   632  	c.Assert(err, jc.ErrorIsNil)
   633  
   634  	missing, reconfigureDelay, err := bridgePolicy.FindMissingBridgesForContainer(s.machine, s.containerMachine)
   635  	c.Assert(err, jc.ErrorIsNil)
   636  	c.Check(missing, gc.DeepEquals, []network.DeviceToBridge{{
   637  		DeviceName: "eth0",
   638  		BridgeName: "br-eth0",
   639  	}})
   640  	c.Check(reconfigureDelay, gc.Equals, 0)
   641  }
   642  
   643  func (s *bridgePolicyStateSuite) TestFindMissingBridgesForContainerNoHostDevices(c *gc.C) {
   644  	s.setupTwoSpaces(c)
   645  	s.createSpaceAndSubnet(c, "third", "10.20.0.0/24")
   646  	s.createNICWithIP(c, s.machine, "eth0", "10.0.0.20/24")
   647  	s.addContainerMachine(c)
   648  	err := s.containerMachine.SetConstraints(constraints.Value{
   649  		Spaces: &[]string{"dmz", "third"},
   650  	})
   651  	c.Assert(err, jc.ErrorIsNil)
   652  
   653  	bridgePolicy, err := containerizer.NewBridgePolicy(cfg(c, 13, "provider"), s.State)
   654  	c.Assert(err, jc.ErrorIsNil)
   655  
   656  	_, reconfigureDelay, err := bridgePolicy.FindMissingBridgesForContainer(s.machine, s.containerMachine)
   657  	c.Assert(err, gc.NotNil)
   658  	c.Assert(err.Error(), gc.Equals, `host machine "0" has no available device in space(s) "dmz", "third"`)
   659  	c.Check(reconfigureDelay, gc.Equals, 0)
   660  }
   661  
   662  func (s *bridgePolicyStateSuite) TestFindMissingBridgesForContainerTwoSpacesOneMissing(c *gc.C) {
   663  	s.setupTwoSpaces(c)
   664  	// dmz
   665  	s.createNICAndBridgeWithIP(c, s.machine, "eth1", "br-eth1", "10.10.0.20/24")
   666  	s.addContainerMachine(c)
   667  	err := s.containerMachine.SetConstraints(constraints.Value{
   668  		Spaces: &[]string{"somespace", "dmz"},
   669  	})
   670  	c.Assert(err, jc.ErrorIsNil)
   671  
   672  	bridgePolicy, err := containerizer.NewBridgePolicy(cfg(c, 13, "provider"), s.State)
   673  	c.Assert(err, jc.ErrorIsNil)
   674  
   675  	_, _, err = bridgePolicy.FindMissingBridgesForContainer(s.machine, s.containerMachine)
   676  	c.Assert(err, gc.NotNil)
   677  	// both somespace and dmz are needed, but somespace is missing
   678  	c.Assert(err.Error(), gc.Equals, `host machine "0" has no available device in space(s) "somespace"`)
   679  }
   680  
   681  func (s *bridgePolicyStateSuite) TestFindMissingBridgesForContainerNoSpaces(c *gc.C) {
   682  	// There is a "somespace" and "dmz" space, and our machine has 2 network
   683  	// interfaces, but is not part of any known space. In this circumstance,
   684  	// we should try to bridge all of the unknown space devices, not just one
   685  	// of them. This is are fallback mode when we don't understand the spaces of a machine.
   686  	s.setupTwoSpaces(c)
   687  	s.createNICWithIP(c, s.machine, "ens3", "172.12.0.10/24")
   688  	s.createNICWithIP(c, s.machine, "ens4", "192.168.0.10/24")
   689  	s.createAllDefaultDevices(c, s.machine)
   690  	s.addContainerMachine(c)
   691  
   692  	bridgePolicy, err := containerizer.NewBridgePolicy(cfg(c, 13, "provider"), s.State)
   693  	c.Assert(err, jc.ErrorIsNil)
   694  
   695  	// No defined spaces for the container, no *known* spaces for the host
   696  	// machine. Triggers the fallback code to have us bridge all devices.
   697  	missing, reconfigureDelay, err := bridgePolicy.FindMissingBridgesForContainer(s.machine, s.containerMachine)
   698  	c.Assert(err, jc.ErrorIsNil)
   699  	c.Check(missing, gc.DeepEquals, []network.DeviceToBridge{{
   700  		DeviceName: "ens3",
   701  		BridgeName: "br-ens3",
   702  	}, {
   703  		DeviceName: "ens4",
   704  		BridgeName: "br-ens4",
   705  	}})
   706  	c.Check(reconfigureDelay, gc.Equals, 0)
   707  }
   708  
   709  func (s *bridgePolicyStateSuite) TestFindMissingBridgesForContainerContainerNetworkingMethodLocal(c *gc.C) {
   710  	// There is a "somespace" and "dmz" space, our machine has 1 network
   711  	// interface, but is not part of a known space. We have containerNetworkingMethod set to "local",
   712  	// which means we should fall back to using 'lxdbr0' instead of
   713  	// bridging the host device.
   714  	s.setupTwoSpaces(c)
   715  	s.createNICWithIP(c, s.machine, "ens3", "172.12.0.10/24")
   716  	s.createAllDefaultDevices(c, s.machine)
   717  	s.addContainerMachine(c)
   718  
   719  	bridgePolicy, err := containerizer.NewBridgePolicy(cfg(c, 13, "local"), s.State)
   720  	c.Assert(err, jc.ErrorIsNil)
   721  
   722  	// No defined spaces for the container, no *known* spaces for the host
   723  	// machine. Triggers the fallback code to have us bridge all devices.
   724  	missing, reconfigureDelay, err := bridgePolicy.FindMissingBridgesForContainer(s.machine, s.containerMachine)
   725  	c.Assert(err, jc.ErrorIsNil)
   726  	c.Check(missing, jc.DeepEquals, []network.DeviceToBridge{})
   727  	c.Check(reconfigureDelay, gc.Equals, 0)
   728  }
   729  
   730  func (s *bridgePolicyStateSuite) TestFindMissingBridgesForContainerContainerNetworkingMethodLocalDefinedHostSpace(c *gc.C) {
   731  	// There is a "somespace" and "dmz" space, our machine has 1 network
   732  	// interface, but is not part of a known space. We have containerNetworkingMethod set to "local",
   733  	// which means we should fall back to using 'lxdbr0' instead of
   734  	// bridging the host device.
   735  	s.setupTwoSpaces(c)
   736  	s.createNICWithIP(c, s.machine, "eth0", "10.0.0.20/24")
   737  	s.createAllDefaultDevices(c, s.machine)
   738  	s.addContainerMachine(c)
   739  
   740  	bridgePolicy, err := containerizer.NewBridgePolicy(cfg(c, 13, "local"), s.State)
   741  	c.Assert(err, jc.ErrorIsNil)
   742  
   743  	// No defined spaces for the container, host has spaces but we have
   744  	// ContainerNetworkingMethodLocal set so we should fall back to lxdbr0
   745  	missing, reconfigureDelay, err := bridgePolicy.FindMissingBridgesForContainer(s.machine, s.containerMachine)
   746  	c.Assert(err, jc.ErrorIsNil)
   747  	c.Check(missing, jc.DeepEquals, []network.DeviceToBridge{})
   748  	c.Check(reconfigureDelay, gc.Equals, 0)
   749  
   750  	info, err := bridgePolicy.PopulateContainerLinkLayerDevices(s.machine, s.containerMachine, false)
   751  	c.Assert(err, jc.ErrorIsNil)
   752  	c.Assert(info, gc.HasLen, 1)
   753  	dev := info[0]
   754  	c.Check(dev.InterfaceName, gc.Equals, "eth0")
   755  	c.Check(dev.ParentInterfaceName, gc.Equals, "lxdbr0")
   756  }
   757  
   758  func (s *bridgePolicyStateSuite) TestFindMissingBridgesForContainerContainerNetworkingMethodLocalNoAddress(c *gc.C) {
   759  	// We should only use 'lxdbr0' instead of bridging the host device.
   760  	s.setupTwoSpaces(c)
   761  	s.createNICWithIP(c, s.machine, "ens3", "172.12.0.10/24")
   762  	err := s.machine.SetLinkLayerDevices(
   763  		state.LinkLayerDeviceArgs{
   764  			Name:       "lxdbr0",
   765  			Type:       corenetwork.BridgeDevice,
   766  			ParentName: "",
   767  			IsUp:       true,
   768  		},
   769  	)
   770  	c.Assert(err, jc.ErrorIsNil)
   771  	s.addContainerMachine(c)
   772  
   773  	bridgePolicy, err := containerizer.NewBridgePolicy(cfg(c, 13, "local"), s.State)
   774  	c.Assert(err, jc.ErrorIsNil)
   775  
   776  	// No defined spaces for the container, no *known* spaces for the host
   777  	// machine. Triggers the fallback code to have us bridge all devices.
   778  	missing, reconfigureDelay, err := bridgePolicy.FindMissingBridgesForContainer(s.machine, s.containerMachine)
   779  	c.Assert(err, jc.ErrorIsNil)
   780  	c.Check(missing, jc.DeepEquals, []network.DeviceToBridge{
   781  		{DeviceName: "ens3", BridgeName: "br-ens3", MACAddress: ""},
   782  	})
   783  	c.Check(reconfigureDelay, gc.Equals, 0)
   784  }
   785  
   786  func (s *bridgePolicyStateSuite) TestFindMissingBridgesForContainerUnknownWithConstraint(c *gc.C) {
   787  	// If we have a host machine where we don't understand its spaces, but
   788  	// the container requests a specific space, we won't use the unknown
   789  	// ones.
   790  	s.setupTwoSpaces(c)
   791  	s.createNICWithIP(c, s.machine, "ens3", "172.12.0.10/24")
   792  	s.createNICWithIP(c, s.machine, "ens4", "192.168.0.10/24")
   793  	s.createAllDefaultDevices(c, s.machine)
   794  	s.addContainerMachine(c)
   795  	err := s.containerMachine.SetConstraints(constraints.Value{
   796  		Spaces: &[]string{"somespace"},
   797  	})
   798  	c.Assert(err, jc.ErrorIsNil)
   799  
   800  	bridgePolicy, err := containerizer.NewBridgePolicy(cfg(c, 13, "provider"), s.State)
   801  	c.Assert(err, jc.ErrorIsNil)
   802  
   803  	_, _, err = bridgePolicy.FindMissingBridgesForContainer(s.machine, s.containerMachine)
   804  	c.Assert(err, gc.NotNil)
   805  	c.Assert(err.Error(), gc.Equals,
   806  		`host machine "0" has no available device in space(s) "somespace"`)
   807  }
   808  
   809  func (s *bridgePolicyStateSuite) TestFindMissingBridgesForContainerUnknownAndDefault(c *gc.C) {
   810  	// The host machine has 2 devices, one is in a known 'somespace' space, the other is in an unknown space.
   811  	// We will ignore the unknown space and just return the one in 'somespace',
   812  	// cause that is the only declared space on the machine.
   813  	s.setupTwoSpaces(c)
   814  	// Default
   815  	s.createNICWithIP(c, s.machine, "ens3", "10.0.0.10/24")
   816  	s.createNICWithIP(c, s.machine, "ens4", "192.168.0.10/24")
   817  	s.createAllDefaultDevices(c, s.machine)
   818  	s.addContainerMachine(c)
   819  
   820  	bridgePolicy, err := containerizer.NewBridgePolicy(cfg(c, 13, "provider"), s.State)
   821  	c.Assert(err, jc.ErrorIsNil)
   822  
   823  	// We don't need a container constraint, as the host machine is in a single space.
   824  	missing, reconfigureDelay, err := bridgePolicy.FindMissingBridgesForContainer(s.machine, s.containerMachine)
   825  	c.Assert(err, jc.ErrorIsNil)
   826  	c.Check(missing, gc.DeepEquals, []network.DeviceToBridge{{
   827  		DeviceName: "ens3",
   828  		BridgeName: "br-ens3",
   829  	}})
   830  	c.Check(reconfigureDelay, gc.Equals, 0)
   831  }
   832  
   833  func (s *bridgePolicyStateSuite) TestFindMissingBridgesForContainerOneOfTwoBridged(c *gc.C) {
   834  	// With two host devices that could be bridged, we will only ask for the
   835  	// first one to be bridged.
   836  	s.setupTwoSpaces(c)
   837  	s.createNICWithIP(c, s.machine, "ens3", "10.0.0.20/24")
   838  	s.createNICWithIP(c, s.machine, "ens4", "10.0.0.21/24")
   839  	s.createNICWithIP(c, s.machine, "ens5", "10.0.0.22/24")
   840  	s.createNICWithIP(c, s.machine, "ens6", "10.0.0.23/24")
   841  	s.createNICWithIP(c, s.machine, "ens7", "10.0.0.24/24")
   842  	s.createNICWithIP(c, s.machine, "ens8", "10.0.0.25/24")
   843  	s.createNICWithIP(c, s.machine, "ens3.1", "10.0.0.26/24")
   844  	s.createNICWithIP(c, s.machine, "ens3:1", "10.0.0.27/24")
   845  	s.createNICWithIP(c, s.machine, "ens2.1", "10.0.0.28/24")
   846  	s.createNICWithIP(c, s.machine, "ens2.2", "10.0.0.29/24")
   847  	s.createNICWithIP(c, s.machine, "ens20", "10.0.0.30/24")
   848  	s.addContainerMachine(c)
   849  	err := s.containerMachine.SetConstraints(constraints.Value{
   850  		Spaces: &[]string{"somespace"},
   851  	})
   852  	c.Assert(err, jc.ErrorIsNil)
   853  
   854  	bridgePolicy, err := containerizer.NewBridgePolicy(cfg(c, 13, "provider"), s.State)
   855  	c.Assert(err, jc.ErrorIsNil)
   856  
   857  	missing, reconfigureDelay, err := bridgePolicy.FindMissingBridgesForContainer(s.machine, s.containerMachine)
   858  	c.Assert(err, jc.ErrorIsNil)
   859  	// Only the first device (by sort order) should be selected
   860  	c.Check(missing, gc.DeepEquals, []network.DeviceToBridge{{
   861  		DeviceName: "ens2.1",
   862  		BridgeName: "br-ens2-1",
   863  	}})
   864  	c.Check(reconfigureDelay, gc.Equals, 0)
   865  }
   866  
   867  func (s *bridgePolicyStateSuite) TestFindMissingBridgesForContainerTwoHostDevicesOneBridged(c *gc.C) {
   868  	// With two host devices that could be bridged, we will only ask for the
   869  	// first one to be bridged.
   870  	s.setupTwoSpaces(c)
   871  	s.createNICWithIP(c, s.machine, "ens3", "10.0.0.20/24")
   872  	s.createNICAndBridgeWithIP(c, s.machine, "ens4", "br-ens4", "10.0.0.21/24") // TODO: different subnet?
   873  	s.addContainerMachine(c)
   874  	err := s.containerMachine.SetConstraints(constraints.Value{
   875  		Spaces: &[]string{"somespace"},
   876  	})
   877  	c.Assert(err, jc.ErrorIsNil)
   878  
   879  	bridgePolicy, err := containerizer.NewBridgePolicy(cfg(c, 13, "provider"), s.State)
   880  	c.Assert(err, jc.ErrorIsNil)
   881  
   882  	missing, reconfigureDelay, err := bridgePolicy.FindMissingBridgesForContainer(s.machine, s.containerMachine)
   883  	c.Assert(err, jc.ErrorIsNil)
   884  	c.Check(missing, jc.DeepEquals, []network.DeviceToBridge{})
   885  	c.Check(reconfigureDelay, gc.Equals, 0)
   886  }
   887  
   888  func (s *bridgePolicyStateSuite) TestFindMissingBridgesForContainerNoConstraintsDefaultNotSpecial(c *gc.C) {
   889  	// TODO(jam): 2016-12-28 Eventually we probably want to have a
   890  	// model-config level default-space, but for now, 'somespace' should not be
   891  	// special.
   892  	s.setupTwoSpaces(c)
   893  	// Default
   894  	s.createNICWithIP(c, s.machine, "eth0", "10.0.0.20/24")
   895  	// DMZ
   896  	s.createNICWithIP(c, s.machine, "eth1", "10.10.0.20/24")
   897  	s.addContainerMachine(c)
   898  
   899  	bridgePolicy, err := containerizer.NewBridgePolicy(cfg(c, 13, "provider"), s.State)
   900  	c.Assert(err, jc.ErrorIsNil)
   901  
   902  	missing, reconfigureDelay, err := bridgePolicy.FindMissingBridgesForContainer(s.machine, s.containerMachine)
   903  	c.Assert(err, gc.ErrorMatches, "no obvious space for container.*")
   904  	c.Assert(missing, gc.IsNil)
   905  	c.Check(reconfigureDelay, gc.Equals, 0)
   906  }
   907  
   908  func (s *bridgePolicyStateSuite) TestFindMissingBridgesForContainerTwoSpacesOneBridged(c *gc.C) {
   909  	s.setupTwoSpaces(c)
   910  	// somespace
   911  	s.createNICWithIP(c, s.machine, "eth0", "10.0.0.20/24")
   912  	// DMZ
   913  	s.createNICAndBridgeWithIP(c, s.machine, "eth1", "br-eth1", "10.10.0.20/24")
   914  	s.addContainerMachine(c)
   915  	err := s.containerMachine.SetConstraints(constraints.Value{
   916  		Spaces: &[]string{"somespace", "dmz"},
   917  	})
   918  	c.Assert(err, jc.ErrorIsNil)
   919  
   920  	bridgePolicy, err := containerizer.NewBridgePolicy(cfg(c, 13, "provider"), s.State)
   921  	c.Assert(err, jc.ErrorIsNil)
   922  
   923  	missing, reconfigureDelay, err := bridgePolicy.FindMissingBridgesForContainer(s.machine, s.containerMachine)
   924  	c.Assert(err, jc.ErrorIsNil)
   925  	// both somespace and dmz are needed, but somespace needs to be bridged
   926  	c.Check(missing, jc.DeepEquals, []network.DeviceToBridge{{
   927  		DeviceName: "eth0",
   928  		BridgeName: "br-eth0",
   929  	}})
   930  	c.Check(reconfigureDelay, gc.Equals, 0)
   931  }
   932  
   933  func (s *bridgePolicyStateSuite) TestFindMissingBridgesForContainerMultipleSpacesNoneBridged(c *gc.C) {
   934  	s.setupTwoSpaces(c)
   935  	// somespace
   936  	s.createNICWithIP(c, s.machine, "eth0", "10.0.0.20/24")
   937  	// DMZ
   938  	s.createNICWithIP(c, s.machine, "eth1", "10.10.0.20/24")
   939  	// abba
   940  	s.createSpaceAndSubnet(c, "abba", "172.12.10.0/24")
   941  	s.createNICWithIP(c, s.machine, "eth0.1", "172.12.10.3/24")
   942  	s.addContainerMachine(c)
   943  	err := s.containerMachine.SetConstraints(constraints.Value{
   944  		Spaces: &[]string{"somespace", "dmz", "abba"},
   945  	})
   946  	c.Assert(err, jc.ErrorIsNil)
   947  
   948  	bridgePolicy, err := containerizer.NewBridgePolicy(cfg(c, 13, "provider"), s.State)
   949  	c.Assert(err, jc.ErrorIsNil)
   950  
   951  	missing, reconfigureDelay, err := bridgePolicy.FindMissingBridgesForContainer(s.machine, s.containerMachine)
   952  	c.Assert(err, jc.ErrorIsNil)
   953  	// both default and dmz are needed, but default needs to be bridged
   954  	c.Check(missing, jc.DeepEquals, []network.DeviceToBridge{{
   955  		DeviceName: "eth0",
   956  		BridgeName: "br-eth0",
   957  	}, {
   958  		DeviceName: "eth0.1",
   959  		BridgeName: "br-eth0-1",
   960  	}, {
   961  		DeviceName: "eth1",
   962  		BridgeName: "br-eth1",
   963  	}})
   964  	c.Check(reconfigureDelay, gc.Equals, 0)
   965  }
   966  
   967  func (s *bridgePolicyStateSuite) TestFindMissingBridgesForContainerBondedNICs(c *gc.C) {
   968  	s.setupTwoSpaces(c)
   969  	// somespace
   970  	// We call it 'zbond' so it sorts late instead of first
   971  	err := s.machine.SetLinkLayerDevices(
   972  		state.LinkLayerDeviceArgs{
   973  			Name:       "zbond0",
   974  			Type:       corenetwork.BondDevice,
   975  			ParentName: "",
   976  			IsUp:       true,
   977  		},
   978  	)
   979  	c.Assert(err, jc.ErrorIsNil)
   980  	err = s.machine.SetLinkLayerDevices(
   981  		state.LinkLayerDeviceArgs{
   982  			Name:       "eth0",
   983  			Type:       corenetwork.EthernetDevice,
   984  			ParentName: "zbond0",
   985  			IsUp:       true,
   986  		},
   987  		state.LinkLayerDeviceArgs{
   988  			Name:       "eth1",
   989  			Type:       corenetwork.EthernetDevice,
   990  			ParentName: "zbond0",
   991  			IsUp:       true,
   992  		},
   993  	)
   994  	c.Assert(err, jc.ErrorIsNil)
   995  	err = s.machine.SetDevicesAddresses(
   996  		state.LinkLayerDeviceAddress{
   997  			DeviceName:   "zbond0",
   998  			CIDRAddress:  "10.0.0.10/24",
   999  			ConfigMethod: corenetwork.ConfigStatic,
  1000  		},
  1001  		// TODO(jam): 2016-12-20 These devices *shouldn't* have IP addresses
  1002  		// when they are in a bond, however eventually we should detect what
  1003  		// space a device is in by something other than just IP address, and
  1004  		// we want to test that we don't try to bond these devices.
  1005  		// So for now we give them IP addresses so they show up in the space
  1006  		state.LinkLayerDeviceAddress{
  1007  			DeviceName:   "eth0",
  1008  			CIDRAddress:  "10.0.0.11/24",
  1009  			ConfigMethod: corenetwork.ConfigStatic,
  1010  		},
  1011  		state.LinkLayerDeviceAddress{
  1012  			DeviceName:   "eth1",
  1013  			CIDRAddress:  "10.0.0.12/24",
  1014  			ConfigMethod: corenetwork.ConfigStatic,
  1015  		},
  1016  	)
  1017  	c.Assert(err, jc.ErrorIsNil)
  1018  	s.addContainerMachine(c)
  1019  	err = s.containerMachine.SetConstraints(constraints.Value{
  1020  		Spaces: &[]string{"somespace"},
  1021  	})
  1022  	c.Assert(err, jc.ErrorIsNil)
  1023  
  1024  	bridgePolicy, err := containerizer.NewBridgePolicy(cfg(c, 13, "provider"), s.State)
  1025  	c.Assert(err, jc.ErrorIsNil)
  1026  
  1027  	missing, reconfigureDelay, err := bridgePolicy.FindMissingBridgesForContainer(s.machine, s.containerMachine)
  1028  	c.Assert(err, jc.ErrorIsNil)
  1029  	// both somespace and dmz are needed, but somespace needs to be bridged
  1030  	c.Check(missing, jc.DeepEquals, []network.DeviceToBridge{{
  1031  		DeviceName: "zbond0",
  1032  		BridgeName: "br-zbond0",
  1033  	}})
  1034  	// We are creating a bridge on a bond, so we use a non-zero delay
  1035  	c.Check(reconfigureDelay, gc.Equals, 13)
  1036  }
  1037  
  1038  func (s *bridgePolicyStateSuite) TestFindMissingBridgesForContainerVLAN(c *gc.C) {
  1039  	s.setupTwoSpaces(c)
  1040  	// We create an eth0 that has an address, and then an eth0.100 which is
  1041  	// VLAN tagged on top of that ethernet device.
  1042  	// "eth0" is in "somespace", "eth0.100" is in "dmz"
  1043  	s.createNICWithIP(c, s.machine, "eth0", "10.0.0.10/24")
  1044  	err := s.machine.SetLinkLayerDevices(
  1045  		state.LinkLayerDeviceArgs{
  1046  			Name:       "eth0.100",
  1047  			Type:       corenetwork.VLAN8021QDevice,
  1048  			ParentName: "eth0",
  1049  			IsUp:       true,
  1050  		},
  1051  	)
  1052  	c.Assert(err, jc.ErrorIsNil)
  1053  	// In dmz
  1054  	err = s.machine.SetDevicesAddresses(
  1055  		state.LinkLayerDeviceAddress{
  1056  			DeviceName:   "eth0.100",
  1057  			CIDRAddress:  "10.10.0.11/24",
  1058  			ConfigMethod: corenetwork.ConfigStatic,
  1059  		},
  1060  	)
  1061  	c.Assert(err, jc.ErrorIsNil)
  1062  
  1063  	// We create a container in both spaces, and we should see that it wants
  1064  	// to bridge both devices.
  1065  	s.addContainerMachine(c)
  1066  	err = s.containerMachine.SetConstraints(constraints.Value{
  1067  		Spaces: &[]string{"somespace", "dmz"},
  1068  	})
  1069  	c.Assert(err, jc.ErrorIsNil)
  1070  
  1071  	bridgePolicy, err := containerizer.NewBridgePolicy(cfg(c, 13, "provider"), s.State)
  1072  	c.Assert(err, jc.ErrorIsNil)
  1073  
  1074  	missing, reconfigureDelay, err := bridgePolicy.FindMissingBridgesForContainer(s.machine, s.containerMachine)
  1075  	c.Assert(err, jc.ErrorIsNil)
  1076  	c.Check(missing, jc.DeepEquals, []network.DeviceToBridge{{
  1077  		DeviceName: "eth0",
  1078  		BridgeName: "br-eth0",
  1079  	}, {
  1080  		DeviceName: "eth0.100",
  1081  		BridgeName: "br-eth0-100",
  1082  	}})
  1083  	c.Check(reconfigureDelay, gc.Equals, 0)
  1084  }
  1085  
  1086  func (s *bridgePolicyStateSuite) TestFindMissingBridgesForContainerVLANOnBond(c *gc.C) {
  1087  	s.setupTwoSpaces(c)
  1088  	// We have eth0 and eth1 that don't have IP addresses, that are in a
  1089  	// bond, which then has a VLAN on top of that bond. The VLAN should still
  1090  	// be a valid target for bridging
  1091  	err := s.machine.SetLinkLayerDevices(
  1092  		state.LinkLayerDeviceArgs{
  1093  			Name:       "bond0",
  1094  			Type:       corenetwork.BondDevice,
  1095  			ParentName: "",
  1096  			IsUp:       true,
  1097  		},
  1098  	)
  1099  	c.Assert(err, jc.ErrorIsNil)
  1100  	err = s.machine.SetLinkLayerDevices(
  1101  		[]state.LinkLayerDeviceArgs{{
  1102  			Name:       "eth0",
  1103  			Type:       corenetwork.EthernetDevice,
  1104  			ParentName: "bond0",
  1105  			IsUp:       true,
  1106  		}, {
  1107  			Name:       "eth1",
  1108  			Type:       corenetwork.EthernetDevice,
  1109  			ParentName: "bond0",
  1110  			IsUp:       true,
  1111  		}, {
  1112  			Name:       "bond0.100",
  1113  			Type:       corenetwork.VLAN8021QDevice,
  1114  			ParentName: "bond0",
  1115  			IsUp:       true,
  1116  		}}...,
  1117  	)
  1118  	c.Assert(err, jc.ErrorIsNil)
  1119  	err = s.machine.SetDevicesAddresses(
  1120  		state.LinkLayerDeviceAddress{
  1121  			DeviceName:   "bond0",
  1122  			CIDRAddress:  "10.0.0.20/24", // somespace
  1123  			ConfigMethod: corenetwork.ConfigStatic,
  1124  		},
  1125  		state.LinkLayerDeviceAddress{
  1126  			DeviceName:   "bond0.100",
  1127  			CIDRAddress:  "10.10.0.20/24", // dmz
  1128  			ConfigMethod: corenetwork.ConfigStatic,
  1129  		},
  1130  	)
  1131  	c.Assert(err, jc.ErrorIsNil)
  1132  
  1133  	// We create a container in both spaces, and we should see that it wants
  1134  	// to bridge both devices.
  1135  	s.addContainerMachine(c)
  1136  	err = s.containerMachine.SetConstraints(constraints.Value{
  1137  		Spaces: &[]string{"somespace", "dmz"},
  1138  	})
  1139  	c.Assert(err, jc.ErrorIsNil)
  1140  
  1141  	bridgePolicy, err := containerizer.NewBridgePolicy(cfg(c, 13, "provider"), s.State)
  1142  	c.Assert(err, jc.ErrorIsNil)
  1143  
  1144  	missing, reconfigureDelay, err := bridgePolicy.FindMissingBridgesForContainer(s.machine, s.containerMachine)
  1145  	c.Assert(err, jc.ErrorIsNil)
  1146  	c.Check(missing, jc.DeepEquals, []network.DeviceToBridge{{
  1147  		DeviceName: "bond0",
  1148  		BridgeName: "br-bond0",
  1149  	}, {
  1150  		DeviceName: "bond0.100",
  1151  		BridgeName: "br-bond0-100",
  1152  	}})
  1153  	c.Check(reconfigureDelay, gc.Equals, 13)
  1154  }
  1155  
  1156  func (s *bridgePolicyStateSuite) TestFindMissingBridgesForContainerNetworkingMethodFAN(c *gc.C) {
  1157  	s.setupTwoSpaces(c)
  1158  	s.createNICWithIP(c, s.machine, "eth0", "10.0.0.20/24")
  1159  	s.addContainerMachine(c)
  1160  	err := s.containerMachine.SetConstraints(constraints.Value{
  1161  		Spaces: &[]string{"somespace"},
  1162  	})
  1163  	c.Assert(err, jc.ErrorIsNil)
  1164  
  1165  	bridgePolicy, err := containerizer.NewBridgePolicy(cfg(c, 13, "fan"), s.State)
  1166  	c.Assert(err, jc.ErrorIsNil)
  1167  
  1168  	_, _, err = bridgePolicy.FindMissingBridgesForContainer(s.machine, s.containerMachine)
  1169  	c.Assert(err, gc.ErrorMatches, `host machine "0" has no available FAN devices in space\(s\) "somespace"`)
  1170  }
  1171  
  1172  var bridgeNames = map[string]string{
  1173  	"eno0":            "br-eno0",
  1174  	"enovlan.123":     "br-enovlan-123",
  1175  	"twelvechars0":    "br-twelvechars0",
  1176  	"thirteenchars":   "b-thirteenchars",
  1177  	"enfourteenchar":  "b-fourteenchar",
  1178  	"enfifteenchars0": "b-fifteenchars0",
  1179  	"fourteenchars1":  "b-5590a4-chars1",
  1180  	"fifteenchars.12": "b-38b496-ars-12",
  1181  	"zeros0526193032": "b-000000-193032",
  1182  	"enx00e07cc81e1d": "b-x00e07cc81e1d",
  1183  }
  1184  
  1185  func (s *bridgePolicyStateSuite) TestBridgeNameForDevice(c *gc.C) {
  1186  	for deviceName, bridgeName := range bridgeNames {
  1187  		generatedBridgeName := containerizer.BridgeNameForDevice(deviceName)
  1188  		c.Assert(generatedBridgeName, gc.Equals, bridgeName)
  1189  	}
  1190  }
  1191  
  1192  type configGetter struct {
  1193  	c *gc.C
  1194  
  1195  	reconfDelay int
  1196  	netMethod   string
  1197  }
  1198  
  1199  func (g configGetter) Config() *config.Config {
  1200  	cfg, err := config.New(false, map[string]interface{}{
  1201  		config.NameKey:                    "some-model",
  1202  		config.TypeKey:                    "some-cloud",
  1203  		config.UUIDKey:                    utils.MustNewUUID().String(),
  1204  		config.SecretBackendKey:           "auto",
  1205  		config.NetBondReconfigureDelayKey: g.reconfDelay,
  1206  		config.ContainerNetworkingMethod:  g.netMethod,
  1207  		config.FanConfig:                  "172.16.0.0/16=253.0.0.0/8",
  1208  	})
  1209  	g.c.Assert(err, jc.ErrorIsNil)
  1210  	return cfg
  1211  }
  1212  
  1213  func cfg(c *gc.C, reconfDelay int, netMethod string) configGetter {
  1214  	return configGetter{
  1215  		c:           c,
  1216  		reconfDelay: reconfDelay,
  1217  		netMethod:   netMethod,
  1218  	}
  1219  }