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