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