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