github.com/axw/juju@v0.0.0-20161005053422-4bd6544d08d4/cmd/juju/controller/kill_test.go (about) 1 // Copyright 2015 Canonical Ltd. 2 // Licensed under the AGPLv3, see LICENCE file for details. 3 4 package controller_test 5 6 import ( 7 "bytes" 8 "time" 9 10 "github.com/juju/cmd" 11 "github.com/juju/errors" 12 "github.com/juju/testing" 13 jc "github.com/juju/testing/checkers" 14 "github.com/juju/utils/clock" 15 gc "gopkg.in/check.v1" 16 "gopkg.in/juju/names.v2" 17 18 "github.com/juju/juju/api" 19 "github.com/juju/juju/api/base" 20 "github.com/juju/juju/apiserver/common" 21 "github.com/juju/juju/apiserver/params" 22 "github.com/juju/juju/cmd/juju/controller" 23 "github.com/juju/juju/cmd/modelcmd" 24 cmdtesting "github.com/juju/juju/cmd/testing" 25 "github.com/juju/juju/jujuclient" 26 _ "github.com/juju/juju/provider/dummy" 27 coretesting "github.com/juju/juju/testing" 28 ) 29 30 type KillSuite struct { 31 baseDestroySuite 32 33 clock *testing.Clock 34 } 35 36 var _ = gc.Suite(&KillSuite{}) 37 38 func (s *KillSuite) SetUpTest(c *gc.C) { 39 s.baseDestroySuite.SetUpTest(c) 40 s.clock = testing.NewClock(time.Now()) 41 } 42 43 func (s *KillSuite) runKillCommand(c *gc.C, args ...string) (*cmd.Context, error) { 44 return coretesting.RunCommand(c, s.newKillCommand(), args...) 45 } 46 47 func (s *KillSuite) newKillCommand() cmd.Command { 48 wrapped, _ := controller.NewKillCommandForTest( 49 s.api, s.clientapi, s.store, s.apierror, s.clock, nil) 50 return wrapped 51 } 52 53 func (s *KillSuite) newKillCommandBoth() (cmd.Command, cmd.Command) { 54 clock := s.clock 55 if clock == nil { 56 clock = testing.NewClock(time.Now()) 57 } 58 return controller.NewKillCommandForTest( 59 s.api, s.clientapi, s.store, s.apierror, clock, nil) 60 } 61 62 func (s *KillSuite) TestKillNoControllerNameError(c *gc.C) { 63 _, err := s.runKillCommand(c) 64 c.Assert(err, gc.ErrorMatches, "no controller specified") 65 } 66 67 func (s *KillSuite) TestKillBadFlags(c *gc.C) { 68 _, err := s.runKillCommand(c, "-n") 69 c.Assert(err, gc.ErrorMatches, "flag provided but not defined: -n") 70 } 71 72 func (s *KillSuite) TestKillDurationFlags(c *gc.C) { 73 for i, test := range []struct { 74 args []string 75 expected time.Duration 76 err string 77 }{ 78 { 79 expected: 5 * time.Minute, 80 }, { 81 args: []string{"-t", "2m"}, 82 expected: 2 * time.Minute, 83 }, { 84 args: []string{"--timeout", "2m"}, 85 expected: 2 * time.Minute, 86 }, { 87 args: []string{"-t", "0"}, 88 expected: 0, 89 }, 90 } { 91 c.Logf("duration test %d", i) 92 wrapped, inner := s.newKillCommandBoth() 93 args := append([]string{"test1"}, test.args...) 94 err := coretesting.InitCommand(wrapped, args) 95 if test.err == "" { 96 c.Check(err, jc.ErrorIsNil) 97 c.Check(controller.KillTimeout(c, inner), gc.Equals, test.expected) 98 } else { 99 c.Check(err, gc.ErrorMatches, test.err) 100 } 101 } 102 } 103 104 func (s *KillSuite) TestKillWaitForModels_AllGood(c *gc.C) { 105 s.resetAPIModels(c) 106 wrapped, inner := s.newKillCommandBoth() 107 err := coretesting.InitCommand(wrapped, []string{"test1", "--timeout=1m"}) 108 c.Assert(err, jc.ErrorIsNil) 109 110 ctx := coretesting.Context(c) 111 err = controller.KillWaitForModels(inner, ctx, s.api, test1UUID) 112 c.Assert(err, jc.ErrorIsNil) 113 c.Assert(coretesting.Stderr(ctx), gc.Equals, "All hosted models reclaimed, cleaning up controller machines\n") 114 } 115 116 func (s *KillSuite) TestKillWaitForModels_ActuallyWaits(c *gc.C) { 117 s.resetAPIModels(c) 118 s.addModel("model-1", base.ModelStatus{ 119 UUID: test2UUID, 120 Life: string(params.Dying), 121 Owner: "admin", 122 HostedMachineCount: 2, 123 ServiceCount: 2, 124 }) 125 wrapped, inner := s.newKillCommandBoth() 126 err := coretesting.InitCommand(wrapped, []string{"test1", "--timeout=1m"}) 127 c.Assert(err, jc.ErrorIsNil) 128 129 ctx := coretesting.Context(c) 130 result := make(chan error) 131 go func() { 132 err := controller.KillWaitForModels(inner, ctx, s.api, test1UUID) 133 result <- err 134 }() 135 136 s.syncClockAlarm(c) 137 s.setModelStatus(base.ModelStatus{ 138 UUID: test2UUID, 139 Life: string(params.Dying), 140 Owner: "admin", 141 HostedMachineCount: 1, 142 }) 143 s.clock.Advance(5 * time.Second) 144 145 s.syncClockAlarm(c) 146 s.removeModel(test2UUID) 147 s.clock.Advance(5 * time.Second) 148 149 select { 150 case err := <-result: 151 c.Assert(err, jc.ErrorIsNil) 152 case <-time.After(coretesting.LongWait): 153 c.Fatal("timed out waiting for result") 154 } 155 expect := "" + 156 "Waiting on 1 model, 2 machines, 2 applications\n" + 157 "Waiting on 1 model, 1 machine\n" + 158 "All hosted models reclaimed, cleaning up controller machines\n" 159 160 c.Assert(coretesting.Stderr(ctx), gc.Equals, expect) 161 } 162 163 func (s *KillSuite) TestKillWaitForModels_TimeoutResetsWithChange(c *gc.C) { 164 s.resetAPIModels(c) 165 s.addModel("model-1", base.ModelStatus{ 166 UUID: test2UUID, 167 Life: string(params.Dying), 168 Owner: "admin", 169 HostedMachineCount: 2, 170 ServiceCount: 2, 171 }) 172 wrapped, inner := s.newKillCommandBoth() 173 err := coretesting.InitCommand(wrapped, []string{"test1", "--timeout=20s"}) 174 c.Assert(err, jc.ErrorIsNil) 175 176 ctx := coretesting.Context(c) 177 result := make(chan error) 178 go func() { 179 err := controller.KillWaitForModels(inner, ctx, s.api, test1UUID) 180 result <- err 181 }() 182 183 s.syncClockAlarm(c) 184 s.clock.Advance(5 * time.Second) 185 186 s.syncClockAlarm(c) 187 s.setModelStatus(base.ModelStatus{ 188 UUID: test2UUID, 189 Life: string(params.Dying), 190 Owner: "admin", 191 HostedMachineCount: 1, 192 }) 193 s.clock.Advance(5 * time.Second) 194 195 s.syncClockAlarm(c) 196 s.removeModel(test2UUID) 197 s.clock.Advance(5 * time.Second) 198 199 select { 200 case err := <-result: 201 c.Assert(err, jc.ErrorIsNil) 202 case <-time.After(coretesting.LongWait): 203 c.Fatal("timed out waiting for result") 204 } 205 expect := "" + 206 "Waiting on 1 model, 2 machines, 2 applications, will kill machines directly in 20s\n" + 207 "Waiting on 1 model, 2 machines, 2 applications, will kill machines directly in 15s\n" + 208 "Waiting on 1 model, 1 machine, will kill machines directly in 20s\n" + 209 "All hosted models reclaimed, cleaning up controller machines\n" 210 211 c.Assert(coretesting.Stderr(ctx), gc.Equals, expect) 212 } 213 214 func (s *KillSuite) TestKillWaitForModels_TimeoutWithNoChange(c *gc.C) { 215 s.resetAPIModels(c) 216 s.addModel("model-1", base.ModelStatus{ 217 UUID: test2UUID, 218 Life: string(params.Dying), 219 Owner: "admin", 220 HostedMachineCount: 2, 221 ServiceCount: 2, 222 }) 223 wrapped, inner := s.newKillCommandBoth() 224 err := coretesting.InitCommand(wrapped, []string{"test1", "--timeout=1m"}) 225 c.Assert(err, jc.ErrorIsNil) 226 227 ctx := coretesting.Context(c) 228 result := make(chan error) 229 go func() { 230 err := controller.KillWaitForModels(inner, ctx, s.api, test1UUID) 231 result <- err 232 }() 233 234 for i := 0; i < 12; i++ { 235 s.syncClockAlarm(c) 236 s.clock.Advance(5 * time.Second) 237 } 238 239 select { 240 case err := <-result: 241 c.Assert(err, gc.ErrorMatches, "timed out") 242 case <-time.After(coretesting.LongWait): 243 c.Fatal("timed out waiting for result") 244 } 245 expect := "" + 246 "Waiting on 1 model, 2 machines, 2 applications\n" + 247 "Waiting on 1 model, 2 machines, 2 applications\n" + 248 "Waiting on 1 model, 2 machines, 2 applications\n" + 249 "Waiting on 1 model, 2 machines, 2 applications\n" + 250 "Waiting on 1 model, 2 machines, 2 applications\n" + 251 "Waiting on 1 model, 2 machines, 2 applications\n" + 252 "Waiting on 1 model, 2 machines, 2 applications\n" + 253 "Waiting on 1 model, 2 machines, 2 applications, will kill machines directly in 25s\n" + 254 "Waiting on 1 model, 2 machines, 2 applications, will kill machines directly in 20s\n" + 255 "Waiting on 1 model, 2 machines, 2 applications, will kill machines directly in 15s\n" + 256 "Waiting on 1 model, 2 machines, 2 applications, will kill machines directly in 10s\n" + 257 "Waiting on 1 model, 2 machines, 2 applications, will kill machines directly in 5s\n" 258 259 c.Assert(coretesting.Stderr(ctx), gc.Equals, expect) 260 } 261 262 func (s *KillSuite) resetAPIModels(c *gc.C) { 263 s.api.allModels = nil 264 s.api.envStatus = map[string]base.ModelStatus{} 265 s.addModel("controller", base.ModelStatus{ 266 UUID: test1UUID, 267 Life: string(params.Alive), 268 Owner: "admin", 269 TotalMachineCount: 1, 270 }) 271 } 272 273 func (s *KillSuite) addModel(name string, status base.ModelStatus) { 274 s.api.allModels = append(s.api.allModels, base.UserModel{ 275 Name: name, 276 UUID: status.UUID, 277 Owner: status.Owner, 278 }) 279 s.api.envStatus[status.UUID] = status 280 } 281 282 func (s *KillSuite) setModelStatus(status base.ModelStatus) { 283 s.api.envStatus[status.UUID] = status 284 } 285 286 func (s *KillSuite) removeModel(uuid string) { 287 for i, v := range s.api.allModels { 288 if v.UUID == uuid { 289 s.api.allModels = append(s.api.allModels[:i], s.api.allModels[i+1:]...) 290 break 291 } 292 } 293 delete(s.api.envStatus, uuid) 294 } 295 296 func (s *KillSuite) syncClockAlarm(c *gc.C) { 297 select { 298 case <-s.clock.Alarms(): 299 case <-time.After(coretesting.LongWait): 300 c.Fatal("timed out waiting for test clock After call") 301 } 302 } 303 304 func (s *KillSuite) TestKillUnknownArgument(c *gc.C) { 305 _, err := s.runKillCommand(c, "model", "whoops") 306 c.Assert(err, gc.ErrorMatches, `unrecognized args: \["whoops"\]`) 307 } 308 309 func (s *KillSuite) TestKillUnknownController(c *gc.C) { 310 _, err := s.runKillCommand(c, "foo") 311 c.Assert(err, gc.ErrorMatches, `controller foo not found`) 312 } 313 314 func (s *KillSuite) TestKillCannotConnectToAPISucceeds(c *gc.C) { 315 s.apierror = errors.New("connection refused") 316 ctx, err := s.runKillCommand(c, "test1", "-y") 317 c.Assert(err, jc.ErrorIsNil) 318 c.Check(coretesting.Stderr(ctx), jc.Contains, "Unable to open API: connection refused") 319 checkControllerRemovedFromStore(c, "test1", s.store) 320 } 321 322 func (s *KillSuite) TestKillWithAPIConnection(c *gc.C) { 323 _, err := s.runKillCommand(c, "test1", "-y") 324 c.Assert(err, jc.ErrorIsNil) 325 c.Assert(s.api.destroyAll, jc.IsTrue) 326 c.Assert(s.clientapi.destroycalled, jc.IsFalse) 327 checkControllerRemovedFromStore(c, "test1", s.store) 328 } 329 330 func (s *KillSuite) TestKillEnvironmentGetFailsWithoutAPIConnection(c *gc.C) { 331 s.apierror = errors.New("connection refused") 332 s.api.SetErrors(errors.NotFoundf(`controller "test3"`)) 333 _, err := s.runKillCommand(c, "test3", "-y") 334 c.Assert(err, gc.ErrorMatches, 335 "getting controller environ: unable to get bootstrap information from client store or API", 336 ) 337 checkControllerExistsInStore(c, "test3", s.store) 338 } 339 340 func (s *KillSuite) TestKillEnvironmentGetFailsWithAPIConnection(c *gc.C) { 341 s.api.SetErrors(errors.NotFoundf(`controller "test3"`)) 342 _, err := s.runKillCommand(c, "test3", "-y") 343 c.Assert(err, gc.ErrorMatches, 344 "getting controller environ: getting model config from API: controller \"test3\" not found", 345 ) 346 checkControllerExistsInStore(c, "test3", s.store) 347 } 348 349 func (s *KillSuite) TestKillDestroysControllerWithAPIError(c *gc.C) { 350 s.api.SetErrors(errors.New("some destroy error")) 351 ctx, err := s.runKillCommand(c, "test1", "-y") 352 c.Assert(err, jc.ErrorIsNil) 353 c.Check(coretesting.Stderr(ctx), jc.Contains, "Unable to destroy controller through the API: some destroy error\nDestroying through provider") 354 c.Assert(s.api.destroyAll, jc.IsTrue) 355 checkControllerRemovedFromStore(c, "test1", s.store) 356 } 357 358 func (s *KillSuite) TestKillCommandConfirmation(c *gc.C) { 359 var stdin, stdout bytes.Buffer 360 ctx, err := cmd.DefaultContext() 361 c.Assert(err, jc.ErrorIsNil) 362 ctx.Stdout = &stdout 363 ctx.Stdin = &stdin 364 365 // Ensure confirmation is requested if "-y" is not specified. 366 stdin.WriteString("n") 367 _, errc := cmdtesting.RunCommand(ctx, s.newKillCommand(), "test1") 368 select { 369 case err := <-errc: 370 c.Check(err, gc.ErrorMatches, "controller destruction aborted") 371 case <-time.After(coretesting.LongWait): 372 c.Fatalf("command took too long") 373 } 374 c.Check(coretesting.Stdout(ctx), gc.Matches, "WARNING!.*test1(.|\n)*") 375 checkControllerExistsInStore(c, "test1", s.store) 376 } 377 378 func (s *KillSuite) TestKillCommandControllerAlias(c *gc.C) { 379 _, err := coretesting.RunCommand(c, s.newKillCommand(), "test1", "-y") 380 c.Assert(err, jc.ErrorIsNil) 381 checkControllerRemovedFromStore(c, "test1:test1", s.store) 382 } 383 384 func (s *KillSuite) TestKillAPIPermErrFails(c *gc.C) { 385 testDialer := func(_ jujuclient.ClientStore, controllerName, modelName string) (api.Connection, error) { 386 return nil, common.ErrPerm 387 } 388 cmd, _ := controller.NewKillCommandForTest(nil, nil, s.store, nil, clock.WallClock, modelcmd.OpenFunc(testDialer)) 389 _, err := coretesting.RunCommand(c, cmd, "test1", "-y") 390 c.Assert(err, gc.ErrorMatches, "cannot destroy controller: permission denied") 391 checkControllerExistsInStore(c, "test1", s.store) 392 } 393 394 func (s *KillSuite) TestKillEarlyAPIConnectionTimeout(c *gc.C) { 395 clock := &mockClock{} 396 397 stop := make(chan struct{}) 398 defer close(stop) 399 testDialer := func(_ jujuclient.ClientStore, controllerName, modelName string) (api.Connection, error) { 400 <-stop 401 return nil, errors.New("kill command waited too long") 402 } 403 404 cmd, _ := controller.NewKillCommandForTest(nil, nil, s.store, nil, clock, modelcmd.OpenFunc(testDialer)) 405 ctx, err := coretesting.RunCommand(c, cmd, "test1", "-y") 406 c.Check(err, jc.ErrorIsNil) 407 c.Check(coretesting.Stderr(ctx), jc.Contains, "Unable to open API: open connection timed out") 408 checkControllerRemovedFromStore(c, "test1", s.store) 409 // Check that we were actually told to wait for 10s. 410 c.Assert(clock.wait, gc.Equals, 10*time.Second) 411 } 412 413 // mockClock will panic if anything but After is called 414 type mockClock struct { 415 clock.Clock 416 wait time.Duration 417 } 418 419 func (m *mockClock) After(duration time.Duration) <-chan time.Time { 420 m.wait = duration 421 return time.After(time.Millisecond) 422 } 423 424 func (s *KillSuite) TestControllerStatus(c *gc.C) { 425 s.api.allModels = []base.UserModel{ 426 {Name: "admin", 427 UUID: "123", 428 Owner: names.NewUserTag("admin").String(), 429 }, {Name: "env1", 430 UUID: "456", 431 Owner: names.NewUserTag("bob").String(), 432 }, {Name: "env2", 433 UUID: "789", 434 Owner: names.NewUserTag("jo").String(), 435 }, 436 } 437 438 s.api.envStatus = make(map[string]base.ModelStatus) 439 for _, env := range s.api.allModels { 440 owner, err := names.ParseUserTag(env.Owner) 441 c.Assert(err, jc.ErrorIsNil) 442 s.api.envStatus[env.UUID] = base.ModelStatus{ 443 UUID: env.UUID, 444 Life: string(params.Dying), 445 HostedMachineCount: 2, 446 ServiceCount: 1, 447 Owner: owner.Canonical(), 448 } 449 } 450 451 ctrStatus, envsStatus, err := controller.NewData(s.api, "123") 452 c.Assert(err, jc.ErrorIsNil) 453 c.Assert(ctrStatus.HostedModelCount, gc.Equals, 2) 454 c.Assert(ctrStatus.HostedMachineCount, gc.Equals, 6) 455 c.Assert(ctrStatus.ServiceCount, gc.Equals, 3) 456 c.Assert(envsStatus, gc.HasLen, 2) 457 458 for i, expected := range []struct { 459 Owner string 460 Name string 461 Life params.Life 462 HostedMachineCount int 463 ServiceCount int 464 }{ 465 { 466 Owner: "bob@local", 467 Name: "env1", 468 Life: params.Dying, 469 HostedMachineCount: 2, 470 ServiceCount: 1, 471 }, { 472 Owner: "jo@local", 473 Name: "env2", 474 Life: params.Dying, 475 HostedMachineCount: 2, 476 ServiceCount: 1, 477 }, 478 } { 479 c.Assert(envsStatus[i].Owner, gc.Equals, expected.Owner) 480 c.Assert(envsStatus[i].Name, gc.Equals, expected.Name) 481 c.Assert(envsStatus[i].Life, gc.Equals, string(expected.Life)) 482 c.Assert(envsStatus[i].HostedMachineCount, gc.Equals, expected.HostedMachineCount) 483 c.Assert(envsStatus[i].ServiceCount, gc.Equals, expected.ServiceCount) 484 } 485 486 } 487 488 func (s *KillSuite) TestFmtControllerStatus(c *gc.C) { 489 data := controller.CtrData{ 490 "uuid", 491 string(params.Alive), 492 3, 493 20, 494 8, 495 } 496 out := controller.FmtCtrStatus(data) 497 c.Assert(out, gc.Equals, "Waiting on 3 models, 20 machines, 8 applications") 498 } 499 500 func (s *KillSuite) TestFmtEnvironStatus(c *gc.C) { 501 data := controller.ModelData{ 502 "uuid", 503 "owner@local", 504 "envname", 505 string(params.Dying), 506 8, 507 1, 508 } 509 510 out := controller.FmtModelStatus(data) 511 c.Assert(out, gc.Equals, "\towner@local/envname (dying), 8 machines, 1 application") 512 }