github.com/niedbalski/juju@v0.0.0-20190215020005-8ff100488e47/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  	"crypto/rsa"
     8  	"fmt"
     9  	"io/ioutil"
    10  	"os"
    11  	"regexp"
    12  	"strings"
    13  	"sync"
    14  	"time"
    15  
    16  	"github.com/juju/cmd/cmdtesting"
    17  	"github.com/juju/errors"
    18  	"github.com/juju/os/series"
    19  	"github.com/juju/testing"
    20  	jc "github.com/juju/testing/checkers"
    21  	"github.com/juju/utils/arch"
    22  	"github.com/juju/utils/ssh"
    23  	"github.com/juju/version"
    24  	cryptossh "golang.org/x/crypto/ssh"
    25  	gc "gopkg.in/check.v1"
    26  
    27  	"github.com/juju/juju/cloudconfig/instancecfg"
    28  	"github.com/juju/juju/cmd/modelcmd"
    29  	"github.com/juju/juju/core/constraints"
    30  	"github.com/juju/juju/core/instance"
    31  	"github.com/juju/juju/core/status"
    32  	"github.com/juju/juju/environs"
    33  	"github.com/juju/juju/environs/config"
    34  	"github.com/juju/juju/environs/context"
    35  	"github.com/juju/juju/environs/instances"
    36  	"github.com/juju/juju/environs/storage"
    37  	envtesting "github.com/juju/juju/environs/testing"
    38  	"github.com/juju/juju/network"
    39  	"github.com/juju/juju/provider/common"
    40  	coretesting "github.com/juju/juju/testing"
    41  	"github.com/juju/juju/tools"
    42  	jujuversion "github.com/juju/juju/version"
    43  )
    44  
    45  type BootstrapSuite struct {
    46  	coretesting.FakeJujuXDGDataHomeSuite
    47  	envtesting.ToolsFixture
    48  
    49  	callCtx context.ProviderCallContext
    50  }
    51  
    52  var _ = gc.Suite(&BootstrapSuite{})
    53  
    54  type cleaner interface {
    55  	AddCleanup(func(*gc.C))
    56  }
    57  
    58  func (s *BootstrapSuite) SetUpTest(c *gc.C) {
    59  	coretesting.SkipUnlessControllerOS(c)
    60  	s.FakeJujuXDGDataHomeSuite.SetUpTest(c)
    61  	s.ToolsFixture.SetUpTest(c)
    62  	s.PatchValue(common.ConnectSSH, func(_ ssh.Client, host, checkHostScript string, opts *ssh.Options) error {
    63  		return fmt.Errorf("mock connection failure to %s", host)
    64  	})
    65  
    66  	s.callCtx = context.NewCloudCallContext()
    67  }
    68  
    69  func (s *BootstrapSuite) TearDownTest(c *gc.C) {
    70  	s.ToolsFixture.TearDownTest(c)
    71  	s.FakeJujuXDGDataHomeSuite.TearDownTest(c)
    72  }
    73  
    74  func newStorage(suite cleaner, c *gc.C) storage.Storage {
    75  	closer, stor, _ := envtesting.CreateLocalTestStorage(c)
    76  	suite.AddCleanup(func(*gc.C) { closer.Close() })
    77  	envtesting.UploadFakeTools(c, stor, "released", "released")
    78  	return stor
    79  }
    80  
    81  func minimalConfig(c *gc.C) *config.Config {
    82  	attrs := map[string]interface{}{
    83  		"name":               "whatever",
    84  		"type":               "anything, really",
    85  		"uuid":               coretesting.ModelTag.Id(),
    86  		"controller-uuid":    coretesting.ControllerTag.Id(),
    87  		"ca-cert":            coretesting.CACert,
    88  		"ca-private-key":     coretesting.CAKey,
    89  		"authorized-keys":    coretesting.FakeAuthKeys,
    90  		"default-series":     series.MustHostSeries(),
    91  		"cloudinit-userdata": validCloudInitUserData,
    92  	}
    93  	cfg, err := config.New(config.UseDefaults, attrs)
    94  	c.Assert(err, jc.ErrorIsNil)
    95  	return cfg
    96  }
    97  
    98  func configGetter(c *gc.C) configFunc {
    99  	cfg := minimalConfig(c)
   100  	return func() *config.Config { return cfg }
   101  }
   102  
   103  func (s *BootstrapSuite) TestCannotStartInstance(c *gc.C) {
   104  	s.PatchValue(&jujuversion.Current, coretesting.FakeVersionNumber)
   105  	checkPlacement := "directive"
   106  	checkCons := constraints.MustParse("mem=8G")
   107  	env := &mockEnviron{
   108  		storage: newStorage(s, c),
   109  		config:  configGetter(c),
   110  	}
   111  
   112  	startInstance := func(ctx context.ProviderCallContext, args environs.StartInstanceParams) (
   113  		instances.Instance,
   114  		*instance.HardwareCharacteristics,
   115  		[]network.InterfaceInfo,
   116  		error,
   117  	) {
   118  		c.Assert(args.Placement, gc.DeepEquals, checkPlacement)
   119  		c.Assert(args.Constraints, gc.DeepEquals, checkCons)
   120  
   121  		// The machine config should set its upgrade behavior based on
   122  		// the environment config.
   123  		expectedMcfg, err := instancecfg.NewBootstrapInstanceConfig(
   124  			coretesting.FakeControllerConfig(),
   125  			args.Constraints,
   126  			args.Constraints,
   127  			args.InstanceConfig.Series,
   128  			"",
   129  		)
   130  		c.Assert(err, jc.ErrorIsNil)
   131  
   132  		expectedMcfg.EnableOSRefreshUpdate = env.Config().EnableOSRefreshUpdate()
   133  		expectedMcfg.EnableOSUpgrade = env.Config().EnableOSUpgrade()
   134  		expectedMcfg.Tags = map[string]string{
   135  			"juju-model-uuid":      coretesting.ModelTag.Id(),
   136  			"juju-controller-uuid": coretesting.ControllerTag.Id(),
   137  			"juju-is-controller":   "true",
   138  		}
   139  		expectedMcfg.NetBondReconfigureDelay = env.Config().NetBondReconfigureDelay()
   140  		args.InstanceConfig.Bootstrap.InitialSSHHostKeys.RSA = nil
   141  		c.Assert(args.InstanceConfig, jc.DeepEquals, expectedMcfg)
   142  		return nil, nil, nil, errors.Errorf("meh, not started")
   143  	}
   144  
   145  	env.startInstance = startInstance
   146  
   147  	ctx := envtesting.BootstrapContext(c)
   148  	_, err := common.Bootstrap(ctx, env, s.callCtx, environs.BootstrapParams{
   149  		ControllerConfig:     coretesting.FakeControllerConfig(),
   150  		BootstrapConstraints: checkCons,
   151  		ModelConstraints:     checkCons,
   152  		Placement:            checkPlacement,
   153  		AvailableTools:       fakeAvailableTools(),
   154  	})
   155  	c.Assert(err, gc.ErrorMatches, "cannot start bootstrap instance: meh, not started")
   156  }
   157  
   158  func (s *BootstrapSuite) TestBootstrapSeries(c *gc.C) {
   159  	s.PatchValue(&jujuversion.Current, coretesting.FakeVersionNumber)
   160  	s.PatchValue(&series.MustHostSeries, func() string { return "precise" })
   161  	stor := newStorage(s, c)
   162  	checkInstanceId := "i-success"
   163  	checkHardware := instance.MustParseHardware("arch=ppc64el mem=2T")
   164  
   165  	startInstance := func(ctx context.ProviderCallContext, args environs.StartInstanceParams) (
   166  		instances.Instance,
   167  		*instance.HardwareCharacteristics,
   168  		[]network.InterfaceInfo,
   169  		error,
   170  	) {
   171  		return &mockInstance{id: checkInstanceId}, &checkHardware, nil, nil
   172  	}
   173  	var mocksConfig = minimalConfig(c)
   174  	var numGetConfigCalled int
   175  	getConfig := func() *config.Config {
   176  		numGetConfigCalled++
   177  		return mocksConfig
   178  	}
   179  	setConfig := func(c *config.Config) error {
   180  		mocksConfig = c
   181  		return nil
   182  	}
   183  
   184  	env := &mockEnviron{
   185  		storage:       stor,
   186  		startInstance: startInstance,
   187  		config:        getConfig,
   188  		setConfig:     setConfig,
   189  	}
   190  	ctx := envtesting.BootstrapContext(c)
   191  	bootstrapSeries := "utopic"
   192  	availableTools := fakeAvailableTools()
   193  	availableTools[0].Version.Series = bootstrapSeries
   194  	result, err := common.Bootstrap(ctx, env, s.callCtx, environs.BootstrapParams{
   195  		ControllerConfig: coretesting.FakeControllerConfig(),
   196  		BootstrapSeries:  bootstrapSeries,
   197  		AvailableTools:   availableTools,
   198  	})
   199  	c.Assert(err, jc.ErrorIsNil)
   200  	c.Check(result.Arch, gc.Equals, "ppc64el") // based on hardware characteristics
   201  	c.Check(result.Series, gc.Equals, bootstrapSeries)
   202  }
   203  
   204  func (s *BootstrapSuite) TestStartInstanceDerivedZone(c *gc.C) {
   205  	s.PatchValue(&jujuversion.Current, coretesting.FakeVersionNumber)
   206  	env := &mockZonedEnviron{
   207  		mockEnviron: mockEnviron{
   208  			storage: newStorage(s, c),
   209  			config:  configGetter(c),
   210  		},
   211  		deriveAvailabilityZones: func(context.ProviderCallContext, environs.StartInstanceParams) ([]string, error) {
   212  			return []string{"derived-zone"}, nil
   213  		},
   214  	}
   215  
   216  	env.startInstance = func(ctx context.ProviderCallContext, args environs.StartInstanceParams) (
   217  		instances.Instance,
   218  		*instance.HardwareCharacteristics,
   219  		[]network.InterfaceInfo,
   220  		error,
   221  	) {
   222  		c.Assert(args.AvailabilityZone, gc.Equals, "derived-zone")
   223  		return nil, nil, nil, errors.New("bloop")
   224  	}
   225  
   226  	ctx := envtesting.BootstrapContext(c)
   227  	_, err := common.Bootstrap(ctx, env, s.callCtx, environs.BootstrapParams{
   228  		ControllerConfig: coretesting.FakeControllerConfig(),
   229  		AvailableTools:   fakeAvailableTools(),
   230  	})
   231  	c.Assert(err, gc.ErrorMatches,
   232  		`cannot start bootstrap instance in availability zone "derived-zone": bloop`,
   233  	)
   234  }
   235  
   236  func (s *BootstrapSuite) TestStartInstanceAttemptAllZones(c *gc.C) {
   237  	s.PatchValue(&jujuversion.Current, coretesting.FakeVersionNumber)
   238  	env := &mockZonedEnviron{
   239  		mockEnviron: mockEnviron{
   240  			storage: newStorage(s, c),
   241  			config:  configGetter(c),
   242  		},
   243  		deriveAvailabilityZones: func(context.ProviderCallContext, environs.StartInstanceParams) ([]string, error) {
   244  			return nil, nil
   245  		},
   246  		availabilityZones: func(ctx context.ProviderCallContext) ([]common.AvailabilityZone, error) {
   247  			z0 := &mockAvailabilityZone{"z0", true}
   248  			z1 := &mockAvailabilityZone{"z1", false}
   249  			z2 := &mockAvailabilityZone{"z2", true}
   250  			return []common.AvailabilityZone{z0, z1, z2}, nil
   251  		},
   252  	}
   253  
   254  	var callZones []string
   255  	env.startInstance = func(ctx context.ProviderCallContext, args environs.StartInstanceParams) (
   256  		instances.Instance,
   257  		*instance.HardwareCharacteristics,
   258  		[]network.InterfaceInfo,
   259  		error,
   260  	) {
   261  		callZones = append(callZones, args.AvailabilityZone)
   262  		return nil, nil, nil, errors.New("bloop")
   263  	}
   264  
   265  	ctx := envtesting.BootstrapContext(c)
   266  	_, err := common.Bootstrap(ctx, env, s.callCtx, environs.BootstrapParams{
   267  		ControllerConfig: coretesting.FakeControllerConfig(),
   268  		AvailableTools:   fakeAvailableTools(),
   269  	})
   270  	c.Assert(err, gc.ErrorMatches,
   271  		`cannot start bootstrap instance in any availability zone \(z0, z2\)`,
   272  	)
   273  	c.Assert(callZones, jc.SameContents, []string{"z0", "z2"})
   274  }
   275  
   276  func (s *BootstrapSuite) TestStartInstanceStopOnZoneIndependentError(c *gc.C) {
   277  	s.PatchValue(&jujuversion.Current, coretesting.FakeVersionNumber)
   278  	env := &mockZonedEnviron{
   279  		mockEnviron: mockEnviron{
   280  			storage: newStorage(s, c),
   281  			config:  configGetter(c),
   282  		},
   283  		deriveAvailabilityZones: func(context.ProviderCallContext, environs.StartInstanceParams) ([]string, error) {
   284  			return nil, nil
   285  		},
   286  		availabilityZones: func(ctx context.ProviderCallContext) ([]common.AvailabilityZone, error) {
   287  			z0 := &mockAvailabilityZone{"z0", true}
   288  			z1 := &mockAvailabilityZone{"z1", true}
   289  			return []common.AvailabilityZone{z0, z1}, nil
   290  		},
   291  	}
   292  
   293  	var callZones []string
   294  	env.startInstance = func(ctx context.ProviderCallContext, args environs.StartInstanceParams) (
   295  		instances.Instance,
   296  		*instance.HardwareCharacteristics,
   297  		[]network.InterfaceInfo,
   298  		error,
   299  	) {
   300  		callZones = append(callZones, args.AvailabilityZone)
   301  		return nil, nil, nil, common.ZoneIndependentError(errors.New("bloop"))
   302  	}
   303  
   304  	ctx := envtesting.BootstrapContext(c)
   305  	_, err := common.Bootstrap(ctx, env, s.callCtx, environs.BootstrapParams{
   306  		ControllerConfig: coretesting.FakeControllerConfig(),
   307  		AvailableTools:   fakeAvailableTools(),
   308  	})
   309  	c.Assert(err, gc.ErrorMatches, `cannot start bootstrap instance: bloop`)
   310  	c.Assert(callZones, jc.SameContents, []string{"z0"})
   311  }
   312  
   313  func (s *BootstrapSuite) TestStartInstanceNoUsableZones(c *gc.C) {
   314  	s.PatchValue(&jujuversion.Current, coretesting.FakeVersionNumber)
   315  	env := &mockZonedEnviron{
   316  		mockEnviron: mockEnviron{
   317  			storage: newStorage(s, c),
   318  			config:  configGetter(c),
   319  		},
   320  		deriveAvailabilityZones: func(context.ProviderCallContext, environs.StartInstanceParams) ([]string, error) {
   321  			return nil, nil
   322  		},
   323  		availabilityZones: func(ctx context.ProviderCallContext) ([]common.AvailabilityZone, error) {
   324  			z0 := &mockAvailabilityZone{"z0", false}
   325  			return []common.AvailabilityZone{z0}, nil
   326  		},
   327  	}
   328  
   329  	ctx := envtesting.BootstrapContext(c)
   330  	_, err := common.Bootstrap(ctx, env, s.callCtx, environs.BootstrapParams{
   331  		ControllerConfig: coretesting.FakeControllerConfig(),
   332  		AvailableTools:   fakeAvailableTools(),
   333  	})
   334  	c.Assert(err, gc.ErrorMatches, `cannot start bootstrap instance: no usable availability zones`)
   335  }
   336  
   337  func (s *BootstrapSuite) TestSuccess(c *gc.C) {
   338  	s.PatchValue(&jujuversion.Current, coretesting.FakeVersionNumber)
   339  	stor := newStorage(s, c)
   340  	checkInstanceId := "i-success"
   341  	checkHardware := instance.MustParseHardware("arch=ppc64el mem=2T")
   342  
   343  	var innerInstanceConfig *instancecfg.InstanceConfig
   344  	inst := &mockInstance{
   345  		id:        checkInstanceId,
   346  		addresses: network.NewAddresses("testing.invalid"),
   347  	}
   348  	startInstance := func(ctx context.ProviderCallContext, args environs.StartInstanceParams) (
   349  		instances.Instance,
   350  		*instance.HardwareCharacteristics,
   351  		[]network.InterfaceInfo,
   352  		error,
   353  	) {
   354  		icfg := args.InstanceConfig
   355  		innerInstanceConfig = icfg
   356  		c.Assert(icfg.Bootstrap.InitialSSHHostKeys.RSA, gc.NotNil)
   357  		privKey, err := cryptossh.ParseRawPrivateKey([]byte(icfg.Bootstrap.InitialSSHHostKeys.RSA.Private))
   358  		c.Assert(err, jc.ErrorIsNil)
   359  		c.Assert(privKey, gc.FitsTypeOf, &rsa.PrivateKey{})
   360  		pubKey, _, _, _, err := cryptossh.ParseAuthorizedKey([]byte(icfg.Bootstrap.InitialSSHHostKeys.RSA.Public))
   361  		c.Assert(err, jc.ErrorIsNil)
   362  		c.Assert(pubKey.Type(), gc.Equals, cryptossh.KeyAlgoRSA)
   363  		return inst, &checkHardware, nil, nil
   364  	}
   365  	var mocksConfig = minimalConfig(c)
   366  	var getConfigCalled int
   367  	getConfig := func() *config.Config {
   368  		getConfigCalled++
   369  		return mocksConfig
   370  	}
   371  	setConfig := func(c *config.Config) error {
   372  		mocksConfig = c
   373  		return nil
   374  	}
   375  
   376  	var instancesMu sync.Mutex
   377  	env := &mockEnviron{
   378  		storage:       stor,
   379  		startInstance: startInstance,
   380  		config:        getConfig,
   381  		setConfig:     setConfig,
   382  		instances: func(ctx context.ProviderCallContext, ids []instance.Id) ([]instances.Instance, error) {
   383  			instancesMu.Lock()
   384  			defer instancesMu.Unlock()
   385  			return []instances.Instance{inst}, nil
   386  		},
   387  	}
   388  	inner := cmdtesting.Context(c)
   389  	ctx := modelcmd.BootstrapContext(inner)
   390  	result, err := common.Bootstrap(ctx, env, s.callCtx, environs.BootstrapParams{
   391  		ControllerConfig: coretesting.FakeControllerConfig(),
   392  		AvailableTools:   fakeAvailableTools(),
   393  	})
   394  	c.Assert(err, jc.ErrorIsNil)
   395  	c.Assert(result.Arch, gc.Equals, "ppc64el") // based on hardware characteristics
   396  	c.Assert(result.Series, gc.Equals, config.PreferredSeries(mocksConfig))
   397  	c.Assert(result.CloudBootstrapFinalizer, gc.NotNil)
   398  
   399  	// Check that we make the SSH connection with desired options.
   400  	var knownHosts string
   401  	re := regexp.MustCompile(
   402  		"ssh '-o' 'StrictHostKeyChecking yes' " +
   403  			"'-o' 'PasswordAuthentication no' " +
   404  			"'-o' 'ServerAliveInterval 30' " +
   405  			"'-o' 'UserKnownHostsFile (.*)' " +
   406  			"'-o' 'HostKeyAlgorithms ssh-rsa' " +
   407  			"'ubuntu@testing.invalid' '/bin/bash'")
   408  	testing.PatchExecutableAsEchoArgs(c, s, "ssh")
   409  	testing.PatchExecutableAsEchoArgs(c, s, "scp")
   410  	s.PatchValue(common.ConnectSSH, func(_ ssh.Client, host, checkHostScript string, opts *ssh.Options) error {
   411  		// Stop WaitSSH from continuing.
   412  		client, err := ssh.NewOpenSSHClient()
   413  		if err != nil {
   414  			return err
   415  		}
   416  		cmd := client.Command("ubuntu@"+host, []string{"/bin/bash"}, opts)
   417  		if err := cmd.Run(); err != nil {
   418  			return err
   419  		}
   420  		sshArgs := testing.ReadEchoArgs(c, "ssh")
   421  		submatch := re.FindStringSubmatch(sshArgs)
   422  		if c.Check(submatch, gc.NotNil, gc.Commentf("%s", sshArgs)) {
   423  			knownHostsFile := submatch[1]
   424  			knownHostsFile = strings.Replace(knownHostsFile, `\"`, ``, -1)
   425  			knownHostsBytes, err := ioutil.ReadFile(knownHostsFile)
   426  			if err != nil {
   427  				return err
   428  			}
   429  			knownHosts = string(knownHostsBytes)
   430  		}
   431  		return nil
   432  	})
   433  	err = result.CloudBootstrapFinalizer(ctx, innerInstanceConfig, environs.BootstrapDialOpts{
   434  		Timeout: coretesting.LongWait,
   435  	})
   436  	c.Assert(err, gc.ErrorMatches, "invalid machine configuration: .*") // icfg hasn't been finalized
   437  	c.Assert(
   438  		string(knownHosts),
   439  		gc.Equals,
   440  		"testing.invalid "+innerInstanceConfig.Bootstrap.InitialSSHHostKeys.RSA.Public,
   441  	)
   442  }
   443  
   444  func (s *BootstrapSuite) TestBootstrapFinalizeCloudInitUserData(c *gc.C) {
   445  	s.PatchValue(&jujuversion.Current, coretesting.FakeVersionNumber)
   446  	s.PatchValue(&series.MustHostSeries, func() string { return "xenial" })
   447  	checkHardware := instance.MustParseHardware("arch=ppc64el mem=2T")
   448  
   449  	var innerInstanceConfig *instancecfg.InstanceConfig
   450  	inst := &mockInstance{
   451  		id:        "i-success",
   452  		addresses: network.NewAddresses("testing.invalid"),
   453  	}
   454  	startInstance := func(ctx context.ProviderCallContext, args environs.StartInstanceParams) (
   455  		instances.Instance,
   456  		*instance.HardwareCharacteristics,
   457  		[]network.InterfaceInfo,
   458  		error,
   459  	) {
   460  		icfg := args.InstanceConfig
   461  		innerInstanceConfig = icfg
   462  		return inst, &checkHardware, nil, nil
   463  	}
   464  
   465  	var instancesMu sync.Mutex
   466  	env := &mockEnviron{
   467  		startInstance: startInstance,
   468  		config:        configGetter(c),
   469  		instances: func(ctx context.ProviderCallContext, ids []instance.Id) ([]instances.Instance, error) {
   470  			instancesMu.Lock()
   471  			defer instancesMu.Unlock()
   472  			return []instances.Instance{inst}, nil
   473  		},
   474  	}
   475  	ctx := envtesting.BootstrapContext(c)
   476  	bootstrapSeries := "utopic"
   477  	availableTools := fakeAvailableTools()
   478  	availableTools[0].Version.Series = bootstrapSeries
   479  	result, err := common.Bootstrap(ctx, env, s.callCtx, environs.BootstrapParams{
   480  		ControllerConfig: coretesting.FakeControllerConfig(),
   481  		BootstrapSeries:  bootstrapSeries,
   482  		AvailableTools:   availableTools,
   483  	})
   484  	c.Assert(err, jc.ErrorIsNil)
   485  
   486  	c.Assert(result.CloudBootstrapFinalizer, gc.NotNil)
   487  	err = result.CloudBootstrapFinalizer(ctx, innerInstanceConfig, environs.BootstrapDialOpts{
   488  		Timeout: coretesting.ShortWait,
   489  	})
   490  	c.Assert(err, gc.ErrorMatches, "waited for 50ms without being able to connect.*")
   491  	c.Assert(innerInstanceConfig.CloudInitUserData, gc.DeepEquals, map[string]interface{}{
   492  		"packages":        []interface{}{"python-keystoneclient", "python-glanceclient"},
   493  		"preruncmd":       []interface{}{"mkdir /tmp/preruncmd", "mkdir /tmp/preruncmd2"},
   494  		"postruncmd":      []interface{}{"mkdir /tmp/postruncmd", "mkdir /tmp/postruncmd2"},
   495  		"package_upgrade": false})
   496  }
   497  
   498  var validCloudInitUserData = `
   499  packages:
   500    - 'python-keystoneclient'
   501    - 'python-glanceclient'
   502  preruncmd:
   503    - mkdir /tmp/preruncmd
   504    - mkdir /tmp/preruncmd2
   505  postruncmd:
   506    - mkdir /tmp/postruncmd
   507    - mkdir /tmp/postruncmd2
   508  package_upgrade: false
   509  `[1:]
   510  
   511  type neverRefreshes struct {
   512  }
   513  
   514  func (neverRefreshes) Refresh(ctx context.ProviderCallContext) error {
   515  	return nil
   516  }
   517  
   518  func (neverRefreshes) Status(ctx context.ProviderCallContext) instance.Status {
   519  	return instance.Status{}
   520  }
   521  
   522  type neverAddresses struct {
   523  	neverRefreshes
   524  }
   525  
   526  func (neverAddresses) Addresses(ctx context.ProviderCallContext) ([]network.Address, error) {
   527  	return nil, nil
   528  }
   529  
   530  type failsProvisioning struct {
   531  	neverAddresses
   532  	message string
   533  }
   534  
   535  func (f failsProvisioning) Status(ctx context.ProviderCallContext) instance.Status {
   536  	return instance.Status{
   537  		Status:  status.ProvisioningError,
   538  		Message: f.message,
   539  	}
   540  }
   541  
   542  var testSSHTimeout = environs.BootstrapDialOpts{
   543  	Timeout:        coretesting.ShortWait,
   544  	RetryDelay:     1 * time.Millisecond,
   545  	AddressesDelay: 1 * time.Millisecond,
   546  }
   547  
   548  func (s *BootstrapSuite) TestWaitSSHTimesOutWaitingForAddresses(c *gc.C) {
   549  	ctx := cmdtesting.Context(c)
   550  	_, err := common.WaitSSH(
   551  		ctx.Stderr, nil, ssh.DefaultClient, "/bin/true", neverAddresses{}, s.callCtx, testSSHTimeout,
   552  		common.DefaultHostSSHOptions,
   553  	)
   554  	c.Check(err, gc.ErrorMatches, `waited for `+testSSHTimeout.Timeout.String()+` without getting any addresses`)
   555  	c.Check(cmdtesting.Stderr(ctx), gc.Matches, "Waiting for address\n")
   556  }
   557  
   558  func (s *BootstrapSuite) TestWaitSSHKilledWaitingForAddresses(c *gc.C) {
   559  	ctx := cmdtesting.Context(c)
   560  	interrupted := make(chan os.Signal, 1)
   561  	interrupted <- os.Interrupt
   562  	_, err := common.WaitSSH(
   563  		ctx.Stderr, interrupted, ssh.DefaultClient, "/bin/true", neverAddresses{}, s.callCtx, testSSHTimeout,
   564  		common.DefaultHostSSHOptions,
   565  	)
   566  	c.Check(err, gc.ErrorMatches, "interrupted")
   567  	c.Check(cmdtesting.Stderr(ctx), gc.Matches, "Waiting for address\n")
   568  }
   569  
   570  func (s *BootstrapSuite) TestWaitSSHNoticesProvisioningFailures(c *gc.C) {
   571  	ctx := cmdtesting.Context(c)
   572  	_, err := common.WaitSSH(
   573  		ctx.Stderr, nil, ssh.DefaultClient, "/bin/true", failsProvisioning{}, s.callCtx, testSSHTimeout,
   574  		common.DefaultHostSSHOptions,
   575  	)
   576  	c.Check(err, gc.ErrorMatches, `instance provisioning failed`)
   577  	_, err = common.WaitSSH(
   578  		ctx.Stderr, nil, ssh.DefaultClient, "/bin/true", failsProvisioning{message: "blargh"}, s.callCtx, testSSHTimeout,
   579  		common.DefaultHostSSHOptions,
   580  	)
   581  	c.Check(err, gc.ErrorMatches, `instance provisioning failed \(blargh\)`)
   582  }
   583  
   584  type brokenAddresses struct {
   585  	neverRefreshes
   586  }
   587  
   588  func (brokenAddresses) Addresses(ctx context.ProviderCallContext) ([]network.Address, error) {
   589  	return nil, errors.Errorf("Addresses will never work")
   590  }
   591  
   592  func (s *BootstrapSuite) TestWaitSSHStopsOnBadError(c *gc.C) {
   593  	ctx := cmdtesting.Context(c)
   594  	_, err := common.WaitSSH(
   595  		ctx.Stderr, nil, ssh.DefaultClient, "/bin/true", brokenAddresses{}, s.callCtx, testSSHTimeout,
   596  		common.DefaultHostSSHOptions,
   597  	)
   598  	c.Check(err, gc.ErrorMatches, "getting addresses: Addresses will never work")
   599  	c.Check(cmdtesting.Stderr(ctx), gc.Equals, "Waiting for address\n")
   600  }
   601  
   602  type neverOpensPort struct {
   603  	neverRefreshes
   604  	addr string
   605  }
   606  
   607  func (n *neverOpensPort) Addresses(ctx context.ProviderCallContext) ([]network.Address, error) {
   608  	return network.NewAddresses(n.addr), nil
   609  }
   610  
   611  func (s *BootstrapSuite) TestWaitSSHTimesOutWaitingForDial(c *gc.C) {
   612  	ctx := cmdtesting.Context(c)
   613  	// 0.x.y.z addresses are always invalid
   614  	_, err := common.WaitSSH(
   615  		ctx.Stderr, nil, ssh.DefaultClient, "/bin/true", &neverOpensPort{addr: "0.1.2.3"}, s.callCtx, testSSHTimeout,
   616  		common.DefaultHostSSHOptions,
   617  	)
   618  	c.Check(err, gc.ErrorMatches,
   619  		`waited for `+testSSHTimeout.Timeout.String()+` without being able to connect: mock connection failure to 0.1.2.3`)
   620  	c.Check(cmdtesting.Stderr(ctx), gc.Matches,
   621  		"Waiting for address\n"+
   622  			"(Attempting to connect to 0.1.2.3:22\n)+")
   623  }
   624  
   625  type interruptOnDial struct {
   626  	neverRefreshes
   627  	name        string
   628  	interrupted chan os.Signal
   629  	returned    bool
   630  }
   631  
   632  func (i *interruptOnDial) Addresses(ctx context.ProviderCallContext) ([]network.Address, error) {
   633  	// kill the tomb the second time Addresses is called
   634  	if !i.returned {
   635  		i.returned = true
   636  	} else {
   637  		i.interrupted <- os.Interrupt
   638  	}
   639  	return network.NewAddresses(i.name), nil
   640  }
   641  
   642  func (s *BootstrapSuite) TestWaitSSHKilledWaitingForDial(c *gc.C) {
   643  	ctx := cmdtesting.Context(c)
   644  	timeout := testSSHTimeout
   645  	timeout.Timeout = 1 * time.Minute
   646  	interrupted := make(chan os.Signal, 1)
   647  	_, err := common.WaitSSH(
   648  		ctx.Stderr, interrupted, ssh.DefaultClient, "", &interruptOnDial{name: "0.1.2.3", interrupted: interrupted}, s.callCtx, timeout,
   649  		common.DefaultHostSSHOptions,
   650  	)
   651  	c.Check(err, gc.ErrorMatches, "interrupted")
   652  	// Exact timing is imprecise but it should have tried a few times before being killed
   653  	c.Check(cmdtesting.Stderr(ctx), gc.Matches,
   654  		"Waiting for address\n"+
   655  			"(Attempting to connect to 0.1.2.3:22\n)+")
   656  }
   657  
   658  type addressesChange struct {
   659  	addrs [][]string
   660  }
   661  
   662  func (ac *addressesChange) Refresh(ctx context.ProviderCallContext) error {
   663  	if len(ac.addrs) > 1 {
   664  		ac.addrs = ac.addrs[1:]
   665  	}
   666  	return nil
   667  }
   668  
   669  func (ac *addressesChange) Status(ctx context.ProviderCallContext) instance.Status {
   670  	return instance.Status{}
   671  }
   672  
   673  func (ac *addressesChange) Addresses(ctx context.ProviderCallContext) ([]network.Address, error) {
   674  	return network.NewAddresses(ac.addrs[0]...), nil
   675  }
   676  
   677  func (s *BootstrapSuite) TestWaitSSHRefreshAddresses(c *gc.C) {
   678  	ctx := cmdtesting.Context(c)
   679  	_, err := common.WaitSSH(ctx.Stderr, nil, ssh.DefaultClient, "", &addressesChange{addrs: [][]string{
   680  		nil,
   681  		nil,
   682  		{"0.1.2.3"},
   683  		{"0.1.2.3"},
   684  		nil,
   685  		{"0.1.2.4"},
   686  	}}, s.callCtx, testSSHTimeout, common.DefaultHostSSHOptions)
   687  	// Not necessarily the last one in the list, due to scheduling.
   688  	c.Check(err, gc.ErrorMatches,
   689  		`waited for `+testSSHTimeout.Timeout.String()+` without being able to connect: mock connection failure to 0.1.2.[34]`)
   690  	stderr := cmdtesting.Stderr(ctx)
   691  	c.Check(stderr, gc.Matches,
   692  		"Waiting for address\n"+
   693  			"(.|\n)*(Attempting to connect to 0.1.2.3:22\n)+(.|\n)*")
   694  	c.Check(stderr, gc.Matches,
   695  		"Waiting for address\n"+
   696  			"(.|\n)*(Attempting to connect to 0.1.2.4:22\n)+(.|\n)*")
   697  }
   698  
   699  type FormatHardwareSuite struct{}
   700  
   701  var _ = gc.Suite(&FormatHardwareSuite{})
   702  
   703  func (s *FormatHardwareSuite) check(c *gc.C, hw *instance.HardwareCharacteristics, expected string) {
   704  	c.Check(common.FormatHardware(hw), gc.Equals, expected)
   705  }
   706  
   707  func (s *FormatHardwareSuite) TestNil(c *gc.C) {
   708  	s.check(c, nil, "")
   709  }
   710  
   711  func (s *FormatHardwareSuite) TestFieldsNil(c *gc.C) {
   712  	s.check(c, &instance.HardwareCharacteristics{}, "")
   713  }
   714  
   715  func (s *FormatHardwareSuite) TestArch(c *gc.C) {
   716  	arch := ""
   717  	s.check(c, &instance.HardwareCharacteristics{Arch: &arch}, "")
   718  	arch = "amd64"
   719  	s.check(c, &instance.HardwareCharacteristics{Arch: &arch}, "arch=amd64")
   720  }
   721  
   722  func (s *FormatHardwareSuite) TestCores(c *gc.C) {
   723  	var cores uint64
   724  	s.check(c, &instance.HardwareCharacteristics{CpuCores: &cores}, "")
   725  	cores = 24
   726  	s.check(c, &instance.HardwareCharacteristics{CpuCores: &cores}, "cores=24")
   727  }
   728  
   729  func (s *FormatHardwareSuite) TestMem(c *gc.C) {
   730  	var mem uint64
   731  	s.check(c, &instance.HardwareCharacteristics{Mem: &mem}, "")
   732  	mem = 800
   733  	s.check(c, &instance.HardwareCharacteristics{Mem: &mem}, "mem=800M")
   734  	mem = 1024
   735  	s.check(c, &instance.HardwareCharacteristics{Mem: &mem}, "mem=1G")
   736  	mem = 2712
   737  	s.check(c, &instance.HardwareCharacteristics{Mem: &mem}, "mem=2.6G")
   738  }
   739  
   740  func (s *FormatHardwareSuite) TestAll(c *gc.C) {
   741  	arch := "ppc64"
   742  	var cores uint64 = 2
   743  	var mem uint64 = 123
   744  	hw := &instance.HardwareCharacteristics{
   745  		Arch:     &arch,
   746  		CpuCores: &cores,
   747  		Mem:      &mem,
   748  	}
   749  	s.check(c, hw, "arch=ppc64 mem=123M cores=2")
   750  }
   751  
   752  func fakeAvailableTools() tools.List {
   753  	return tools.List{
   754  		&tools.Tools{
   755  			Version: version.Binary{
   756  				Number: jujuversion.Current,
   757  				Arch:   arch.HostArch(),
   758  				Series: series.MustHostSeries(),
   759  			},
   760  		},
   761  	}
   762  }