github.com/wallyworld/juju@v0.0.0-20161013125918-6cf1bc9d917a/provider/common/bootstrap_test.go (about)

     1  // Copyright 2013 Canonical Ltd.
     2  // Licensed under the AGPLv3, see LICENCE file for details.
     3  
     4  package common_test
     5  
     6  import (
     7  	"bytes"
     8  	"fmt"
     9  	"os"
    10  	"strings"
    11  	"time"
    12  
    13  	"github.com/juju/errors"
    14  	jc "github.com/juju/testing/checkers"
    15  	"github.com/juju/utils/arch"
    16  	"github.com/juju/utils/series"
    17  	"github.com/juju/utils/ssh"
    18  	"github.com/juju/version"
    19  	gc "gopkg.in/check.v1"
    20  
    21  	"github.com/juju/juju/cloudconfig/instancecfg"
    22  	"github.com/juju/juju/cmd/modelcmd"
    23  	"github.com/juju/juju/constraints"
    24  	"github.com/juju/juju/environs"
    25  	"github.com/juju/juju/environs/config"
    26  	"github.com/juju/juju/environs/storage"
    27  	envtesting "github.com/juju/juju/environs/testing"
    28  	"github.com/juju/juju/instance"
    29  	"github.com/juju/juju/network"
    30  	"github.com/juju/juju/provider/common"
    31  	"github.com/juju/juju/status"
    32  	coretesting "github.com/juju/juju/testing"
    33  	"github.com/juju/juju/tools"
    34  	jujuversion "github.com/juju/juju/version"
    35  )
    36  
    37  type BootstrapSuite struct {
    38  	coretesting.FakeJujuXDGDataHomeSuite
    39  	envtesting.ToolsFixture
    40  }
    41  
    42  var _ = gc.Suite(&BootstrapSuite{})
    43  
    44  type cleaner interface {
    45  	AddCleanup(func(*gc.C))
    46  }
    47  
    48  func (s *BootstrapSuite) SetUpTest(c *gc.C) {
    49  	s.FakeJujuXDGDataHomeSuite.SetUpTest(c)
    50  	s.ToolsFixture.SetUpTest(c)
    51  	s.PatchValue(common.ConnectSSH, func(_ ssh.Client, host, checkHostScript string) error {
    52  		return fmt.Errorf("mock connection failure to %s", host)
    53  	})
    54  }
    55  
    56  func (s *BootstrapSuite) TearDownTest(c *gc.C) {
    57  	s.ToolsFixture.TearDownTest(c)
    58  	s.FakeJujuXDGDataHomeSuite.TearDownTest(c)
    59  }
    60  
    61  func newStorage(suite cleaner, c *gc.C) storage.Storage {
    62  	closer, stor, _ := envtesting.CreateLocalTestStorage(c)
    63  	suite.AddCleanup(func(*gc.C) { closer.Close() })
    64  	envtesting.UploadFakeTools(c, stor, "released", "released")
    65  	return stor
    66  }
    67  
    68  func minimalConfig(c *gc.C) *config.Config {
    69  	attrs := map[string]interface{}{
    70  		"name":            "whatever",
    71  		"type":            "anything, really",
    72  		"uuid":            coretesting.ModelTag.Id(),
    73  		"controller-uuid": coretesting.ControllerTag.Id(),
    74  		"ca-cert":         coretesting.CACert,
    75  		"ca-private-key":  coretesting.CAKey,
    76  		"authorized-keys": coretesting.FakeAuthKeys,
    77  		"default-series":  series.HostSeries(),
    78  	}
    79  	cfg, err := config.New(config.UseDefaults, attrs)
    80  	c.Assert(err, jc.ErrorIsNil)
    81  	return cfg
    82  }
    83  
    84  func configGetter(c *gc.C) configFunc {
    85  	cfg := minimalConfig(c)
    86  	return func() *config.Config { return cfg }
    87  }
    88  
    89  func (s *BootstrapSuite) TestCannotStartInstance(c *gc.C) {
    90  	s.PatchValue(&jujuversion.Current, coretesting.FakeVersionNumber)
    91  	checkPlacement := "directive"
    92  	checkCons := constraints.MustParse("mem=8G")
    93  	env := &mockEnviron{
    94  		storage: newStorage(s, c),
    95  		config:  configGetter(c),
    96  	}
    97  
    98  	startInstance := func(
    99  		placement string,
   100  		cons constraints.Value,
   101  		_ []string,
   102  		possibleTools tools.List,
   103  		icfg *instancecfg.InstanceConfig,
   104  	) (instance.Instance, *instance.HardwareCharacteristics, []network.InterfaceInfo, error) {
   105  		c.Assert(placement, gc.DeepEquals, checkPlacement)
   106  		c.Assert(cons, gc.DeepEquals, checkCons)
   107  
   108  		// The machine config should set its upgrade behavior based on
   109  		// the environment config.
   110  		expectedMcfg, err := instancecfg.NewBootstrapInstanceConfig(coretesting.FakeControllerConfig(), cons, cons, icfg.Series, "")
   111  		c.Assert(err, jc.ErrorIsNil)
   112  		expectedMcfg.EnableOSRefreshUpdate = env.Config().EnableOSRefreshUpdate()
   113  		expectedMcfg.EnableOSUpgrade = env.Config().EnableOSUpgrade()
   114  		expectedMcfg.Tags = map[string]string{
   115  			"juju-model-uuid":      coretesting.ModelTag.Id(),
   116  			"juju-controller-uuid": coretesting.ControllerTag.Id(),
   117  			"juju-is-controller":   "true",
   118  		}
   119  
   120  		c.Assert(icfg, jc.DeepEquals, expectedMcfg)
   121  		return nil, nil, nil, errors.Errorf("meh, not started")
   122  	}
   123  
   124  	env.startInstance = startInstance
   125  
   126  	ctx := envtesting.BootstrapContext(c)
   127  	_, err := common.Bootstrap(ctx, env, environs.BootstrapParams{
   128  		ControllerConfig:     coretesting.FakeControllerConfig(),
   129  		BootstrapConstraints: checkCons,
   130  		ModelConstraints:     checkCons,
   131  		Placement:            checkPlacement,
   132  		AvailableTools: tools.List{
   133  			&tools.Tools{
   134  				Version: version.Binary{
   135  					Number: jujuversion.Current,
   136  					Arch:   arch.HostArch(),
   137  					Series: series.HostSeries(),
   138  				},
   139  			},
   140  		}})
   141  	c.Assert(err, gc.ErrorMatches, "cannot start bootstrap instance: meh, not started")
   142  }
   143  
   144  func (s *BootstrapSuite) TestBootstrapSeries(c *gc.C) {
   145  	s.PatchValue(&jujuversion.Current, coretesting.FakeVersionNumber)
   146  	s.PatchValue(&series.HostSeries, func() string { return "precise" })
   147  	stor := newStorage(s, c)
   148  	checkInstanceId := "i-success"
   149  	checkHardware := instance.MustParseHardware("arch=ppc64el mem=2T")
   150  
   151  	startInstance := func(_ string, _ constraints.Value, _ []string, _ tools.List, icfg *instancecfg.InstanceConfig) (instance.Instance,
   152  		*instance.HardwareCharacteristics, []network.InterfaceInfo, error) {
   153  		return &mockInstance{id: checkInstanceId}, &checkHardware, nil, nil
   154  	}
   155  	var mocksConfig = minimalConfig(c)
   156  	var numGetConfigCalled int
   157  	getConfig := func() *config.Config {
   158  		numGetConfigCalled++
   159  		return mocksConfig
   160  	}
   161  	setConfig := func(c *config.Config) error {
   162  		mocksConfig = c
   163  		return nil
   164  	}
   165  
   166  	env := &mockEnviron{
   167  		storage:       stor,
   168  		startInstance: startInstance,
   169  		config:        getConfig,
   170  		setConfig:     setConfig,
   171  	}
   172  	ctx := envtesting.BootstrapContext(c)
   173  	bootstrapSeries := "utopic"
   174  	result, err := common.Bootstrap(ctx, env, environs.BootstrapParams{
   175  		ControllerConfig: coretesting.FakeControllerConfig(),
   176  		BootstrapSeries:  bootstrapSeries,
   177  		AvailableTools: tools.List{
   178  			&tools.Tools{
   179  				Version: version.Binary{
   180  					Number: jujuversion.Current,
   181  					Arch:   arch.HostArch(),
   182  					Series: bootstrapSeries,
   183  				},
   184  			},
   185  		}})
   186  	c.Assert(err, jc.ErrorIsNil)
   187  	c.Check(result.Arch, gc.Equals, "ppc64el") // based on hardware characteristics
   188  	c.Check(result.Series, gc.Equals, bootstrapSeries)
   189  }
   190  
   191  func (s *BootstrapSuite) TestSuccess(c *gc.C) {
   192  	s.PatchValue(&jujuversion.Current, coretesting.FakeVersionNumber)
   193  	stor := newStorage(s, c)
   194  	checkInstanceId := "i-success"
   195  	checkHardware := instance.MustParseHardware("arch=ppc64el mem=2T")
   196  
   197  	startInstance := func(
   198  		_ string, _ constraints.Value, _ []string, _ tools.List, icfg *instancecfg.InstanceConfig,
   199  	) (
   200  		instance.Instance, *instance.HardwareCharacteristics, []network.InterfaceInfo, error,
   201  	) {
   202  		return &mockInstance{id: checkInstanceId}, &checkHardware, nil, nil
   203  	}
   204  	var mocksConfig = minimalConfig(c)
   205  	var getConfigCalled int
   206  	getConfig := func() *config.Config {
   207  		getConfigCalled++
   208  		return mocksConfig
   209  	}
   210  	setConfig := func(c *config.Config) error {
   211  		mocksConfig = c
   212  		return nil
   213  	}
   214  
   215  	env := &mockEnviron{
   216  		storage:       stor,
   217  		startInstance: startInstance,
   218  		config:        getConfig,
   219  		setConfig:     setConfig,
   220  	}
   221  	inner := coretesting.Context(c)
   222  	ctx := modelcmd.BootstrapContext(inner)
   223  	result, err := common.Bootstrap(ctx, env, environs.BootstrapParams{
   224  		ControllerConfig: coretesting.FakeControllerConfig(),
   225  		AvailableTools: tools.List{
   226  			&tools.Tools{
   227  				Version: version.Binary{
   228  					Number: jujuversion.Current,
   229  					Arch:   arch.HostArch(),
   230  					Series: series.HostSeries(),
   231  				},
   232  			},
   233  		}})
   234  	c.Assert(err, jc.ErrorIsNil)
   235  	c.Assert(result.Arch, gc.Equals, "ppc64el") // based on hardware characteristics
   236  	c.Assert(result.Series, gc.Equals, config.PreferredSeries(mocksConfig))
   237  	output := inner.Stderr.(*bytes.Buffer)
   238  	lines := strings.Split(output.String(), "\n")
   239  	c.Assert(len(lines), jc.GreaterThan, 1)
   240  	c.Assert(lines[0], gc.Equals, "Some message")
   241  }
   242  
   243  type neverRefreshes struct {
   244  }
   245  
   246  func (neverRefreshes) Refresh() error {
   247  	return nil
   248  }
   249  
   250  func (neverRefreshes) Status() instance.InstanceStatus {
   251  	return instance.InstanceStatus{}
   252  }
   253  
   254  type neverAddresses struct {
   255  	neverRefreshes
   256  }
   257  
   258  func (neverAddresses) Addresses() ([]network.Address, error) {
   259  	return nil, nil
   260  }
   261  
   262  type failsProvisioning struct {
   263  	neverAddresses
   264  	message string
   265  }
   266  
   267  func (f failsProvisioning) Status() instance.InstanceStatus {
   268  	return instance.InstanceStatus{
   269  		Status:  status.ProvisioningError,
   270  		Message: f.message,
   271  	}
   272  }
   273  
   274  var testSSHTimeout = environs.BootstrapDialOpts{
   275  	Timeout:        coretesting.ShortWait,
   276  	RetryDelay:     1 * time.Millisecond,
   277  	AddressesDelay: 1 * time.Millisecond,
   278  }
   279  
   280  func (s *BootstrapSuite) TestWaitSSHTimesOutWaitingForAddresses(c *gc.C) {
   281  	ctx := coretesting.Context(c)
   282  	_, err := common.WaitSSH(ctx.Stderr, nil, ssh.DefaultClient, "/bin/true", neverAddresses{}, testSSHTimeout)
   283  	c.Check(err, gc.ErrorMatches, `waited for `+testSSHTimeout.Timeout.String()+` without getting any addresses`)
   284  	c.Check(coretesting.Stderr(ctx), gc.Matches, "Waiting for address\n")
   285  }
   286  
   287  func (s *BootstrapSuite) TestWaitSSHKilledWaitingForAddresses(c *gc.C) {
   288  	ctx := coretesting.Context(c)
   289  	interrupted := make(chan os.Signal, 1)
   290  	interrupted <- os.Interrupt
   291  	_, err := common.WaitSSH(ctx.Stderr, interrupted, ssh.DefaultClient, "/bin/true", neverAddresses{}, testSSHTimeout)
   292  	c.Check(err, gc.ErrorMatches, "interrupted")
   293  	c.Check(coretesting.Stderr(ctx), gc.Matches, "Waiting for address\n")
   294  }
   295  
   296  func (s *BootstrapSuite) TestWaitSSHNoticesProvisioningFailures(c *gc.C) {
   297  	ctx := coretesting.Context(c)
   298  	_, err := common.WaitSSH(ctx.Stderr, nil, ssh.DefaultClient, "/bin/true", failsProvisioning{}, testSSHTimeout)
   299  	c.Check(err, gc.ErrorMatches, `instance provisioning failed`)
   300  	_, err = common.WaitSSH(ctx.Stderr, nil, ssh.DefaultClient, "/bin/true", failsProvisioning{message: "blargh"}, testSSHTimeout)
   301  	c.Check(err, gc.ErrorMatches, `instance provisioning failed \(blargh\)`)
   302  }
   303  
   304  type brokenAddresses struct {
   305  	neverRefreshes
   306  }
   307  
   308  func (brokenAddresses) Addresses() ([]network.Address, error) {
   309  	return nil, errors.Errorf("Addresses will never work")
   310  }
   311  
   312  func (s *BootstrapSuite) TestWaitSSHStopsOnBadError(c *gc.C) {
   313  	ctx := coretesting.Context(c)
   314  	_, err := common.WaitSSH(ctx.Stderr, nil, ssh.DefaultClient, "/bin/true", brokenAddresses{}, testSSHTimeout)
   315  	c.Check(err, gc.ErrorMatches, "getting addresses: Addresses will never work")
   316  	c.Check(coretesting.Stderr(ctx), gc.Equals, "Waiting for address\n")
   317  }
   318  
   319  type neverOpensPort struct {
   320  	neverRefreshes
   321  	addr string
   322  }
   323  
   324  func (n *neverOpensPort) Addresses() ([]network.Address, error) {
   325  	return network.NewAddresses(n.addr), nil
   326  }
   327  
   328  func (s *BootstrapSuite) TestWaitSSHTimesOutWaitingForDial(c *gc.C) {
   329  	ctx := coretesting.Context(c)
   330  	// 0.x.y.z addresses are always invalid
   331  	_, err := common.WaitSSH(ctx.Stderr, nil, ssh.DefaultClient, "/bin/true", &neverOpensPort{addr: "0.1.2.3"}, testSSHTimeout)
   332  	c.Check(err, gc.ErrorMatches,
   333  		`waited for `+testSSHTimeout.Timeout.String()+` without being able to connect: mock connection failure to 0.1.2.3`)
   334  	c.Check(coretesting.Stderr(ctx), gc.Matches,
   335  		"Waiting for address\n"+
   336  			"(Attempting to connect to 0.1.2.3:22\n)+")
   337  }
   338  
   339  type interruptOnDial struct {
   340  	neverRefreshes
   341  	name        string
   342  	interrupted chan os.Signal
   343  	returned    bool
   344  }
   345  
   346  func (i *interruptOnDial) Addresses() ([]network.Address, error) {
   347  	// kill the tomb the second time Addresses is called
   348  	if !i.returned {
   349  		i.returned = true
   350  	} else {
   351  		i.interrupted <- os.Interrupt
   352  	}
   353  	return network.NewAddresses(i.name), nil
   354  }
   355  
   356  func (s *BootstrapSuite) TestWaitSSHKilledWaitingForDial(c *gc.C) {
   357  	ctx := coretesting.Context(c)
   358  	timeout := testSSHTimeout
   359  	timeout.Timeout = 1 * time.Minute
   360  	interrupted := make(chan os.Signal, 1)
   361  	_, err := common.WaitSSH(ctx.Stderr, interrupted, ssh.DefaultClient, "", &interruptOnDial{name: "0.1.2.3", interrupted: interrupted}, timeout)
   362  	c.Check(err, gc.ErrorMatches, "interrupted")
   363  	// Exact timing is imprecise but it should have tried a few times before being killed
   364  	c.Check(coretesting.Stderr(ctx), gc.Matches,
   365  		"Waiting for address\n"+
   366  			"(Attempting to connect to 0.1.2.3:22\n)+")
   367  }
   368  
   369  type addressesChange struct {
   370  	addrs [][]string
   371  }
   372  
   373  func (ac *addressesChange) Refresh() error {
   374  	if len(ac.addrs) > 1 {
   375  		ac.addrs = ac.addrs[1:]
   376  	}
   377  	return nil
   378  }
   379  
   380  func (ac *addressesChange) Status() instance.InstanceStatus {
   381  	return instance.InstanceStatus{}
   382  }
   383  
   384  func (ac *addressesChange) Addresses() ([]network.Address, error) {
   385  	return network.NewAddresses(ac.addrs[0]...), nil
   386  }
   387  
   388  func (s *BootstrapSuite) TestWaitSSHRefreshAddresses(c *gc.C) {
   389  	ctx := coretesting.Context(c)
   390  	_, err := common.WaitSSH(ctx.Stderr, nil, ssh.DefaultClient, "", &addressesChange{addrs: [][]string{
   391  		nil,
   392  		nil,
   393  		{"0.1.2.3"},
   394  		{"0.1.2.3"},
   395  		nil,
   396  		{"0.1.2.4"},
   397  	}}, testSSHTimeout)
   398  	// Not necessarily the last one in the list, due to scheduling.
   399  	c.Check(err, gc.ErrorMatches,
   400  		`waited for `+testSSHTimeout.Timeout.String()+` without being able to connect: mock connection failure to 0.1.2.[34]`)
   401  	stderr := coretesting.Stderr(ctx)
   402  	c.Check(stderr, gc.Matches,
   403  		"Waiting for address\n"+
   404  			"(.|\n)*(Attempting to connect to 0.1.2.3:22\n)+(.|\n)*")
   405  	c.Check(stderr, gc.Matches,
   406  		"Waiting for address\n"+
   407  			"(.|\n)*(Attempting to connect to 0.1.2.4:22\n)+(.|\n)*")
   408  }
   409  
   410  type FormatHardwareSuite struct{}
   411  
   412  var _ = gc.Suite(&FormatHardwareSuite{})
   413  
   414  func (s *FormatHardwareSuite) check(c *gc.C, hw *instance.HardwareCharacteristics, expected string) {
   415  	c.Check(common.FormatHardware(hw), gc.Equals, expected)
   416  }
   417  
   418  func (s *FormatHardwareSuite) TestNil(c *gc.C) {
   419  	s.check(c, nil, "")
   420  }
   421  
   422  func (s *FormatHardwareSuite) TestFieldsNil(c *gc.C) {
   423  	s.check(c, &instance.HardwareCharacteristics{}, "")
   424  }
   425  
   426  func (s *FormatHardwareSuite) TestArch(c *gc.C) {
   427  	arch := ""
   428  	s.check(c, &instance.HardwareCharacteristics{Arch: &arch}, "")
   429  	arch = "amd64"
   430  	s.check(c, &instance.HardwareCharacteristics{Arch: &arch}, "arch=amd64")
   431  }
   432  
   433  func (s *FormatHardwareSuite) TestCores(c *gc.C) {
   434  	var cores uint64
   435  	s.check(c, &instance.HardwareCharacteristics{CpuCores: &cores}, "")
   436  	cores = 24
   437  	s.check(c, &instance.HardwareCharacteristics{CpuCores: &cores}, "cores=24")
   438  }
   439  
   440  func (s *FormatHardwareSuite) TestMem(c *gc.C) {
   441  	var mem uint64
   442  	s.check(c, &instance.HardwareCharacteristics{Mem: &mem}, "")
   443  	mem = 800
   444  	s.check(c, &instance.HardwareCharacteristics{Mem: &mem}, "mem=800M")
   445  	mem = 1024
   446  	s.check(c, &instance.HardwareCharacteristics{Mem: &mem}, "mem=1G")
   447  	mem = 2712
   448  	s.check(c, &instance.HardwareCharacteristics{Mem: &mem}, "mem=2.6G")
   449  }
   450  
   451  func (s *FormatHardwareSuite) TestAll(c *gc.C) {
   452  	arch := "ppc64"
   453  	var cores uint64 = 2
   454  	var mem uint64 = 123
   455  	hw := &instance.HardwareCharacteristics{
   456  		Arch:     &arch,
   457  		CpuCores: &cores,
   458  		Mem:      &mem,
   459  	}
   460  	s.check(c, hw, "arch=ppc64 mem=123M cores=2")
   461  }