github.com/mwhudson/juju@v0.0.0-20160512215208-90ff01f3497f/worker/provisioner/kvm-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  	"fmt"
     8  	"io/ioutil"
     9  	"net"
    10  	"path/filepath"
    11  	"runtime"
    12  	"time"
    13  
    14  	"github.com/juju/errors"
    15  	"github.com/juju/names"
    16  	gitjujutesting "github.com/juju/testing"
    17  	jc "github.com/juju/testing/checkers"
    18  	"github.com/juju/utils/arch"
    19  	"github.com/juju/utils/series"
    20  	"github.com/juju/version"
    21  	gc "gopkg.in/check.v1"
    22  
    23  	"github.com/juju/juju/agent"
    24  	"github.com/juju/juju/cloudconfig/instancecfg"
    25  	"github.com/juju/juju/constraints"
    26  	"github.com/juju/juju/container"
    27  	"github.com/juju/juju/container/kvm/mock"
    28  	kvmtesting "github.com/juju/juju/container/kvm/testing"
    29  	"github.com/juju/juju/environs"
    30  	"github.com/juju/juju/feature"
    31  	"github.com/juju/juju/instance"
    32  	instancetest "github.com/juju/juju/instance/testing"
    33  	jujutesting "github.com/juju/juju/juju/testing"
    34  	"github.com/juju/juju/network"
    35  	"github.com/juju/juju/state"
    36  	"github.com/juju/juju/status"
    37  	coretesting "github.com/juju/juju/testing"
    38  	coretools "github.com/juju/juju/tools"
    39  	jujuversion "github.com/juju/juju/version"
    40  	"github.com/juju/juju/worker/provisioner"
    41  )
    42  
    43  type kvmSuite struct {
    44  	kvmtesting.TestSuite
    45  	events     chan mock.Event
    46  	eventsDone chan struct{}
    47  }
    48  
    49  type kvmBrokerSuite struct {
    50  	kvmSuite
    51  	broker      environs.InstanceBroker
    52  	agentConfig agent.Config
    53  	api         *fakeAPI
    54  }
    55  
    56  var _ = gc.Suite(&kvmBrokerSuite{})
    57  
    58  func (s *kvmSuite) SetUpTest(c *gc.C) {
    59  	if runtime.GOOS == "windows" {
    60  		c.Skip("Skipping kvm tests on windows")
    61  	}
    62  	s.TestSuite.SetUpTest(c)
    63  	s.events = make(chan mock.Event)
    64  	s.eventsDone = make(chan struct{})
    65  	go func() {
    66  		defer close(s.eventsDone)
    67  		for event := range s.events {
    68  			c.Output(3, fmt.Sprintf("kvm event: <%s, %s>", event.Action, event.InstanceId))
    69  		}
    70  	}()
    71  	s.TestSuite.ContainerFactory.AddListener(s.events)
    72  }
    73  
    74  func (s *kvmSuite) TearDownTest(c *gc.C) {
    75  	close(s.events)
    76  	<-s.eventsDone
    77  	s.TestSuite.TearDownTest(c)
    78  }
    79  
    80  func (s *kvmBrokerSuite) SetUpTest(c *gc.C) {
    81  	if runtime.GOOS == "windows" {
    82  		c.Skip("Skipping kvm tests on windows")
    83  	}
    84  	s.kvmSuite.SetUpTest(c)
    85  	var err error
    86  	s.agentConfig, err = agent.NewAgentConfig(
    87  		agent.AgentConfigParams{
    88  			Paths:             agent.NewPathsWithDefaults(agent.Paths{DataDir: "/not/used/here"}),
    89  			Tag:               names.NewUnitTag("ubuntu/1"),
    90  			UpgradedToVersion: jujuversion.Current,
    91  			Password:          "dummy-secret",
    92  			Nonce:             "nonce",
    93  			APIAddresses:      []string{"10.0.0.1:1234"},
    94  			CACert:            coretesting.CACert,
    95  			Model:             coretesting.ModelTag,
    96  		})
    97  	c.Assert(err, jc.ErrorIsNil)
    98  	s.api = NewFakeAPI()
    99  	managerConfig := container.ManagerConfig{container.ConfigName: "juju"}
   100  	s.broker, err = provisioner.NewKvmBroker(s.api, s.agentConfig, managerConfig, false)
   101  	c.Assert(err, jc.ErrorIsNil)
   102  }
   103  
   104  func (s *kvmBrokerSuite) instanceConfig(c *gc.C, machineId string) *instancecfg.InstanceConfig {
   105  	machineNonce := "fake-nonce"
   106  	// To isolate the tests from the host's architecture, we override it here.
   107  	s.PatchValue(&arch.HostArch, func() string { return arch.AMD64 })
   108  	stateInfo := jujutesting.FakeStateInfo(machineId)
   109  	apiInfo := jujutesting.FakeAPIInfo(machineId)
   110  	instanceConfig, err := instancecfg.NewInstanceConfig(machineId, machineNonce, "released", "quantal", "", true, stateInfo, apiInfo)
   111  	c.Assert(err, jc.ErrorIsNil)
   112  	return instanceConfig
   113  }
   114  
   115  func (s *kvmBrokerSuite) startInstance(c *gc.C, machineId string) instance.Instance {
   116  	instanceConfig := s.instanceConfig(c, machineId)
   117  	cons := constraints.Value{}
   118  	possibleTools := coretools.List{&coretools.Tools{
   119  		Version: version.MustParseBinary("2.3.4-quantal-amd64"),
   120  		URL:     "http://tools.testing.invalid/2.3.4-quantal-amd64.tgz",
   121  	}, {
   122  		// non-host-arch tools should be filtered out by StartInstance
   123  		Version: version.MustParseBinary("2.3.4-quantal-arm64"),
   124  		URL:     "http://tools.testing.invalid/2.3.4-quantal-arm64.tgz",
   125  	}}
   126  	callback := func(settableStatus status.Status, info string, data map[string]interface{}) error {
   127  		return nil
   128  	}
   129  	result, err := s.broker.StartInstance(environs.StartInstanceParams{
   130  		Constraints:    cons,
   131  		Tools:          possibleTools,
   132  		InstanceConfig: instanceConfig,
   133  		StatusCallback: callback,
   134  	})
   135  	c.Assert(err, jc.ErrorIsNil)
   136  	return result.Instance
   137  }
   138  
   139  func (s *kvmBrokerSuite) maintainInstance(c *gc.C, machineId string) {
   140  	machineNonce := "fake-nonce"
   141  	stateInfo := jujutesting.FakeStateInfo(machineId)
   142  	apiInfo := jujutesting.FakeAPIInfo(machineId)
   143  	instanceConfig, err := instancecfg.NewInstanceConfig(machineId, machineNonce, "released", "quantal", "", true, stateInfo, apiInfo)
   144  	c.Assert(err, jc.ErrorIsNil)
   145  	cons := constraints.Value{}
   146  	possibleTools := coretools.List{&coretools.Tools{
   147  		Version: version.MustParseBinary("2.3.4-quantal-amd64"),
   148  		URL:     "http://tools.testing.invalid/2.3.4-quantal-amd64.tgz",
   149  	}}
   150  	callback := func(settableStatus status.Status, info string, data map[string]interface{}) error {
   151  		return nil
   152  	}
   153  	err = s.broker.MaintainInstance(environs.StartInstanceParams{
   154  		Constraints:    cons,
   155  		Tools:          possibleTools,
   156  		InstanceConfig: instanceConfig,
   157  		StatusCallback: callback,
   158  	})
   159  	c.Assert(err, jc.ErrorIsNil)
   160  }
   161  
   162  func (s *kvmBrokerSuite) TestStartInstance(c *gc.C) {
   163  	machineId := "1/kvm/0"
   164  	s.SetFeatureFlags(feature.AddressAllocation)
   165  	kvm := s.startInstance(c, machineId)
   166  	s.api.CheckCalls(c, []gitjujutesting.StubCall{{
   167  		FuncName: "ContainerConfig",
   168  	}, {
   169  		FuncName: "PrepareContainerInterfaceInfo",
   170  		Args:     []interface{}{names.NewMachineTag("1-kvm-0")},
   171  	}})
   172  	c.Assert(kvm.Id(), gc.Equals, instance.Id("juju-machine-1-kvm-0"))
   173  	s.assertInstances(c, kvm)
   174  }
   175  
   176  func (s *kvmBrokerSuite) TestStartInstanceAddressAllocationDisabled(c *gc.C) {
   177  	machineId := "1/kvm/0"
   178  	kvm := s.startInstance(c, machineId)
   179  	s.api.CheckCalls(c, []gitjujutesting.StubCall{{
   180  		FuncName: "ContainerConfig",
   181  	}, {
   182  		FuncName: "PrepareContainerInterfaceInfo",
   183  		Args:     []interface{}{names.NewMachineTag("1-kvm-0")},
   184  	}})
   185  	c.Assert(kvm.Id(), gc.Equals, instance.Id("juju-machine-1-kvm-0"))
   186  	s.assertInstances(c, kvm)
   187  }
   188  
   189  func (s *kvmBrokerSuite) TestMaintainInstance(c *gc.C) {
   190  	machineId := "1/kvm/0"
   191  	s.SetFeatureFlags(feature.AddressAllocation)
   192  	kvm := s.startInstance(c, machineId)
   193  	s.api.ResetCalls()
   194  
   195  	s.maintainInstance(c, machineId)
   196  	s.api.CheckCalls(c, []gitjujutesting.StubCall{{
   197  		FuncName: "GetContainerInterfaceInfo",
   198  		Args:     []interface{}{names.NewMachineTag("1-kvm-0")},
   199  	}})
   200  	c.Assert(kvm.Id(), gc.Equals, instance.Id("juju-machine-1-kvm-0"))
   201  	s.assertInstances(c, kvm)
   202  }
   203  
   204  func (s *kvmBrokerSuite) TestMaintainInstanceAddressAllocationDisabled(c *gc.C) {
   205  	machineId := "1/kvm/0"
   206  	kvm := s.startInstance(c, machineId)
   207  	s.api.ResetCalls()
   208  
   209  	s.maintainInstance(c, machineId)
   210  	s.api.CheckCalls(c, []gitjujutesting.StubCall{})
   211  	c.Assert(kvm.Id(), gc.Equals, instance.Id("juju-machine-1-kvm-0"))
   212  	s.assertInstances(c, kvm)
   213  }
   214  
   215  func (s *kvmBrokerSuite) TestStopInstance(c *gc.C) {
   216  	kvm0 := s.startInstance(c, "1/kvm/0")
   217  	kvm1 := s.startInstance(c, "1/kvm/1")
   218  	kvm2 := s.startInstance(c, "1/kvm/2")
   219  
   220  	err := s.broker.StopInstances(kvm0.Id())
   221  	c.Assert(err, jc.ErrorIsNil)
   222  	s.assertInstances(c, kvm1, kvm2)
   223  	c.Assert(s.kvmContainerDir(kvm0), jc.DoesNotExist)
   224  	c.Assert(s.kvmRemovedContainerDir(kvm0), jc.IsDirectory)
   225  
   226  	err = s.broker.StopInstances(kvm1.Id(), kvm2.Id())
   227  	c.Assert(err, jc.ErrorIsNil)
   228  	s.assertInstances(c)
   229  }
   230  
   231  func (s *kvmBrokerSuite) TestAllInstances(c *gc.C) {
   232  	kvm0 := s.startInstance(c, "1/kvm/0")
   233  	kvm1 := s.startInstance(c, "1/kvm/1")
   234  	s.assertInstances(c, kvm0, kvm1)
   235  
   236  	err := s.broker.StopInstances(kvm1.Id())
   237  	c.Assert(err, jc.ErrorIsNil)
   238  	kvm2 := s.startInstance(c, "1/kvm/2")
   239  	s.assertInstances(c, kvm0, kvm2)
   240  }
   241  
   242  func (s *kvmBrokerSuite) assertInstances(c *gc.C, inst ...instance.Instance) {
   243  	results, err := s.broker.AllInstances()
   244  	c.Assert(err, jc.ErrorIsNil)
   245  	instancetest.MatchInstances(c, results, inst...)
   246  }
   247  
   248  func (s *kvmBrokerSuite) kvmContainerDir(inst instance.Instance) string {
   249  	return filepath.Join(s.ContainerDir, string(inst.Id()))
   250  }
   251  
   252  func (s *kvmBrokerSuite) kvmRemovedContainerDir(inst instance.Instance) string {
   253  	return filepath.Join(s.RemovedDir, string(inst.Id()))
   254  }
   255  
   256  func (s *kvmBrokerSuite) TestStartInstancePopulatesNetworkInfo(c *gc.C) {
   257  	s.SetFeatureFlags(feature.AddressAllocation)
   258  	s.PatchValue(provisioner.InterfaceAddrs, func(i *net.Interface) ([]net.Addr, error) {
   259  		return []net.Addr{&fakeAddr{"0.1.2.1/24"}}, nil
   260  	})
   261  	fakeResolvConf := filepath.Join(c.MkDir(), "resolv.conf")
   262  	err := ioutil.WriteFile(fakeResolvConf, []byte("nameserver ns1.dummy\n"), 0644)
   263  	c.Assert(err, jc.ErrorIsNil)
   264  	s.PatchValue(provisioner.ResolvConf, fakeResolvConf)
   265  
   266  	instanceConfig := s.instanceConfig(c, "42")
   267  	possibleTools := coretools.List{&coretools.Tools{
   268  		Version: version.MustParseBinary("2.3.4-quantal-amd64"),
   269  		URL:     "http://tools.testing.invalid/2.3.4-quantal-amd64.tgz",
   270  	}}
   271  	callback := func(settableStatus status.Status, info string, data map[string]interface{}) error {
   272  		return nil
   273  	}
   274  	result, err := s.broker.StartInstance(environs.StartInstanceParams{
   275  		Constraints:    constraints.Value{},
   276  		Tools:          possibleTools,
   277  		InstanceConfig: instanceConfig,
   278  		StatusCallback: callback,
   279  	})
   280  	c.Assert(err, jc.ErrorIsNil)
   281  	c.Assert(result.NetworkInfo, gc.HasLen, 1)
   282  	iface := result.NetworkInfo[0]
   283  	c.Assert(err, jc.ErrorIsNil)
   284  	c.Assert(iface, jc.DeepEquals, network.InterfaceInfo{
   285  		DeviceIndex:      0,
   286  		CIDR:             "0.1.2.0/24",
   287  		ConfigType:       network.ConfigStatic,
   288  		InterfaceName:    "eth0", // generated from the device index.
   289  		DNSServers:       network.NewAddresses("ns1.dummy"),
   290  		DNSSearchDomains: []string{""},
   291  		MACAddress:       "aa:bb:cc:dd:ee:ff",
   292  		Address:          network.NewAddress("0.1.2.3"),
   293  		GatewayAddress:   network.NewAddress("0.1.2.1"),
   294  	})
   295  }
   296  
   297  type kvmProvisionerSuite struct {
   298  	CommonProvisionerSuite
   299  	kvmSuite
   300  	events chan mock.Event
   301  }
   302  
   303  var _ = gc.Suite(&kvmProvisionerSuite{})
   304  
   305  func (s *kvmProvisionerSuite) SetUpSuite(c *gc.C) {
   306  	if runtime.GOOS == "windows" {
   307  		c.Skip("Skipping kvm tests on windows")
   308  	}
   309  	s.CommonProvisionerSuite.SetUpSuite(c)
   310  	s.kvmSuite.SetUpSuite(c)
   311  }
   312  
   313  func (s *kvmProvisionerSuite) TearDownSuite(c *gc.C) {
   314  	s.kvmSuite.TearDownSuite(c)
   315  	s.CommonProvisionerSuite.TearDownSuite(c)
   316  }
   317  
   318  func (s *kvmProvisionerSuite) SetUpTest(c *gc.C) {
   319  	s.CommonProvisionerSuite.SetUpTest(c)
   320  	s.kvmSuite.SetUpTest(c)
   321  
   322  	s.events = make(chan mock.Event, 25)
   323  	s.ContainerFactory.AddListener(s.events)
   324  }
   325  
   326  func (s *kvmProvisionerSuite) nextEvent(c *gc.C) mock.Event {
   327  	select {
   328  	case event := <-s.events:
   329  		return event
   330  	case <-time.After(coretesting.LongWait):
   331  		c.Fatalf("no event arrived")
   332  	}
   333  	panic("not reachable")
   334  }
   335  
   336  func (s *kvmProvisionerSuite) expectStarted(c *gc.C, machine *state.Machine) string {
   337  	s.State.StartSync()
   338  	event := s.nextEvent(c)
   339  	c.Assert(event.Action, gc.Equals, mock.Started)
   340  	err := machine.Refresh()
   341  	c.Assert(err, jc.ErrorIsNil)
   342  	s.waitInstanceId(c, machine, instance.Id(event.InstanceId))
   343  	return event.InstanceId
   344  }
   345  
   346  func (s *kvmProvisionerSuite) expectStopped(c *gc.C, instId string) {
   347  	s.State.StartSync()
   348  	event := s.nextEvent(c)
   349  	c.Assert(event.Action, gc.Equals, mock.Stopped)
   350  	c.Assert(event.InstanceId, gc.Equals, instId)
   351  }
   352  
   353  func (s *kvmProvisionerSuite) expectNoEvents(c *gc.C) {
   354  	select {
   355  	case event := <-s.events:
   356  		c.Fatalf("unexpected event %#v", event)
   357  	case <-time.After(coretesting.ShortWait):
   358  		return
   359  	}
   360  }
   361  
   362  func (s *kvmProvisionerSuite) TearDownTest(c *gc.C) {
   363  	close(s.events)
   364  	s.kvmSuite.TearDownTest(c)
   365  	s.CommonProvisionerSuite.TearDownTest(c)
   366  }
   367  
   368  func (s *kvmProvisionerSuite) newKvmProvisioner(c *gc.C) provisioner.Provisioner {
   369  	machineTag := names.NewMachineTag("0")
   370  	agentConfig := s.AgentConfigForTag(c, machineTag)
   371  	managerConfig := container.ManagerConfig{container.ConfigName: "juju"}
   372  	broker, err := provisioner.NewKvmBroker(s.provisioner, agentConfig, managerConfig, false)
   373  	c.Assert(err, jc.ErrorIsNil)
   374  	toolsFinder := (*provisioner.GetToolsFinder)(s.provisioner)
   375  	w, err := provisioner.NewContainerProvisioner(instance.KVM, s.provisioner, agentConfig, broker, toolsFinder)
   376  	c.Assert(err, jc.ErrorIsNil)
   377  	return w
   378  }
   379  
   380  func (s *kvmProvisionerSuite) TestProvisionerStartStop(c *gc.C) {
   381  	p := s.newKvmProvisioner(c)
   382  	stop(c, p)
   383  }
   384  
   385  func (s *kvmProvisionerSuite) TestDoesNotStartEnvironMachines(c *gc.C) {
   386  	p := s.newKvmProvisioner(c)
   387  	defer stop(c, p)
   388  
   389  	// Check that an instance is not provisioned when the machine is created.
   390  	_, err := s.State.AddMachine(series.LatestLts(), state.JobHostUnits)
   391  	c.Assert(err, jc.ErrorIsNil)
   392  
   393  	s.expectNoEvents(c)
   394  }
   395  
   396  func (s *kvmProvisionerSuite) TestDoesNotHaveRetryWatcher(c *gc.C) {
   397  	p := s.newKvmProvisioner(c)
   398  	defer stop(c, p)
   399  
   400  	w, err := provisioner.GetRetryWatcher(p)
   401  	c.Assert(w, gc.IsNil)
   402  	c.Assert(err, jc.Satisfies, errors.IsNotImplemented)
   403  }
   404  
   405  func (s *kvmProvisionerSuite) addContainer(c *gc.C) *state.Machine {
   406  	template := state.MachineTemplate{
   407  		Series: series.LatestLts(),
   408  		Jobs:   []state.MachineJob{state.JobHostUnits},
   409  	}
   410  	container, err := s.State.AddMachineInsideMachine(template, "0", instance.KVM)
   411  	c.Assert(err, jc.ErrorIsNil)
   412  	return container
   413  }
   414  
   415  func (s *kvmProvisionerSuite) TestContainerStartedAndStopped(c *gc.C) {
   416  	if arch.NormaliseArch(runtime.GOARCH) != arch.AMD64 {
   417  		c.Skip("Test only enabled on amd64, see bug lp:1572145")
   418  	}
   419  	p := s.newKvmProvisioner(c)
   420  	defer stop(c, p)
   421  
   422  	container := s.addContainer(c)
   423  
   424  	instId := s.expectStarted(c, container)
   425  
   426  	// ...and removed, along with the machine, when the machine is Dead.
   427  	c.Assert(container.EnsureDead(), gc.IsNil)
   428  	s.expectStopped(c, instId)
   429  	s.waitRemoved(c, container)
   430  }
   431  
   432  func (s *kvmProvisionerSuite) TestKVMProvisionerObservesConfigChanges(c *gc.C) {
   433  	p := s.newKvmProvisioner(c)
   434  	defer stop(c, p)
   435  	s.assertProvisionerObservesConfigChanges(c, p)
   436  }