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