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