github.com/makyo/juju@v0.0.0-20160425123129-2608902037e9/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  	"fmt"
     8  	"os"
     9  	"time"
    10  
    11  	jc "github.com/juju/testing/checkers"
    12  	"github.com/juju/utils/arch"
    13  	"github.com/juju/utils/series"
    14  	"github.com/juju/utils/ssh"
    15  	"github.com/juju/version"
    16  	gc "gopkg.in/check.v1"
    17  
    18  	"github.com/juju/juju/cloudconfig/instancecfg"
    19  	"github.com/juju/juju/constraints"
    20  	"github.com/juju/juju/environs"
    21  	"github.com/juju/juju/environs/config"
    22  	"github.com/juju/juju/environs/storage"
    23  	envtesting "github.com/juju/juju/environs/testing"
    24  	"github.com/juju/juju/instance"
    25  	"github.com/juju/juju/network"
    26  	"github.com/juju/juju/provider/common"
    27  	coretesting "github.com/juju/juju/testing"
    28  	"github.com/juju/juju/tools"
    29  	jujuversion "github.com/juju/juju/version"
    30  )
    31  
    32  type BootstrapSuite struct {
    33  	coretesting.FakeJujuXDGDataHomeSuite
    34  	envtesting.ToolsFixture
    35  }
    36  
    37  var _ = gc.Suite(&BootstrapSuite{})
    38  
    39  type cleaner interface {
    40  	AddCleanup(func(*gc.C))
    41  }
    42  
    43  func (s *BootstrapSuite) SetUpTest(c *gc.C) {
    44  	s.FakeJujuXDGDataHomeSuite.SetUpTest(c)
    45  	s.ToolsFixture.SetUpTest(c)
    46  	s.PatchValue(common.ConnectSSH, func(_ ssh.Client, host, checkHostScript string) error {
    47  		return fmt.Errorf("mock connection failure to %s", host)
    48  	})
    49  }
    50  
    51  func (s *BootstrapSuite) TearDownTest(c *gc.C) {
    52  	s.ToolsFixture.TearDownTest(c)
    53  	s.FakeJujuXDGDataHomeSuite.TearDownTest(c)
    54  }
    55  
    56  func newStorage(suite cleaner, c *gc.C) storage.Storage {
    57  	closer, stor, _ := envtesting.CreateLocalTestStorage(c)
    58  	suite.AddCleanup(func(*gc.C) { closer.Close() })
    59  	envtesting.UploadFakeTools(c, stor, "released", "released")
    60  	return stor
    61  }
    62  
    63  func minimalConfig(c *gc.C) *config.Config {
    64  	attrs := map[string]interface{}{
    65  		"name":            "whatever",
    66  		"type":            "anything, really",
    67  		"uuid":            coretesting.ModelTag.Id(),
    68  		"controller-uuid": coretesting.ModelTag.Id(),
    69  		"ca-cert":         coretesting.CACert,
    70  		"ca-private-key":  coretesting.CAKey,
    71  		"authorized-keys": coretesting.FakeAuthKeys,
    72  		"default-series":  series.HostSeries(),
    73  	}
    74  	cfg, err := config.New(config.UseDefaults, attrs)
    75  	c.Assert(err, jc.ErrorIsNil)
    76  	return cfg
    77  }
    78  
    79  func configGetter(c *gc.C) configFunc {
    80  	cfg := minimalConfig(c)
    81  	return func() *config.Config { return cfg }
    82  }
    83  
    84  func (s *BootstrapSuite) TestCannotStartInstance(c *gc.C) {
    85  	s.PatchValue(&jujuversion.Current, coretesting.FakeVersionNumber)
    86  	checkPlacement := "directive"
    87  	checkCons := constraints.MustParse("mem=8G")
    88  	env := &mockEnviron{
    89  		storage: newStorage(s, c),
    90  		config:  configGetter(c),
    91  	}
    92  
    93  	startInstance := func(
    94  		placement string,
    95  		cons constraints.Value,
    96  		_ []string,
    97  		possibleTools tools.List,
    98  		icfg *instancecfg.InstanceConfig,
    99  	) (instance.Instance, *instance.HardwareCharacteristics, []network.InterfaceInfo, error) {
   100  		c.Assert(placement, gc.DeepEquals, checkPlacement)
   101  		c.Assert(cons, gc.DeepEquals, checkCons)
   102  
   103  		// The machine config should set its upgrade behavior based on
   104  		// the environment config.
   105  		expectedMcfg, err := instancecfg.NewBootstrapInstanceConfig(cons, cons, icfg.Series, "")
   106  		c.Assert(err, jc.ErrorIsNil)
   107  		expectedMcfg.EnableOSRefreshUpdate = env.Config().EnableOSRefreshUpdate()
   108  		expectedMcfg.EnableOSUpgrade = env.Config().EnableOSUpgrade()
   109  		expectedMcfg.Tags = map[string]string{
   110  			"juju-model-uuid":      coretesting.ModelTag.Id(),
   111  			"juju-controller-uuid": coretesting.ModelTag.Id(),
   112  			"juju-is-controller":   "true",
   113  		}
   114  
   115  		c.Assert(icfg, jc.DeepEquals, expectedMcfg)
   116  		return nil, nil, nil, fmt.Errorf("meh, not started")
   117  	}
   118  
   119  	env.startInstance = startInstance
   120  
   121  	ctx := envtesting.BootstrapContext(c)
   122  	_, err := common.Bootstrap(ctx, env, environs.BootstrapParams{
   123  		BootstrapConstraints: checkCons,
   124  		ModelConstraints:     checkCons,
   125  		Placement:            checkPlacement,
   126  		AvailableTools: tools.List{
   127  			&tools.Tools{
   128  				Version: version.Binary{
   129  					Number: jujuversion.Current,
   130  					Arch:   arch.HostArch(),
   131  					Series: series.HostSeries(),
   132  				},
   133  			},
   134  		}})
   135  	c.Assert(err, gc.ErrorMatches, "cannot start bootstrap instance: meh, not started")
   136  }
   137  
   138  func (s *BootstrapSuite) TestBootstrapSeries(c *gc.C) {
   139  	s.PatchValue(&jujuversion.Current, coretesting.FakeVersionNumber)
   140  	s.PatchValue(&series.HostSeries, func() string { return "precise" })
   141  	stor := newStorage(s, c)
   142  	checkInstanceId := "i-success"
   143  	checkHardware := instance.MustParseHardware("arch=ppc64el mem=2T")
   144  
   145  	startInstance := func(_ string, _ constraints.Value, _ []string, _ tools.List, icfg *instancecfg.InstanceConfig) (instance.Instance,
   146  		*instance.HardwareCharacteristics, []network.InterfaceInfo, error) {
   147  		return &mockInstance{id: checkInstanceId}, &checkHardware, nil, nil
   148  	}
   149  	var mocksConfig = minimalConfig(c)
   150  	var numGetConfigCalled int
   151  	getConfig := func() *config.Config {
   152  		numGetConfigCalled++
   153  		return mocksConfig
   154  	}
   155  	setConfig := func(c *config.Config) error {
   156  		mocksConfig = c
   157  		return nil
   158  	}
   159  
   160  	env := &mockEnviron{
   161  		storage:       stor,
   162  		startInstance: startInstance,
   163  		config:        getConfig,
   164  		setConfig:     setConfig,
   165  	}
   166  	ctx := envtesting.BootstrapContext(c)
   167  	bootstrapSeries := "utopic"
   168  	result, err := common.Bootstrap(ctx, env, environs.BootstrapParams{
   169  		BootstrapSeries: bootstrapSeries,
   170  		AvailableTools: tools.List{
   171  			&tools.Tools{
   172  				Version: version.Binary{
   173  					Number: jujuversion.Current,
   174  					Arch:   arch.HostArch(),
   175  					Series: bootstrapSeries,
   176  				},
   177  			},
   178  		}})
   179  	c.Assert(err, jc.ErrorIsNil)
   180  	c.Check(result.Arch, gc.Equals, "ppc64el") // based on hardware characteristics
   181  	c.Check(result.Series, gc.Equals, bootstrapSeries)
   182  }
   183  
   184  func (s *BootstrapSuite) TestSuccess(c *gc.C) {
   185  	s.PatchValue(&jujuversion.Current, coretesting.FakeVersionNumber)
   186  	stor := newStorage(s, c)
   187  	checkInstanceId := "i-success"
   188  	checkHardware := instance.MustParseHardware("arch=ppc64el mem=2T")
   189  
   190  	startInstance := func(
   191  		_ string, _ constraints.Value, _ []string, _ tools.List, icfg *instancecfg.InstanceConfig,
   192  	) (
   193  		instance.Instance, *instance.HardwareCharacteristics, []network.InterfaceInfo, error,
   194  	) {
   195  		return &mockInstance{id: checkInstanceId}, &checkHardware, nil, nil
   196  	}
   197  	var mocksConfig = minimalConfig(c)
   198  	var getConfigCalled int
   199  	getConfig := func() *config.Config {
   200  		getConfigCalled++
   201  		return mocksConfig
   202  	}
   203  	setConfig := func(c *config.Config) error {
   204  		mocksConfig = c
   205  		return nil
   206  	}
   207  
   208  	env := &mockEnviron{
   209  		storage:       stor,
   210  		startInstance: startInstance,
   211  		config:        getConfig,
   212  		setConfig:     setConfig,
   213  	}
   214  	ctx := envtesting.BootstrapContext(c)
   215  	result, err := common.Bootstrap(ctx, env, environs.BootstrapParams{
   216  		AvailableTools: tools.List{
   217  			&tools.Tools{
   218  				Version: version.Binary{
   219  					Number: jujuversion.Current,
   220  					Arch:   arch.HostArch(),
   221  					Series: series.HostSeries(),
   222  				},
   223  			},
   224  		}})
   225  	c.Assert(err, jc.ErrorIsNil)
   226  	c.Assert(result.Arch, gc.Equals, "ppc64el") // based on hardware characteristics
   227  	c.Assert(result.Series, gc.Equals, config.PreferredSeries(mocksConfig))
   228  }
   229  
   230  type neverRefreshes struct {
   231  }
   232  
   233  func (neverRefreshes) Refresh() error {
   234  	return nil
   235  }
   236  
   237  type neverAddresses struct {
   238  	neverRefreshes
   239  }
   240  
   241  func (neverAddresses) Addresses() ([]network.Address, error) {
   242  	return nil, nil
   243  }
   244  
   245  var testSSHTimeout = config.SSHTimeoutOpts{
   246  	Timeout:        coretesting.ShortWait,
   247  	RetryDelay:     1 * time.Millisecond,
   248  	AddressesDelay: 1 * time.Millisecond,
   249  }
   250  
   251  func (s *BootstrapSuite) TestWaitSSHTimesOutWaitingForAddresses(c *gc.C) {
   252  	ctx := coretesting.Context(c)
   253  	_, err := common.WaitSSH(ctx.Stderr, nil, ssh.DefaultClient, "/bin/true", neverAddresses{}, testSSHTimeout)
   254  	c.Check(err, gc.ErrorMatches, `waited for `+testSSHTimeout.Timeout.String()+` without getting any addresses`)
   255  	c.Check(coretesting.Stderr(ctx), gc.Matches, "Waiting for address\n")
   256  }
   257  
   258  func (s *BootstrapSuite) TestWaitSSHKilledWaitingForAddresses(c *gc.C) {
   259  	ctx := coretesting.Context(c)
   260  	interrupted := make(chan os.Signal, 1)
   261  	interrupted <- os.Interrupt
   262  	_, err := common.WaitSSH(ctx.Stderr, interrupted, ssh.DefaultClient, "/bin/true", neverAddresses{}, testSSHTimeout)
   263  	c.Check(err, gc.ErrorMatches, "interrupted")
   264  	c.Check(coretesting.Stderr(ctx), gc.Matches, "Waiting for address\n")
   265  }
   266  
   267  type brokenAddresses struct {
   268  	neverRefreshes
   269  }
   270  
   271  func (brokenAddresses) Addresses() ([]network.Address, error) {
   272  	return nil, fmt.Errorf("Addresses will never work")
   273  }
   274  
   275  func (s *BootstrapSuite) TestWaitSSHStopsOnBadError(c *gc.C) {
   276  	ctx := coretesting.Context(c)
   277  	_, err := common.WaitSSH(ctx.Stderr, nil, ssh.DefaultClient, "/bin/true", brokenAddresses{}, testSSHTimeout)
   278  	c.Check(err, gc.ErrorMatches, "getting addresses: Addresses will never work")
   279  	c.Check(coretesting.Stderr(ctx), gc.Equals, "Waiting for address\n")
   280  }
   281  
   282  type neverOpensPort struct {
   283  	neverRefreshes
   284  	addr string
   285  }
   286  
   287  func (n *neverOpensPort) Addresses() ([]network.Address, error) {
   288  	return network.NewAddresses(n.addr), nil
   289  }
   290  
   291  func (s *BootstrapSuite) TestWaitSSHTimesOutWaitingForDial(c *gc.C) {
   292  	ctx := coretesting.Context(c)
   293  	// 0.x.y.z addresses are always invalid
   294  	_, err := common.WaitSSH(ctx.Stderr, nil, ssh.DefaultClient, "/bin/true", &neverOpensPort{addr: "0.1.2.3"}, testSSHTimeout)
   295  	c.Check(err, gc.ErrorMatches,
   296  		`waited for `+testSSHTimeout.Timeout.String()+` without being able to connect: mock connection failure to 0.1.2.3`)
   297  	c.Check(coretesting.Stderr(ctx), gc.Matches,
   298  		"Waiting for address\n"+
   299  			"(Attempting to connect to 0.1.2.3:22\n)+")
   300  }
   301  
   302  type interruptOnDial struct {
   303  	neverRefreshes
   304  	name        string
   305  	interrupted chan os.Signal
   306  	returned    bool
   307  }
   308  
   309  func (i *interruptOnDial) Addresses() ([]network.Address, error) {
   310  	// kill the tomb the second time Addresses is called
   311  	if !i.returned {
   312  		i.returned = true
   313  	} else {
   314  		i.interrupted <- os.Interrupt
   315  	}
   316  	return network.NewAddresses(i.name), nil
   317  }
   318  
   319  func (s *BootstrapSuite) TestWaitSSHKilledWaitingForDial(c *gc.C) {
   320  	ctx := coretesting.Context(c)
   321  	timeout := testSSHTimeout
   322  	timeout.Timeout = 1 * time.Minute
   323  	interrupted := make(chan os.Signal, 1)
   324  	_, err := common.WaitSSH(ctx.Stderr, interrupted, ssh.DefaultClient, "", &interruptOnDial{name: "0.1.2.3", interrupted: interrupted}, timeout)
   325  	c.Check(err, gc.ErrorMatches, "interrupted")
   326  	// Exact timing is imprecise but it should have tried a few times before being killed
   327  	c.Check(coretesting.Stderr(ctx), gc.Matches,
   328  		"Waiting for address\n"+
   329  			"(Attempting to connect to 0.1.2.3:22\n)+")
   330  }
   331  
   332  type addressesChange struct {
   333  	addrs [][]string
   334  }
   335  
   336  func (ac *addressesChange) Refresh() error {
   337  	if len(ac.addrs) > 1 {
   338  		ac.addrs = ac.addrs[1:]
   339  	}
   340  	return nil
   341  }
   342  
   343  func (ac *addressesChange) Addresses() ([]network.Address, error) {
   344  	return network.NewAddresses(ac.addrs[0]...), nil
   345  }
   346  
   347  func (s *BootstrapSuite) TestWaitSSHRefreshAddresses(c *gc.C) {
   348  	ctx := coretesting.Context(c)
   349  	_, err := common.WaitSSH(ctx.Stderr, nil, ssh.DefaultClient, "", &addressesChange{addrs: [][]string{
   350  		nil,
   351  		nil,
   352  		{"0.1.2.3"},
   353  		{"0.1.2.3"},
   354  		nil,
   355  		{"0.1.2.4"},
   356  	}}, testSSHTimeout)
   357  	// Not necessarily the last one in the list, due to scheduling.
   358  	c.Check(err, gc.ErrorMatches,
   359  		`waited for `+testSSHTimeout.Timeout.String()+` without being able to connect: mock connection failure to 0.1.2.[34]`)
   360  	stderr := coretesting.Stderr(ctx)
   361  	c.Check(stderr, gc.Matches,
   362  		"Waiting for address\n"+
   363  			"(.|\n)*(Attempting to connect to 0.1.2.3:22\n)+(.|\n)*")
   364  	c.Check(stderr, gc.Matches,
   365  		"Waiting for address\n"+
   366  			"(.|\n)*(Attempting to connect to 0.1.2.4:22\n)+(.|\n)*")
   367  }