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