github.com/wallyworld/juju@v0.0.0-20161013125918-6cf1bc9d917a/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 "bytes" 8 "fmt" 9 "os" 10 "strings" 11 "time" 12 13 "github.com/juju/errors" 14 jc "github.com/juju/testing/checkers" 15 "github.com/juju/utils/arch" 16 "github.com/juju/utils/series" 17 "github.com/juju/utils/ssh" 18 "github.com/juju/version" 19 gc "gopkg.in/check.v1" 20 21 "github.com/juju/juju/cloudconfig/instancecfg" 22 "github.com/juju/juju/cmd/modelcmd" 23 "github.com/juju/juju/constraints" 24 "github.com/juju/juju/environs" 25 "github.com/juju/juju/environs/config" 26 "github.com/juju/juju/environs/storage" 27 envtesting "github.com/juju/juju/environs/testing" 28 "github.com/juju/juju/instance" 29 "github.com/juju/juju/network" 30 "github.com/juju/juju/provider/common" 31 "github.com/juju/juju/status" 32 coretesting "github.com/juju/juju/testing" 33 "github.com/juju/juju/tools" 34 jujuversion "github.com/juju/juju/version" 35 ) 36 37 type BootstrapSuite struct { 38 coretesting.FakeJujuXDGDataHomeSuite 39 envtesting.ToolsFixture 40 } 41 42 var _ = gc.Suite(&BootstrapSuite{}) 43 44 type cleaner interface { 45 AddCleanup(func(*gc.C)) 46 } 47 48 func (s *BootstrapSuite) SetUpTest(c *gc.C) { 49 s.FakeJujuXDGDataHomeSuite.SetUpTest(c) 50 s.ToolsFixture.SetUpTest(c) 51 s.PatchValue(common.ConnectSSH, func(_ ssh.Client, host, checkHostScript string) error { 52 return fmt.Errorf("mock connection failure to %s", host) 53 }) 54 } 55 56 func (s *BootstrapSuite) TearDownTest(c *gc.C) { 57 s.ToolsFixture.TearDownTest(c) 58 s.FakeJujuXDGDataHomeSuite.TearDownTest(c) 59 } 60 61 func newStorage(suite cleaner, c *gc.C) storage.Storage { 62 closer, stor, _ := envtesting.CreateLocalTestStorage(c) 63 suite.AddCleanup(func(*gc.C) { closer.Close() }) 64 envtesting.UploadFakeTools(c, stor, "released", "released") 65 return stor 66 } 67 68 func minimalConfig(c *gc.C) *config.Config { 69 attrs := map[string]interface{}{ 70 "name": "whatever", 71 "type": "anything, really", 72 "uuid": coretesting.ModelTag.Id(), 73 "controller-uuid": coretesting.ControllerTag.Id(), 74 "ca-cert": coretesting.CACert, 75 "ca-private-key": coretesting.CAKey, 76 "authorized-keys": coretesting.FakeAuthKeys, 77 "default-series": series.HostSeries(), 78 } 79 cfg, err := config.New(config.UseDefaults, attrs) 80 c.Assert(err, jc.ErrorIsNil) 81 return cfg 82 } 83 84 func configGetter(c *gc.C) configFunc { 85 cfg := minimalConfig(c) 86 return func() *config.Config { return cfg } 87 } 88 89 func (s *BootstrapSuite) TestCannotStartInstance(c *gc.C) { 90 s.PatchValue(&jujuversion.Current, coretesting.FakeVersionNumber) 91 checkPlacement := "directive" 92 checkCons := constraints.MustParse("mem=8G") 93 env := &mockEnviron{ 94 storage: newStorage(s, c), 95 config: configGetter(c), 96 } 97 98 startInstance := func( 99 placement string, 100 cons constraints.Value, 101 _ []string, 102 possibleTools tools.List, 103 icfg *instancecfg.InstanceConfig, 104 ) (instance.Instance, *instance.HardwareCharacteristics, []network.InterfaceInfo, error) { 105 c.Assert(placement, gc.DeepEquals, checkPlacement) 106 c.Assert(cons, gc.DeepEquals, checkCons) 107 108 // The machine config should set its upgrade behavior based on 109 // the environment config. 110 expectedMcfg, err := instancecfg.NewBootstrapInstanceConfig(coretesting.FakeControllerConfig(), cons, cons, icfg.Series, "") 111 c.Assert(err, jc.ErrorIsNil) 112 expectedMcfg.EnableOSRefreshUpdate = env.Config().EnableOSRefreshUpdate() 113 expectedMcfg.EnableOSUpgrade = env.Config().EnableOSUpgrade() 114 expectedMcfg.Tags = map[string]string{ 115 "juju-model-uuid": coretesting.ModelTag.Id(), 116 "juju-controller-uuid": coretesting.ControllerTag.Id(), 117 "juju-is-controller": "true", 118 } 119 120 c.Assert(icfg, jc.DeepEquals, expectedMcfg) 121 return nil, nil, nil, errors.Errorf("meh, not started") 122 } 123 124 env.startInstance = startInstance 125 126 ctx := envtesting.BootstrapContext(c) 127 _, err := common.Bootstrap(ctx, env, environs.BootstrapParams{ 128 ControllerConfig: coretesting.FakeControllerConfig(), 129 BootstrapConstraints: checkCons, 130 ModelConstraints: checkCons, 131 Placement: checkPlacement, 132 AvailableTools: tools.List{ 133 &tools.Tools{ 134 Version: version.Binary{ 135 Number: jujuversion.Current, 136 Arch: arch.HostArch(), 137 Series: series.HostSeries(), 138 }, 139 }, 140 }}) 141 c.Assert(err, gc.ErrorMatches, "cannot start bootstrap instance: meh, not started") 142 } 143 144 func (s *BootstrapSuite) TestBootstrapSeries(c *gc.C) { 145 s.PatchValue(&jujuversion.Current, coretesting.FakeVersionNumber) 146 s.PatchValue(&series.HostSeries, func() string { return "precise" }) 147 stor := newStorage(s, c) 148 checkInstanceId := "i-success" 149 checkHardware := instance.MustParseHardware("arch=ppc64el mem=2T") 150 151 startInstance := func(_ string, _ constraints.Value, _ []string, _ tools.List, icfg *instancecfg.InstanceConfig) (instance.Instance, 152 *instance.HardwareCharacteristics, []network.InterfaceInfo, error) { 153 return &mockInstance{id: checkInstanceId}, &checkHardware, nil, nil 154 } 155 var mocksConfig = minimalConfig(c) 156 var numGetConfigCalled int 157 getConfig := func() *config.Config { 158 numGetConfigCalled++ 159 return mocksConfig 160 } 161 setConfig := func(c *config.Config) error { 162 mocksConfig = c 163 return nil 164 } 165 166 env := &mockEnviron{ 167 storage: stor, 168 startInstance: startInstance, 169 config: getConfig, 170 setConfig: setConfig, 171 } 172 ctx := envtesting.BootstrapContext(c) 173 bootstrapSeries := "utopic" 174 result, err := common.Bootstrap(ctx, env, environs.BootstrapParams{ 175 ControllerConfig: coretesting.FakeControllerConfig(), 176 BootstrapSeries: bootstrapSeries, 177 AvailableTools: tools.List{ 178 &tools.Tools{ 179 Version: version.Binary{ 180 Number: jujuversion.Current, 181 Arch: arch.HostArch(), 182 Series: bootstrapSeries, 183 }, 184 }, 185 }}) 186 c.Assert(err, jc.ErrorIsNil) 187 c.Check(result.Arch, gc.Equals, "ppc64el") // based on hardware characteristics 188 c.Check(result.Series, gc.Equals, bootstrapSeries) 189 } 190 191 func (s *BootstrapSuite) TestSuccess(c *gc.C) { 192 s.PatchValue(&jujuversion.Current, coretesting.FakeVersionNumber) 193 stor := newStorage(s, c) 194 checkInstanceId := "i-success" 195 checkHardware := instance.MustParseHardware("arch=ppc64el mem=2T") 196 197 startInstance := func( 198 _ string, _ constraints.Value, _ []string, _ tools.List, icfg *instancecfg.InstanceConfig, 199 ) ( 200 instance.Instance, *instance.HardwareCharacteristics, []network.InterfaceInfo, error, 201 ) { 202 return &mockInstance{id: checkInstanceId}, &checkHardware, nil, nil 203 } 204 var mocksConfig = minimalConfig(c) 205 var getConfigCalled int 206 getConfig := func() *config.Config { 207 getConfigCalled++ 208 return mocksConfig 209 } 210 setConfig := func(c *config.Config) error { 211 mocksConfig = c 212 return nil 213 } 214 215 env := &mockEnviron{ 216 storage: stor, 217 startInstance: startInstance, 218 config: getConfig, 219 setConfig: setConfig, 220 } 221 inner := coretesting.Context(c) 222 ctx := modelcmd.BootstrapContext(inner) 223 result, err := common.Bootstrap(ctx, env, environs.BootstrapParams{ 224 ControllerConfig: coretesting.FakeControllerConfig(), 225 AvailableTools: tools.List{ 226 &tools.Tools{ 227 Version: version.Binary{ 228 Number: jujuversion.Current, 229 Arch: arch.HostArch(), 230 Series: series.HostSeries(), 231 }, 232 }, 233 }}) 234 c.Assert(err, jc.ErrorIsNil) 235 c.Assert(result.Arch, gc.Equals, "ppc64el") // based on hardware characteristics 236 c.Assert(result.Series, gc.Equals, config.PreferredSeries(mocksConfig)) 237 output := inner.Stderr.(*bytes.Buffer) 238 lines := strings.Split(output.String(), "\n") 239 c.Assert(len(lines), jc.GreaterThan, 1) 240 c.Assert(lines[0], gc.Equals, "Some message") 241 } 242 243 type neverRefreshes struct { 244 } 245 246 func (neverRefreshes) Refresh() error { 247 return nil 248 } 249 250 func (neverRefreshes) Status() instance.InstanceStatus { 251 return instance.InstanceStatus{} 252 } 253 254 type neverAddresses struct { 255 neverRefreshes 256 } 257 258 func (neverAddresses) Addresses() ([]network.Address, error) { 259 return nil, nil 260 } 261 262 type failsProvisioning struct { 263 neverAddresses 264 message string 265 } 266 267 func (f failsProvisioning) Status() instance.InstanceStatus { 268 return instance.InstanceStatus{ 269 Status: status.ProvisioningError, 270 Message: f.message, 271 } 272 } 273 274 var testSSHTimeout = environs.BootstrapDialOpts{ 275 Timeout: coretesting.ShortWait, 276 RetryDelay: 1 * time.Millisecond, 277 AddressesDelay: 1 * time.Millisecond, 278 } 279 280 func (s *BootstrapSuite) TestWaitSSHTimesOutWaitingForAddresses(c *gc.C) { 281 ctx := coretesting.Context(c) 282 _, err := common.WaitSSH(ctx.Stderr, nil, ssh.DefaultClient, "/bin/true", neverAddresses{}, testSSHTimeout) 283 c.Check(err, gc.ErrorMatches, `waited for `+testSSHTimeout.Timeout.String()+` without getting any addresses`) 284 c.Check(coretesting.Stderr(ctx), gc.Matches, "Waiting for address\n") 285 } 286 287 func (s *BootstrapSuite) TestWaitSSHKilledWaitingForAddresses(c *gc.C) { 288 ctx := coretesting.Context(c) 289 interrupted := make(chan os.Signal, 1) 290 interrupted <- os.Interrupt 291 _, err := common.WaitSSH(ctx.Stderr, interrupted, ssh.DefaultClient, "/bin/true", neverAddresses{}, testSSHTimeout) 292 c.Check(err, gc.ErrorMatches, "interrupted") 293 c.Check(coretesting.Stderr(ctx), gc.Matches, "Waiting for address\n") 294 } 295 296 func (s *BootstrapSuite) TestWaitSSHNoticesProvisioningFailures(c *gc.C) { 297 ctx := coretesting.Context(c) 298 _, err := common.WaitSSH(ctx.Stderr, nil, ssh.DefaultClient, "/bin/true", failsProvisioning{}, testSSHTimeout) 299 c.Check(err, gc.ErrorMatches, `instance provisioning failed`) 300 _, err = common.WaitSSH(ctx.Stderr, nil, ssh.DefaultClient, "/bin/true", failsProvisioning{message: "blargh"}, testSSHTimeout) 301 c.Check(err, gc.ErrorMatches, `instance provisioning failed \(blargh\)`) 302 } 303 304 type brokenAddresses struct { 305 neverRefreshes 306 } 307 308 func (brokenAddresses) Addresses() ([]network.Address, error) { 309 return nil, errors.Errorf("Addresses will never work") 310 } 311 312 func (s *BootstrapSuite) TestWaitSSHStopsOnBadError(c *gc.C) { 313 ctx := coretesting.Context(c) 314 _, err := common.WaitSSH(ctx.Stderr, nil, ssh.DefaultClient, "/bin/true", brokenAddresses{}, testSSHTimeout) 315 c.Check(err, gc.ErrorMatches, "getting addresses: Addresses will never work") 316 c.Check(coretesting.Stderr(ctx), gc.Equals, "Waiting for address\n") 317 } 318 319 type neverOpensPort struct { 320 neverRefreshes 321 addr string 322 } 323 324 func (n *neverOpensPort) Addresses() ([]network.Address, error) { 325 return network.NewAddresses(n.addr), nil 326 } 327 328 func (s *BootstrapSuite) TestWaitSSHTimesOutWaitingForDial(c *gc.C) { 329 ctx := coretesting.Context(c) 330 // 0.x.y.z addresses are always invalid 331 _, err := common.WaitSSH(ctx.Stderr, nil, ssh.DefaultClient, "/bin/true", &neverOpensPort{addr: "0.1.2.3"}, testSSHTimeout) 332 c.Check(err, gc.ErrorMatches, 333 `waited for `+testSSHTimeout.Timeout.String()+` without being able to connect: mock connection failure to 0.1.2.3`) 334 c.Check(coretesting.Stderr(ctx), gc.Matches, 335 "Waiting for address\n"+ 336 "(Attempting to connect to 0.1.2.3:22\n)+") 337 } 338 339 type interruptOnDial struct { 340 neverRefreshes 341 name string 342 interrupted chan os.Signal 343 returned bool 344 } 345 346 func (i *interruptOnDial) Addresses() ([]network.Address, error) { 347 // kill the tomb the second time Addresses is called 348 if !i.returned { 349 i.returned = true 350 } else { 351 i.interrupted <- os.Interrupt 352 } 353 return network.NewAddresses(i.name), nil 354 } 355 356 func (s *BootstrapSuite) TestWaitSSHKilledWaitingForDial(c *gc.C) { 357 ctx := coretesting.Context(c) 358 timeout := testSSHTimeout 359 timeout.Timeout = 1 * time.Minute 360 interrupted := make(chan os.Signal, 1) 361 _, err := common.WaitSSH(ctx.Stderr, interrupted, ssh.DefaultClient, "", &interruptOnDial{name: "0.1.2.3", interrupted: interrupted}, timeout) 362 c.Check(err, gc.ErrorMatches, "interrupted") 363 // Exact timing is imprecise but it should have tried a few times before being killed 364 c.Check(coretesting.Stderr(ctx), gc.Matches, 365 "Waiting for address\n"+ 366 "(Attempting to connect to 0.1.2.3:22\n)+") 367 } 368 369 type addressesChange struct { 370 addrs [][]string 371 } 372 373 func (ac *addressesChange) Refresh() error { 374 if len(ac.addrs) > 1 { 375 ac.addrs = ac.addrs[1:] 376 } 377 return nil 378 } 379 380 func (ac *addressesChange) Status() instance.InstanceStatus { 381 return instance.InstanceStatus{} 382 } 383 384 func (ac *addressesChange) Addresses() ([]network.Address, error) { 385 return network.NewAddresses(ac.addrs[0]...), nil 386 } 387 388 func (s *BootstrapSuite) TestWaitSSHRefreshAddresses(c *gc.C) { 389 ctx := coretesting.Context(c) 390 _, err := common.WaitSSH(ctx.Stderr, nil, ssh.DefaultClient, "", &addressesChange{addrs: [][]string{ 391 nil, 392 nil, 393 {"0.1.2.3"}, 394 {"0.1.2.3"}, 395 nil, 396 {"0.1.2.4"}, 397 }}, testSSHTimeout) 398 // Not necessarily the last one in the list, due to scheduling. 399 c.Check(err, gc.ErrorMatches, 400 `waited for `+testSSHTimeout.Timeout.String()+` without being able to connect: mock connection failure to 0.1.2.[34]`) 401 stderr := coretesting.Stderr(ctx) 402 c.Check(stderr, gc.Matches, 403 "Waiting for address\n"+ 404 "(.|\n)*(Attempting to connect to 0.1.2.3:22\n)+(.|\n)*") 405 c.Check(stderr, gc.Matches, 406 "Waiting for address\n"+ 407 "(.|\n)*(Attempting to connect to 0.1.2.4:22\n)+(.|\n)*") 408 } 409 410 type FormatHardwareSuite struct{} 411 412 var _ = gc.Suite(&FormatHardwareSuite{}) 413 414 func (s *FormatHardwareSuite) check(c *gc.C, hw *instance.HardwareCharacteristics, expected string) { 415 c.Check(common.FormatHardware(hw), gc.Equals, expected) 416 } 417 418 func (s *FormatHardwareSuite) TestNil(c *gc.C) { 419 s.check(c, nil, "") 420 } 421 422 func (s *FormatHardwareSuite) TestFieldsNil(c *gc.C) { 423 s.check(c, &instance.HardwareCharacteristics{}, "") 424 } 425 426 func (s *FormatHardwareSuite) TestArch(c *gc.C) { 427 arch := "" 428 s.check(c, &instance.HardwareCharacteristics{Arch: &arch}, "") 429 arch = "amd64" 430 s.check(c, &instance.HardwareCharacteristics{Arch: &arch}, "arch=amd64") 431 } 432 433 func (s *FormatHardwareSuite) TestCores(c *gc.C) { 434 var cores uint64 435 s.check(c, &instance.HardwareCharacteristics{CpuCores: &cores}, "") 436 cores = 24 437 s.check(c, &instance.HardwareCharacteristics{CpuCores: &cores}, "cores=24") 438 } 439 440 func (s *FormatHardwareSuite) TestMem(c *gc.C) { 441 var mem uint64 442 s.check(c, &instance.HardwareCharacteristics{Mem: &mem}, "") 443 mem = 800 444 s.check(c, &instance.HardwareCharacteristics{Mem: &mem}, "mem=800M") 445 mem = 1024 446 s.check(c, &instance.HardwareCharacteristics{Mem: &mem}, "mem=1G") 447 mem = 2712 448 s.check(c, &instance.HardwareCharacteristics{Mem: &mem}, "mem=2.6G") 449 } 450 451 func (s *FormatHardwareSuite) TestAll(c *gc.C) { 452 arch := "ppc64" 453 var cores uint64 = 2 454 var mem uint64 = 123 455 hw := &instance.HardwareCharacteristics{ 456 Arch: &arch, 457 CpuCores: &cores, 458 Mem: &mem, 459 } 460 s.check(c, hw, "arch=ppc64 mem=123M cores=2") 461 }