github.com/mwhudson/juju@v0.0.0-20160512215208-90ff01f3497f/worker/provisioner/lxc-broker_test.go (about)

     1  // Copyright 2013 Canonical Ltd.
     2  // Licensed under the AGPLv3, see LICENCE file for details.
     3  
     4  package provisioner_test
     5  
     6  import (
     7  	"bytes"
     8  	"fmt"
     9  	"io/ioutil"
    10  	"net"
    11  	"path/filepath"
    12  	"runtime"
    13  	"text/template"
    14  	"time"
    15  
    16  	"github.com/juju/errors"
    17  	"github.com/juju/names"
    18  	gitjujutesting "github.com/juju/testing"
    19  	jc "github.com/juju/testing/checkers"
    20  	"github.com/juju/utils/arch"
    21  	"github.com/juju/utils/series"
    22  	"github.com/juju/utils/set"
    23  	"github.com/juju/version"
    24  	gc "gopkg.in/check.v1"
    25  
    26  	"github.com/juju/juju/agent"
    27  	"github.com/juju/juju/apiserver/params"
    28  	"github.com/juju/juju/cloudconfig/instancecfg"
    29  	"github.com/juju/juju/constraints"
    30  	"github.com/juju/juju/container"
    31  	"github.com/juju/juju/container/lxc/mock"
    32  	lxctesting "github.com/juju/juju/container/lxc/testing"
    33  	containertesting "github.com/juju/juju/container/testing"
    34  	"github.com/juju/juju/environs"
    35  	"github.com/juju/juju/environs/filestorage"
    36  	envtesting "github.com/juju/juju/environs/testing"
    37  	"github.com/juju/juju/environs/tools"
    38  	"github.com/juju/juju/feature"
    39  	"github.com/juju/juju/instance"
    40  	instancetest "github.com/juju/juju/instance/testing"
    41  	jujutesting "github.com/juju/juju/juju/testing"
    42  	"github.com/juju/juju/network"
    43  	"github.com/juju/juju/state"
    44  	"github.com/juju/juju/status"
    45  	"github.com/juju/juju/storage"
    46  	"github.com/juju/juju/storage/provider"
    47  	coretesting "github.com/juju/juju/testing"
    48  	coretools "github.com/juju/juju/tools"
    49  	jujuversion "github.com/juju/juju/version"
    50  	"github.com/juju/juju/worker/provisioner"
    51  )
    52  
    53  type lxcSuite struct {
    54  	lxctesting.TestSuite
    55  	events     chan mock.Event
    56  	eventsDone chan struct{}
    57  }
    58  
    59  type lxcBrokerSuite struct {
    60  	lxcSuite
    61  	broker      environs.InstanceBroker
    62  	agentConfig agent.ConfigSetterWriter
    63  	api         *fakeAPI
    64  }
    65  
    66  var _ = gc.Suite(&lxcBrokerSuite{})
    67  
    68  func (s *lxcSuite) SetUpTest(c *gc.C) {
    69  	s.TestSuite.SetUpTest(c)
    70  	if runtime.GOOS == "windows" {
    71  		c.Skip("Skipping lxc tests on windows")
    72  	}
    73  	s.events = make(chan mock.Event)
    74  	s.eventsDone = make(chan struct{})
    75  	go func() {
    76  		defer close(s.eventsDone)
    77  		for event := range s.events {
    78  			c.Output(3, fmt.Sprintf("lxc event: <%s, %s>", event.Action, event.InstanceId))
    79  		}
    80  	}()
    81  	s.TestSuite.ContainerFactory.AddListener(s.events)
    82  }
    83  
    84  func (s *lxcSuite) TearDownTest(c *gc.C) {
    85  	close(s.events)
    86  	<-s.eventsDone
    87  	s.TestSuite.TearDownTest(c)
    88  }
    89  
    90  func (s *lxcBrokerSuite) SetUpTest(c *gc.C) {
    91  	if runtime.GOOS == "windows" {
    92  		c.Skip("Skipping lxc tests on windows")
    93  	}
    94  	s.lxcSuite.SetUpTest(c)
    95  	var err error
    96  	s.agentConfig, err = agent.NewAgentConfig(
    97  		agent.AgentConfigParams{
    98  			Paths:             agent.NewPathsWithDefaults(agent.Paths{DataDir: "/not/used/here"}),
    99  			Tag:               names.NewMachineTag("1"),
   100  			UpgradedToVersion: jujuversion.Current,
   101  			Password:          "dummy-secret",
   102  			Nonce:             "nonce",
   103  			APIAddresses:      []string{"10.0.0.1:1234"},
   104  			CACert:            coretesting.CACert,
   105  			Model:             coretesting.ModelTag,
   106  		})
   107  	c.Assert(err, jc.ErrorIsNil)
   108  	managerConfig := container.ManagerConfig{
   109  		container.ConfigName: "juju",
   110  		"log-dir":            c.MkDir(),
   111  		"use-clone":          "false",
   112  	}
   113  	s.api = NewFakeAPI()
   114  	s.broker, err = provisioner.NewLxcBroker(s.api, s.agentConfig, managerConfig, nil, false, 0)
   115  	c.Assert(err, jc.ErrorIsNil)
   116  }
   117  
   118  func (s *lxcBrokerSuite) instanceConfig(c *gc.C, machineId string) *instancecfg.InstanceConfig {
   119  	machineNonce := "fake-nonce"
   120  	// To isolate the tests from the host's architecture, we override it here.
   121  	s.PatchValue(&arch.HostArch, func() string { return arch.AMD64 })
   122  	stateInfo := jujutesting.FakeStateInfo(machineId)
   123  	apiInfo := jujutesting.FakeAPIInfo(machineId)
   124  	instanceConfig, err := instancecfg.NewInstanceConfig(machineId, machineNonce, "released", "quantal", "", true, stateInfo, apiInfo)
   125  	c.Assert(err, jc.ErrorIsNil)
   126  	// Ensure the <rootfs>/etc/network path exists.
   127  	containertesting.EnsureLXCRootFSEtcNetwork(c, "juju-"+names.NewMachineTag(machineId).String())
   128  	return instanceConfig
   129  }
   130  
   131  func (s *lxcBrokerSuite) startInstance(c *gc.C, machineId string, volumes []storage.VolumeParams) instance.Instance {
   132  	instanceConfig := s.instanceConfig(c, machineId)
   133  	cons := constraints.Value{}
   134  	possibleTools := coretools.List{&coretools.Tools{
   135  		Version: version.MustParseBinary("2.3.4-quantal-amd64"),
   136  		URL:     "http://tools.testing.invalid/2.3.4-quantal-amd64.tgz",
   137  	}, {
   138  		// non-host-arch tools should be filtered out by StartInstance
   139  		Version: version.MustParseBinary("2.3.4-quantal-arm64"),
   140  		URL:     "http://tools.testing.invalid/2.3.4-quantal-arm64.tgz",
   141  	}}
   142  	callback := func(settableStatus status.Status, info string, data map[string]interface{}) error {
   143  		return nil
   144  	}
   145  	result, err := s.broker.StartInstance(environs.StartInstanceParams{
   146  		Constraints:    cons,
   147  		Tools:          possibleTools,
   148  		InstanceConfig: instanceConfig,
   149  		Volumes:        volumes,
   150  		StatusCallback: callback,
   151  	})
   152  	c.Assert(err, jc.ErrorIsNil)
   153  	return result.Instance
   154  }
   155  
   156  func (s *lxcBrokerSuite) maintainInstance(c *gc.C, machineId string, volumes []storage.VolumeParams) {
   157  	instanceConfig := s.instanceConfig(c, machineId)
   158  	cons := constraints.Value{}
   159  	possibleTools := coretools.List{&coretools.Tools{
   160  		Version: version.MustParseBinary("2.3.4-quantal-amd64"),
   161  		URL:     "http://tools.testing.invalid/2.3.4-quantal-amd64.tgz",
   162  	}}
   163  	callback := func(settableStatus status.Status, info string, data map[string]interface{}) error {
   164  		return nil
   165  	}
   166  	err := s.broker.MaintainInstance(environs.StartInstanceParams{
   167  		Constraints:    cons,
   168  		Tools:          possibleTools,
   169  		InstanceConfig: instanceConfig,
   170  		Volumes:        volumes,
   171  		StatusCallback: callback,
   172  	})
   173  	c.Assert(err, jc.ErrorIsNil)
   174  }
   175  
   176  func (s *lxcBrokerSuite) assertDefaultStorageConfig(c *gc.C, lxc instance.Instance) {
   177  	config := filepath.Join(s.LxcDir, string(lxc.Id()), "config")
   178  	AssertFileContents(c, gc.Not(jc.Contains), config, "lxc.aa_profile = lxc-container-default-with-mounting")
   179  }
   180  
   181  func (s *lxcBrokerSuite) assertDefaultNetworkConfig(c *gc.C, lxc instance.Instance) {
   182  	lxc_conf := filepath.Join(s.ContainerDir, string(lxc.Id()), "lxc.conf")
   183  	expect := []string{
   184  		"lxc.network.type = veth",
   185  		"lxc.network.link = lxcbr0",
   186  	}
   187  	AssertFileContains(c, lxc_conf, expect...)
   188  }
   189  
   190  func (s *lxcBrokerSuite) TestStartInstance(c *gc.C) {
   191  	machineId := "1/lxc/0"
   192  	s.SetFeatureFlags(feature.AddressAllocation)
   193  	lxc := s.startInstance(c, machineId, nil)
   194  	s.api.CheckCalls(c, []gitjujutesting.StubCall{{
   195  		FuncName: "ContainerConfig",
   196  	}, {
   197  		FuncName: "PrepareContainerInterfaceInfo",
   198  		Args:     []interface{}{names.NewMachineTag("1-lxc-0")},
   199  	}})
   200  	c.Assert(lxc.Id(), gc.Equals, instance.Id("juju-machine-1-lxc-0"))
   201  	c.Assert(s.lxcContainerDir(lxc), jc.IsDirectory)
   202  	s.assertInstances(c, lxc)
   203  	s.assertDefaultNetworkConfig(c, lxc)
   204  	s.assertDefaultStorageConfig(c, lxc)
   205  }
   206  
   207  func (s *lxcBrokerSuite) TestStartInstanceAddressAllocationDisabled(c *gc.C) {
   208  	machineId := "1/lxc/0"
   209  	lxc := s.startInstance(c, machineId, nil)
   210  	s.api.CheckCalls(c, []gitjujutesting.StubCall{{
   211  		FuncName: "ContainerConfig",
   212  	}, {
   213  		FuncName: "PrepareContainerInterfaceInfo",
   214  		Args:     []interface{}{names.NewMachineTag("1-lxc-0")},
   215  	}})
   216  	c.Assert(lxc.Id(), gc.Equals, instance.Id("juju-machine-1-lxc-0"))
   217  	c.Assert(s.lxcContainerDir(lxc), jc.IsDirectory)
   218  	s.assertInstances(c, lxc)
   219  	s.assertDefaultNetworkConfig(c, lxc)
   220  	s.assertDefaultStorageConfig(c, lxc)
   221  }
   222  
   223  func (s *lxcBrokerSuite) TestMaintainInstance(c *gc.C) {
   224  	machineId := "1/lxc/0"
   225  	s.SetFeatureFlags(feature.AddressAllocation)
   226  	lxc := s.startInstance(c, machineId, nil)
   227  	s.api.ResetCalls()
   228  
   229  	s.maintainInstance(c, machineId, nil)
   230  	s.api.CheckCalls(c, []gitjujutesting.StubCall{{
   231  		FuncName: "GetContainerInterfaceInfo",
   232  		Args:     []interface{}{names.NewMachineTag("1-lxc-0")},
   233  	}})
   234  	c.Assert(lxc.Id(), gc.Equals, instance.Id("juju-machine-1-lxc-0"))
   235  	c.Assert(s.lxcContainerDir(lxc), jc.IsDirectory)
   236  	s.assertInstances(c, lxc)
   237  	s.assertDefaultNetworkConfig(c, lxc)
   238  	s.assertDefaultStorageConfig(c, lxc)
   239  }
   240  
   241  func (s *lxcBrokerSuite) TestMaintainInstanceAddressAllocationDisabled(c *gc.C) {
   242  	machineId := "1/lxc/0"
   243  	lxc := s.startInstance(c, machineId, nil)
   244  	s.api.ResetCalls()
   245  
   246  	s.maintainInstance(c, machineId, nil)
   247  	s.api.CheckCalls(c, []gitjujutesting.StubCall{})
   248  	c.Assert(lxc.Id(), gc.Equals, instance.Id("juju-machine-1-lxc-0"))
   249  	c.Assert(s.lxcContainerDir(lxc), jc.IsDirectory)
   250  	s.assertInstances(c, lxc)
   251  	s.assertDefaultNetworkConfig(c, lxc)
   252  	s.assertDefaultStorageConfig(c, lxc)
   253  }
   254  
   255  func (s *lxcBrokerSuite) TestStartInstanceWithStorage(c *gc.C) {
   256  	s.api.fakeContainerConfig.AllowLXCLoopMounts = true
   257  	s.SetFeatureFlags(feature.AddressAllocation)
   258  
   259  	machineId := "1/lxc/0"
   260  	lxc := s.startInstance(c, machineId, []storage.VolumeParams{{Provider: provider.LoopProviderType}})
   261  	s.api.CheckCalls(c, []gitjujutesting.StubCall{{
   262  		FuncName: "ContainerConfig",
   263  	}, {
   264  		FuncName: "PrepareContainerInterfaceInfo",
   265  		Args:     []interface{}{names.NewMachineTag("1-lxc-0")},
   266  	}})
   267  	c.Assert(lxc.Id(), gc.Equals, instance.Id("juju-machine-1-lxc-0"))
   268  	c.Assert(s.lxcContainerDir(lxc), jc.IsDirectory)
   269  	s.assertInstances(c, lxc)
   270  	// Check storage config.
   271  	config := filepath.Join(s.LxcDir, string(lxc.Id()), "config")
   272  	AssertFileContents(c, jc.Contains, config, "lxc.aa_profile = lxc-container-default-with-mounting")
   273  }
   274  
   275  func (s *lxcBrokerSuite) TestStartInstanceLoopMountsDisallowed(c *gc.C) {
   276  	s.api.fakeContainerConfig.AllowLXCLoopMounts = false
   277  	s.SetFeatureFlags(feature.AddressAllocation)
   278  	machineId := "1/lxc/0"
   279  	lxc := s.startInstance(c, machineId, []storage.VolumeParams{{Provider: provider.LoopProviderType}})
   280  	s.api.CheckCalls(c, []gitjujutesting.StubCall{{
   281  		FuncName: "ContainerConfig",
   282  	}, {
   283  		FuncName: "PrepareContainerInterfaceInfo",
   284  		Args:     []interface{}{names.NewMachineTag("1-lxc-0")},
   285  	}})
   286  	c.Assert(lxc.Id(), gc.Equals, instance.Id("juju-machine-1-lxc-0"))
   287  	c.Assert(s.lxcContainerDir(lxc), jc.IsDirectory)
   288  	s.assertInstances(c, lxc)
   289  	s.assertDefaultStorageConfig(c, lxc)
   290  }
   291  
   292  func (s *lxcBrokerSuite) TestStartInstanceHostArch(c *gc.C) {
   293  	instanceConfig := s.instanceConfig(c, "1/lxc/0")
   294  
   295  	// Patch the host's arch, so the LXC broker will filter tools.
   296  	s.PatchValue(&arch.HostArch, func() string { return arch.PPC64EL })
   297  	possibleTools := coretools.List{&coretools.Tools{
   298  		Version: version.MustParseBinary("2.3.4-quantal-amd64"),
   299  		URL:     "http://tools.testing.invalid/2.3.4-quantal-amd64.tgz",
   300  	}, {
   301  		Version: version.MustParseBinary("2.3.4-quantal-ppc64el"),
   302  		URL:     "http://tools.testing.invalid/2.3.4-quantal-ppc64el.tgz",
   303  	}}
   304  	callback := func(settableStatus status.Status, info string, data map[string]interface{}) error {
   305  		return nil
   306  	}
   307  
   308  	_, err := s.broker.StartInstance(environs.StartInstanceParams{
   309  		Constraints:    constraints.Value{},
   310  		Tools:          possibleTools,
   311  		InstanceConfig: instanceConfig,
   312  		StatusCallback: callback,
   313  	})
   314  	c.Assert(err, jc.ErrorIsNil)
   315  	c.Assert(instanceConfig.AgentVersion().Arch, gc.Equals, arch.PPC64EL)
   316  }
   317  
   318  func (s *lxcBrokerSuite) TestStartInstanceToolsArchNotFound(c *gc.C) {
   319  	instanceConfig := s.instanceConfig(c, "1/lxc/0")
   320  
   321  	// Patch the host's arch, so the LXC broker will filter tools.
   322  	s.PatchValue(&arch.HostArch, func() string { return arch.PPC64EL })
   323  	possibleTools := coretools.List{&coretools.Tools{
   324  		Version: version.MustParseBinary("2.3.4-quantal-amd64"),
   325  		URL:     "http://tools.testing.invalid/2.3.4-quantal-amd64.tgz",
   326  	}}
   327  	callback := func(settableStatus status.Status, info string, data map[string]interface{}) error {
   328  		return nil
   329  	}
   330  
   331  	_, err := s.broker.StartInstance(environs.StartInstanceParams{
   332  		Constraints:    constraints.Value{},
   333  		Tools:          possibleTools,
   334  		InstanceConfig: instanceConfig,
   335  		StatusCallback: callback,
   336  	})
   337  	c.Assert(err, gc.ErrorMatches, "need tools for arch ppc64el, only found \\[amd64\\]")
   338  }
   339  
   340  func (s *lxcBrokerSuite) TestStartInstanceWithBridgeEnviron(c *gc.C) {
   341  	s.agentConfig.SetValue(agent.LxcBridge, "br0")
   342  	machineId := "1/lxc/0"
   343  	lxc := s.startInstance(c, machineId, nil)
   344  	s.api.CheckCalls(c, []gitjujutesting.StubCall{{
   345  		FuncName: "ContainerConfig",
   346  	}, {
   347  		FuncName: "PrepareContainerInterfaceInfo",
   348  		Args:     []interface{}{names.NewMachineTag("1-lxc-0")},
   349  	}})
   350  	c.Assert(lxc.Id(), gc.Equals, instance.Id("juju-machine-1-lxc-0"))
   351  	c.Assert(s.lxcContainerDir(lxc), jc.IsDirectory)
   352  	s.assertInstances(c, lxc)
   353  	// Uses default network config
   354  	lxc_conf := filepath.Join(s.ContainerDir, string(lxc.Id()), "lxc.conf")
   355  	expect := []string{
   356  		"lxc.network.type = veth",
   357  		"lxc.network.link = br0",
   358  	}
   359  	AssertFileContains(c, lxc_conf, expect...)
   360  }
   361  
   362  func (s *lxcBrokerSuite) startInstancePopulatesNetworkInfo(c *gc.C) (*environs.StartInstanceResult, error) {
   363  	s.PatchValue(provisioner.InterfaceAddrs, func(i *net.Interface) ([]net.Addr, error) {
   364  		return []net.Addr{&fakeAddr{"0.1.2.1/24"}}, nil
   365  	})
   366  	fakeResolvConf := filepath.Join(c.MkDir(), "resolv.conf")
   367  	err := ioutil.WriteFile(fakeResolvConf, []byte("nameserver ns1.dummy\nnameserver ns2.dummy\nsearch dummy\n"), 0644)
   368  	c.Assert(err, jc.ErrorIsNil)
   369  	s.PatchValue(provisioner.ResolvConf, fakeResolvConf)
   370  
   371  	instanceConfig := s.instanceConfig(c, "42")
   372  	possibleTools := coretools.List{&coretools.Tools{
   373  		Version: version.MustParseBinary("2.3.4-quantal-amd64"),
   374  		URL:     "http://tools.testing.invalid/2.3.4-quantal-amd64.tgz",
   375  	}}
   376  	callback := func(settableStatus status.Status, info string, data map[string]interface{}) error {
   377  		return nil
   378  	}
   379  
   380  	return s.broker.StartInstance(environs.StartInstanceParams{
   381  		Constraints:    constraints.Value{},
   382  		Tools:          possibleTools,
   383  		InstanceConfig: instanceConfig,
   384  		StatusCallback: callback,
   385  	})
   386  }
   387  
   388  func (s *lxcBrokerSuite) TestStartInstancePopulatesNetworkInfoWithAddressAllocation(c *gc.C) {
   389  	s.SetFeatureFlags(feature.AddressAllocation)
   390  	result, err := s.startInstancePopulatesNetworkInfo(c)
   391  	c.Assert(err, jc.ErrorIsNil)
   392  	c.Assert(result.NetworkInfo, gc.HasLen, 1)
   393  	iface := result.NetworkInfo[0]
   394  	c.Assert(err, jc.ErrorIsNil)
   395  	c.Assert(iface, jc.DeepEquals, network.InterfaceInfo{
   396  		DeviceIndex:      0,
   397  		CIDR:             "0.1.2.0/24",
   398  		ConfigType:       network.ConfigStatic,
   399  		InterfaceName:    "eth0", // generated from the device index.
   400  		DNSServers:       network.NewAddresses("ns1.dummy", "ns2.dummy"),
   401  		DNSSearchDomains: []string{"dummy"},
   402  		MACAddress:       "aa:bb:cc:dd:ee:ff",
   403  		Address:          network.NewAddress("0.1.2.3"),
   404  		GatewayAddress:   network.NewAddress("0.1.2.1"),
   405  	})
   406  }
   407  
   408  func (s *lxcBrokerSuite) TestStartInstancePopulatesNetworkInfoWithoutAddressAllocation(c *gc.C) {
   409  	s.SetFeatureFlags()
   410  	result, err := s.startInstancePopulatesNetworkInfo(c)
   411  	c.Assert(err, jc.ErrorIsNil)
   412  	c.Assert(result.NetworkInfo, gc.HasLen, 1)
   413  	iface := result.NetworkInfo[0]
   414  	c.Assert(err, jc.ErrorIsNil)
   415  	c.Assert(iface, jc.DeepEquals, network.InterfaceInfo{
   416  		DeviceIndex:      0,
   417  		CIDR:             "0.1.2.0/24",
   418  		InterfaceName:    "dummy0", // generated from the device index.
   419  		DNSServers:       network.NewAddresses("ns1.dummy", "ns2.dummy"),
   420  		DNSSearchDomains: []string{"dummy"},
   421  		MACAddress:       "aa:bb:cc:dd:ee:ff",
   422  		Address:          network.NewAddress("0.1.2.3"),
   423  		GatewayAddress:   network.NewAddress("0.1.2.1"),
   424  	})
   425  }
   426  
   427  func (s *lxcBrokerSuite) TestStopInstance(c *gc.C) {
   428  	lxc0 := s.startInstance(c, "1/lxc/0", nil)
   429  	lxc1 := s.startInstance(c, "1/lxc/1", nil)
   430  	lxc2 := s.startInstance(c, "1/lxc/2", nil)
   431  
   432  	s.assertInstances(c, lxc0, lxc1, lxc2)
   433  	err := s.broker.StopInstances(lxc0.Id())
   434  	c.Assert(err, jc.ErrorIsNil)
   435  	s.assertInstances(c, lxc1, lxc2)
   436  	c.Assert(s.lxcContainerDir(lxc0), jc.DoesNotExist)
   437  	c.Assert(s.lxcRemovedContainerDir(lxc0), jc.IsDirectory)
   438  
   439  	err = s.broker.StopInstances(lxc1.Id(), lxc2.Id())
   440  	c.Assert(err, jc.ErrorIsNil)
   441  	s.assertInstances(c)
   442  }
   443  
   444  func (s *lxcBrokerSuite) TestAllInstances(c *gc.C) {
   445  	lxc0 := s.startInstance(c, "1/lxc/0", nil)
   446  	lxc1 := s.startInstance(c, "1/lxc/1", nil)
   447  	s.assertInstances(c, lxc0, lxc1)
   448  
   449  	err := s.broker.StopInstances(lxc1.Id())
   450  	c.Assert(err, jc.ErrorIsNil)
   451  	lxc2 := s.startInstance(c, "1/lxc/2", nil)
   452  	s.assertInstances(c, lxc0, lxc2)
   453  }
   454  
   455  func (s *lxcBrokerSuite) assertInstances(c *gc.C, inst ...instance.Instance) {
   456  	results, err := s.broker.AllInstances()
   457  	c.Assert(err, jc.ErrorIsNil)
   458  	instancetest.MatchInstances(c, results, inst...)
   459  }
   460  
   461  func (s *lxcBrokerSuite) lxcContainerDir(inst instance.Instance) string {
   462  	return filepath.Join(s.ContainerDir, string(inst.Id()))
   463  }
   464  
   465  func (s *lxcBrokerSuite) lxcRemovedContainerDir(inst instance.Instance) string {
   466  	return filepath.Join(s.RemovedDir, string(inst.Id()))
   467  }
   468  
   469  func (s *lxcBrokerSuite) TestLocalDNSServers(c *gc.C) {
   470  	fakeConf := filepath.Join(c.MkDir(), "resolv.conf")
   471  	s.PatchValue(provisioner.ResolvConf, fakeConf)
   472  
   473  	// If config is missing, that's OK.
   474  	dnses, dnsSearch, err := provisioner.LocalDNSServers()
   475  	c.Assert(err, jc.ErrorIsNil)
   476  	c.Assert(dnses, gc.HasLen, 0)
   477  	c.Assert(dnsSearch, gc.Equals, "")
   478  
   479  	// Enter some data in fakeConf.
   480  	data := `
   481   anything else is ignored
   482    # comments are ignored
   483    nameserver  0.1.2.3  # that's parsed
   484  search  foo.baz # comment ignored
   485  # nameserver 42.42.42.42 - ignored as well
   486  nameserver 8.8.8.8
   487  nameserver example.com # comment after is ok
   488  `
   489  	err = ioutil.WriteFile(fakeConf, []byte(data), 0644)
   490  	c.Assert(err, jc.ErrorIsNil)
   491  
   492  	dnses, dnsSearch, err = provisioner.LocalDNSServers()
   493  	c.Assert(err, jc.ErrorIsNil)
   494  	c.Assert(dnses, jc.DeepEquals, network.NewAddresses(
   495  		"0.1.2.3", "8.8.8.8", "example.com",
   496  	))
   497  	c.Assert(dnsSearch, gc.Equals, "foo.baz")
   498  }
   499  
   500  func (s *lxcBrokerSuite) TestMustParseTemplate(c *gc.C) {
   501  	f := func() { provisioner.MustParseTemplate("", "{{invalid}") }
   502  	c.Assert(f, gc.PanicMatches, `template: :1: function "invalid" not defined`)
   503  
   504  	tmpl := provisioner.MustParseTemplate("name", "X={{.X}}")
   505  	c.Assert(tmpl, gc.NotNil)
   506  	c.Assert(tmpl.Name(), gc.Equals, "name")
   507  
   508  	var buf bytes.Buffer
   509  	err := tmpl.Execute(&buf, struct{ X string }{"42"})
   510  	c.Assert(err, jc.ErrorIsNil)
   511  	c.Assert(buf.String(), gc.Equals, "X=42")
   512  }
   513  
   514  func (s *lxcBrokerSuite) TestRunTemplateCommand(c *gc.C) {
   515  	for i, test := range []struct {
   516  		source        string
   517  		exitNonZeroOK bool
   518  		data          interface{}
   519  		exitCode      int
   520  		expectErr     string
   521  	}{{
   522  		source:        "echo {{.Name}}",
   523  		exitNonZeroOK: false,
   524  		data:          struct{ Name string }{"foo"},
   525  		exitCode:      0,
   526  	}, {
   527  		source:        "exit {{.Code}}",
   528  		exitNonZeroOK: false,
   529  		data:          struct{ Code int }{123},
   530  		exitCode:      123,
   531  		expectErr:     `command "exit 123" failed with exit code 123`,
   532  	}, {
   533  		source:        "exit {{.Code}}",
   534  		exitNonZeroOK: true,
   535  		data:          struct{ Code int }{56},
   536  		exitCode:      56,
   537  	}, {
   538  		source:        "exit 42",
   539  		exitNonZeroOK: true,
   540  		exitCode:      42,
   541  	}, {
   542  		source:        "some-invalid-command",
   543  		exitNonZeroOK: false,
   544  		exitCode:      127, // returned by bash.
   545  		expectErr:     `command "some-invalid-command" failed with exit code 127`,
   546  	}} {
   547  		c.Logf("test %d: %q -> %d", i, test.source, test.exitCode)
   548  		t, err := template.New(fmt.Sprintf("test %d", i)).Parse(test.source)
   549  		if !c.Check(err, jc.ErrorIsNil, gc.Commentf("parsing %q", test.source)) {
   550  			continue
   551  		}
   552  		exitCode, err := provisioner.RunTemplateCommand(t, test.exitNonZeroOK, test.data)
   553  		if test.expectErr != "" {
   554  			c.Check(err, gc.ErrorMatches, test.expectErr)
   555  		} else {
   556  			c.Check(err, jc.ErrorIsNil)
   557  		}
   558  		c.Check(exitCode, gc.Equals, test.exitCode)
   559  	}
   560  }
   561  
   562  func (s *lxcBrokerSuite) TestSetupRoutesAndIPTablesInvalidArgs(c *gc.C) {
   563  	// Isolate the test from the host machine.
   564  	gitjujutesting.PatchExecutableThrowError(c, s, "iptables", 42)
   565  	gitjujutesting.PatchExecutableThrowError(c, s, "ip", 123)
   566  
   567  	// Check that all the arguments are verified to be non-empty.
   568  	expectStartupErr := "primaryNIC, primaryAddr, bridgeName, and ifaceInfo must be all set"
   569  	emptyIfaceInfo := []network.InterfaceInfo{}
   570  	for i, test := range []struct {
   571  		about       string
   572  		primaryNIC  string
   573  		primaryAddr network.Address
   574  		bridgeName  string
   575  		ifaceInfo   []network.InterfaceInfo
   576  		expectErr   string
   577  	}{{
   578  		about:       "all empty",
   579  		primaryNIC:  "",
   580  		primaryAddr: network.Address{},
   581  		bridgeName:  "",
   582  		ifaceInfo:   nil,
   583  		expectErr:   expectStartupErr,
   584  	}, {
   585  		about:       "all but primaryNIC empty",
   586  		primaryNIC:  "nic",
   587  		primaryAddr: network.Address{},
   588  		bridgeName:  "",
   589  		ifaceInfo:   nil,
   590  		expectErr:   expectStartupErr,
   591  	}, {
   592  		about:       "all but primaryAddr empty",
   593  		primaryNIC:  "",
   594  		primaryAddr: network.NewAddress("0.1.2.1"),
   595  		bridgeName:  "",
   596  		ifaceInfo:   nil,
   597  		expectErr:   expectStartupErr,
   598  	}, {
   599  		about:       "all but bridgeName empty",
   600  		primaryNIC:  "",
   601  		primaryAddr: network.Address{},
   602  		bridgeName:  "bridge",
   603  		ifaceInfo:   nil,
   604  		expectErr:   expectStartupErr,
   605  	}, {
   606  		about:       "all but primaryNIC and bridgeName empty",
   607  		primaryNIC:  "nic",
   608  		primaryAddr: network.Address{},
   609  		bridgeName:  "bridge",
   610  		ifaceInfo:   nil,
   611  		expectErr:   expectStartupErr,
   612  	}, {
   613  		about:       "all but primaryNIC and primaryAddr empty",
   614  		primaryNIC:  "nic",
   615  		primaryAddr: network.NewAddress("0.1.2.1"),
   616  		bridgeName:  "",
   617  		ifaceInfo:   nil,
   618  		expectErr:   expectStartupErr,
   619  	}, {
   620  		about:       "all but primaryAddr and bridgeName empty",
   621  		primaryNIC:  "",
   622  		primaryAddr: network.NewAddress("0.1.2.1"),
   623  		bridgeName:  "bridge",
   624  		ifaceInfo:   nil,
   625  		expectErr:   expectStartupErr,
   626  	}, {
   627  		about:       "all set except ifaceInfo",
   628  		primaryNIC:  "nic",
   629  		primaryAddr: network.NewAddress("0.1.2.1"),
   630  		bridgeName:  "bridge",
   631  		ifaceInfo:   nil,
   632  		expectErr:   expectStartupErr,
   633  	}, {
   634  		about:       "all empty (ifaceInfo set but empty)",
   635  		primaryNIC:  "",
   636  		primaryAddr: network.Address{},
   637  		bridgeName:  "",
   638  		ifaceInfo:   emptyIfaceInfo,
   639  		expectErr:   expectStartupErr,
   640  	}, {
   641  		about:       "all but primaryNIC empty (ifaceInfo set but empty)",
   642  		primaryNIC:  "nic",
   643  		primaryAddr: network.Address{},
   644  		bridgeName:  "",
   645  		ifaceInfo:   emptyIfaceInfo,
   646  		expectErr:   expectStartupErr,
   647  	}, {
   648  		about:       "all but primaryAddr empty (ifaceInfo set but empty)",
   649  		primaryNIC:  "",
   650  		primaryAddr: network.NewAddress("0.1.2.1"),
   651  		bridgeName:  "",
   652  		ifaceInfo:   emptyIfaceInfo,
   653  		expectErr:   expectStartupErr,
   654  	}, {
   655  		about:       "all but bridgeName empty (ifaceInfo set but empty)",
   656  		primaryNIC:  "",
   657  		primaryAddr: network.Address{},
   658  		bridgeName:  "bridge",
   659  		ifaceInfo:   emptyIfaceInfo,
   660  		expectErr:   expectStartupErr,
   661  	}, {
   662  		about:       "just primaryAddr is empty and ifaceInfo set but empty",
   663  		primaryNIC:  "nic",
   664  		primaryAddr: network.Address{},
   665  		bridgeName:  "bridge",
   666  		ifaceInfo:   emptyIfaceInfo,
   667  		expectErr:   expectStartupErr,
   668  	}, {
   669  		about:       "just bridgeName is empty and ifaceInfo set but empty",
   670  		primaryNIC:  "nic",
   671  		primaryAddr: network.NewAddress("0.1.2.1"),
   672  		bridgeName:  "",
   673  		ifaceInfo:   emptyIfaceInfo,
   674  		expectErr:   expectStartupErr,
   675  	}, {
   676  		about:       "just primaryNIC is empty and ifaceInfo set but empty",
   677  		primaryNIC:  "",
   678  		primaryAddr: network.NewAddress("0.1.2.1"),
   679  		bridgeName:  "bridge",
   680  		ifaceInfo:   emptyIfaceInfo,
   681  		expectErr:   expectStartupErr,
   682  	}, {
   683  		about:       "all set except ifaceInfo, which is set but empty",
   684  		primaryNIC:  "nic",
   685  		primaryAddr: network.NewAddress("0.1.2.1"),
   686  		bridgeName:  "bridge",
   687  		ifaceInfo:   emptyIfaceInfo,
   688  		expectErr:   expectStartupErr,
   689  	}, {
   690  		about:       "all set, but ifaceInfo has empty Address",
   691  		primaryNIC:  "nic",
   692  		primaryAddr: network.NewAddress("0.1.2.1"),
   693  		bridgeName:  "bridge",
   694  		// No Address set.
   695  		ifaceInfo: []network.InterfaceInfo{{DeviceIndex: 0}},
   696  		expectErr: `container IP "" must be set`,
   697  	}} {
   698  		c.Logf("test %d: %s", i, test.about)
   699  		err := provisioner.SetupRoutesAndIPTables(
   700  			test.primaryNIC,
   701  			test.primaryAddr,
   702  			test.bridgeName,
   703  			test.ifaceInfo,
   704  			false, // TODO(dimitern): Untested.
   705  		)
   706  		c.Check(err, gc.ErrorMatches, test.expectErr)
   707  	}
   708  }
   709  
   710  func (s *lxcBrokerSuite) TestSetupRoutesAndIPTablesIPTablesCheckError(c *gc.C) {
   711  	// Isolate the test from the host machine.
   712  	gitjujutesting.PatchExecutableThrowError(c, s, "iptables", 42)
   713  	gitjujutesting.PatchExecutableThrowError(c, s, "ip", 123)
   714  
   715  	ifaceInfo := []network.InterfaceInfo{{
   716  		Address: network.NewAddress("0.1.2.3"),
   717  	}}
   718  
   719  	addr := network.NewAddress("0.1.2.1")
   720  	err := provisioner.SetupRoutesAndIPTables("nic", addr, "bridge", ifaceInfo, false)
   721  	c.Assert(err, gc.ErrorMatches, "iptables failed with unexpected exit code 42")
   722  }
   723  
   724  func (s *lxcBrokerSuite) TestSetupRoutesAndIPTablesIPTablesAddError(c *gc.C) {
   725  	// Isolate the test from the host machine. Patch iptables with a
   726  	// script which returns code=1 for the check but fails when adding
   727  	// the rule.
   728  	script := `if [[ "$3" == "-C" ]]; then exit 1; else exit 42; fi`
   729  	gitjujutesting.PatchExecutable(c, s, "iptables", script)
   730  	gitjujutesting.PatchExecutableThrowError(c, s, "ip", 123)
   731  
   732  	fakeptablesRules := map[string]provisioner.IptablesRule{
   733  		"IPTablesSNAT": {
   734  			"nat",
   735  			"POSTROUTING",
   736  			"{{.HostIF}} {{.HostIP}}",
   737  		},
   738  	}
   739  	s.PatchValue(provisioner.IptablesRules, fakeptablesRules)
   740  
   741  	ifaceInfo := []network.InterfaceInfo{{
   742  		Address: network.NewAddress("0.1.2.3"),
   743  	}}
   744  
   745  	addr := network.NewAddress("0.1.2.1")
   746  	err := provisioner.SetupRoutesAndIPTables("nic", addr, "bridge", ifaceInfo, false)
   747  	c.Assert(err, gc.ErrorMatches, `command "iptables -t nat -I .*" failed with exit code 42`)
   748  }
   749  
   750  func (s *lxcBrokerSuite) TestSetupRoutesAndIPTablesIPRouteError(c *gc.C) {
   751  	// Isolate the test from the host machine.
   752  	// Returning code=0 from iptables means we won't add a rule.
   753  	gitjujutesting.PatchExecutableThrowError(c, s, "iptables", 0)
   754  	gitjujutesting.PatchExecutableThrowError(c, s, "ip", 123)
   755  
   756  	ifaceInfo := []network.InterfaceInfo{{
   757  		Address: network.NewAddress("0.1.2.3"),
   758  	}}
   759  
   760  	addr := network.NewAddress("0.1.2.1")
   761  	err := provisioner.SetupRoutesAndIPTables("nic", addr, "bridge", ifaceInfo, false)
   762  	c.Assert(err, gc.ErrorMatches,
   763  		`command "ip route add 0.1.2.3 dev bridge" failed with exit code 123`,
   764  	)
   765  }
   766  
   767  func (s *lxcBrokerSuite) TestSetupRoutesAndIPTablesAddsRuleIfMissing(c *gc.C) {
   768  	// Isolate the test from the host machine. Because PatchExecutable
   769  	// does not allow us to assert on subsequent executions of the
   770  	// same binary, we need to replace the iptables commands with
   771  	// separate ones. The check returns code=1 to trigger calling
   772  	// add.
   773  	fakeptablesRules := map[string]provisioner.IptablesRule{
   774  		"IPTablesSNAT": {
   775  			"nat",
   776  			"POSTROUTING",
   777  			"{{.HostIF}} {{.HostIP}}",
   778  		},
   779  	}
   780  	s.PatchValue(provisioner.IptablesRules, fakeptablesRules)
   781  
   782  	gitjujutesting.PatchExecutableAsEchoArgs(c, s, "iptables", 1, 0)
   783  	gitjujutesting.PatchExecutableAsEchoArgs(c, s, "ip")
   784  
   785  	ifaceInfo := []network.InterfaceInfo{{
   786  		Address: network.NewAddress("0.1.2.3"),
   787  	}}
   788  
   789  	addr := network.NewAddress("0.1.2.1")
   790  	err := provisioner.SetupRoutesAndIPTables("nic", addr, "bridge", ifaceInfo, false)
   791  	c.Assert(err, jc.ErrorIsNil)
   792  
   793  	// Now verify the expected commands - since check returns 1, add
   794  	// will be called before ip route add.
   795  
   796  	gitjujutesting.AssertEchoArgs(c, "iptables", "-t", "nat", "-C", "POSTROUTING", "nic", "0.1.2.1")
   797  	gitjujutesting.AssertEchoArgs(c, "iptables", "-t", "nat", "-I", "POSTROUTING", "1", "nic", "0.1.2.1")
   798  	gitjujutesting.AssertEchoArgs(c, "ip", "route", "add", "0.1.2.3", "dev", "bridge")
   799  }
   800  
   801  func (s *lxcBrokerSuite) patchNetInterfaceByName(c *gc.C, interfaceName string) {
   802  	s.PatchValue(provisioner.NetInterfaceByName, func(name string) (*net.Interface, error) {
   803  		if interfaceName != name {
   804  			return nil, errors.New("no such network interface")
   805  		}
   806  		return &net.Interface{
   807  			Index: 0,
   808  			Name:  name,
   809  			Flags: net.FlagUp,
   810  		}, nil
   811  	})
   812  }
   813  
   814  func (s *lxcBrokerSuite) patchNetInterfaceByNameAddrs(c *gc.C, interfaceName string, fakeAddrs ...string) {
   815  	addrs := make([]net.Addr, len(fakeAddrs))
   816  
   817  	for i, a := range fakeAddrs {
   818  		addrs[i] = &fakeAddr{a}
   819  	}
   820  
   821  	s.PatchValue(provisioner.InterfaceAddrs, func(i *net.Interface) ([]net.Addr, error) {
   822  		c.Assert(i.Name, gc.Matches, interfaceName)
   823  		return addrs, nil
   824  	})
   825  }
   826  
   827  func (s *lxcBrokerSuite) TestDiscoverPrimaryNICNetInterfacesError(c *gc.C) {
   828  	s.PatchValue(provisioner.NetInterfaces, func() ([]net.Interface, error) {
   829  		return nil, errors.New("boom!")
   830  	})
   831  
   832  	nic, addr, err := provisioner.DiscoverPrimaryNIC()
   833  	c.Assert(err, gc.ErrorMatches, "cannot get network interfaces: boom!")
   834  	c.Assert(nic, gc.Equals, "")
   835  	c.Assert(addr, jc.DeepEquals, network.Address{})
   836  }
   837  
   838  func (s *lxcBrokerSuite) TestDiscoverPrimaryNICInterfaceAddrsError(c *gc.C) {
   839  	s.PatchValue(provisioner.NetInterfaces, func() ([]net.Interface, error) {
   840  		return []net.Interface{{
   841  			Index: 0,
   842  			Name:  "fake",
   843  			Flags: net.FlagUp,
   844  		}}, nil
   845  	})
   846  	s.PatchValue(provisioner.InterfaceAddrs, func(i *net.Interface) ([]net.Addr, error) {
   847  		return nil, errors.New("boom!")
   848  	})
   849  
   850  	nic, addr, err := provisioner.DiscoverPrimaryNIC()
   851  	c.Assert(err, gc.ErrorMatches, `cannot get "fake" addresses: boom!`)
   852  	c.Assert(nic, gc.Equals, "")
   853  	c.Assert(addr, jc.DeepEquals, network.Address{})
   854  }
   855  
   856  func (s *lxcBrokerSuite) TestDiscoverPrimaryNICInvalidAddr(c *gc.C) {
   857  	s.PatchValue(provisioner.NetInterfaces, func() ([]net.Interface, error) {
   858  		return []net.Interface{{
   859  			Index: 0,
   860  			Name:  "fake",
   861  			Flags: net.FlagUp,
   862  		}}, nil
   863  	})
   864  	s.PatchValue(provisioner.InterfaceAddrs, func(i *net.Interface) ([]net.Addr, error) {
   865  		return []net.Addr{&fakeAddr{}}, nil
   866  	})
   867  
   868  	nic, addr, err := provisioner.DiscoverPrimaryNIC()
   869  	c.Assert(err, gc.ErrorMatches, `cannot parse address "fakeAddr": invalid CIDR address: fakeAddr`)
   870  	c.Assert(nic, gc.Equals, "")
   871  	c.Assert(addr, jc.DeepEquals, network.Address{})
   872  }
   873  
   874  func (s *lxcBrokerSuite) TestDiscoverPrimaryNICInterfaceNotFound(c *gc.C) {
   875  	s.PatchValue(provisioner.NetInterfaces, func() ([]net.Interface, error) {
   876  		return nil, nil
   877  	})
   878  
   879  	nic, addr, err := provisioner.DiscoverPrimaryNIC()
   880  	c.Assert(err, gc.ErrorMatches, "cannot detect the primary network interface")
   881  	c.Assert(nic, gc.Equals, "")
   882  	c.Assert(addr, jc.DeepEquals, network.Address{})
   883  }
   884  
   885  type fakeAddr struct{ value string }
   886  
   887  func (f *fakeAddr) Network() string { return "net" }
   888  func (f *fakeAddr) String() string {
   889  	if f.value != "" {
   890  		return f.value
   891  	}
   892  	return "fakeAddr"
   893  }
   894  
   895  var _ net.Addr = (*fakeAddr)(nil)
   896  
   897  func (s *lxcBrokerSuite) TestDiscoverPrimaryNICSuccess(c *gc.C) {
   898  	s.PatchValue(provisioner.NetInterfaces, func() ([]net.Interface, error) {
   899  		return []net.Interface{{
   900  			Index: 0,
   901  			Name:  "lo",
   902  			Flags: net.FlagUp | net.FlagLoopback, // up but loopback - ignored.
   903  		}, {
   904  			Index: 1,
   905  			Name:  "if0",
   906  			Flags: net.FlagPointToPoint, // not up - ignored.
   907  		}, {
   908  			Index: 2,
   909  			Name:  "if1",
   910  			Flags: net.FlagUp, // up but no addresses - ignored.
   911  		}, {
   912  			Index: 3,
   913  			Name:  "if2",
   914  			Flags: net.FlagUp, // up and has addresses - returned.
   915  		}}, nil
   916  	})
   917  	s.PatchValue(provisioner.InterfaceAddrs, func(i *net.Interface) ([]net.Addr, error) {
   918  		// We should be called only for the last two NICs. The first
   919  		// one (if1) won't have addresses, only the last one (if2).
   920  		c.Assert(i, gc.NotNil)
   921  		c.Assert(i.Name, gc.Matches, "if[12]")
   922  		if i.Name == "if2" {
   923  			return []net.Addr{&fakeAddr{"0.1.2.3/24"}}, nil
   924  		}
   925  		// For if1 we return no addresses.
   926  		return nil, nil
   927  	})
   928  
   929  	nic, addr, err := provisioner.DiscoverPrimaryNIC()
   930  	c.Assert(err, jc.ErrorIsNil)
   931  	c.Assert(nic, gc.Equals, "if2")
   932  	c.Assert(addr, jc.DeepEquals, network.NewAddress("0.1.2.3"))
   933  }
   934  
   935  func (s *lxcBrokerSuite) TestConfigureContainerNetwork(c *gc.C) {
   936  	// All the pieces used by this func are separately tested, we just
   937  	// test the integration between them.
   938  	s.PatchValue(provisioner.NetInterfaces, func() ([]net.Interface, error) {
   939  		return []net.Interface{{
   940  			Index: 0,
   941  			Name:  "fake0",
   942  			Flags: net.FlagUp,
   943  		}}, nil
   944  	})
   945  	s.PatchValue(provisioner.InterfaceAddrs, func(i *net.Interface) ([]net.Addr, error) {
   946  		return []net.Addr{&fakeAddr{"0.1.2.1/24"}}, nil
   947  	})
   948  	fakeResolvConf := filepath.Join(c.MkDir(), "resolv.conf")
   949  	err := ioutil.WriteFile(fakeResolvConf, []byte("nameserver ns1.dummy\n"), 0644)
   950  	c.Assert(err, jc.ErrorIsNil)
   951  	s.PatchValue(provisioner.ResolvConf, fakeResolvConf)
   952  
   953  	// When ifaceInfo is not empty it shouldn't do anything and both
   954  	// the error and the result are nil.
   955  	ifaceInfo := []network.InterfaceInfo{{DeviceIndex: 0}}
   956  	// First call as if we are configuring the container for the first time
   957  	result, err := provisioner.ConfigureContainerNetwork("42", "bridge", s.api, ifaceInfo, true, false)
   958  	c.Assert(err, jc.ErrorIsNil)
   959  	c.Assert(result, gc.IsNil)
   960  	s.api.CheckCalls(c, []gitjujutesting.StubCall{})
   961  
   962  	// Next call as if the container has already been configured.
   963  	s.api.ResetCalls()
   964  	result, err = provisioner.ConfigureContainerNetwork("42", "bridge", s.api, ifaceInfo, false, false)
   965  	c.Assert(err, jc.ErrorIsNil)
   966  	c.Assert(result, gc.IsNil)
   967  	s.api.CheckCalls(c, []gitjujutesting.StubCall{})
   968  
   969  	// Call as if the container already has a network configuration, but doesn't.
   970  	s.api.ResetCalls()
   971  	s.api.SetErrors(errors.NotProvisionedf("machine-42 has no network provisioning info"))
   972  	ifaceInfo = []network.InterfaceInfo{}
   973  	result, err = provisioner.ConfigureContainerNetwork("42", "bridge", s.api, ifaceInfo, false, false)
   974  	c.Assert(err, gc.ErrorMatches, "machine-42 has no network provisioning info not provisioned")
   975  	c.Assert(result, jc.DeepEquals, []network.InterfaceInfo{})
   976  	s.api.CheckCalls(c, []gitjujutesting.StubCall{{
   977  		FuncName: "GetContainerInterfaceInfo",
   978  		Args:     []interface{}{names.NewMachineTag("42")},
   979  	}})
   980  
   981  	// When it's not empty, result should be populated as expected.
   982  	s.api.ResetCalls()
   983  	result, err = provisioner.ConfigureContainerNetwork("42", "bridge", s.api, ifaceInfo, false, false)
   984  
   985  	c.Assert(err, jc.ErrorIsNil)
   986  	c.Assert(result, gc.HasLen, 1)
   987  	c.Assert(err, jc.ErrorIsNil)
   988  	c.Assert(result, jc.DeepEquals, []network.InterfaceInfo{{
   989  		DeviceIndex:      0,
   990  		CIDR:             "0.1.2.0/24",
   991  		ConfigType:       network.ConfigStatic,
   992  		InterfaceName:    "eth0", // generated from the device index.
   993  		MACAddress:       "aa:bb:cc:dd:ee:ff",
   994  		DNSServers:       network.NewAddresses("ns1.dummy"),
   995  		DNSSearchDomains: []string{""},
   996  		Address:          network.NewAddress("0.1.2.3"),
   997  		GatewayAddress:   network.NewAddress("0.1.2.1"),
   998  	}})
   999  	s.api.CheckCalls(c, []gitjujutesting.StubCall{{
  1000  		FuncName: "GetContainerInterfaceInfo",
  1001  		Args:     []interface{}{names.NewMachineTag("42")},
  1002  	}})
  1003  
  1004  	s.api.ResetCalls()
  1005  	result, err = provisioner.ConfigureContainerNetwork("42", "bridge", s.api, ifaceInfo, false, false)
  1006  	c.Assert(result, gc.HasLen, 1)
  1007  	c.Assert(err, jc.ErrorIsNil)
  1008  	c.Assert(result, jc.DeepEquals, []network.InterfaceInfo{{
  1009  		DeviceIndex:      0,
  1010  		CIDR:             "0.1.2.0/24",
  1011  		ConfigType:       network.ConfigStatic,
  1012  		InterfaceName:    "eth0", // generated from the device index.
  1013  		MACAddress:       "aa:bb:cc:dd:ee:ff",
  1014  		DNSServers:       network.NewAddresses("ns1.dummy"),
  1015  		DNSSearchDomains: []string{""},
  1016  		Address:          network.NewAddress("0.1.2.3"),
  1017  		GatewayAddress:   network.NewAddress("0.1.2.1"),
  1018  	}})
  1019  
  1020  	s.api.CheckCalls(c, []gitjujutesting.StubCall{{
  1021  		FuncName: "GetContainerInterfaceInfo",
  1022  		Args:     []interface{}{names.NewMachineTag("42")},
  1023  	}})
  1024  }
  1025  
  1026  type lxcProvisionerSuite struct {
  1027  	CommonProvisionerSuite
  1028  	lxcSuite
  1029  	events chan mock.Event
  1030  }
  1031  
  1032  var _ = gc.Suite(&lxcProvisionerSuite{})
  1033  
  1034  func (s *lxcProvisionerSuite) SetUpSuite(c *gc.C) {
  1035  	if runtime.GOOS == "windows" {
  1036  		c.Skip("Skipping lxc tests on windows")
  1037  	}
  1038  	s.CommonProvisionerSuite.SetUpSuite(c)
  1039  	s.lxcSuite.SetUpSuite(c)
  1040  }
  1041  
  1042  func (s *lxcProvisionerSuite) TearDownSuite(c *gc.C) {
  1043  	s.lxcSuite.TearDownSuite(c)
  1044  	s.CommonProvisionerSuite.TearDownSuite(c)
  1045  }
  1046  
  1047  func (s *lxcProvisionerSuite) SetUpTest(c *gc.C) {
  1048  	s.CommonProvisionerSuite.SetUpTest(c)
  1049  	s.lxcSuite.SetUpTest(c)
  1050  
  1051  	s.events = make(chan mock.Event, 25)
  1052  	s.ContainerFactory.AddListener(s.events)
  1053  }
  1054  
  1055  func (s *lxcProvisionerSuite) expectStarted(c *gc.C, machine *state.Machine) string {
  1056  	// This check in particular leads to tests just hanging
  1057  	// indefinitely quite often on i386.
  1058  	coretesting.SkipIfI386(c, "lp:1425569")
  1059  
  1060  	var event mock.Event
  1061  	s.State.StartSync()
  1062  	select {
  1063  	case event = <-s.events:
  1064  		c.Assert(event.Action, gc.Equals, mock.Created)
  1065  		argsSet := set.NewStrings(event.TemplateArgs...)
  1066  		c.Assert(argsSet.Contains("imageURL"), jc.IsTrue)
  1067  	case <-time.After(coretesting.LongWait):
  1068  		c.Fatalf("timeout while waiting the mock container to get created")
  1069  	}
  1070  
  1071  	select {
  1072  	case event = <-s.events:
  1073  		c.Assert(event.Action, gc.Equals, mock.Started)
  1074  		err := machine.Refresh()
  1075  		c.Assert(err, jc.ErrorIsNil)
  1076  	case <-time.After(coretesting.LongWait):
  1077  		c.Fatalf("timeout while waiting the mock container to start")
  1078  	}
  1079  
  1080  	s.waitInstanceId(c, machine, instance.Id(event.InstanceId))
  1081  	return event.InstanceId
  1082  }
  1083  
  1084  func (s *lxcProvisionerSuite) expectStopped(c *gc.C, instId string) {
  1085  	// This check in particular leads to tests just hanging
  1086  	// indefinitely quite often on i386.
  1087  	coretesting.SkipIfI386(c, "lp:1425569")
  1088  
  1089  	s.State.StartSync()
  1090  	select {
  1091  	case event := <-s.events:
  1092  		c.Assert(event.Action, gc.Equals, mock.Stopped)
  1093  	case <-time.After(coretesting.LongWait):
  1094  		c.Fatalf("timeout while waiting the mock container to stop")
  1095  	}
  1096  
  1097  	select {
  1098  	case event := <-s.events:
  1099  		c.Assert(event.Action, gc.Equals, mock.Destroyed)
  1100  		c.Assert(event.InstanceId, gc.Equals, instId)
  1101  	case <-time.After(coretesting.LongWait):
  1102  		c.Fatalf("timeout while waiting the mock container to get destroyed")
  1103  	}
  1104  }
  1105  
  1106  func (s *lxcProvisionerSuite) expectNoEvents(c *gc.C) {
  1107  	select {
  1108  	case event := <-s.events:
  1109  		c.Fatalf("unexpected event %#v", event)
  1110  	case <-time.After(coretesting.ShortWait):
  1111  		return
  1112  	}
  1113  }
  1114  
  1115  func (s *lxcProvisionerSuite) TearDownTest(c *gc.C) {
  1116  	close(s.events)
  1117  	s.lxcSuite.TearDownTest(c)
  1118  	s.CommonProvisionerSuite.TearDownTest(c)
  1119  }
  1120  
  1121  func (s *lxcProvisionerSuite) newLxcProvisioner(c *gc.C) provisioner.Provisioner {
  1122  	parentMachineTag := names.NewMachineTag("0")
  1123  	agentConfig := s.AgentConfigForTag(c, parentMachineTag)
  1124  	managerConfig := container.ManagerConfig{
  1125  		container.ConfigName: "juju",
  1126  		"log-dir":            c.MkDir(),
  1127  		"use-clone":          "false",
  1128  	}
  1129  	broker, err := provisioner.NewLxcBroker(s.provisioner, agentConfig, managerConfig, &containertesting.MockURLGetter{}, false, 0)
  1130  	c.Assert(err, jc.ErrorIsNil)
  1131  	toolsFinder := (*provisioner.GetToolsFinder)(s.provisioner)
  1132  	w, err := provisioner.NewContainerProvisioner(instance.LXC, s.provisioner, agentConfig, broker, toolsFinder)
  1133  	c.Assert(err, jc.ErrorIsNil)
  1134  	return w
  1135  }
  1136  
  1137  func (s *lxcProvisionerSuite) TestProvisionerStartStop(c *gc.C) {
  1138  	p := s.newLxcProvisioner(c)
  1139  	stop(c, p)
  1140  }
  1141  
  1142  func (s *lxcProvisionerSuite) TestDoesNotStartEnvironMachines(c *gc.C) {
  1143  	p := s.newLxcProvisioner(c)
  1144  	defer stop(c, p)
  1145  
  1146  	// Check that an instance is not provisioned when the machine is created.
  1147  	_, err := s.State.AddMachine(series.LatestLts(), state.JobHostUnits)
  1148  	c.Assert(err, jc.ErrorIsNil)
  1149  
  1150  	s.expectNoEvents(c)
  1151  }
  1152  
  1153  func (s *lxcProvisionerSuite) TestDoesNotHaveRetryWatcher(c *gc.C) {
  1154  	p := s.newLxcProvisioner(c)
  1155  	defer stop(c, p)
  1156  
  1157  	w, err := provisioner.GetRetryWatcher(p)
  1158  	c.Assert(w, gc.IsNil)
  1159  	c.Assert(err, jc.Satisfies, errors.IsNotImplemented)
  1160  }
  1161  
  1162  func (s *lxcProvisionerSuite) addContainer(c *gc.C) *state.Machine {
  1163  	template := state.MachineTemplate{
  1164  		Series: series.LatestLts(),
  1165  		Jobs:   []state.MachineJob{state.JobHostUnits},
  1166  	}
  1167  	container, err := s.State.AddMachineInsideMachine(template, "0", instance.LXC)
  1168  	c.Assert(err, jc.ErrorIsNil)
  1169  	return container
  1170  }
  1171  
  1172  func (s *lxcProvisionerSuite) maybeUploadTools(c *gc.C) {
  1173  	// The default series tools are already uploaded
  1174  	// for amd64 in the base suite.
  1175  	if arch.HostArch() == arch.AMD64 {
  1176  		return
  1177  	}
  1178  
  1179  	storageDir := c.MkDir()
  1180  	s.CommonProvisionerSuite.PatchValue(&tools.DefaultBaseURL, storageDir)
  1181  	stor, err := filestorage.NewFileStorageWriter(storageDir)
  1182  	c.Assert(err, jc.ErrorIsNil)
  1183  
  1184  	defaultTools := version.Binary{
  1185  		Number: jujuversion.Current,
  1186  		Arch:   arch.HostArch(),
  1187  		Series: series.LatestLts(),
  1188  	}
  1189  
  1190  	envtesting.AssertUploadFakeToolsVersions(c, stor, "devel", "devel", defaultTools)
  1191  	envtesting.AssertUploadFakeToolsVersions(c, stor, "released", "released", defaultTools)
  1192  }
  1193  
  1194  func (s *lxcProvisionerSuite) TestContainerStartedAndStopped(c *gc.C) {
  1195  	coretesting.SkipIfI386(c, "lp:1425569")
  1196  	s.maybeUploadTools(c)
  1197  
  1198  	p := s.newLxcProvisioner(c)
  1199  	defer stop(c, p)
  1200  
  1201  	container := s.addContainer(c)
  1202  	name := "juju-" + container.Tag().String()
  1203  	containertesting.EnsureLXCRootFSEtcNetwork(c, name)
  1204  	instId := s.expectStarted(c, container)
  1205  
  1206  	// ...and removed, along with the machine, when the machine is Dead.
  1207  	c.Assert(container.EnsureDead(), gc.IsNil)
  1208  	s.expectStopped(c, instId)
  1209  	s.waitRemoved(c, container)
  1210  }
  1211  
  1212  func (s *lxcProvisionerSuite) TestLXCProvisionerObservesConfigChanges(c *gc.C) {
  1213  	p := s.newLxcProvisioner(c)
  1214  	defer stop(c, p)
  1215  	s.assertProvisionerObservesConfigChanges(c, p)
  1216  }
  1217  
  1218  type fakeAPI struct {
  1219  	*gitjujutesting.Stub
  1220  
  1221  	fakeContainerConfig params.ContainerConfig
  1222  	fakeInterfaceInfo   network.InterfaceInfo
  1223  }
  1224  
  1225  var _ provisioner.APICalls = (*fakeAPI)(nil)
  1226  
  1227  var fakeInterfaceInfo network.InterfaceInfo = network.InterfaceInfo{
  1228  	DeviceIndex:    0,
  1229  	MACAddress:     "aa:bb:cc:dd:ee:ff",
  1230  	CIDR:           "0.1.2.0/24",
  1231  	InterfaceName:  "dummy0",
  1232  	Address:        network.NewAddress("0.1.2.3"),
  1233  	GatewayAddress: network.NewAddress("0.1.2.1"),
  1234  }
  1235  
  1236  var fakeContainerConfig = params.ContainerConfig{
  1237  	UpdateBehavior:          &params.UpdateBehavior{true, true},
  1238  	ProviderType:            "fake",
  1239  	AuthorizedKeys:          coretesting.FakeAuthKeys,
  1240  	SSLHostnameVerification: true,
  1241  }
  1242  
  1243  func NewFakeAPI() *fakeAPI {
  1244  	return &fakeAPI{
  1245  		Stub:                &gitjujutesting.Stub{},
  1246  		fakeContainerConfig: fakeContainerConfig,
  1247  		fakeInterfaceInfo:   fakeInterfaceInfo,
  1248  	}
  1249  }
  1250  
  1251  func (f *fakeAPI) ContainerConfig() (params.ContainerConfig, error) {
  1252  	f.MethodCall(f, "ContainerConfig")
  1253  	if err := f.NextErr(); err != nil {
  1254  		return params.ContainerConfig{}, err
  1255  	}
  1256  	return f.fakeContainerConfig, nil
  1257  }
  1258  
  1259  func (f *fakeAPI) PrepareContainerInterfaceInfo(tag names.MachineTag) ([]network.InterfaceInfo, error) {
  1260  	f.MethodCall(f, "PrepareContainerInterfaceInfo", tag)
  1261  	if err := f.NextErr(); err != nil {
  1262  		return nil, err
  1263  	}
  1264  	return []network.InterfaceInfo{f.fakeInterfaceInfo}, nil
  1265  }
  1266  
  1267  func (f *fakeAPI) GetContainerInterfaceInfo(tag names.MachineTag) ([]network.InterfaceInfo, error) {
  1268  	f.MethodCall(f, "GetContainerInterfaceInfo", tag)
  1269  	if err := f.NextErr(); err != nil {
  1270  		return nil, err
  1271  	}
  1272  	return []network.InterfaceInfo{f.fakeInterfaceInfo}, nil
  1273  }
  1274  
  1275  func (f *fakeAPI) ReleaseContainerAddresses(tag names.MachineTag) error {
  1276  	f.MethodCall(f, "ReleaseContainerAddresses", tag)
  1277  	if err := f.NextErr(); err != nil {
  1278  		return err
  1279  	}
  1280  	return nil
  1281  }