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 }