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