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