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