github.com/axw/juju@v0.0.0-20161005053422-4bd6544d08d4/cmd/juju/status/status_test.go (about) 1 // Copyright 2015 Canonical Ltd. 2 // Licensed under the AGPLv3, see LICENCE file for details. 3 4 package status 5 6 import ( 7 "bufio" 8 "bytes" 9 "encoding/json" 10 "fmt" 11 "os" 12 "regexp" 13 "strings" 14 "time" 15 16 "github.com/juju/cmd" 17 jc "github.com/juju/testing/checkers" 18 "github.com/juju/utils" 19 "github.com/juju/version" 20 gc "gopkg.in/check.v1" 21 "gopkg.in/juju/charm.v6-unstable" 22 "gopkg.in/juju/names.v2" 23 goyaml "gopkg.in/yaml.v2" 24 25 "github.com/juju/juju/apiserver/params" 26 "github.com/juju/juju/cmd/modelcmd" 27 "github.com/juju/juju/constraints" 28 "github.com/juju/juju/core/migration" 29 "github.com/juju/juju/environs" 30 "github.com/juju/juju/instance" 31 "github.com/juju/juju/juju/osenv" 32 "github.com/juju/juju/juju/testing" 33 "github.com/juju/juju/network" 34 "github.com/juju/juju/state" 35 "github.com/juju/juju/state/multiwatcher" 36 "github.com/juju/juju/state/presence" 37 "github.com/juju/juju/status" 38 "github.com/juju/juju/testcharms" 39 coretesting "github.com/juju/juju/testing" 40 "github.com/juju/juju/testing/factory" 41 coreversion "github.com/juju/juju/version" 42 ) 43 44 var ( 45 currentVersion = version.Number{Major: 1, Minor: 2, Patch: 3} 46 nextVersion = version.Number{Major: 1, Minor: 2, Patch: 4} 47 ) 48 49 func runStatus(c *gc.C, args ...string) (code int, stdout, stderr []byte) { 50 ctx := coretesting.Context(c) 51 code = cmd.Main(NewStatusCommand(), ctx, args) 52 stdout = ctx.Stdout.(*bytes.Buffer).Bytes() 53 stderr = ctx.Stderr.(*bytes.Buffer).Bytes() 54 return 55 } 56 57 type StatusSuite struct { 58 testing.JujuConnSuite 59 } 60 61 var _ = gc.Suite(&StatusSuite{}) 62 63 func (s *StatusSuite) SetUpSuite(c *gc.C) { 64 s.JujuConnSuite.SetUpSuite(c) 65 s.PatchValue(&coreversion.Current, currentVersion) 66 } 67 68 func (s *StatusSuite) SetUpTest(c *gc.C) { 69 s.ConfigAttrs = map[string]interface{}{ 70 "agent-version": currentVersion.String(), 71 } 72 s.JujuConnSuite.SetUpTest(c) 73 } 74 75 type M map[string]interface{} 76 77 type L []interface{} 78 79 type testCase struct { 80 summary string 81 steps []stepper 82 } 83 84 func test(summary string, steps ...stepper) testCase { 85 return testCase{summary, steps} 86 } 87 88 type stepper interface { 89 step(c *gc.C, ctx *context) 90 } 91 92 // 93 // context 94 // 95 96 func newContext(st *state.State, env environs.Environ, adminUserTag string) *context { 97 // We make changes in the API server's state so that 98 // our changes to presence are immediately noticed 99 // in the status. 100 return &context{ 101 st: st, 102 env: env, 103 charms: make(map[string]*state.Charm), 104 pingers: make(map[string]*presence.Pinger), 105 adminUserTag: adminUserTag, 106 } 107 } 108 109 type context struct { 110 st *state.State 111 env environs.Environ 112 charms map[string]*state.Charm 113 pingers map[string]*presence.Pinger 114 adminUserTag string // A string repr of the tag. 115 expectIsoTime bool 116 } 117 118 func (ctx *context) reset(c *gc.C) { 119 for _, up := range ctx.pingers { 120 err := up.KillForTesting() 121 c.Check(err, jc.ErrorIsNil) 122 } 123 } 124 125 func (ctx *context) run(c *gc.C, steps []stepper) { 126 for i, s := range steps { 127 c.Logf("step %d", i) 128 c.Logf("%#v", s) 129 s.step(c, ctx) 130 } 131 } 132 133 func (ctx *context) setAgentPresence(c *gc.C, p presence.Agent) *presence.Pinger { 134 pinger, err := p.SetAgentPresence() 135 c.Assert(err, jc.ErrorIsNil) 136 ctx.st.StartSync() 137 err = p.WaitAgentPresence(coretesting.LongWait) 138 c.Assert(err, jc.ErrorIsNil) 139 agentPresence, err := p.AgentPresence() 140 c.Assert(err, jc.ErrorIsNil) 141 c.Assert(agentPresence, jc.IsTrue) 142 return pinger 143 } 144 145 func (s *StatusSuite) newContext(c *gc.C) *context { 146 st := s.Environ.(testing.GetStater).GetStateInAPIServer() 147 148 // We make changes in the API server's state so that 149 // our changes to presence are immediately noticed 150 // in the status. 151 return newContext(st, s.Environ, s.AdminUserTag(c).String()) 152 } 153 154 func (s *StatusSuite) resetContext(c *gc.C, ctx *context) { 155 ctx.reset(c) 156 s.JujuConnSuite.Reset(c) 157 } 158 159 // shortcuts for expected output. 160 var ( 161 model = M{ 162 "name": "controller", 163 "controller": "kontroll", 164 "cloud": "dummy", 165 "region": "dummy-region", 166 "version": "1.2.3", 167 } 168 169 machine0 = M{ 170 "juju-status": M{ 171 "current": "started", 172 "since": "01 Apr 15 01:23+10:00", 173 }, 174 "dns-name": "controller-0.dns", 175 "instance-id": "controller-0", 176 "machine-status": M{ 177 "current": "pending", 178 "since": "01 Apr 15 01:23+10:00", 179 }, 180 "series": "quantal", 181 "hardware": "arch=amd64 cores=1 mem=1024M root-disk=8192M", 182 "controller-member-status": "adding-vote", 183 } 184 machine1 = M{ 185 "juju-status": M{ 186 "current": "started", 187 "since": "01 Apr 15 01:23+10:00", 188 }, 189 "dns-name": "controller-1.dns", 190 "instance-id": "controller-1", 191 "machine-status": M{ 192 "current": "pending", 193 "since": "01 Apr 15 01:23+10:00", 194 }, 195 "series": "quantal", 196 "hardware": "arch=amd64 cores=1 mem=1024M root-disk=8192M", 197 } 198 machine2 = M{ 199 "juju-status": M{ 200 "current": "started", 201 "since": "01 Apr 15 01:23+10:00", 202 }, 203 "dns-name": "controller-2.dns", 204 "instance-id": "controller-2", 205 "machine-status": M{ 206 "current": "pending", 207 "since": "01 Apr 15 01:23+10:00", 208 }, 209 "series": "quantal", 210 "hardware": "arch=amd64 cores=1 mem=1024M root-disk=8192M", 211 } 212 machine3 = M{ 213 "juju-status": M{ 214 "current": "started", 215 "since": "01 Apr 15 01:23+10:00", 216 }, 217 "dns-name": "controller-3.dns", 218 "instance-id": "controller-3", 219 "machine-status": M{ 220 "current": "pending", 221 "since": "01 Apr 15 01:23+10:00", 222 }, 223 "series": "quantal", 224 "hardware": "arch=amd64 cores=1 mem=1024M root-disk=8192M", 225 } 226 machine4 = M{ 227 "juju-status": M{ 228 "current": "started", 229 "since": "01 Apr 15 01:23+10:00", 230 }, 231 "dns-name": "controller-4.dns", 232 "instance-id": "controller-4", 233 "machine-status": M{ 234 "current": "pending", 235 "since": "01 Apr 15 01:23+10:00", 236 }, 237 "series": "quantal", 238 "hardware": "arch=amd64 cores=1 mem=1024M root-disk=8192M", 239 } 240 machine1WithContainers = M{ 241 "juju-status": M{ 242 "current": "started", 243 "since": "01 Apr 15 01:23+10:00", 244 }, 245 "containers": M{ 246 "1/lxd/0": M{ 247 "juju-status": M{ 248 "current": "started", 249 "since": "01 Apr 15 01:23+10:00", 250 }, 251 "containers": M{ 252 "1/lxd/0/lxd/0": M{ 253 "juju-status": M{ 254 "current": "started", 255 "since": "01 Apr 15 01:23+10:00", 256 }, 257 "dns-name": "controller-3.dns", 258 "instance-id": "controller-3", 259 "machine-status": M{ 260 "current": "pending", 261 "since": "01 Apr 15 01:23+10:00", 262 }, 263 "series": "quantal", 264 }, 265 }, 266 "dns-name": "controller-2.dns", 267 "instance-id": "controller-2", 268 "machine-status": M{ 269 "current": "pending", 270 "since": "01 Apr 15 01:23+10:00", 271 }, 272 "series": "quantal", 273 }, 274 "1/lxd/1": M{ 275 "juju-status": M{ 276 "current": "pending", 277 "since": "01 Apr 15 01:23+10:00", 278 }, 279 "instance-id": "pending", 280 "machine-status": M{ 281 "current": "pending", 282 "since": "01 Apr 15 01:23+10:00", 283 }, 284 "series": "quantal", 285 }, 286 }, 287 "dns-name": "controller-1.dns", 288 "instance-id": "controller-1", 289 "machine-status": M{ 290 "current": "pending", 291 "since": "01 Apr 15 01:23+10:00", 292 }, 293 294 "series": "quantal", 295 "hardware": "arch=amd64 cores=1 mem=1024M root-disk=8192M", 296 } 297 unexposedService = dummyCharm(M{ 298 "application-status": M{ 299 "current": "waiting", 300 "message": "waiting for machine", 301 "since": "01 Apr 15 01:23+10:00", 302 }, 303 }) 304 exposedService = dummyCharm(M{ 305 "application-status": M{ 306 "current": "waiting", 307 "message": "waiting for machine", 308 "since": "01 Apr 15 01:23+10:00", 309 }, 310 "exposed": true, 311 }) 312 loggingCharm = M{ 313 "charm": "cs:quantal/logging-1", 314 "charm-origin": "jujucharms", 315 "charm-name": "logging", 316 "charm-rev": 1, 317 "series": "quantal", 318 "os": "ubuntu", 319 "exposed": true, 320 "application-status": M{ 321 "current": "error", 322 "message": "somehow lost in all those logs", 323 "since": "01 Apr 15 01:23+10:00", 324 }, 325 "relations": M{ 326 "logging-directory": L{"wordpress"}, 327 "info": L{"mysql"}, 328 }, 329 "subordinate-to": L{"mysql", "wordpress"}, 330 } 331 ) 332 333 type outputFormat struct { 334 name string 335 marshal func(v interface{}) ([]byte, error) 336 unmarshal func(data []byte, v interface{}) error 337 } 338 339 // statusFormats list all output formats that can be marshalled as structured data, 340 // supported by status command. 341 var statusFormats = []outputFormat{ 342 {"yaml", goyaml.Marshal, goyaml.Unmarshal}, 343 {"json", json.Marshal, json.Unmarshal}, 344 } 345 346 var machineCons = constraints.MustParse("cores=2 mem=8G root-disk=8G") 347 348 var statusTests = []testCase{ 349 // Status tests 350 test( // 0 351 "bootstrap and starting a single instance", 352 353 addMachine{machineId: "0", job: state.JobManageModel}, 354 expect{ 355 "simulate juju bootstrap by adding machine/0 to the state", 356 M{ 357 "model": model, 358 "machines": M{ 359 "0": M{ 360 "juju-status": M{ 361 "current": "pending", 362 "since": "01 Apr 15 01:23+10:00", 363 }, 364 "instance-id": "pending", 365 "machine-status": M{ 366 "current": "pending", 367 "since": "01 Apr 15 01:23+10:00", 368 }, 369 "series": "quantal", 370 "controller-member-status": "adding-vote", 371 }, 372 }, 373 "applications": M{}, 374 }, 375 }, 376 377 startAliveMachine{"0"}, 378 setAddresses{"0", []network.Address{ 379 network.NewAddress("10.0.0.1"), 380 network.NewScopedAddress("controller-0.dns", network.ScopePublic), 381 }}, 382 expect{ 383 "simulate the PA starting an instance in response to the state change", 384 M{ 385 "model": model, 386 "machines": M{ 387 "0": M{ 388 "juju-status": M{ 389 "current": "pending", 390 "since": "01 Apr 15 01:23+10:00", 391 }, 392 "dns-name": "controller-0.dns", 393 "instance-id": "controller-0", 394 "machine-status": M{ 395 "current": "pending", 396 "since": "01 Apr 15 01:23+10:00", 397 }, 398 "series": "quantal", 399 "hardware": "arch=amd64 cores=1 mem=1024M root-disk=8192M", 400 "controller-member-status": "adding-vote", 401 }, 402 }, 403 "applications": M{}, 404 }, 405 }, 406 407 setMachineStatus{"0", status.Started, ""}, 408 expect{ 409 "simulate the MA started and set the machine status", 410 M{ 411 "model": model, 412 "machines": M{ 413 "0": machine0, 414 }, 415 "applications": M{}, 416 }, 417 }, 418 419 setTools{"0", version.MustParseBinary("1.2.3-trusty-ppc")}, 420 expect{ 421 "simulate the MA setting the version", 422 M{ 423 "model": model, 424 "machines": M{ 425 "0": M{ 426 "dns-name": "controller-0.dns", 427 "instance-id": "controller-0", 428 "machine-status": M{ 429 "current": "pending", 430 "since": "01 Apr 15 01:23+10:00", 431 }, 432 "juju-status": M{ 433 "current": "started", 434 "since": "01 Apr 15 01:23+10:00", 435 "version": "1.2.3", 436 }, 437 "series": "quantal", 438 "hardware": "arch=amd64 cores=1 mem=1024M root-disk=8192M", 439 "controller-member-status": "adding-vote", 440 }, 441 }, 442 "applications": M{}, 443 }, 444 }, 445 ), 446 test( // 1 447 "instance with different hardware characteristics", 448 addMachine{machineId: "0", cons: machineCons, job: state.JobManageModel}, 449 setAddresses{"0", []network.Address{ 450 network.NewAddress("10.0.0.1"), 451 network.NewScopedAddress("controller-0.dns", network.ScopePublic), 452 }}, 453 startAliveMachine{"0"}, 454 setMachineStatus{"0", status.Started, ""}, 455 expect{ 456 "machine 0 has specific hardware characteristics", 457 M{ 458 "model": model, 459 "machines": M{ 460 "0": M{ 461 "juju-status": M{ 462 "current": "started", 463 "since": "01 Apr 15 01:23+10:00", 464 }, 465 "dns-name": "controller-0.dns", 466 "instance-id": "controller-0", 467 "machine-status": M{ 468 "current": "pending", 469 "since": "01 Apr 15 01:23+10:00", 470 }, 471 "series": "quantal", 472 "hardware": "arch=amd64 cores=2 mem=8192M root-disk=8192M", 473 "controller-member-status": "adding-vote", 474 }, 475 }, 476 "applications": M{}, 477 }, 478 }, 479 ), 480 test( // 2 481 "instance without addresses", 482 addMachine{machineId: "0", cons: machineCons, job: state.JobManageModel}, 483 startAliveMachine{"0"}, 484 setMachineStatus{"0", status.Started, ""}, 485 expect{ 486 "machine 0 has no dns-name", 487 M{ 488 "model": model, 489 "machines": M{ 490 "0": M{ 491 "juju-status": M{ 492 "current": "started", 493 "since": "01 Apr 15 01:23+10:00", 494 }, 495 "instance-id": "controller-0", 496 "machine-status": M{ 497 "current": "pending", 498 "since": "01 Apr 15 01:23+10:00", 499 }, 500 "series": "quantal", 501 "hardware": "arch=amd64 cores=2 mem=8192M root-disk=8192M", 502 "controller-member-status": "adding-vote", 503 }, 504 }, 505 "applications": M{}, 506 }, 507 }, 508 ), 509 test( // 3 510 "test pending and missing machines", 511 addMachine{machineId: "0", job: state.JobManageModel}, 512 expect{ 513 "machine 0 reports pending", 514 M{ 515 "model": model, 516 "machines": M{ 517 "0": M{ 518 "juju-status": M{ 519 "current": "pending", 520 "since": "01 Apr 15 01:23+10:00", 521 }, 522 "instance-id": "pending", 523 "machine-status": M{ 524 "current": "pending", 525 "since": "01 Apr 15 01:23+10:00", 526 }, 527 "series": "quantal", 528 "controller-member-status": "adding-vote", 529 }, 530 }, 531 "applications": M{}, 532 }, 533 }, 534 535 startMissingMachine{"0"}, 536 expect{ 537 "machine 0 reports missing", 538 M{ 539 "model": model, 540 "machines": M{ 541 "0": M{ 542 "instance-id": "i-missing", 543 "juju-status": M{ 544 "current": "pending", 545 "since": "01 Apr 15 01:23+10:00", 546 }, 547 "machine-status": M{ 548 "current": "unknown", 549 "message": "missing", 550 "since": "01 Apr 15 01:23+10:00", 551 }, 552 "series": "quantal", 553 "hardware": "arch=amd64 cores=1 mem=1024M root-disk=8192M", 554 "controller-member-status": "adding-vote", 555 }, 556 }, 557 "applications": M{}, 558 }, 559 }, 560 ), 561 test( // 4 562 "add two services and expose one, then add 2 more machines and some units", 563 // step 0 564 addMachine{machineId: "0", job: state.JobManageModel}, 565 setAddresses{"0", network.NewAddresses("controller-0.dns")}, 566 startAliveMachine{"0"}, 567 setMachineStatus{"0", status.Started, ""}, 568 addCharm{"dummy"}, 569 addService{name: "dummy-application", charm: "dummy"}, 570 addService{name: "exposed-application", charm: "dummy"}, 571 expect{ 572 "no applications exposed yet", 573 M{ 574 "model": model, 575 "machines": M{ 576 "0": machine0, 577 }, 578 "applications": M{ 579 "dummy-application": unexposedService, 580 "exposed-application": unexposedService, 581 }, 582 }, 583 }, 584 585 // step 8 586 setServiceExposed{"exposed-application", true}, 587 expect{ 588 "one exposed application", 589 M{ 590 "model": model, 591 "machines": M{ 592 "0": machine0, 593 }, 594 "applications": M{ 595 "dummy-application": unexposedService, 596 "exposed-application": exposedService, 597 }, 598 }, 599 }, 600 601 // step 10 602 addMachine{machineId: "1", job: state.JobHostUnits}, 603 setAddresses{"1", network.NewAddresses("controller-1.dns")}, 604 startAliveMachine{"1"}, 605 setMachineStatus{"1", status.Started, ""}, 606 addMachine{machineId: "2", job: state.JobHostUnits}, 607 setAddresses{"2", network.NewAddresses("controller-2.dns")}, 608 startAliveMachine{"2"}, 609 setMachineStatus{"2", status.Started, ""}, 610 expect{ 611 "two more machines added", 612 M{ 613 "model": model, 614 "machines": M{ 615 "0": machine0, 616 "1": machine1, 617 "2": machine2, 618 }, 619 "applications": M{ 620 "dummy-application": unexposedService, 621 "exposed-application": exposedService, 622 }, 623 }, 624 }, 625 626 // step 19 627 addAliveUnit{"dummy-application", "1"}, 628 addAliveUnit{"exposed-application", "2"}, 629 setAgentStatus{"exposed-application/0", status.Error, "You Require More Vespene Gas", nil}, 630 // Open multiple ports with different protocols, 631 // ensure they're sorted on protocol, then number. 632 openUnitPort{"exposed-application/0", "udp", 10}, 633 openUnitPort{"exposed-application/0", "udp", 2}, 634 openUnitPort{"exposed-application/0", "tcp", 3}, 635 openUnitPort{"exposed-application/0", "tcp", 2}, 636 // Simulate some status with no info, while the agent is down. 637 // Status used to be down, we no longer support said state. 638 // now is one of: pending, started, error. 639 setUnitStatus{"dummy-application/0", status.Terminated, "", nil}, 640 setAgentStatus{"dummy-application/0", status.Idle, "", nil}, 641 642 expect{ 643 "add two units, one alive (in error state), one started", 644 M{ 645 "model": model, 646 "machines": M{ 647 "0": machine0, 648 "1": machine1, 649 "2": machine2, 650 }, 651 "applications": M{ 652 "exposed-application": dummyCharm(M{ 653 "exposed": true, 654 "application-status": M{ 655 "current": "error", 656 "message": "You Require More Vespene Gas", 657 "since": "01 Apr 15 01:23+10:00", 658 }, 659 "units": M{ 660 "exposed-application/0": M{ 661 "machine": "2", 662 "workload-status": M{ 663 "current": "error", 664 "message": "You Require More Vespene Gas", 665 "since": "01 Apr 15 01:23+10:00", 666 }, 667 "juju-status": M{ 668 "current": "idle", 669 "since": "01 Apr 15 01:23+10:00", 670 }, 671 "open-ports": L{ 672 "2/tcp", "3/tcp", "2/udp", "10/udp", 673 }, 674 "public-address": "controller-2.dns", 675 }, 676 }, 677 }), 678 "dummy-application": dummyCharm(M{ 679 "application-status": M{ 680 "current": "terminated", 681 "since": "01 Apr 15 01:23+10:00", 682 }, 683 "units": M{ 684 "dummy-application/0": M{ 685 "machine": "1", 686 "workload-status": M{ 687 "current": "terminated", 688 "since": "01 Apr 15 01:23+10:00", 689 }, 690 "juju-status": M{ 691 "current": "idle", 692 "since": "01 Apr 15 01:23+10:00", 693 }, 694 "public-address": "controller-1.dns", 695 }, 696 }, 697 }), 698 }, 699 }, 700 }, 701 702 // step 29 703 addMachine{machineId: "3", job: state.JobHostUnits}, 704 startMachine{"3"}, 705 // Simulate some status with info, while the agent is down. 706 setAddresses{"3", network.NewAddresses("controller-3.dns")}, 707 setMachineStatus{"3", status.Stopped, "Really?"}, 708 addMachine{machineId: "4", job: state.JobHostUnits}, 709 setAddresses{"4", network.NewAddresses("controller-4.dns")}, 710 startAliveMachine{"4"}, 711 setMachineStatus{"4", status.Error, "Beware the red toys"}, 712 ensureDyingUnit{"dummy-application/0"}, 713 addMachine{machineId: "5", job: state.JobHostUnits}, 714 ensureDeadMachine{"5"}, 715 expect{ 716 "add three more machine, one with a dead agent, one in error state and one dead itself; also one dying unit", 717 M{ 718 "model": model, 719 "machines": M{ 720 "0": machine0, 721 "1": machine1, 722 "2": machine2, 723 "3": M{ 724 "dns-name": "controller-3.dns", 725 "instance-id": "controller-3", 726 "machine-status": M{ 727 "current": "pending", 728 "since": "01 Apr 15 01:23+10:00", 729 }, 730 "juju-status": M{ 731 "current": "stopped", 732 "message": "Really?", 733 "since": "01 Apr 15 01:23+10:00", 734 }, 735 "series": "quantal", 736 "hardware": "arch=amd64 cores=1 mem=1024M root-disk=8192M", 737 }, 738 "4": M{ 739 "dns-name": "controller-4.dns", 740 "instance-id": "controller-4", 741 "machine-status": M{ 742 "current": "pending", 743 "since": "01 Apr 15 01:23+10:00", 744 }, 745 "juju-status": M{ 746 "current": "error", 747 "message": "Beware the red toys", 748 "since": "01 Apr 15 01:23+10:00", 749 }, 750 "series": "quantal", 751 "hardware": "arch=amd64 cores=1 mem=1024M root-disk=8192M", 752 }, 753 "5": M{ 754 "juju-status": M{ 755 "current": "pending", 756 "since": "01 Apr 15 01:23+10:00", 757 "life": "dead", 758 }, 759 "instance-id": "pending", 760 "machine-status": M{ 761 "current": "pending", 762 "since": "01 Apr 15 01:23+10:00", 763 }, 764 "series": "quantal", 765 }, 766 }, 767 "applications": M{ 768 "exposed-application": dummyCharm(M{ 769 "exposed": true, 770 "application-status": M{ 771 "current": "error", 772 "message": "You Require More Vespene Gas", 773 "since": "01 Apr 15 01:23+10:00", 774 }, 775 "units": M{ 776 "exposed-application/0": M{ 777 "machine": "2", 778 "workload-status": M{ 779 "current": "error", 780 "message": "You Require More Vespene Gas", 781 "since": "01 Apr 15 01:23+10:00", 782 }, 783 "juju-status": M{ 784 "current": "idle", 785 "since": "01 Apr 15 01:23+10:00", 786 }, 787 "open-ports": L{ 788 "2/tcp", "3/tcp", "2/udp", "10/udp", 789 }, 790 "public-address": "controller-2.dns", 791 }, 792 }, 793 }), 794 "dummy-application": dummyCharm(M{ 795 "application-status": M{ 796 "current": "terminated", 797 "since": "01 Apr 15 01:23+10:00", 798 }, 799 "units": M{ 800 "dummy-application/0": M{ 801 "machine": "1", 802 "workload-status": M{ 803 "current": "terminated", 804 "since": "01 Apr 15 01:23+10:00", 805 }, 806 "juju-status": M{ 807 "current": "idle", 808 "since": "01 Apr 15 01:23+10:00", 809 }, 810 "public-address": "controller-1.dns", 811 }, 812 }, 813 }), 814 }, 815 }, 816 }, 817 818 // step 41 819 scopedExpect{ 820 "scope status on dummy-application/0 unit", 821 []string{"dummy-application/0"}, 822 M{ 823 "model": model, 824 "machines": M{ 825 "1": machine1, 826 }, 827 "applications": M{ 828 "dummy-application": dummyCharm(M{ 829 "application-status": M{ 830 "current": "terminated", 831 "since": "01 Apr 15 01:23+10:00", 832 }, 833 "units": M{ 834 "dummy-application/0": M{ 835 "machine": "1", 836 "workload-status": M{ 837 "current": "terminated", 838 "since": "01 Apr 15 01:23+10:00", 839 }, 840 "juju-status": M{ 841 "current": "idle", 842 "since": "01 Apr 15 01:23+10:00", 843 }, 844 "public-address": "controller-1.dns", 845 }, 846 }, 847 }), 848 }, 849 }, 850 }, 851 scopedExpect{ 852 "scope status on exposed-application application", 853 []string{"exposed-application"}, 854 M{ 855 "model": model, 856 "machines": M{ 857 "2": machine2, 858 }, 859 "applications": M{ 860 "exposed-application": dummyCharm(M{ 861 "exposed": true, 862 "application-status": M{ 863 "current": "error", 864 "message": "You Require More Vespene Gas", 865 "since": "01 Apr 15 01:23+10:00", 866 }, 867 "units": M{ 868 "exposed-application/0": M{ 869 "machine": "2", 870 "workload-status": M{ 871 "current": "error", 872 "message": "You Require More Vespene Gas", 873 "since": "01 Apr 15 01:23+10:00", 874 }, 875 "juju-status": M{ 876 "current": "idle", 877 "since": "01 Apr 15 01:23+10:00", 878 }, 879 "open-ports": L{ 880 "2/tcp", "3/tcp", "2/udp", "10/udp", 881 }, 882 "public-address": "controller-2.dns", 883 }, 884 }, 885 }), 886 }, 887 }, 888 }, 889 scopedExpect{ 890 "scope status on application pattern", 891 []string{"d*-application"}, 892 M{ 893 "model": model, 894 "machines": M{ 895 "1": machine1, 896 }, 897 "applications": M{ 898 "dummy-application": dummyCharm(M{ 899 "application-status": M{ 900 "current": "terminated", 901 "since": "01 Apr 15 01:23+10:00", 902 }, 903 "units": M{ 904 "dummy-application/0": M{ 905 "machine": "1", 906 "workload-status": M{ 907 "current": "terminated", 908 "since": "01 Apr 15 01:23+10:00", 909 }, 910 "juju-status": M{ 911 "current": "idle", 912 "since": "01 Apr 15 01:23+10:00", 913 }, 914 "public-address": "controller-1.dns", 915 }, 916 }, 917 }), 918 }, 919 }, 920 }, 921 scopedExpect{ 922 "scope status on unit pattern", 923 []string{"e*posed-application/*"}, 924 M{ 925 "model": model, 926 "machines": M{ 927 "2": machine2, 928 }, 929 "applications": M{ 930 "exposed-application": dummyCharm(M{ 931 "exposed": true, 932 "application-status": M{ 933 "current": "error", 934 "message": "You Require More Vespene Gas", 935 "since": "01 Apr 15 01:23+10:00", 936 }, 937 "units": M{ 938 "exposed-application/0": M{ 939 "machine": "2", 940 "workload-status": M{ 941 "current": "error", 942 "message": "You Require More Vespene Gas", 943 "since": "01 Apr 15 01:23+10:00", 944 }, 945 "juju-status": M{ 946 "current": "idle", 947 "since": "01 Apr 15 01:23+10:00", 948 }, 949 "open-ports": L{ 950 "2/tcp", "3/tcp", "2/udp", "10/udp", 951 }, 952 "public-address": "controller-2.dns", 953 }, 954 }, 955 }), 956 }, 957 }, 958 }, 959 scopedExpect{ 960 "scope status on combination of application and unit patterns", 961 []string{"exposed-application", "dummy-application", "e*posed-application/*", "dummy-application/*"}, 962 M{ 963 "model": model, 964 "machines": M{ 965 "1": machine1, 966 "2": machine2, 967 }, 968 "applications": M{ 969 "dummy-application": dummyCharm(M{ 970 "application-status": M{ 971 "current": "terminated", 972 "since": "01 Apr 15 01:23+10:00", 973 }, 974 "units": M{ 975 "dummy-application/0": M{ 976 "machine": "1", 977 "workload-status": M{ 978 "current": "terminated", 979 "since": "01 Apr 15 01:23+10:00", 980 }, 981 "juju-status": M{ 982 "current": "idle", 983 "since": "01 Apr 15 01:23+10:00", 984 }, 985 "public-address": "controller-1.dns", 986 }, 987 }, 988 }), 989 "exposed-application": dummyCharm(M{ 990 "exposed": true, 991 "application-status": M{ 992 "current": "error", 993 "message": "You Require More Vespene Gas", 994 "since": "01 Apr 15 01:23+10:00", 995 }, 996 "units": M{ 997 "exposed-application/0": M{ 998 "machine": "2", 999 "workload-status": M{ 1000 "current": "error", 1001 "message": "You Require More Vespene Gas", 1002 "since": "01 Apr 15 01:23+10:00", 1003 }, 1004 "juju-status": M{ 1005 "current": "idle", 1006 "since": "01 Apr 15 01:23+10:00", 1007 }, 1008 "open-ports": L{ 1009 "2/tcp", "3/tcp", "2/udp", "10/udp", 1010 }, 1011 "public-address": "controller-2.dns", 1012 }, 1013 }, 1014 }), 1015 }, 1016 }, 1017 }, 1018 ), 1019 test( // 5 1020 "a unit with a hook relation error", 1021 addMachine{machineId: "0", job: state.JobManageModel}, 1022 setAddresses{"0", network.NewAddresses("controller-0.dns")}, 1023 startAliveMachine{"0"}, 1024 setMachineStatus{"0", status.Started, ""}, 1025 1026 addMachine{machineId: "1", job: state.JobHostUnits}, 1027 setAddresses{"1", network.NewAddresses("controller-1.dns")}, 1028 startAliveMachine{"1"}, 1029 setMachineStatus{"1", status.Started, ""}, 1030 1031 addCharm{"wordpress"}, 1032 addService{name: "wordpress", charm: "wordpress"}, 1033 addAliveUnit{"wordpress", "1"}, 1034 1035 addCharm{"mysql"}, 1036 addService{name: "mysql", charm: "mysql"}, 1037 addAliveUnit{"mysql", "1"}, 1038 1039 relateServices{"wordpress", "mysql"}, 1040 1041 setAgentStatus{"wordpress/0", status.Error, 1042 "hook failed: some-relation-changed", 1043 map[string]interface{}{"relation-id": 0}}, 1044 1045 expect{ 1046 "a unit with a hook relation error", 1047 M{ 1048 "model": model, 1049 "machines": M{ 1050 "0": machine0, 1051 "1": machine1, 1052 }, 1053 "applications": M{ 1054 "wordpress": wordpressCharm(M{ 1055 "relations": M{ 1056 "db": L{"mysql"}, 1057 }, 1058 "application-status": M{ 1059 "current": "error", 1060 "message": "hook failed: some-relation-changed", 1061 "since": "01 Apr 15 01:23+10:00", 1062 }, 1063 "units": M{ 1064 "wordpress/0": M{ 1065 "machine": "1", 1066 "workload-status": M{ 1067 "current": "error", 1068 "message": "hook failed: some-relation-changed for mysql:server", 1069 "since": "01 Apr 15 01:23+10:00", 1070 }, 1071 "juju-status": M{ 1072 "current": "idle", 1073 "since": "01 Apr 15 01:23+10:00", 1074 }, 1075 "public-address": "controller-1.dns", 1076 }, 1077 }, 1078 }), 1079 "mysql": mysqlCharm(M{ 1080 "relations": M{ 1081 "server": L{"wordpress"}, 1082 }, 1083 "application-status": M{ 1084 "current": "waiting", 1085 "message": "waiting for machine", 1086 "since": "01 Apr 15 01:23+10:00", 1087 }, 1088 "units": M{ 1089 "mysql/0": M{ 1090 "machine": "1", 1091 "workload-status": M{ 1092 "current": "waiting", 1093 "message": "waiting for machine", 1094 "since": "01 Apr 15 01:23+10:00", 1095 }, 1096 "juju-status": M{ 1097 "current": "allocating", 1098 "since": "01 Apr 15 01:23+10:00", 1099 }, 1100 "public-address": "controller-1.dns", 1101 }, 1102 }, 1103 }), 1104 }, 1105 }, 1106 }, 1107 ), 1108 test( // 6 1109 "a unit with a hook relation error when the agent is down", 1110 addMachine{machineId: "0", job: state.JobManageModel}, 1111 setAddresses{"0", network.NewAddresses("controller-0.dns")}, 1112 startAliveMachine{"0"}, 1113 setMachineStatus{"0", status.Started, ""}, 1114 1115 addMachine{machineId: "1", job: state.JobHostUnits}, 1116 setAddresses{"1", network.NewAddresses("controller-1.dns")}, 1117 startAliveMachine{"1"}, 1118 setMachineStatus{"1", status.Started, ""}, 1119 1120 addCharm{"wordpress"}, 1121 addService{name: "wordpress", charm: "wordpress"}, 1122 addAliveUnit{"wordpress", "1"}, 1123 1124 addCharm{"mysql"}, 1125 addService{name: "mysql", charm: "mysql"}, 1126 addAliveUnit{"mysql", "1"}, 1127 1128 relateServices{"wordpress", "mysql"}, 1129 1130 setAgentStatus{"wordpress/0", status.Error, 1131 "hook failed: some-relation-changed", 1132 map[string]interface{}{"relation-id": 0}}, 1133 1134 expect{ 1135 "a unit with a hook relation error when the agent is down", 1136 M{ 1137 "model": model, 1138 "machines": M{ 1139 "0": machine0, 1140 "1": machine1, 1141 }, 1142 "applications": M{ 1143 "wordpress": wordpressCharm(M{ 1144 "relations": M{ 1145 "db": L{"mysql"}, 1146 }, 1147 "application-status": M{ 1148 "current": "error", 1149 "message": "hook failed: some-relation-changed", 1150 "since": "01 Apr 15 01:23+10:00", 1151 }, 1152 "units": M{ 1153 "wordpress/0": M{ 1154 "machine": "1", 1155 "workload-status": M{ 1156 "current": "error", 1157 "message": "hook failed: some-relation-changed for mysql:server", 1158 "since": "01 Apr 15 01:23+10:00", 1159 }, 1160 "juju-status": M{ 1161 "current": "idle", 1162 "since": "01 Apr 15 01:23+10:00", 1163 }, 1164 "public-address": "controller-1.dns", 1165 }, 1166 }, 1167 }), 1168 "mysql": mysqlCharm(M{ 1169 "relations": M{ 1170 "server": L{"wordpress"}, 1171 }, 1172 "application-status": M{ 1173 "current": "waiting", 1174 "message": "waiting for machine", 1175 "since": "01 Apr 15 01:23+10:00", 1176 }, 1177 "units": M{ 1178 "mysql/0": M{ 1179 "machine": "1", 1180 "workload-status": M{ 1181 "current": "waiting", 1182 "message": "waiting for machine", 1183 "since": "01 Apr 15 01:23+10:00", 1184 }, 1185 "juju-status": M{ 1186 "current": "allocating", 1187 "since": "01 Apr 15 01:23+10:00", 1188 }, 1189 "public-address": "controller-1.dns", 1190 }, 1191 }, 1192 }), 1193 }, 1194 }, 1195 }, 1196 ), 1197 test( // 7 1198 "add a dying application", 1199 addCharm{"dummy"}, 1200 addService{name: "dummy-application", charm: "dummy"}, 1201 addMachine{machineId: "0", job: state.JobHostUnits}, 1202 addAliveUnit{"dummy-application", "0"}, 1203 ensureDyingService{"dummy-application"}, 1204 expect{ 1205 "application shows life==dying", 1206 M{ 1207 "model": model, 1208 "machines": M{ 1209 "0": M{ 1210 "juju-status": M{ 1211 "current": "pending", 1212 "since": "01 Apr 15 01:23+10:00", 1213 }, 1214 "instance-id": "pending", 1215 "machine-status": M{ 1216 "current": "pending", 1217 "since": "01 Apr 15 01:23+10:00", 1218 }, 1219 1220 "series": "quantal", 1221 }, 1222 }, 1223 "applications": M{ 1224 "dummy-application": dummyCharm(M{ 1225 "life": "dying", 1226 "application-status": M{ 1227 "current": "waiting", 1228 "message": "waiting for machine", 1229 "since": "01 Apr 15 01:23+10:00", 1230 }, 1231 "units": M{ 1232 "dummy-application/0": M{ 1233 "machine": "0", 1234 "workload-status": M{ 1235 "current": "waiting", 1236 "message": "waiting for machine", 1237 "since": "01 Apr 15 01:23+10:00", 1238 }, 1239 "juju-status": M{ 1240 "current": "allocating", 1241 "since": "01 Apr 15 01:23+10:00", 1242 }, 1243 }, 1244 }, 1245 }), 1246 }, 1247 }, 1248 }, 1249 ), 1250 test( // 8 1251 "a unit where the agent is down shows as lost", 1252 addCharm{"dummy"}, 1253 addService{name: "dummy-application", charm: "dummy"}, 1254 addMachine{machineId: "0", job: state.JobHostUnits}, 1255 startAliveMachine{"0"}, 1256 setMachineStatus{"0", status.Started, ""}, 1257 addUnit{"dummy-application", "0"}, 1258 setAgentStatus{"dummy-application/0", status.Idle, "", nil}, 1259 setUnitStatus{"dummy-application/0", status.Active, "", nil}, 1260 expect{ 1261 "unit shows that agent is lost", 1262 M{ 1263 "model": model, 1264 "machines": M{ 1265 "0": M{ 1266 "juju-status": M{ 1267 "current": "started", 1268 "since": "01 Apr 15 01:23+10:00", 1269 }, 1270 "instance-id": "controller-0", 1271 "machine-status": M{ 1272 "current": "pending", 1273 "since": "01 Apr 15 01:23+10:00", 1274 }, 1275 1276 "series": "quantal", 1277 "hardware": "arch=amd64 cores=1 mem=1024M root-disk=8192M", 1278 }, 1279 }, 1280 "applications": M{ 1281 "dummy-application": dummyCharm(M{ 1282 "application-status": M{ 1283 "current": "active", 1284 "since": "01 Apr 15 01:23+10:00", 1285 }, 1286 "units": M{ 1287 "dummy-application/0": M{ 1288 "machine": "0", 1289 "workload-status": M{ 1290 "current": "unknown", 1291 "message": "agent lost, see 'juju show-status-log dummy-application/0'", 1292 "since": "01 Apr 15 01:23+10:00", 1293 }, 1294 "juju-status": M{ 1295 "current": "lost", 1296 "message": "agent is not communicating with the server", 1297 "since": "01 Apr 15 01:23+10:00", 1298 }, 1299 }, 1300 }, 1301 }), 1302 }, 1303 }, 1304 }, 1305 ), 1306 1307 // Relation tests 1308 test( // 9 1309 "complex scenario with multiple related services", 1310 addMachine{machineId: "0", job: state.JobManageModel}, 1311 setAddresses{"0", network.NewAddresses("controller-0.dns")}, 1312 startAliveMachine{"0"}, 1313 setMachineStatus{"0", status.Started, ""}, 1314 addCharm{"wordpress"}, 1315 addCharm{"mysql"}, 1316 addCharm{"varnish"}, 1317 1318 addService{name: "project", charm: "wordpress"}, 1319 setServiceExposed{"project", true}, 1320 addMachine{machineId: "1", job: state.JobHostUnits}, 1321 setAddresses{"1", network.NewAddresses("controller-1.dns")}, 1322 startAliveMachine{"1"}, 1323 setMachineStatus{"1", status.Started, ""}, 1324 addAliveUnit{"project", "1"}, 1325 setAgentStatus{"project/0", status.Idle, "", nil}, 1326 setUnitStatus{"project/0", status.Active, "", nil}, 1327 1328 addService{name: "mysql", charm: "mysql"}, 1329 setServiceExposed{"mysql", true}, 1330 addMachine{machineId: "2", job: state.JobHostUnits}, 1331 setAddresses{"2", network.NewAddresses("controller-2.dns")}, 1332 startAliveMachine{"2"}, 1333 setMachineStatus{"2", status.Started, ""}, 1334 addAliveUnit{"mysql", "2"}, 1335 setAgentStatus{"mysql/0", status.Idle, "", nil}, 1336 setUnitStatus{"mysql/0", status.Active, "", nil}, 1337 1338 addService{name: "varnish", charm: "varnish"}, 1339 setServiceExposed{"varnish", true}, 1340 addMachine{machineId: "3", job: state.JobHostUnits}, 1341 setAddresses{"3", network.NewAddresses("controller-3.dns")}, 1342 startAliveMachine{"3"}, 1343 setMachineStatus{"3", status.Started, ""}, 1344 addAliveUnit{"varnish", "3"}, 1345 1346 addService{name: "private", charm: "wordpress"}, 1347 setServiceExposed{"private", true}, 1348 addMachine{machineId: "4", job: state.JobHostUnits}, 1349 setAddresses{"4", network.NewAddresses("controller-4.dns")}, 1350 startAliveMachine{"4"}, 1351 setMachineStatus{"4", status.Started, ""}, 1352 addAliveUnit{"private", "4"}, 1353 1354 relateServices{"project", "mysql"}, 1355 relateServices{"project", "varnish"}, 1356 relateServices{"private", "mysql"}, 1357 1358 expect{ 1359 "multiples services with relations between some of them", 1360 M{ 1361 "model": model, 1362 "machines": M{ 1363 "0": machine0, 1364 "1": machine1, 1365 "2": machine2, 1366 "3": machine3, 1367 "4": machine4, 1368 }, 1369 "applications": M{ 1370 "project": wordpressCharm(M{ 1371 "exposed": true, 1372 "application-status": M{ 1373 "current": "active", 1374 "since": "01 Apr 15 01:23+10:00", 1375 }, 1376 "units": M{ 1377 "project/0": M{ 1378 "machine": "1", 1379 "workload-status": M{ 1380 "current": "active", 1381 "since": "01 Apr 15 01:23+10:00", 1382 }, 1383 "juju-status": M{ 1384 "current": "idle", 1385 "since": "01 Apr 15 01:23+10:00", 1386 }, 1387 "public-address": "controller-1.dns", 1388 }, 1389 }, 1390 "relations": M{ 1391 "db": L{"mysql"}, 1392 "cache": L{"varnish"}, 1393 }, 1394 }), 1395 "mysql": mysqlCharm(M{ 1396 "exposed": true, 1397 "application-status": M{ 1398 "current": "active", 1399 "since": "01 Apr 15 01:23+10:00", 1400 }, 1401 "units": M{ 1402 "mysql/0": M{ 1403 "machine": "2", 1404 "workload-status": M{ 1405 "current": "active", 1406 "since": "01 Apr 15 01:23+10:00", 1407 }, 1408 "juju-status": M{ 1409 "current": "idle", 1410 "since": "01 Apr 15 01:23+10:00", 1411 }, 1412 "public-address": "controller-2.dns", 1413 }, 1414 }, 1415 "relations": M{ 1416 "server": L{"private", "project"}, 1417 }, 1418 }), 1419 "varnish": M{ 1420 "charm": "cs:quantal/varnish-1", 1421 "charm-origin": "jujucharms", 1422 "charm-name": "varnish", 1423 "charm-rev": 1, 1424 "series": "quantal", 1425 "os": "ubuntu", 1426 "exposed": true, 1427 "application-status": M{ 1428 "current": "waiting", 1429 "message": "waiting for machine", 1430 "since": "01 Apr 15 01:23+10:00", 1431 }, 1432 "units": M{ 1433 "varnish/0": M{ 1434 "machine": "3", 1435 "workload-status": M{ 1436 "current": "waiting", 1437 "message": "waiting for machine", 1438 "since": "01 Apr 15 01:23+10:00", 1439 }, 1440 "juju-status": M{ 1441 "current": "allocating", 1442 "since": "01 Apr 15 01:23+10:00", 1443 }, 1444 "public-address": "controller-3.dns", 1445 }, 1446 }, 1447 "relations": M{ 1448 "webcache": L{"project"}, 1449 }, 1450 }, 1451 "private": wordpressCharm(M{ 1452 "exposed": true, 1453 "application-status": M{ 1454 "current": "waiting", 1455 "message": "waiting for machine", 1456 "since": "01 Apr 15 01:23+10:00", 1457 }, 1458 "units": M{ 1459 "private/0": M{ 1460 "machine": "4", 1461 "workload-status": M{ 1462 "current": "waiting", 1463 "message": "waiting for machine", 1464 "since": "01 Apr 15 01:23+10:00", 1465 }, 1466 "juju-status": M{ 1467 "current": "allocating", 1468 "since": "01 Apr 15 01:23+10:00", 1469 }, 1470 "public-address": "controller-4.dns", 1471 }, 1472 }, 1473 "relations": M{ 1474 "db": L{"mysql"}, 1475 }, 1476 }), 1477 }, 1478 }, 1479 }, 1480 ), 1481 test( // 10 1482 "simple peer scenario with leader", 1483 addMachine{machineId: "0", job: state.JobManageModel}, 1484 setAddresses{"0", network.NewAddresses("controller-0.dns")}, 1485 startAliveMachine{"0"}, 1486 setMachineStatus{"0", status.Started, ""}, 1487 addCharm{"riak"}, 1488 addCharm{"wordpress"}, 1489 1490 addService{name: "riak", charm: "riak"}, 1491 setServiceExposed{"riak", true}, 1492 addMachine{machineId: "1", job: state.JobHostUnits}, 1493 setAddresses{"1", network.NewAddresses("controller-1.dns")}, 1494 startAliveMachine{"1"}, 1495 setMachineStatus{"1", status.Started, ""}, 1496 addAliveUnit{"riak", "1"}, 1497 setAgentStatus{"riak/0", status.Idle, "", nil}, 1498 setUnitStatus{"riak/0", status.Active, "", nil}, 1499 addMachine{machineId: "2", job: state.JobHostUnits}, 1500 setAddresses{"2", network.NewAddresses("controller-2.dns")}, 1501 startAliveMachine{"2"}, 1502 setMachineStatus{"2", status.Started, ""}, 1503 addAliveUnit{"riak", "2"}, 1504 setAgentStatus{"riak/1", status.Idle, "", nil}, 1505 setUnitStatus{"riak/1", status.Active, "", nil}, 1506 addMachine{machineId: "3", job: state.JobHostUnits}, 1507 setAddresses{"3", network.NewAddresses("controller-3.dns")}, 1508 startAliveMachine{"3"}, 1509 setMachineStatus{"3", status.Started, ""}, 1510 addAliveUnit{"riak", "3"}, 1511 setAgentStatus{"riak/2", status.Idle, "", nil}, 1512 setUnitStatus{"riak/2", status.Active, "", nil}, 1513 setUnitAsLeader{"riak/1"}, 1514 1515 expect{ 1516 "multiples related peer units", 1517 M{ 1518 "model": model, 1519 "machines": M{ 1520 "0": machine0, 1521 "1": machine1, 1522 "2": machine2, 1523 "3": machine3, 1524 }, 1525 "applications": M{ 1526 "riak": M{ 1527 "charm": "cs:quantal/riak-7", 1528 "charm-origin": "jujucharms", 1529 "charm-name": "riak", 1530 "charm-rev": 7, 1531 "series": "quantal", 1532 "os": "ubuntu", 1533 "exposed": true, 1534 "application-status": M{ 1535 "current": "active", 1536 "since": "01 Apr 15 01:23+10:00", 1537 }, 1538 "units": M{ 1539 "riak/0": M{ 1540 "machine": "1", 1541 "workload-status": M{ 1542 "current": "active", 1543 "since": "01 Apr 15 01:23+10:00", 1544 }, 1545 "juju-status": M{ 1546 "current": "idle", 1547 "since": "01 Apr 15 01:23+10:00", 1548 }, 1549 "public-address": "controller-1.dns", 1550 }, 1551 "riak/1": M{ 1552 "machine": "2", 1553 "workload-status": M{ 1554 "current": "active", 1555 "since": "01 Apr 15 01:23+10:00", 1556 }, 1557 "juju-status": M{ 1558 "current": "idle", 1559 "since": "01 Apr 15 01:23+10:00", 1560 }, 1561 "public-address": "controller-2.dns", 1562 "leader": true, 1563 }, 1564 "riak/2": M{ 1565 "machine": "3", 1566 "workload-status": M{ 1567 "current": "active", 1568 "since": "01 Apr 15 01:23+10:00", 1569 }, 1570 "juju-status": M{ 1571 "current": "idle", 1572 "since": "01 Apr 15 01:23+10:00", 1573 }, 1574 "public-address": "controller-3.dns", 1575 }, 1576 }, 1577 "relations": M{ 1578 "ring": L{"riak"}, 1579 }, 1580 }, 1581 }, 1582 }, 1583 }, 1584 ), 1585 1586 // Subordinate tests 1587 test( // 11 1588 "one application with one subordinate application and leader", 1589 addMachine{machineId: "0", job: state.JobManageModel}, 1590 setAddresses{"0", network.NewAddresses("controller-0.dns")}, 1591 startAliveMachine{"0"}, 1592 setMachineStatus{"0", status.Started, ""}, 1593 addCharm{"wordpress"}, 1594 addCharm{"mysql"}, 1595 addCharm{"logging"}, 1596 1597 addService{name: "wordpress", charm: "wordpress"}, 1598 setServiceExposed{"wordpress", true}, 1599 addMachine{machineId: "1", job: state.JobHostUnits}, 1600 setAddresses{"1", network.NewAddresses("controller-1.dns")}, 1601 startAliveMachine{"1"}, 1602 setMachineStatus{"1", status.Started, ""}, 1603 addAliveUnit{"wordpress", "1"}, 1604 setAgentStatus{"wordpress/0", status.Idle, "", nil}, 1605 setUnitStatus{"wordpress/0", status.Active, "", nil}, 1606 1607 addService{name: "mysql", charm: "mysql"}, 1608 setServiceExposed{"mysql", true}, 1609 addMachine{machineId: "2", job: state.JobHostUnits}, 1610 setAddresses{"2", network.NewAddresses("controller-2.dns")}, 1611 startAliveMachine{"2"}, 1612 setMachineStatus{"2", status.Started, ""}, 1613 addAliveUnit{"mysql", "2"}, 1614 setAgentStatus{"mysql/0", status.Idle, "", nil}, 1615 setUnitStatus{"mysql/0", status.Active, "", nil}, 1616 1617 addService{name: "logging", charm: "logging"}, 1618 setServiceExposed{"logging", true}, 1619 1620 relateServices{"wordpress", "mysql"}, 1621 relateServices{"wordpress", "logging"}, 1622 relateServices{"mysql", "logging"}, 1623 1624 addSubordinate{"wordpress/0", "logging"}, 1625 addSubordinate{"mysql/0", "logging"}, 1626 1627 setUnitsAlive{"logging"}, 1628 setAgentStatus{"logging/0", status.Idle, "", nil}, 1629 setUnitStatus{"logging/0", status.Active, "", nil}, 1630 setAgentStatus{"logging/1", status.Error, "somehow lost in all those logs", nil}, 1631 1632 setUnitAsLeader{"mysql/0"}, 1633 setUnitAsLeader{"logging/1"}, 1634 setUnitAsLeader{"wordpress/0"}, 1635 1636 expect{ 1637 "multiples related peer units", 1638 M{ 1639 "model": model, 1640 "machines": M{ 1641 "0": machine0, 1642 "1": machine1, 1643 "2": machine2, 1644 }, 1645 "applications": M{ 1646 "wordpress": wordpressCharm(M{ 1647 "exposed": true, 1648 "application-status": M{ 1649 "current": "active", 1650 "since": "01 Apr 15 01:23+10:00", 1651 }, 1652 "units": M{ 1653 "wordpress/0": M{ 1654 "machine": "1", 1655 "workload-status": M{ 1656 "current": "active", 1657 "since": "01 Apr 15 01:23+10:00", 1658 }, 1659 "juju-status": M{ 1660 "current": "idle", 1661 "since": "01 Apr 15 01:23+10:00", 1662 }, 1663 "subordinates": M{ 1664 "logging/0": M{ 1665 "workload-status": M{ 1666 "current": "active", 1667 "since": "01 Apr 15 01:23+10:00", 1668 }, 1669 "juju-status": M{ 1670 "current": "idle", 1671 "since": "01 Apr 15 01:23+10:00", 1672 }, 1673 "public-address": "controller-1.dns", 1674 }, 1675 }, 1676 "public-address": "controller-1.dns", 1677 "leader": true, 1678 }, 1679 }, 1680 "relations": M{ 1681 "db": L{"mysql"}, 1682 "logging-dir": L{"logging"}, 1683 }, 1684 }), 1685 "mysql": mysqlCharm(M{ 1686 "exposed": true, 1687 "application-status": M{ 1688 "current": "active", 1689 "since": "01 Apr 15 01:23+10:00", 1690 }, 1691 "units": M{ 1692 "mysql/0": M{ 1693 "machine": "2", 1694 "workload-status": M{ 1695 "current": "active", 1696 "since": "01 Apr 15 01:23+10:00", 1697 }, 1698 "juju-status": M{ 1699 "current": "idle", 1700 "since": "01 Apr 15 01:23+10:00", 1701 }, 1702 "subordinates": M{ 1703 "logging/1": M{ 1704 "workload-status": M{ 1705 "current": "error", 1706 "message": "somehow lost in all those logs", 1707 "since": "01 Apr 15 01:23+10:00", 1708 }, 1709 "juju-status": M{ 1710 "current": "idle", 1711 "since": "01 Apr 15 01:23+10:00", 1712 }, 1713 "public-address": "controller-2.dns", 1714 "leader": true, 1715 }, 1716 }, 1717 "public-address": "controller-2.dns", 1718 "leader": true, 1719 }, 1720 }, 1721 "relations": M{ 1722 "server": L{"wordpress"}, 1723 "juju-info": L{"logging"}, 1724 }, 1725 }), 1726 "logging": loggingCharm, 1727 }, 1728 }, 1729 }, 1730 1731 // scoped on 'logging' 1732 scopedExpect{ 1733 "subordinates scoped on logging", 1734 []string{"logging"}, 1735 M{ 1736 "model": model, 1737 "machines": M{ 1738 "1": machine1, 1739 "2": machine2, 1740 }, 1741 "applications": M{ 1742 "wordpress": wordpressCharm(M{ 1743 "exposed": true, 1744 "application-status": M{ 1745 "current": "active", 1746 "since": "01 Apr 15 01:23+10:00", 1747 }, 1748 "units": M{ 1749 "wordpress/0": M{ 1750 "machine": "1", 1751 "workload-status": M{ 1752 "current": "active", 1753 "since": "01 Apr 15 01:23+10:00", 1754 }, 1755 "juju-status": M{ 1756 "current": "idle", 1757 "since": "01 Apr 15 01:23+10:00", 1758 }, 1759 "subordinates": M{ 1760 "logging/0": M{ 1761 "workload-status": M{ 1762 "current": "active", 1763 "since": "01 Apr 15 01:23+10:00", 1764 }, 1765 "juju-status": M{ 1766 "current": "idle", 1767 "since": "01 Apr 15 01:23+10:00", 1768 }, 1769 "public-address": "controller-1.dns", 1770 }, 1771 }, 1772 "public-address": "controller-1.dns", 1773 "leader": true, 1774 }, 1775 }, 1776 "relations": M{ 1777 "db": L{"mysql"}, 1778 "logging-dir": L{"logging"}, 1779 }, 1780 }), 1781 "mysql": mysqlCharm(M{ 1782 "exposed": true, 1783 "application-status": M{ 1784 "current": "active", 1785 "since": "01 Apr 15 01:23+10:00", 1786 }, 1787 "units": M{ 1788 "mysql/0": M{ 1789 "machine": "2", 1790 "workload-status": M{ 1791 "current": "active", 1792 "since": "01 Apr 15 01:23+10:00", 1793 }, 1794 "juju-status": M{ 1795 "current": "idle", 1796 "since": "01 Apr 15 01:23+10:00", 1797 }, 1798 "subordinates": M{ 1799 "logging/1": M{ 1800 "workload-status": M{ 1801 "current": "error", 1802 "message": "somehow lost in all those logs", 1803 "since": "01 Apr 15 01:23+10:00", 1804 }, 1805 "juju-status": M{ 1806 "current": "idle", 1807 "since": "01 Apr 15 01:23+10:00", 1808 }, 1809 "public-address": "controller-2.dns", 1810 "leader": true, 1811 }, 1812 }, 1813 "public-address": "controller-2.dns", 1814 "leader": true, 1815 }, 1816 }, 1817 "relations": M{ 1818 "server": L{"wordpress"}, 1819 "juju-info": L{"logging"}, 1820 }, 1821 }), 1822 "logging": loggingCharm, 1823 }, 1824 }, 1825 }, 1826 1827 // scoped on wordpress/0 1828 scopedExpect{ 1829 "subordinates scoped on wordpress", 1830 []string{"wordpress/0"}, 1831 M{ 1832 "model": model, 1833 "machines": M{ 1834 "1": machine1, 1835 }, 1836 "applications": M{ 1837 "wordpress": wordpressCharm(M{ 1838 "exposed": true, 1839 "application-status": M{ 1840 "current": "active", 1841 "since": "01 Apr 15 01:23+10:00", 1842 }, 1843 "units": M{ 1844 "wordpress/0": M{ 1845 "machine": "1", 1846 "workload-status": M{ 1847 "current": "active", 1848 "since": "01 Apr 15 01:23+10:00", 1849 }, 1850 "juju-status": M{ 1851 "current": "idle", 1852 "since": "01 Apr 15 01:23+10:00", 1853 }, 1854 "subordinates": M{ 1855 "logging/0": M{ 1856 "workload-status": M{ 1857 "current": "active", 1858 "since": "01 Apr 15 01:23+10:00", 1859 }, 1860 "juju-status": M{ 1861 "current": "idle", 1862 "since": "01 Apr 15 01:23+10:00", 1863 }, 1864 "public-address": "controller-1.dns", 1865 }, 1866 }, 1867 "public-address": "controller-1.dns", 1868 "leader": true, 1869 }, 1870 }, 1871 "relations": M{ 1872 "db": L{"mysql"}, 1873 "logging-dir": L{"logging"}, 1874 }, 1875 }), 1876 "logging": loggingCharm, 1877 }, 1878 }, 1879 }, 1880 ), 1881 test( // 12 1882 "machines with containers", 1883 // step 0 1884 addMachine{machineId: "0", job: state.JobManageModel}, 1885 setAddresses{"0", network.NewAddresses("controller-0.dns")}, 1886 startAliveMachine{"0"}, 1887 setMachineStatus{"0", status.Started, ""}, 1888 addCharm{"mysql"}, 1889 addService{name: "mysql", charm: "mysql"}, 1890 setServiceExposed{"mysql", true}, 1891 1892 // step 7 1893 addMachine{machineId: "1", job: state.JobHostUnits}, 1894 setAddresses{"1", network.NewAddresses("controller-1.dns")}, 1895 startAliveMachine{"1"}, 1896 setMachineStatus{"1", status.Started, ""}, 1897 addAliveUnit{"mysql", "1"}, 1898 setAgentStatus{"mysql/0", status.Idle, "", nil}, 1899 setUnitStatus{"mysql/0", status.Active, "", nil}, 1900 1901 // step 14: A container on machine 1. 1902 addContainer{"1", "1/lxd/0", state.JobHostUnits}, 1903 setAddresses{"1/lxd/0", network.NewAddresses("controller-2.dns")}, 1904 startAliveMachine{"1/lxd/0"}, 1905 setMachineStatus{"1/lxd/0", status.Started, ""}, 1906 addAliveUnit{"mysql", "1/lxd/0"}, 1907 setAgentStatus{"mysql/1", status.Idle, "", nil}, 1908 setUnitStatus{"mysql/1", status.Active, "", nil}, 1909 addContainer{"1", "1/lxd/1", state.JobHostUnits}, 1910 1911 // step 22: A nested container. 1912 addContainer{"1/lxd/0", "1/lxd/0/lxd/0", state.JobHostUnits}, 1913 setAddresses{"1/lxd/0/lxd/0", network.NewAddresses("controller-3.dns")}, 1914 startAliveMachine{"1/lxd/0/lxd/0"}, 1915 setMachineStatus{"1/lxd/0/lxd/0", status.Started, ""}, 1916 1917 expect{ 1918 "machines with nested containers", 1919 M{ 1920 "model": model, 1921 "machines": M{ 1922 "0": machine0, 1923 "1": machine1WithContainers, 1924 }, 1925 "applications": M{ 1926 "mysql": mysqlCharm(M{ 1927 "exposed": true, 1928 "application-status": M{ 1929 "current": "active", 1930 "since": "01 Apr 15 01:23+10:00", 1931 }, 1932 "units": M{ 1933 "mysql/0": M{ 1934 "machine": "1", 1935 "workload-status": M{ 1936 "current": "active", 1937 "since": "01 Apr 15 01:23+10:00", 1938 }, 1939 "juju-status": M{ 1940 "current": "idle", 1941 "since": "01 Apr 15 01:23+10:00", 1942 }, 1943 "public-address": "controller-1.dns", 1944 }, 1945 "mysql/1": M{ 1946 "machine": "1/lxd/0", 1947 "workload-status": M{ 1948 "current": "active", 1949 "since": "01 Apr 15 01:23+10:00", 1950 }, 1951 "juju-status": M{ 1952 "current": "idle", 1953 "since": "01 Apr 15 01:23+10:00", 1954 }, 1955 "public-address": "controller-2.dns", 1956 }, 1957 }, 1958 }), 1959 }, 1960 }, 1961 }, 1962 1963 // step 27: once again, with a scope on mysql/1 1964 scopedExpect{ 1965 "machines with nested containers 2", 1966 []string{"mysql/1"}, 1967 M{ 1968 "model": model, 1969 "machines": M{ 1970 "1": M{ 1971 "juju-status": M{ 1972 "current": "started", 1973 "since": "01 Apr 15 01:23+10:00", 1974 }, 1975 "containers": M{ 1976 "1/lxd/0": M{ 1977 "juju-status": M{ 1978 "current": "started", 1979 "since": "01 Apr 15 01:23+10:00", 1980 }, 1981 "dns-name": "controller-2.dns", 1982 "instance-id": "controller-2", 1983 "machine-status": M{ 1984 "current": "pending", 1985 "since": "01 Apr 15 01:23+10:00", 1986 }, 1987 1988 "series": "quantal", 1989 }, 1990 }, 1991 "dns-name": "controller-1.dns", 1992 "instance-id": "controller-1", 1993 "machine-status": M{ 1994 "current": "pending", 1995 "since": "01 Apr 15 01:23+10:00", 1996 }, 1997 1998 "series": "quantal", 1999 "hardware": "arch=amd64 cores=1 mem=1024M root-disk=8192M", 2000 }, 2001 }, 2002 "applications": M{ 2003 "mysql": mysqlCharm(M{ 2004 "exposed": true, 2005 "application-status": M{ 2006 "current": "active", 2007 "since": "01 Apr 15 01:23+10:00", 2008 }, 2009 "units": M{ 2010 "mysql/1": M{ 2011 "machine": "1/lxd/0", 2012 "workload-status": M{ 2013 "current": "active", 2014 "since": "01 Apr 15 01:23+10:00", 2015 }, 2016 "juju-status": M{ 2017 "current": "idle", 2018 "since": "01 Apr 15 01:23+10:00", 2019 }, 2020 "public-address": "controller-2.dns", 2021 }, 2022 }, 2023 }), 2024 }, 2025 }, 2026 }, 2027 ), 2028 test( // 13 2029 "application with out of date charm", 2030 addMachine{machineId: "0", job: state.JobManageModel}, 2031 setAddresses{"0", network.NewAddresses("controller-0.dns")}, 2032 startAliveMachine{"0"}, 2033 setMachineStatus{"0", status.Started, ""}, 2034 addMachine{machineId: "1", job: state.JobHostUnits}, 2035 setAddresses{"1", network.NewAddresses("controller-1.dns")}, 2036 startAliveMachine{"1"}, 2037 setMachineStatus{"1", status.Started, ""}, 2038 addCharm{"mysql"}, 2039 addService{name: "mysql", charm: "mysql"}, 2040 setServiceExposed{"mysql", true}, 2041 addCharmPlaceholder{"mysql", 23}, 2042 addAliveUnit{"mysql", "1"}, 2043 2044 expect{ 2045 "services and units with correct charm status", 2046 M{ 2047 "model": model, 2048 "machines": M{ 2049 "0": machine0, 2050 "1": machine1, 2051 }, 2052 "applications": M{ 2053 "mysql": mysqlCharm(M{ 2054 "can-upgrade-to": "cs:quantal/mysql-23", 2055 "exposed": true, 2056 "application-status": M{ 2057 "current": "waiting", 2058 "message": "waiting for machine", 2059 "since": "01 Apr 15 01:23+10:00", 2060 }, 2061 "units": M{ 2062 "mysql/0": M{ 2063 "machine": "1", 2064 "workload-status": M{ 2065 "current": "waiting", 2066 "message": "waiting for machine", 2067 "since": "01 Apr 15 01:23+10:00", 2068 }, 2069 "juju-status": M{ 2070 "current": "allocating", 2071 "since": "01 Apr 15 01:23+10:00", 2072 }, 2073 "public-address": "controller-1.dns", 2074 }, 2075 }, 2076 }), 2077 }, 2078 }, 2079 }, 2080 ), 2081 test( // 14 2082 "unit with out of date charm", 2083 addMachine{machineId: "0", job: state.JobManageModel}, 2084 setAddresses{"0", network.NewAddresses("controller-0.dns")}, 2085 startAliveMachine{"0"}, 2086 setMachineStatus{"0", status.Started, ""}, 2087 addMachine{machineId: "1", job: state.JobHostUnits}, 2088 setAddresses{"1", network.NewAddresses("controller-1.dns")}, 2089 startAliveMachine{"1"}, 2090 setMachineStatus{"1", status.Started, ""}, 2091 addCharm{"mysql"}, 2092 addService{name: "mysql", charm: "mysql"}, 2093 setServiceExposed{"mysql", true}, 2094 addAliveUnit{"mysql", "1"}, 2095 setUnitCharmURL{"mysql/0", "cs:quantal/mysql-1"}, 2096 addCharmWithRevision{addCharm{"mysql"}, "local", 1}, 2097 setServiceCharm{"mysql", "local:quantal/mysql-1"}, 2098 2099 expect{ 2100 "services and units with correct charm status", 2101 M{ 2102 "model": model, 2103 "machines": M{ 2104 "0": machine0, 2105 "1": machine1, 2106 }, 2107 "applications": M{ 2108 "mysql": mysqlCharm(M{ 2109 "charm": "local:quantal/mysql-1", 2110 "charm-origin": "local", 2111 "exposed": true, 2112 "application-status": M{ 2113 "current": "active", 2114 "since": "01 Apr 15 01:23+10:00", 2115 }, 2116 "units": M{ 2117 "mysql/0": M{ 2118 "machine": "1", 2119 "workload-status": M{ 2120 "current": "active", 2121 "since": "01 Apr 15 01:23+10:00", 2122 }, 2123 "juju-status": M{ 2124 "current": "idle", 2125 "since": "01 Apr 15 01:23+10:00", 2126 }, 2127 "upgrading-from": "cs:quantal/mysql-1", 2128 "public-address": "controller-1.dns", 2129 }, 2130 }, 2131 }), 2132 }, 2133 }, 2134 }, 2135 ), 2136 test( // 15 2137 "application and unit with out of date charms", 2138 addMachine{machineId: "0", job: state.JobManageModel}, 2139 setAddresses{"0", network.NewAddresses("controller-0.dns")}, 2140 startAliveMachine{"0"}, 2141 setMachineStatus{"0", status.Started, ""}, 2142 addMachine{machineId: "1", job: state.JobHostUnits}, 2143 setAddresses{"1", network.NewAddresses("controller-1.dns")}, 2144 startAliveMachine{"1"}, 2145 setMachineStatus{"1", status.Started, ""}, 2146 addCharm{"mysql"}, 2147 addService{name: "mysql", charm: "mysql"}, 2148 setServiceExposed{"mysql", true}, 2149 addAliveUnit{"mysql", "1"}, 2150 setUnitCharmURL{"mysql/0", "cs:quantal/mysql-1"}, 2151 addCharmWithRevision{addCharm{"mysql"}, "cs", 2}, 2152 setServiceCharm{"mysql", "cs:quantal/mysql-2"}, 2153 addCharmPlaceholder{"mysql", 23}, 2154 2155 expect{ 2156 "services and units with correct charm status", 2157 M{ 2158 "model": model, 2159 "machines": M{ 2160 "0": machine0, 2161 "1": machine1, 2162 }, 2163 "applications": M{ 2164 "mysql": mysqlCharm(M{ 2165 "charm": "cs:quantal/mysql-2", 2166 "charm-rev": 2, 2167 "can-upgrade-to": "cs:quantal/mysql-23", 2168 "exposed": true, 2169 "application-status": M{ 2170 "current": "active", 2171 "since": "01 Apr 15 01:23+10:00", 2172 }, 2173 "units": M{ 2174 "mysql/0": M{ 2175 "machine": "1", 2176 "workload-status": M{ 2177 "current": "active", 2178 "since": "01 Apr 15 01:23+10:00", 2179 }, 2180 "juju-status": M{ 2181 "current": "idle", 2182 "since": "01 Apr 15 01:23+10:00", 2183 }, 2184 "upgrading-from": "cs:quantal/mysql-1", 2185 "public-address": "controller-1.dns", 2186 }, 2187 }, 2188 }), 2189 }, 2190 }, 2191 }, 2192 ), 2193 test( // 16 2194 "application with local charm not shown as out of date", 2195 addMachine{machineId: "0", job: state.JobManageModel}, 2196 setAddresses{"0", network.NewAddresses("controller-0.dns")}, 2197 startAliveMachine{"0"}, 2198 setMachineStatus{"0", status.Started, ""}, 2199 addMachine{machineId: "1", job: state.JobHostUnits}, 2200 setAddresses{"1", network.NewAddresses("controller-1.dns")}, 2201 startAliveMachine{"1"}, 2202 setMachineStatus{"1", status.Started, ""}, 2203 addCharm{"mysql"}, 2204 addService{name: "mysql", charm: "mysql"}, 2205 setServiceExposed{"mysql", true}, 2206 addAliveUnit{"mysql", "1"}, 2207 setUnitCharmURL{"mysql/0", "cs:quantal/mysql-1"}, 2208 addCharmWithRevision{addCharm{"mysql"}, "local", 1}, 2209 setServiceCharm{"mysql", "local:quantal/mysql-1"}, 2210 addCharmPlaceholder{"mysql", 23}, 2211 2212 expect{ 2213 "services and units with correct charm status", 2214 M{ 2215 "model": model, 2216 "machines": M{ 2217 "0": machine0, 2218 "1": machine1, 2219 }, 2220 "applications": M{ 2221 "mysql": mysqlCharm(M{ 2222 "charm": "local:quantal/mysql-1", 2223 "charm-origin": "local", 2224 "exposed": true, 2225 "application-status": M{ 2226 "current": "active", 2227 "since": "01 Apr 15 01:23+10:00", 2228 }, 2229 "units": M{ 2230 "mysql/0": M{ 2231 "machine": "1", 2232 "workload-status": M{ 2233 "current": "active", 2234 "since": "01 Apr 15 01:23+10:00", 2235 }, 2236 "juju-status": M{ 2237 "current": "idle", 2238 "since": "01 Apr 15 01:23+10:00", 2239 }, 2240 "upgrading-from": "cs:quantal/mysql-1", 2241 "public-address": "controller-1.dns", 2242 }, 2243 }, 2244 }), 2245 }, 2246 }, 2247 }, 2248 ), 2249 test( // 17 2250 "deploy two services; set meter statuses on one", 2251 addMachine{machineId: "0", job: state.JobManageModel}, 2252 setAddresses{"0", network.NewAddresses("controller-0.dns")}, 2253 startAliveMachine{"0"}, 2254 setMachineStatus{"0", status.Started, ""}, 2255 2256 addMachine{machineId: "1", job: state.JobHostUnits}, 2257 setAddresses{"1", network.NewAddresses("controller-1.dns")}, 2258 startAliveMachine{"1"}, 2259 setMachineStatus{"1", status.Started, ""}, 2260 2261 addMachine{machineId: "2", job: state.JobHostUnits}, 2262 setAddresses{"2", network.NewAddresses("controller-2.dns")}, 2263 startAliveMachine{"2"}, 2264 setMachineStatus{"2", status.Started, ""}, 2265 2266 addMachine{machineId: "3", job: state.JobHostUnits}, 2267 setAddresses{"3", network.NewAddresses("controller-3.dns")}, 2268 startAliveMachine{"3"}, 2269 setMachineStatus{"3", status.Started, ""}, 2270 2271 addMachine{machineId: "4", job: state.JobHostUnits}, 2272 setAddresses{"4", network.NewAddresses("controller-4.dns")}, 2273 startAliveMachine{"4"}, 2274 setMachineStatus{"4", status.Started, ""}, 2275 2276 addCharm{"mysql"}, 2277 addService{name: "mysql", charm: "mysql"}, 2278 setServiceExposed{"mysql", true}, 2279 2280 addCharm{"metered"}, 2281 addService{name: "servicewithmeterstatus", charm: "metered"}, 2282 2283 addAliveUnit{"mysql", "1"}, 2284 addAliveUnit{"servicewithmeterstatus", "2"}, 2285 addAliveUnit{"servicewithmeterstatus", "3"}, 2286 addAliveUnit{"servicewithmeterstatus", "4"}, 2287 2288 setServiceExposed{"mysql", true}, 2289 2290 setAgentStatus{"mysql/0", status.Idle, "", nil}, 2291 setUnitStatus{"mysql/0", status.Active, "", nil}, 2292 setAgentStatus{"servicewithmeterstatus/0", status.Idle, "", nil}, 2293 setUnitStatus{"servicewithmeterstatus/0", status.Active, "", nil}, 2294 setAgentStatus{"servicewithmeterstatus/1", status.Idle, "", nil}, 2295 setUnitStatus{"servicewithmeterstatus/1", status.Active, "", nil}, 2296 setAgentStatus{"servicewithmeterstatus/2", status.Idle, "", nil}, 2297 setUnitStatus{"servicewithmeterstatus/2", status.Active, "", nil}, 2298 2299 setUnitMeterStatus{"servicewithmeterstatus/1", "GREEN", "test green status"}, 2300 setUnitMeterStatus{"servicewithmeterstatus/2", "RED", "test red status"}, 2301 2302 expect{ 2303 "simulate just the two services and a bootstrap node", 2304 M{ 2305 "model": model, 2306 "machines": M{ 2307 "0": machine0, 2308 "1": machine1, 2309 "2": machine2, 2310 "3": machine3, 2311 "4": machine4, 2312 }, 2313 "applications": M{ 2314 "mysql": mysqlCharm(M{ 2315 "exposed": true, 2316 "application-status": M{ 2317 "current": "active", 2318 "since": "01 Apr 15 01:23+10:00", 2319 }, 2320 "units": M{ 2321 "mysql/0": M{ 2322 "machine": "1", 2323 "workload-status": M{ 2324 "current": "active", 2325 "since": "01 Apr 15 01:23+10:00", 2326 }, 2327 "juju-status": M{ 2328 "current": "idle", 2329 "since": "01 Apr 15 01:23+10:00", 2330 }, 2331 "public-address": "controller-1.dns", 2332 }, 2333 }, 2334 }), 2335 2336 "servicewithmeterstatus": meteredCharm(M{ 2337 "application-status": M{ 2338 "current": "active", 2339 "since": "01 Apr 15 01:23+10:00", 2340 }, 2341 "units": M{ 2342 "servicewithmeterstatus/0": M{ 2343 "machine": "2", 2344 "workload-status": M{ 2345 "current": "active", 2346 "since": "01 Apr 15 01:23+10:00", 2347 }, 2348 "juju-status": M{ 2349 "current": "idle", 2350 "since": "01 Apr 15 01:23+10:00", 2351 }, 2352 "public-address": "controller-2.dns", 2353 }, 2354 "servicewithmeterstatus/1": M{ 2355 "machine": "3", 2356 "workload-status": M{ 2357 "current": "active", 2358 "since": "01 Apr 15 01:23+10:00", 2359 }, 2360 "juju-status": M{ 2361 "current": "idle", 2362 "since": "01 Apr 15 01:23+10:00", 2363 }, 2364 "meter-status": M{ 2365 "color": "green", 2366 "message": "test green status", 2367 }, 2368 "public-address": "controller-3.dns", 2369 }, 2370 "servicewithmeterstatus/2": M{ 2371 "machine": "4", 2372 "workload-status": M{ 2373 "current": "active", 2374 "since": "01 Apr 15 01:23+10:00", 2375 }, 2376 "juju-status": M{ 2377 "current": "idle", 2378 "since": "01 Apr 15 01:23+10:00", 2379 }, 2380 "meter-status": M{ 2381 "color": "red", 2382 "message": "test red status", 2383 }, 2384 "public-address": "controller-4.dns", 2385 }, 2386 }, 2387 }), 2388 }, 2389 }, 2390 }, 2391 ), 2392 test( // 18 2393 "upgrade available", 2394 setToolsUpgradeAvailable{}, 2395 expect{ 2396 "upgrade availability should be shown in model-status", 2397 M{ 2398 "model": M{ 2399 "name": "controller", 2400 "controller": "kontroll", 2401 "cloud": "dummy", 2402 "region": "dummy-region", 2403 "version": "1.2.3", 2404 "upgrade-available": "1.2.4", 2405 }, 2406 "machines": M{}, 2407 "applications": M{}, 2408 }, 2409 }, 2410 ), 2411 test( // 19 2412 "consistent workload version", 2413 addMachine{machineId: "0", job: state.JobManageModel}, 2414 setAddresses{"0", network.NewAddresses("controller-0.dns")}, 2415 startAliveMachine{"0"}, 2416 setMachineStatus{"0", status.Started, ""}, 2417 2418 addCharm{"mysql"}, 2419 addService{name: "mysql", charm: "mysql"}, 2420 2421 addMachine{machineId: "1", job: state.JobHostUnits}, 2422 setAddresses{"1", network.NewAddresses("controller-1.dns")}, 2423 startAliveMachine{"1"}, 2424 setMachineStatus{"1", status.Started, ""}, 2425 addAliveUnit{"mysql", "1"}, 2426 setUnitWorkloadVersion{"mysql/0", "the best!"}, 2427 2428 expect{ 2429 "application and unit with correct workload version", 2430 M{ 2431 "model": model, 2432 "machines": M{ 2433 "0": machine0, 2434 "1": machine1, 2435 }, 2436 "applications": M{ 2437 "mysql": mysqlCharm(M{ 2438 "version": "the best!", 2439 "application-status": M{ 2440 "current": "waiting", 2441 "message": "waiting for machine", 2442 "since": "01 Apr 15 01:23+10:00", 2443 }, 2444 "units": M{ 2445 "mysql/0": M{ 2446 "machine": "1", 2447 "workload-status": M{ 2448 "current": "waiting", 2449 "message": "waiting for machine", 2450 "since": "01 Apr 15 01:23+10:00", 2451 }, 2452 "juju-status": M{ 2453 "current": "allocating", 2454 "since": "01 Apr 15 01:23+10:00", 2455 }, 2456 "public-address": "controller-1.dns", 2457 }, 2458 }, 2459 }), 2460 }, 2461 }, 2462 }, 2463 ), 2464 test( // 20 2465 "mixed workload version", 2466 addMachine{machineId: "0", job: state.JobManageModel}, 2467 setAddresses{"0", network.NewAddresses("controller-0.dns")}, 2468 startAliveMachine{"0"}, 2469 setMachineStatus{"0", status.Started, ""}, 2470 2471 addCharm{"mysql"}, 2472 addService{name: "mysql", charm: "mysql"}, 2473 2474 addMachine{machineId: "1", job: state.JobHostUnits}, 2475 setAddresses{"1", network.NewAddresses("controller-1.dns")}, 2476 startAliveMachine{"1"}, 2477 setMachineStatus{"1", status.Started, ""}, 2478 addAliveUnit{"mysql", "1"}, 2479 setUnitWorkloadVersion{"mysql/0", "the best!"}, 2480 2481 addMachine{machineId: "2", job: state.JobHostUnits}, 2482 setAddresses{"2", network.NewAddresses("controller-2.dns")}, 2483 startAliveMachine{"2"}, 2484 setMachineStatus{"2", status.Started, ""}, 2485 addAliveUnit{"mysql", "2"}, 2486 setUnitWorkloadVersion{"mysql/1", "not as good"}, 2487 2488 expect{ 2489 "application and unit with correct workload version", 2490 M{ 2491 "model": model, 2492 "machines": M{ 2493 "0": machine0, 2494 "1": machine1, 2495 "2": machine2, 2496 }, 2497 "applications": M{ 2498 "mysql": mysqlCharm(M{ 2499 "version": "not as good", 2500 "application-status": M{ 2501 "current": "waiting", 2502 "message": "waiting for machine", 2503 "since": "01 Apr 15 01:23+10:00", 2504 }, 2505 "units": M{ 2506 "mysql/0": M{ 2507 "machine": "1", 2508 "workload-status": M{ 2509 "current": "waiting", 2510 "message": "waiting for machine", 2511 "since": "01 Apr 15 01:23+10:00", 2512 }, 2513 "juju-status": M{ 2514 "current": "allocating", 2515 "since": "01 Apr 15 01:23+10:00", 2516 }, 2517 "public-address": "controller-1.dns", 2518 }, 2519 "mysql/1": M{ 2520 "machine": "2", 2521 "workload-status": M{ 2522 "current": "waiting", 2523 "message": "waiting for machine", 2524 "since": "01 Apr 15 01:23+10:00", 2525 }, 2526 "juju-status": M{ 2527 "current": "allocating", 2528 "since": "01 Apr 15 01:23+10:00", 2529 }, 2530 "public-address": "controller-2.dns", 2531 }, 2532 }, 2533 }), 2534 }, 2535 }, 2536 }, 2537 ), 2538 } 2539 2540 func mysqlCharm(extras M) M { 2541 charm := M{ 2542 "charm": "cs:quantal/mysql-1", 2543 "charm-origin": "jujucharms", 2544 "charm-name": "mysql", 2545 "charm-rev": 1, 2546 "series": "quantal", 2547 "os": "ubuntu", 2548 "exposed": false, 2549 } 2550 for key, value := range extras { 2551 charm[key] = value 2552 } 2553 return charm 2554 } 2555 2556 func meteredCharm(extras M) M { 2557 charm := M{ 2558 "charm": "cs:quantal/metered-1", 2559 "charm-origin": "jujucharms", 2560 "charm-name": "metered", 2561 "charm-rev": 1, 2562 "series": "quantal", 2563 "os": "ubuntu", 2564 "exposed": false, 2565 } 2566 for key, value := range extras { 2567 charm[key] = value 2568 } 2569 return charm 2570 } 2571 2572 func dummyCharm(extras M) M { 2573 charm := M{ 2574 "charm": "cs:quantal/dummy-1", 2575 "charm-origin": "jujucharms", 2576 "charm-name": "dummy", 2577 "charm-rev": 1, 2578 "series": "quantal", 2579 "os": "ubuntu", 2580 "exposed": false, 2581 } 2582 for key, value := range extras { 2583 charm[key] = value 2584 } 2585 return charm 2586 } 2587 2588 func wordpressCharm(extras M) M { 2589 charm := M{ 2590 "charm": "cs:quantal/wordpress-3", 2591 "charm-origin": "jujucharms", 2592 "charm-name": "wordpress", 2593 "charm-rev": 3, 2594 "series": "quantal", 2595 "os": "ubuntu", 2596 "exposed": false, 2597 } 2598 for key, value := range extras { 2599 charm[key] = value 2600 } 2601 return charm 2602 } 2603 2604 // TODO(dfc) test failing components by destructively mutating the state under the hood 2605 2606 type addMachine struct { 2607 machineId string 2608 cons constraints.Value 2609 job state.MachineJob 2610 } 2611 2612 func (am addMachine) step(c *gc.C, ctx *context) { 2613 m, err := ctx.st.AddOneMachine(state.MachineTemplate{ 2614 Series: "quantal", 2615 Constraints: am.cons, 2616 Jobs: []state.MachineJob{am.job}, 2617 }) 2618 c.Assert(err, jc.ErrorIsNil) 2619 c.Assert(m.Id(), gc.Equals, am.machineId) 2620 } 2621 2622 type addContainer struct { 2623 parentId string 2624 machineId string 2625 job state.MachineJob 2626 } 2627 2628 func (ac addContainer) step(c *gc.C, ctx *context) { 2629 template := state.MachineTemplate{ 2630 Series: "quantal", 2631 Jobs: []state.MachineJob{ac.job}, 2632 } 2633 m, err := ctx.st.AddMachineInsideMachine(template, ac.parentId, instance.LXD) 2634 c.Assert(err, jc.ErrorIsNil) 2635 c.Assert(m.Id(), gc.Equals, ac.machineId) 2636 } 2637 2638 type startMachine struct { 2639 machineId string 2640 } 2641 2642 func (sm startMachine) step(c *gc.C, ctx *context) { 2643 m, err := ctx.st.Machine(sm.machineId) 2644 c.Assert(err, jc.ErrorIsNil) 2645 cons, err := m.Constraints() 2646 c.Assert(err, jc.ErrorIsNil) 2647 cfg, err := ctx.st.ControllerConfig() 2648 c.Assert(err, jc.ErrorIsNil) 2649 inst, hc := testing.AssertStartInstanceWithConstraints(c, ctx.env, cfg.ControllerUUID(), m.Id(), cons) 2650 err = m.SetProvisioned(inst.Id(), "fake_nonce", hc) 2651 c.Assert(err, jc.ErrorIsNil) 2652 } 2653 2654 type startMissingMachine struct { 2655 machineId string 2656 } 2657 2658 func (sm startMissingMachine) step(c *gc.C, ctx *context) { 2659 m, err := ctx.st.Machine(sm.machineId) 2660 c.Assert(err, jc.ErrorIsNil) 2661 cons, err := m.Constraints() 2662 c.Assert(err, jc.ErrorIsNil) 2663 cfg, err := ctx.st.ControllerConfig() 2664 c.Assert(err, jc.ErrorIsNil) 2665 _, hc := testing.AssertStartInstanceWithConstraints(c, ctx.env, cfg.ControllerUUID(), m.Id(), cons) 2666 err = m.SetProvisioned("i-missing", "fake_nonce", hc) 2667 c.Assert(err, jc.ErrorIsNil) 2668 // lp:1558657 2669 now := time.Now() 2670 s := status.StatusInfo{ 2671 Status: status.Unknown, 2672 Message: "missing", 2673 Since: &now, 2674 } 2675 err = m.SetInstanceStatus(s) 2676 c.Assert(err, jc.ErrorIsNil) 2677 } 2678 2679 type startAliveMachine struct { 2680 machineId string 2681 } 2682 2683 func (sam startAliveMachine) step(c *gc.C, ctx *context) { 2684 m, err := ctx.st.Machine(sam.machineId) 2685 c.Assert(err, jc.ErrorIsNil) 2686 pinger := ctx.setAgentPresence(c, m) 2687 cons, err := m.Constraints() 2688 c.Assert(err, jc.ErrorIsNil) 2689 cfg, err := ctx.st.ControllerConfig() 2690 c.Assert(err, jc.ErrorIsNil) 2691 inst, hc := testing.AssertStartInstanceWithConstraints(c, ctx.env, cfg.ControllerUUID(), m.Id(), cons) 2692 err = m.SetProvisioned(inst.Id(), "fake_nonce", hc) 2693 c.Assert(err, jc.ErrorIsNil) 2694 ctx.pingers[m.Id()] = pinger 2695 } 2696 2697 type startMachineWithHardware struct { 2698 machineId string 2699 hc instance.HardwareCharacteristics 2700 } 2701 2702 func (sm startMachineWithHardware) step(c *gc.C, ctx *context) { 2703 m, err := ctx.st.Machine(sm.machineId) 2704 c.Assert(err, jc.ErrorIsNil) 2705 pinger := ctx.setAgentPresence(c, m) 2706 cons, err := m.Constraints() 2707 c.Assert(err, jc.ErrorIsNil) 2708 cfg, err := ctx.st.ControllerConfig() 2709 c.Assert(err, jc.ErrorIsNil) 2710 inst, _ := testing.AssertStartInstanceWithConstraints(c, ctx.env, cfg.ControllerUUID(), m.Id(), cons) 2711 err = m.SetProvisioned(inst.Id(), "fake_nonce", &sm.hc) 2712 c.Assert(err, jc.ErrorIsNil) 2713 ctx.pingers[m.Id()] = pinger 2714 } 2715 2716 type setAddresses struct { 2717 machineId string 2718 addresses []network.Address 2719 } 2720 2721 func (sa setAddresses) step(c *gc.C, ctx *context) { 2722 m, err := ctx.st.Machine(sa.machineId) 2723 c.Assert(err, jc.ErrorIsNil) 2724 err = m.SetProviderAddresses(sa.addresses...) 2725 c.Assert(err, jc.ErrorIsNil) 2726 } 2727 2728 type setTools struct { 2729 machineId string 2730 version version.Binary 2731 } 2732 2733 func (st setTools) step(c *gc.C, ctx *context) { 2734 m, err := ctx.st.Machine(st.machineId) 2735 c.Assert(err, jc.ErrorIsNil) 2736 err = m.SetAgentVersion(st.version) 2737 c.Assert(err, jc.ErrorIsNil) 2738 } 2739 2740 type setUnitTools struct { 2741 unitName string 2742 version version.Binary 2743 } 2744 2745 func (st setUnitTools) step(c *gc.C, ctx *context) { 2746 m, err := ctx.st.Unit(st.unitName) 2747 c.Assert(err, jc.ErrorIsNil) 2748 err = m.SetAgentVersion(st.version) 2749 c.Assert(err, jc.ErrorIsNil) 2750 } 2751 2752 type addCharm struct { 2753 name string 2754 } 2755 2756 func (ac addCharm) addCharmStep(c *gc.C, ctx *context, scheme string, rev int) { 2757 ch := testcharms.Repo.CharmDir(ac.name) 2758 name := ch.Meta().Name 2759 curl := charm.MustParseURL(fmt.Sprintf("%s:quantal/%s-%d", scheme, name, rev)) 2760 info := state.CharmInfo{ 2761 Charm: ch, 2762 ID: curl, 2763 StoragePath: "dummy-path", 2764 SHA256: fmt.Sprintf("%s-%d-sha256", name, rev), 2765 } 2766 dummy, err := ctx.st.AddCharm(info) 2767 c.Assert(err, jc.ErrorIsNil) 2768 ctx.charms[ac.name] = dummy 2769 } 2770 2771 func (ac addCharm) step(c *gc.C, ctx *context) { 2772 ch := testcharms.Repo.CharmDir(ac.name) 2773 ac.addCharmStep(c, ctx, "cs", ch.Revision()) 2774 } 2775 2776 type addCharmWithRevision struct { 2777 addCharm 2778 scheme string 2779 rev int 2780 } 2781 2782 func (ac addCharmWithRevision) step(c *gc.C, ctx *context) { 2783 ac.addCharmStep(c, ctx, ac.scheme, ac.rev) 2784 } 2785 2786 type addService struct { 2787 name string 2788 charm string 2789 cons constraints.Value 2790 } 2791 2792 func (as addService) step(c *gc.C, ctx *context) { 2793 ch, ok := ctx.charms[as.charm] 2794 c.Assert(ok, jc.IsTrue) 2795 svc, err := ctx.st.AddApplication(state.AddApplicationArgs{Name: as.name, Charm: ch}) 2796 c.Assert(err, jc.ErrorIsNil) 2797 if svc.IsPrincipal() { 2798 err = svc.SetConstraints(as.cons) 2799 c.Assert(err, jc.ErrorIsNil) 2800 } 2801 } 2802 2803 type setServiceExposed struct { 2804 name string 2805 exposed bool 2806 } 2807 2808 func (sse setServiceExposed) step(c *gc.C, ctx *context) { 2809 s, err := ctx.st.Application(sse.name) 2810 c.Assert(err, jc.ErrorIsNil) 2811 err = s.ClearExposed() 2812 c.Assert(err, jc.ErrorIsNil) 2813 if sse.exposed { 2814 err = s.SetExposed() 2815 c.Assert(err, jc.ErrorIsNil) 2816 } 2817 } 2818 2819 type setServiceCharm struct { 2820 name string 2821 charm string 2822 } 2823 2824 func (ssc setServiceCharm) step(c *gc.C, ctx *context) { 2825 ch, err := ctx.st.Charm(charm.MustParseURL(ssc.charm)) 2826 c.Assert(err, jc.ErrorIsNil) 2827 s, err := ctx.st.Application(ssc.name) 2828 c.Assert(err, jc.ErrorIsNil) 2829 cfg := state.SetCharmConfig{Charm: ch} 2830 err = s.SetCharm(cfg) 2831 c.Assert(err, jc.ErrorIsNil) 2832 } 2833 2834 type addCharmPlaceholder struct { 2835 name string 2836 rev int 2837 } 2838 2839 func (ac addCharmPlaceholder) step(c *gc.C, ctx *context) { 2840 ch := testcharms.Repo.CharmDir(ac.name) 2841 name := ch.Meta().Name 2842 curl := charm.MustParseURL(fmt.Sprintf("cs:quantal/%s-%d", name, ac.rev)) 2843 err := ctx.st.AddStoreCharmPlaceholder(curl) 2844 c.Assert(err, jc.ErrorIsNil) 2845 } 2846 2847 type addUnit struct { 2848 serviceName string 2849 machineId string 2850 } 2851 2852 func (au addUnit) step(c *gc.C, ctx *context) { 2853 s, err := ctx.st.Application(au.serviceName) 2854 c.Assert(err, jc.ErrorIsNil) 2855 u, err := s.AddUnit() 2856 c.Assert(err, jc.ErrorIsNil) 2857 m, err := ctx.st.Machine(au.machineId) 2858 c.Assert(err, jc.ErrorIsNil) 2859 err = u.AssignToMachine(m) 2860 c.Assert(err, jc.ErrorIsNil) 2861 } 2862 2863 type addAliveUnit struct { 2864 serviceName string 2865 machineId string 2866 } 2867 2868 func (aau addAliveUnit) step(c *gc.C, ctx *context) { 2869 s, err := ctx.st.Application(aau.serviceName) 2870 c.Assert(err, jc.ErrorIsNil) 2871 u, err := s.AddUnit() 2872 c.Assert(err, jc.ErrorIsNil) 2873 pinger := ctx.setAgentPresence(c, u) 2874 m, err := ctx.st.Machine(aau.machineId) 2875 c.Assert(err, jc.ErrorIsNil) 2876 err = u.AssignToMachine(m) 2877 c.Assert(err, jc.ErrorIsNil) 2878 ctx.pingers[u.Name()] = pinger 2879 } 2880 2881 type setUnitsAlive struct { 2882 serviceName string 2883 } 2884 2885 func (sua setUnitsAlive) step(c *gc.C, ctx *context) { 2886 s, err := ctx.st.Application(sua.serviceName) 2887 c.Assert(err, jc.ErrorIsNil) 2888 us, err := s.AllUnits() 2889 c.Assert(err, jc.ErrorIsNil) 2890 for _, u := range us { 2891 ctx.pingers[u.Name()] = ctx.setAgentPresence(c, u) 2892 } 2893 } 2894 2895 type setUnitMeterStatus struct { 2896 unitName string 2897 color string 2898 message string 2899 } 2900 2901 func (s setUnitMeterStatus) step(c *gc.C, ctx *context) { 2902 u, err := ctx.st.Unit(s.unitName) 2903 c.Assert(err, jc.ErrorIsNil) 2904 err = u.SetMeterStatus(s.color, s.message) 2905 c.Assert(err, jc.ErrorIsNil) 2906 } 2907 2908 type setUnitAsLeader struct { 2909 unitName string 2910 } 2911 2912 func (s setUnitAsLeader) step(c *gc.C, ctx *context) { 2913 u, err := ctx.st.Unit(s.unitName) 2914 c.Assert(err, jc.ErrorIsNil) 2915 err = ctx.st.LeadershipClaimer().ClaimLeadership(u.ApplicationName(), u.Name(), time.Minute) 2916 c.Assert(err, jc.ErrorIsNil) 2917 } 2918 2919 type setUnitStatus struct { 2920 unitName string 2921 status status.Status 2922 statusInfo string 2923 statusData map[string]interface{} 2924 } 2925 2926 func (sus setUnitStatus) step(c *gc.C, ctx *context) { 2927 u, err := ctx.st.Unit(sus.unitName) 2928 c.Assert(err, jc.ErrorIsNil) 2929 // lp:1558657 2930 now := time.Now() 2931 s := status.StatusInfo{ 2932 Status: sus.status, 2933 Message: sus.statusInfo, 2934 Data: sus.statusData, 2935 Since: &now, 2936 } 2937 err = u.SetStatus(s) 2938 c.Assert(err, jc.ErrorIsNil) 2939 } 2940 2941 type setAgentStatus struct { 2942 unitName string 2943 status status.Status 2944 statusInfo string 2945 statusData map[string]interface{} 2946 } 2947 2948 func (sus setAgentStatus) step(c *gc.C, ctx *context) { 2949 u, err := ctx.st.Unit(sus.unitName) 2950 c.Assert(err, jc.ErrorIsNil) 2951 // lp:1558657 2952 now := time.Now() 2953 sInfo := status.StatusInfo{ 2954 Status: sus.status, 2955 Message: sus.statusInfo, 2956 Data: sus.statusData, 2957 Since: &now, 2958 } 2959 err = u.SetAgentStatus(sInfo) 2960 c.Assert(err, jc.ErrorIsNil) 2961 } 2962 2963 type setUnitCharmURL struct { 2964 unitName string 2965 charm string 2966 } 2967 2968 func (uc setUnitCharmURL) step(c *gc.C, ctx *context) { 2969 u, err := ctx.st.Unit(uc.unitName) 2970 c.Assert(err, jc.ErrorIsNil) 2971 curl := charm.MustParseURL(uc.charm) 2972 err = u.SetCharmURL(curl) 2973 c.Assert(err, jc.ErrorIsNil) 2974 // lp:1558657 2975 now := time.Now() 2976 s := status.StatusInfo{ 2977 Status: status.Active, 2978 Message: "", 2979 Since: &now, 2980 } 2981 err = u.SetStatus(s) 2982 c.Assert(err, jc.ErrorIsNil) 2983 sInfo := status.StatusInfo{ 2984 Status: status.Idle, 2985 Message: "", 2986 Since: &now, 2987 } 2988 err = u.SetAgentStatus(sInfo) 2989 c.Assert(err, jc.ErrorIsNil) 2990 2991 } 2992 2993 type setUnitWorkloadVersion struct { 2994 unitName string 2995 version string 2996 } 2997 2998 func (wv setUnitWorkloadVersion) step(c *gc.C, ctx *context) { 2999 u, err := ctx.st.Unit(wv.unitName) 3000 c.Assert(err, jc.ErrorIsNil) 3001 err = u.SetWorkloadVersion(wv.version) 3002 c.Assert(err, jc.ErrorIsNil) 3003 } 3004 3005 type openUnitPort struct { 3006 unitName string 3007 protocol string 3008 number int 3009 } 3010 3011 func (oup openUnitPort) step(c *gc.C, ctx *context) { 3012 u, err := ctx.st.Unit(oup.unitName) 3013 c.Assert(err, jc.ErrorIsNil) 3014 err = u.OpenPort(oup.protocol, oup.number) 3015 c.Assert(err, jc.ErrorIsNil) 3016 } 3017 3018 type ensureDyingUnit struct { 3019 unitName string 3020 } 3021 3022 func (e ensureDyingUnit) step(c *gc.C, ctx *context) { 3023 u, err := ctx.st.Unit(e.unitName) 3024 c.Assert(err, jc.ErrorIsNil) 3025 err = u.Destroy() 3026 c.Assert(err, jc.ErrorIsNil) 3027 c.Assert(u.Life(), gc.Equals, state.Dying) 3028 } 3029 3030 type ensureDyingService struct { 3031 serviceName string 3032 } 3033 3034 func (e ensureDyingService) step(c *gc.C, ctx *context) { 3035 svc, err := ctx.st.Application(e.serviceName) 3036 c.Assert(err, jc.ErrorIsNil) 3037 err = svc.Destroy() 3038 c.Assert(err, jc.ErrorIsNil) 3039 err = svc.Refresh() 3040 c.Assert(err, jc.ErrorIsNil) 3041 c.Assert(svc.Life(), gc.Equals, state.Dying) 3042 } 3043 3044 type ensureDeadMachine struct { 3045 machineId string 3046 } 3047 3048 func (e ensureDeadMachine) step(c *gc.C, ctx *context) { 3049 m, err := ctx.st.Machine(e.machineId) 3050 c.Assert(err, jc.ErrorIsNil) 3051 err = m.EnsureDead() 3052 c.Assert(err, jc.ErrorIsNil) 3053 c.Assert(m.Life(), gc.Equals, state.Dead) 3054 } 3055 3056 type setMachineStatus struct { 3057 machineId string 3058 status status.Status 3059 statusInfo string 3060 } 3061 3062 func (sms setMachineStatus) step(c *gc.C, ctx *context) { 3063 // lp:1558657 3064 now := time.Now() 3065 m, err := ctx.st.Machine(sms.machineId) 3066 c.Assert(err, jc.ErrorIsNil) 3067 sInfo := status.StatusInfo{ 3068 Status: sms.status, 3069 Message: sms.statusInfo, 3070 Since: &now, 3071 } 3072 err = m.SetStatus(sInfo) 3073 c.Assert(err, jc.ErrorIsNil) 3074 } 3075 3076 type relateServices struct { 3077 ep1, ep2 string 3078 } 3079 3080 func (rs relateServices) step(c *gc.C, ctx *context) { 3081 eps, err := ctx.st.InferEndpoints(rs.ep1, rs.ep2) 3082 c.Assert(err, jc.ErrorIsNil) 3083 _, err = ctx.st.AddRelation(eps...) 3084 c.Assert(err, jc.ErrorIsNil) 3085 } 3086 3087 type addSubordinate struct { 3088 prinUnit string 3089 subService string 3090 } 3091 3092 func (as addSubordinate) step(c *gc.C, ctx *context) { 3093 u, err := ctx.st.Unit(as.prinUnit) 3094 c.Assert(err, jc.ErrorIsNil) 3095 eps, err := ctx.st.InferEndpoints(u.ApplicationName(), as.subService) 3096 c.Assert(err, jc.ErrorIsNil) 3097 rel, err := ctx.st.EndpointsRelation(eps...) 3098 c.Assert(err, jc.ErrorIsNil) 3099 ru, err := rel.Unit(u) 3100 c.Assert(err, jc.ErrorIsNil) 3101 err = ru.EnterScope(nil) 3102 c.Assert(err, jc.ErrorIsNil) 3103 } 3104 3105 type scopedExpect struct { 3106 what string 3107 scope []string 3108 output M 3109 } 3110 3111 type expect struct { 3112 what string 3113 output M 3114 } 3115 3116 // substituteFakeTime replaces all "since" values 3117 // in actual status output with a known fake value. 3118 func substituteFakeSinceTime(c *gc.C, in []byte, expectIsoTime bool) []byte { 3119 // This regexp will work for yaml and json. 3120 exp := regexp.MustCompile(`(?P<since>"?since"?:\ ?)(?P<quote>"?)(?P<timestamp>[^("|\n)]*)*"?`) 3121 // Before the substritution is done, check that the timestamp produced 3122 // by status is in the correct format. 3123 if matches := exp.FindStringSubmatch(string(in)); matches != nil { 3124 for i, name := range exp.SubexpNames() { 3125 if name != "timestamp" { 3126 continue 3127 } 3128 timeFormat := "02 Jan 2006 15:04:05Z07:00" 3129 if expectIsoTime { 3130 timeFormat = "2006-01-02 15:04:05Z" 3131 } 3132 _, err := time.Parse(timeFormat, matches[i]) 3133 c.Assert(err, jc.ErrorIsNil) 3134 } 3135 } 3136 3137 out := exp.ReplaceAllString(string(in), `$since$quote<timestamp>$quote`) 3138 // Substitute a made up time used in our expected output. 3139 out = strings.Replace(out, "<timestamp>", "01 Apr 15 01:23+10:00", -1) 3140 return []byte(out) 3141 } 3142 3143 func (e scopedExpect) step(c *gc.C, ctx *context) { 3144 c.Logf("\nexpect: %s %s\n", e.what, strings.Join(e.scope, " ")) 3145 3146 // Now execute the command for each format. 3147 for _, format := range statusFormats { 3148 c.Logf("format %q", format.name) 3149 // Run command with the required format. 3150 args := []string{"--format", format.name} 3151 if ctx.expectIsoTime { 3152 args = append(args, "--utc") 3153 } 3154 args = append(args, e.scope...) 3155 c.Logf("running status %s", strings.Join(args, " ")) 3156 code, stdout, stderr := runStatus(c, args...) 3157 c.Assert(code, gc.Equals, 0) 3158 if !c.Check(stderr, gc.HasLen, 0) { 3159 c.Fatalf("status failed: %s", string(stderr)) 3160 } 3161 3162 // Prepare the output in the same format. 3163 buf, err := format.marshal(e.output) 3164 c.Assert(err, jc.ErrorIsNil) 3165 expected := make(M) 3166 err = format.unmarshal(buf, &expected) 3167 c.Assert(err, jc.ErrorIsNil) 3168 3169 // Check the output is as expected. 3170 actual := make(M) 3171 out := substituteFakeSinceTime(c, stdout, ctx.expectIsoTime) 3172 err = format.unmarshal(out, &actual) 3173 c.Assert(err, jc.ErrorIsNil) 3174 c.Assert(actual, jc.DeepEquals, expected) 3175 } 3176 } 3177 3178 func (e expect) step(c *gc.C, ctx *context) { 3179 scopedExpect{e.what, nil, e.output}.step(c, ctx) 3180 } 3181 3182 type setToolsUpgradeAvailable struct{} 3183 3184 func (ua setToolsUpgradeAvailable) step(c *gc.C, ctx *context) { 3185 model, err := ctx.st.Model() 3186 c.Assert(err, jc.ErrorIsNil) 3187 err = model.UpdateLatestToolsVersion(nextVersion) 3188 c.Assert(err, jc.ErrorIsNil) 3189 } 3190 3191 func (s *StatusSuite) TestStatusAllFormats(c *gc.C) { 3192 for i, t := range statusTests { 3193 c.Logf("test %d: %s", i, t.summary) 3194 func(t testCase) { 3195 // Prepare context and run all steps to setup. 3196 ctx := s.newContext(c) 3197 defer s.resetContext(c, ctx) 3198 ctx.run(c, t.steps) 3199 }(t) 3200 } 3201 } 3202 3203 func (s *StatusSuite) TestMigrationInProgress(c *gc.C) { 3204 // This test isn't part of statusTests because migrations can't be 3205 // run on controller models. 3206 st := s.setupMigrationTest(c) 3207 defer st.Close() 3208 3209 expected := M{ 3210 "model": M{ 3211 "name": "hosted", 3212 "controller": "kontroll", 3213 "cloud": "dummy", 3214 "region": "dummy-region", 3215 "version": "1.2.3", 3216 "migration": "foo bar", 3217 }, 3218 "machines": M{}, 3219 "applications": M{}, 3220 } 3221 3222 for _, format := range statusFormats { 3223 code, stdout, stderr := runStatus(c, "-m", "hosted", "--format", format.name) 3224 c.Check(code, gc.Equals, 0) 3225 c.Assert(stderr, gc.HasLen, 0, gc.Commentf("status failed: %s", stderr)) 3226 3227 // Roundtrip expected through format so that types will match. 3228 buf, err := format.marshal(expected) 3229 c.Assert(err, jc.ErrorIsNil) 3230 var expectedForFormat M 3231 err = format.unmarshal(buf, &expectedForFormat) 3232 c.Assert(err, jc.ErrorIsNil) 3233 3234 var actual M 3235 c.Assert(format.unmarshal(stdout, &actual), jc.ErrorIsNil) 3236 c.Check(actual, jc.DeepEquals, expectedForFormat) 3237 } 3238 } 3239 3240 func (s *StatusSuite) TestMigrationInProgressTabular(c *gc.C) { 3241 expected := ` 3242 MODEL CONTROLLER CLOUD/REGION VERSION NOTES 3243 hosted kontroll dummy/dummy-region 1.2.3 migrating: foo bar 3244 3245 APP VERSION STATUS SCALE CHARM STORE REV OS NOTES 3246 3247 UNIT WORKLOAD AGENT MACHINE PUBLIC-ADDRESS PORTS MESSAGE 3248 3249 MACHINE STATE DNS INS-ID SERIES AZ 3250 3251 `[1:] 3252 3253 st := s.setupMigrationTest(c) 3254 defer st.Close() 3255 code, stdout, stderr := runStatus(c, "-m", "hosted", "--format", "tabular") 3256 c.Check(code, gc.Equals, 0) 3257 c.Assert(stderr, gc.HasLen, 0, gc.Commentf("status failed: %s", stderr)) 3258 c.Assert(string(stdout), gc.Equals, expected) 3259 } 3260 3261 func (s *StatusSuite) TestMigrationInProgressAndUpgradeAvailable(c *gc.C) { 3262 expected := ` 3263 MODEL CONTROLLER CLOUD/REGION VERSION NOTES 3264 hosted kontroll dummy/dummy-region 1.2.3 migrating: foo bar 3265 3266 APP VERSION STATUS SCALE CHARM STORE REV OS NOTES 3267 3268 UNIT WORKLOAD AGENT MACHINE PUBLIC-ADDRESS PORTS MESSAGE 3269 3270 MACHINE STATE DNS INS-ID SERIES AZ 3271 3272 `[1:] 3273 3274 st := s.setupMigrationTest(c) 3275 defer st.Close() 3276 3277 model, err := st.Model() 3278 c.Assert(err, jc.ErrorIsNil) 3279 err = model.UpdateLatestToolsVersion(nextVersion) 3280 c.Assert(err, jc.ErrorIsNil) 3281 3282 code, stdout, stderr := runStatus(c, "-m", "hosted", "--format", "tabular") 3283 c.Check(code, gc.Equals, 0) 3284 c.Assert(stderr, gc.HasLen, 0, gc.Commentf("status failed: %s", stderr)) 3285 c.Assert(string(stdout), gc.Equals, expected) 3286 } 3287 3288 func (s *StatusSuite) setupMigrationTest(c *gc.C) *state.State { 3289 const hostedModelName = "hosted" 3290 const statusText = "foo bar" 3291 3292 f := factory.NewFactory(s.BackingState) 3293 hostedSt := f.MakeModel(c, &factory.ModelParams{ 3294 Name: hostedModelName, 3295 }) 3296 3297 mig, err := hostedSt.CreateMigration(state.MigrationSpec{ 3298 InitiatedBy: names.NewUserTag("admin"), 3299 TargetInfo: migration.TargetInfo{ 3300 ControllerTag: names.NewControllerTag(utils.MustNewUUID().String()), 3301 Addrs: []string{"1.2.3.4:5555", "4.3.2.1:6666"}, 3302 CACert: "cert", 3303 AuthTag: names.NewUserTag("user"), 3304 Password: "password", 3305 }, 3306 }) 3307 c.Assert(err, jc.ErrorIsNil) 3308 err = mig.SetStatusMessage(statusText) 3309 c.Assert(err, jc.ErrorIsNil) 3310 3311 return hostedSt 3312 } 3313 3314 type fakeAPIClient struct { 3315 statusReturn *params.FullStatus 3316 patternsUsed []string 3317 closeCalled bool 3318 } 3319 3320 func (a *fakeAPIClient) Status(patterns []string) (*params.FullStatus, error) { 3321 a.patternsUsed = patterns 3322 return a.statusReturn, nil 3323 } 3324 3325 func (a *fakeAPIClient) Close() error { 3326 a.closeCalled = true 3327 return nil 3328 } 3329 3330 func (s *StatusSuite) TestStatusWithFormatSummary(c *gc.C) { 3331 ctx := s.newContext(c) 3332 defer s.resetContext(c, ctx) 3333 steps := []stepper{ 3334 addMachine{machineId: "0", job: state.JobManageModel}, 3335 setAddresses{"0", network.NewAddresses("localhost")}, 3336 startAliveMachine{"0"}, 3337 setMachineStatus{"0", status.Started, ""}, 3338 addCharm{"wordpress"}, 3339 addCharm{"mysql"}, 3340 addCharm{"logging"}, 3341 addService{name: "wordpress", charm: "wordpress"}, 3342 setServiceExposed{"wordpress", true}, 3343 addMachine{machineId: "1", job: state.JobHostUnits}, 3344 setAddresses{"1", network.NewAddresses("localhost")}, 3345 startAliveMachine{"1"}, 3346 setMachineStatus{"1", status.Started, ""}, 3347 addAliveUnit{"wordpress", "1"}, 3348 setAgentStatus{"wordpress/0", status.Idle, "", nil}, 3349 setUnitStatus{"wordpress/0", status.Active, "", nil}, 3350 addService{name: "mysql", charm: "mysql"}, 3351 setServiceExposed{"mysql", true}, 3352 addMachine{machineId: "2", job: state.JobHostUnits}, 3353 setAddresses{"2", network.NewAddresses("10.0.0.1")}, 3354 startAliveMachine{"2"}, 3355 setMachineStatus{"2", status.Started, ""}, 3356 addAliveUnit{"mysql", "2"}, 3357 setAgentStatus{"mysql/0", status.Idle, "", nil}, 3358 setUnitStatus{"mysql/0", status.Active, "", nil}, 3359 addService{name: "logging", charm: "logging"}, 3360 setServiceExposed{"logging", true}, 3361 relateServices{"wordpress", "mysql"}, 3362 relateServices{"wordpress", "logging"}, 3363 relateServices{"mysql", "logging"}, 3364 addSubordinate{"wordpress/0", "logging"}, 3365 addSubordinate{"mysql/0", "logging"}, 3366 setUnitsAlive{"logging"}, 3367 setAgentStatus{"logging/0", status.Idle, "", nil}, 3368 setUnitStatus{"logging/0", status.Active, "", nil}, 3369 setAgentStatus{"logging/1", status.Error, "somehow lost in all those logs", nil}, 3370 } 3371 for _, s := range steps { 3372 s.step(c, ctx) 3373 } 3374 code, stdout, stderr := runStatus(c, "--format", "summary") 3375 c.Check(code, gc.Equals, 0) 3376 c.Check(string(stderr), gc.Equals, "") 3377 c.Assert(string(stdout), gc.Equals, ` 3378 Running on subnets: 127.0.0.1/8, 10.0.0.1/8 3379 Utilizing ports: 3380 # MACHINES: (3) 3381 started: 3 3382 3383 # UNITS: (4) 3384 active: 3 3385 error: 1 3386 3387 # APPLICATIONS: (3) 3388 logging 1/1 exposed 3389 mysql 1/1 exposed 3390 wordpress 1/1 exposed 3391 3392 `[1:]) 3393 } 3394 func (s *StatusSuite) TestStatusWithFormatOneline(c *gc.C) { 3395 ctx := s.newContext(c) 3396 defer s.resetContext(c, ctx) 3397 steps := []stepper{ 3398 addMachine{machineId: "0", job: state.JobManageModel}, 3399 setAddresses{"0", network.NewAddresses("controller-0.dns")}, 3400 startAliveMachine{"0"}, 3401 setMachineStatus{"0", status.Started, ""}, 3402 addCharm{"wordpress"}, 3403 addCharm{"mysql"}, 3404 addCharm{"logging"}, 3405 3406 addService{name: "wordpress", charm: "wordpress"}, 3407 setServiceExposed{"wordpress", true}, 3408 addMachine{machineId: "1", job: state.JobHostUnits}, 3409 setAddresses{"1", network.NewAddresses("controller-1.dns")}, 3410 startAliveMachine{"1"}, 3411 setMachineStatus{"1", status.Started, ""}, 3412 addAliveUnit{"wordpress", "1"}, 3413 setAgentStatus{"wordpress/0", status.Idle, "", nil}, 3414 setUnitStatus{"wordpress/0", status.Active, "", nil}, 3415 3416 addService{name: "mysql", charm: "mysql"}, 3417 setServiceExposed{"mysql", true}, 3418 addMachine{machineId: "2", job: state.JobHostUnits}, 3419 setAddresses{"2", network.NewAddresses("controller-2.dns")}, 3420 startAliveMachine{"2"}, 3421 setMachineStatus{"2", status.Started, ""}, 3422 addAliveUnit{"mysql", "2"}, 3423 setAgentStatus{"mysql/0", status.Idle, "", nil}, 3424 setUnitStatus{"mysql/0", status.Active, "", nil}, 3425 3426 addService{name: "logging", charm: "logging"}, 3427 setServiceExposed{"logging", true}, 3428 3429 relateServices{"wordpress", "mysql"}, 3430 relateServices{"wordpress", "logging"}, 3431 relateServices{"mysql", "logging"}, 3432 3433 addSubordinate{"wordpress/0", "logging"}, 3434 addSubordinate{"mysql/0", "logging"}, 3435 3436 setUnitsAlive{"logging"}, 3437 setAgentStatus{"logging/0", status.Idle, "", nil}, 3438 setUnitStatus{"logging/0", status.Active, "", nil}, 3439 setAgentStatus{"logging/1", status.Error, "somehow lost in all those logs", nil}, 3440 } 3441 3442 ctx.run(c, steps) 3443 3444 const expected = ` 3445 - mysql/0: controller-2.dns (agent:idle, workload:active) 3446 - logging/1: controller-2.dns (agent:idle, workload:error) 3447 - wordpress/0: controller-1.dns (agent:idle, workload:active) 3448 - logging/0: controller-1.dns (agent:idle, workload:active) 3449 ` 3450 assertOneLineStatus(c, expected) 3451 } 3452 3453 func assertOneLineStatus(c *gc.C, expected string) { 3454 code, stdout, stderr := runStatus(c, "--format", "oneline") 3455 c.Check(code, gc.Equals, 0) 3456 c.Check(string(stderr), gc.Equals, "") 3457 c.Assert(string(stdout), gc.Equals, expected) 3458 3459 c.Log(`Check that "short" is an alias for oneline.`) 3460 code, stdout, stderr = runStatus(c, "--format", "short") 3461 c.Check(code, gc.Equals, 0) 3462 c.Check(string(stderr), gc.Equals, "") 3463 c.Assert(string(stdout), gc.Equals, expected) 3464 3465 c.Log(`Check that "line" is an alias for oneline.`) 3466 code, stdout, stderr = runStatus(c, "--format", "line") 3467 c.Check(code, gc.Equals, 0) 3468 c.Check(string(stderr), gc.Equals, "") 3469 c.Assert(string(stdout), gc.Equals, expected) 3470 } 3471 3472 func (s *StatusSuite) prepareTabularData(c *gc.C) *context { 3473 ctx := s.newContext(c) 3474 steps := []stepper{ 3475 setToolsUpgradeAvailable{}, 3476 addMachine{machineId: "0", job: state.JobManageModel}, 3477 setAddresses{"0", network.NewAddresses("controller-0.dns")}, 3478 startMachineWithHardware{"0", instance.MustParseHardware("availability-zone=us-east-1a")}, 3479 setMachineStatus{"0", status.Started, ""}, 3480 addCharm{"wordpress"}, 3481 addCharm{"mysql"}, 3482 addCharm{"logging"}, 3483 addService{name: "wordpress", charm: "wordpress"}, 3484 setServiceExposed{"wordpress", true}, 3485 addMachine{machineId: "1", job: state.JobHostUnits}, 3486 setAddresses{"1", network.NewAddresses("controller-1.dns")}, 3487 startAliveMachine{"1"}, 3488 setMachineStatus{"1", status.Started, ""}, 3489 addAliveUnit{"wordpress", "1"}, 3490 setAgentStatus{"wordpress/0", status.Idle, "", nil}, 3491 setUnitStatus{"wordpress/0", status.Active, "", nil}, 3492 setUnitTools{"wordpress/0", version.MustParseBinary("1.2.3-trusty-ppc")}, 3493 addService{name: "mysql", charm: "mysql"}, 3494 setServiceExposed{"mysql", true}, 3495 addMachine{machineId: "2", job: state.JobHostUnits}, 3496 setAddresses{"2", network.NewAddresses("controller-2.dns")}, 3497 startAliveMachine{"2"}, 3498 setMachineStatus{"2", status.Started, ""}, 3499 addAliveUnit{"mysql", "2"}, 3500 setAgentStatus{"mysql/0", status.Idle, "", nil}, 3501 setUnitStatus{ 3502 "mysql/0", 3503 status.Maintenance, 3504 "installing all the things", nil}, 3505 setUnitTools{"mysql/0", version.MustParseBinary("1.2.3-trusty-ppc")}, 3506 addService{name: "logging", charm: "logging"}, 3507 setServiceExposed{"logging", true}, 3508 relateServices{"wordpress", "mysql"}, 3509 relateServices{"wordpress", "logging"}, 3510 relateServices{"mysql", "logging"}, 3511 addSubordinate{"wordpress/0", "logging"}, 3512 addSubordinate{"mysql/0", "logging"}, 3513 setUnitsAlive{"logging"}, 3514 setAgentStatus{"logging/0", status.Idle, "", nil}, 3515 setUnitStatus{"logging/0", status.Active, "", nil}, 3516 setAgentStatus{"logging/1", status.Error, "somehow lost in all those logs", nil}, 3517 setUnitWorkloadVersion{"logging/1", "a bit too long, really"}, 3518 setUnitWorkloadVersion{"wordpress/0", "4.5.3"}, 3519 setUnitWorkloadVersion{"mysql/0", "5.7.13"}, 3520 setUnitAsLeader{"mysql/0"}, 3521 setUnitAsLeader{"logging/1"}, 3522 setUnitAsLeader{"wordpress/0"}, 3523 } 3524 for _, s := range steps { 3525 s.step(c, ctx) 3526 } 3527 return ctx 3528 } 3529 3530 func (s *StatusSuite) testStatusWithFormatTabular(c *gc.C, useFeatureFlag bool) { 3531 ctx := s.prepareTabularData(c) 3532 defer s.resetContext(c, ctx) 3533 var args []string 3534 if !useFeatureFlag { 3535 args = []string{"--format", "tabular"} 3536 } 3537 code, stdout, stderr := runStatus(c, args...) 3538 c.Check(code, gc.Equals, 0) 3539 c.Check(string(stderr), gc.Equals, "") 3540 expected := ` 3541 MODEL CONTROLLER CLOUD/REGION VERSION NOTES 3542 controller kontroll dummy/dummy-region 1.2.3 upgrade available: 1.2.4 3543 3544 APP VERSION STATUS SCALE CHARM STORE REV OS NOTES 3545 logging a bit too lo... error 2 logging jujucharms 1 ubuntu exposed 3546 mysql 5.7.13 maintenance 1 mysql jujucharms 1 ubuntu exposed 3547 wordpress 4.5.3 active 1 wordpress jujucharms 3 ubuntu exposed 3548 3549 UNIT WORKLOAD AGENT MACHINE PUBLIC-ADDRESS PORTS MESSAGE 3550 mysql/0* maintenance idle 2 controller-2.dns installing all the things 3551 logging/1* error idle controller-2.dns somehow lost in all those logs 3552 wordpress/0* active idle 1 controller-1.dns 3553 logging/0 active idle controller-1.dns 3554 3555 MACHINE STATE DNS INS-ID SERIES AZ 3556 0 started controller-0.dns controller-0 quantal us-east-1a 3557 1 started controller-1.dns controller-1 quantal 3558 2 started controller-2.dns controller-2 quantal 3559 3560 RELATION PROVIDES CONSUMES TYPE 3561 juju-info logging mysql regular 3562 logging-dir logging wordpress regular 3563 info mysql logging subordinate 3564 db mysql wordpress regular 3565 logging-directory wordpress logging subordinate 3566 3567 `[1:] 3568 c.Assert(string(stdout), gc.Equals, expected) 3569 } 3570 3571 func (s *StatusSuite) TestStatusWithFormatTabular(c *gc.C) { 3572 s.testStatusWithFormatTabular(c, false) 3573 } 3574 3575 func (s *StatusSuite) TestFormatTabularHookActionName(c *gc.C) { 3576 status := formattedStatus{ 3577 Applications: map[string]applicationStatus{ 3578 "foo": { 3579 Units: map[string]unitStatus{ 3580 "foo/0": { 3581 JujuStatusInfo: statusInfoContents{ 3582 Current: status.Executing, 3583 Message: "running config-changed hook", 3584 }, 3585 WorkloadStatusInfo: statusInfoContents{ 3586 Current: status.Maintenance, 3587 Message: "doing some work", 3588 }, 3589 }, 3590 "foo/1": { 3591 JujuStatusInfo: statusInfoContents{ 3592 Current: status.Executing, 3593 Message: "running action backup database", 3594 }, 3595 WorkloadStatusInfo: statusInfoContents{ 3596 Current: status.Maintenance, 3597 Message: "doing some work", 3598 }, 3599 }, 3600 }, 3601 }, 3602 }, 3603 } 3604 out := &bytes.Buffer{} 3605 err := FormatTabular(out, false, status) 3606 c.Assert(err, jc.ErrorIsNil) 3607 c.Assert(out.String(), gc.Equals, ` 3608 MODEL CONTROLLER CLOUD/REGION VERSION 3609 3610 3611 APP VERSION STATUS SCALE CHARM STORE REV OS NOTES 3612 foo 2 0 3613 3614 UNIT WORKLOAD AGENT MACHINE PUBLIC-ADDRESS PORTS MESSAGE 3615 foo/0 maintenance executing (config-changed) doing some work 3616 foo/1 maintenance executing (backup database) doing some work 3617 3618 MACHINE STATE DNS INS-ID SERIES AZ 3619 `[1:]) 3620 } 3621 3622 func (s *StatusSuite) TestFormatTabularConsistentPeerRelationName(c *gc.C) { 3623 status := formattedStatus{ 3624 Applications: map[string]applicationStatus{ 3625 "foo": { 3626 Relations: map[string][]string{ 3627 "coordinator": {"foo"}, 3628 "frobulator": {"foo"}, 3629 "encapsulator": {"foo"}, 3630 "catchulator": {"foo"}, 3631 "perforator": {"foo"}, 3632 "deliverator": {"foo"}, 3633 "replicator": {"foo"}, 3634 }, 3635 }, 3636 }, 3637 } 3638 out := &bytes.Buffer{} 3639 err := FormatTabular(out, false, status) 3640 c.Assert(err, jc.ErrorIsNil) 3641 sections, err := splitTableSections(out.Bytes()) 3642 c.Assert(err, jc.ErrorIsNil) 3643 c.Assert(sections["RELATION"], gc.DeepEquals, []string{ 3644 "RELATION PROVIDES CONSUMES TYPE", 3645 "replicator foo foo peer", 3646 }) 3647 } 3648 3649 func (s *StatusSuite) TestStatusWithNilStatusAPI(c *gc.C) { 3650 ctx := s.newContext(c) 3651 defer s.resetContext(c, ctx) 3652 steps := []stepper{ 3653 addMachine{machineId: "0", job: state.JobManageModel}, 3654 setAddresses{"0", network.NewAddresses("controller-0.dns")}, 3655 startAliveMachine{"0"}, 3656 setMachineStatus{"0", status.Started, ""}, 3657 } 3658 3659 for _, s := range steps { 3660 s.step(c, ctx) 3661 } 3662 3663 client := fakeAPIClient{} 3664 var status = client.Status 3665 s.PatchValue(&status, func(_ []string) (*params.FullStatus, error) { 3666 return nil, nil 3667 }) 3668 s.PatchValue(&newAPIClientForStatus, func(_ *statusCommand) (statusAPI, error) { 3669 return &client, nil 3670 }) 3671 3672 code, _, stderr := runStatus(c, "--format", "tabular") 3673 c.Check(code, gc.Equals, 1) 3674 c.Check(string(stderr), gc.Equals, "error: unable to obtain the current status\n") 3675 } 3676 3677 func (s *StatusSuite) TestFormatTabularMetering(c *gc.C) { 3678 status := formattedStatus{ 3679 Applications: map[string]applicationStatus{ 3680 "foo": { 3681 Units: map[string]unitStatus{ 3682 "foo/0": { 3683 MeterStatus: &meterStatus{ 3684 Color: "strange", 3685 Message: "warning: stable strangelets", 3686 }, 3687 }, 3688 "foo/1": { 3689 MeterStatus: &meterStatus{ 3690 Color: "up", 3691 Message: "things are looking up", 3692 }, 3693 }, 3694 }, 3695 }, 3696 }, 3697 } 3698 out := &bytes.Buffer{} 3699 err := FormatTabular(out, false, status) 3700 c.Assert(err, jc.ErrorIsNil) 3701 c.Assert(out.String(), gc.Equals, ` 3702 MODEL CONTROLLER CLOUD/REGION VERSION 3703 3704 3705 APP VERSION STATUS SCALE CHARM STORE REV OS NOTES 3706 foo 0/2 0 3707 3708 UNIT WORKLOAD AGENT MACHINE PUBLIC-ADDRESS PORTS MESSAGE 3709 foo/0 3710 foo/1 3711 3712 METER STATUS MESSAGE 3713 foo/0 strange warning: stable strangelets 3714 foo/1 up things are looking up 3715 3716 MACHINE STATE DNS INS-ID SERIES AZ 3717 `[1:]) 3718 } 3719 3720 // 3721 // Filtering Feature 3722 // 3723 3724 func (s *StatusSuite) FilteringTestSetup(c *gc.C) *context { 3725 ctx := s.newContext(c) 3726 3727 steps := []stepper{ 3728 // Given a machine is started 3729 // And the machine's ID is "0" 3730 // And the machine's job is to manage the environment 3731 addMachine{machineId: "0", job: state.JobManageModel}, 3732 startAliveMachine{"0"}, 3733 setMachineStatus{"0", status.Started, ""}, 3734 // And the machine's address is "controller-0.dns" 3735 setAddresses{"0", network.NewAddresses("controller-0.dns")}, 3736 // And a container is started 3737 // And the container's ID is "0/lxd/0" 3738 addContainer{"0", "0/lxd/0", state.JobHostUnits}, 3739 3740 // And the "wordpress" charm is available 3741 addCharm{"wordpress"}, 3742 addService{name: "wordpress", charm: "wordpress"}, 3743 // And the "mysql" charm is available 3744 addCharm{"mysql"}, 3745 addService{name: "mysql", charm: "mysql"}, 3746 // And the "logging" charm is available 3747 addCharm{"logging"}, 3748 3749 // And a machine is started 3750 // And the machine's ID is "1" 3751 // And the machine's job is to host units 3752 addMachine{machineId: "1", job: state.JobHostUnits}, 3753 startAliveMachine{"1"}, 3754 setMachineStatus{"1", status.Started, ""}, 3755 // And the machine's address is "controller-1.dns" 3756 setAddresses{"1", network.NewAddresses("controller-1.dns")}, 3757 // And a unit of "wordpress" is deployed to machine "1" 3758 addAliveUnit{"wordpress", "1"}, 3759 // And the unit is started 3760 setAgentStatus{"wordpress/0", status.Idle, "", nil}, 3761 setUnitStatus{"wordpress/0", status.Active, "", nil}, 3762 // And a machine is started 3763 3764 // And the machine's ID is "2" 3765 // And the machine's job is to host units 3766 addMachine{machineId: "2", job: state.JobHostUnits}, 3767 startAliveMachine{"2"}, 3768 setMachineStatus{"2", status.Started, ""}, 3769 // And the machine's address is "controller-2.dns" 3770 setAddresses{"2", network.NewAddresses("controller-2.dns")}, 3771 // And a unit of "mysql" is deployed to machine "2" 3772 addAliveUnit{"mysql", "2"}, 3773 // And the unit is started 3774 setAgentStatus{"mysql/0", status.Idle, "", nil}, 3775 setUnitStatus{"mysql/0", status.Active, "", nil}, 3776 // And the "logging" service is added 3777 addService{name: "logging", charm: "logging"}, 3778 // And the service is exposed 3779 setServiceExposed{"logging", true}, 3780 // And the "wordpress" service is related to the "mysql" service 3781 relateServices{"wordpress", "mysql"}, 3782 // And the "wordpress" service is related to the "logging" service 3783 relateServices{"wordpress", "logging"}, 3784 // And the "mysql" service is related to the "logging" service 3785 relateServices{"mysql", "logging"}, 3786 // And the "logging" service is a subordinate to unit 0 of the "wordpress" service 3787 addSubordinate{"wordpress/0", "logging"}, 3788 setAgentStatus{"logging/0", status.Idle, "", nil}, 3789 setUnitStatus{"logging/0", status.Active, "", nil}, 3790 // And the "logging" service is a subordinate to unit 0 of the "mysql" service 3791 addSubordinate{"mysql/0", "logging"}, 3792 setAgentStatus{"logging/1", status.Idle, "", nil}, 3793 setUnitStatus{"logging/1", status.Active, "", nil}, 3794 setUnitsAlive{"logging"}, 3795 } 3796 3797 ctx.run(c, steps) 3798 return ctx 3799 } 3800 3801 // Scenario: One unit is in an errored state and user filters to active 3802 func (s *StatusSuite) TestFilterToActive(c *gc.C) { 3803 ctx := s.FilteringTestSetup(c) 3804 defer s.resetContext(c, ctx) 3805 3806 // Given unit 1 of the "logging" service has an error 3807 setAgentStatus{"logging/1", status.Error, "mock error", nil}.step(c, ctx) 3808 // And unit 0 of the "mysql" service has an error 3809 setAgentStatus{"mysql/0", status.Error, "mock error", nil}.step(c, ctx) 3810 // When I run juju status --format oneline started 3811 _, stdout, stderr := runStatus(c, "--format", "oneline", "active") 3812 c.Assert(string(stderr), gc.Equals, "") 3813 // Then I should receive output prefixed with: 3814 const expected = ` 3815 3816 - wordpress/0: controller-1.dns (agent:idle, workload:active) 3817 - logging/0: controller-1.dns (agent:idle, workload:active) 3818 ` 3819 c.Assert(string(stdout), gc.Equals, expected[1:]) 3820 } 3821 3822 // Scenario: user filters to a single machine 3823 func (s *StatusSuite) TestFilterToMachine(c *gc.C) { 3824 ctx := s.FilteringTestSetup(c) 3825 defer s.resetContext(c, ctx) 3826 3827 // When I run juju status --format oneline 1 3828 _, stdout, stderr := runStatus(c, "--format", "oneline", "1") 3829 c.Assert(string(stderr), gc.Equals, "") 3830 // Then I should receive output prefixed with: 3831 const expected = ` 3832 3833 - wordpress/0: controller-1.dns (agent:idle, workload:active) 3834 - logging/0: controller-1.dns (agent:idle, workload:active) 3835 ` 3836 c.Assert(string(stdout), gc.Equals, expected[1:]) 3837 } 3838 3839 // Scenario: user filters to a machine, shows containers 3840 func (s *StatusSuite) TestFilterToMachineShowsContainer(c *gc.C) { 3841 ctx := s.FilteringTestSetup(c) 3842 defer s.resetContext(c, ctx) 3843 3844 // When I run juju status --format yaml 0 3845 _, stdout, stderr := runStatus(c, "--format", "yaml", "0") 3846 c.Assert(string(stderr), gc.Equals, "") 3847 // Then I should receive output matching: 3848 const expected = "(.|\n)*machines:(.|\n)*\"0\"(.|\n)*0/lxd/0(.|\n)*" 3849 c.Assert(string(stdout), gc.Matches, expected) 3850 } 3851 3852 // Scenario: user filters to a container 3853 func (s *StatusSuite) TestFilterToContainer(c *gc.C) { 3854 ctx := s.FilteringTestSetup(c) 3855 defer s.resetContext(c, ctx) 3856 3857 // When I run juju status --format yaml 0/lxd/0 3858 _, stdout, stderr := runStatus(c, "--format", "yaml", "0/lxd/0") 3859 c.Assert(string(stderr), gc.Equals, "") 3860 out := substituteFakeSinceTime(c, stdout, ctx.expectIsoTime) 3861 const expected = "" + 3862 "model:\n" + 3863 " name: controller\n" + 3864 " controller: kontroll\n" + 3865 " cloud: dummy\n" + 3866 " region: dummy-region\n" + 3867 " version: 1.2.3\n" + 3868 "machines:\n" + 3869 " \"0\":\n" + 3870 " juju-status:\n" + 3871 " current: started\n" + 3872 " since: 01 Apr 15 01:23+10:00\n" + 3873 " dns-name: controller-0.dns\n" + 3874 " instance-id: controller-0\n" + 3875 " machine-status:\n" + 3876 " current: pending\n" + 3877 " since: 01 Apr 15 01:23+10:00\n" + 3878 " series: quantal\n" + 3879 " containers:\n" + 3880 " 0/lxd/0:\n" + 3881 " juju-status:\n" + 3882 " current: pending\n" + 3883 " since: 01 Apr 15 01:23+10:00\n" + 3884 " instance-id: pending\n" + 3885 " machine-status:\n" + 3886 " current: pending\n" + 3887 " since: 01 Apr 15 01:23+10:00\n" + 3888 " series: quantal\n" + 3889 " hardware: arch=amd64 cores=1 mem=1024M root-disk=8192M\n" + 3890 " controller-member-status: adding-vote\n" + 3891 "applications: {}\n" 3892 3893 c.Assert(string(out), gc.Equals, expected) 3894 } 3895 3896 // Scenario: One unit is in an errored state and user filters to errored 3897 func (s *StatusSuite) TestFilterToErrored(c *gc.C) { 3898 ctx := s.FilteringTestSetup(c) 3899 defer s.resetContext(c, ctx) 3900 3901 // Given unit 1 of the "logging" service has an error 3902 setAgentStatus{"logging/1", status.Error, "mock error", nil}.step(c, ctx) 3903 // When I run juju status --format oneline error 3904 _, stdout, stderr := runStatus(c, "--format", "oneline", "error") 3905 c.Assert(stderr, gc.IsNil) 3906 // Then I should receive output prefixed with: 3907 const expected = ` 3908 3909 - mysql/0: controller-2.dns (agent:idle, workload:active) 3910 - logging/1: controller-2.dns (agent:idle, workload:error) 3911 ` 3912 c.Assert(string(stdout), gc.Equals, expected[1:]) 3913 } 3914 3915 // Scenario: User filters to mysql service 3916 func (s *StatusSuite) TestFilterToService(c *gc.C) { 3917 ctx := s.FilteringTestSetup(c) 3918 defer s.resetContext(c, ctx) 3919 3920 // When I run juju status --format oneline error 3921 _, stdout, stderr := runStatus(c, "--format", "oneline", "mysql") 3922 c.Assert(stderr, gc.IsNil) 3923 // Then I should receive output prefixed with: 3924 const expected = ` 3925 3926 - mysql/0: controller-2.dns (agent:idle, workload:active) 3927 - logging/1: controller-2.dns (agent:idle, workload:active) 3928 ` 3929 3930 c.Assert(string(stdout), gc.Equals, expected[1:]) 3931 } 3932 3933 // Scenario: User filters to exposed services 3934 func (s *StatusSuite) TestFilterToExposedService(c *gc.C) { 3935 ctx := s.FilteringTestSetup(c) 3936 defer s.resetContext(c, ctx) 3937 3938 // Given unit 1 of the "mysql" service is exposed 3939 setServiceExposed{"mysql", true}.step(c, ctx) 3940 // And the logging service is not exposed 3941 setServiceExposed{"logging", false}.step(c, ctx) 3942 // And the wordpress service is not exposed 3943 setServiceExposed{"wordpress", false}.step(c, ctx) 3944 // When I run juju status --format oneline exposed 3945 _, stdout, stderr := runStatus(c, "--format", "oneline", "exposed") 3946 c.Assert(stderr, gc.IsNil) 3947 // Then I should receive output prefixed with: 3948 const expected = ` 3949 3950 - mysql/0: controller-2.dns (agent:idle, workload:active) 3951 - logging/1: controller-2.dns (agent:idle, workload:active) 3952 ` 3953 c.Assert(string(stdout), gc.Equals, expected[1:]) 3954 } 3955 3956 // Scenario: User filters to non-exposed services 3957 func (s *StatusSuite) TestFilterToNotExposedService(c *gc.C) { 3958 ctx := s.FilteringTestSetup(c) 3959 defer s.resetContext(c, ctx) 3960 3961 setServiceExposed{"mysql", true}.step(c, ctx) 3962 // When I run juju status --format oneline not exposed 3963 _, stdout, stderr := runStatus(c, "--format", "oneline", "not", "exposed") 3964 c.Assert(stderr, gc.IsNil) 3965 // Then I should receive output prefixed with: 3966 const expected = ` 3967 3968 - wordpress/0: controller-1.dns (agent:idle, workload:active) 3969 - logging/0: controller-1.dns (agent:idle, workload:active) 3970 ` 3971 c.Assert(string(stdout), gc.Equals, expected[1:]) 3972 } 3973 3974 // Scenario: Filtering on Subnets 3975 func (s *StatusSuite) TestFilterOnSubnet(c *gc.C) { 3976 ctx := s.FilteringTestSetup(c) 3977 defer s.resetContext(c, ctx) 3978 3979 // Given the address for machine "1" is "localhost" 3980 setAddresses{"1", network.NewAddresses("localhost", "127.0.0.1")}.step(c, ctx) 3981 // And the address for machine "2" is "10.0.0.1" 3982 setAddresses{"2", network.NewAddresses("10.0.0.1")}.step(c, ctx) 3983 // When I run juju status --format oneline 127.0.0.1 3984 _, stdout, stderr := runStatus(c, "--format", "oneline", "127.0.0.1") 3985 c.Assert(stderr, gc.IsNil) 3986 // Then I should receive output prefixed with: 3987 const expected = ` 3988 3989 - wordpress/0: localhost (agent:idle, workload:active) 3990 - logging/0: localhost (agent:idle, workload:active) 3991 ` 3992 c.Assert(string(stdout), gc.Equals, expected[1:]) 3993 } 3994 3995 // Scenario: Filtering on Ports 3996 func (s *StatusSuite) TestFilterOnPorts(c *gc.C) { 3997 ctx := s.FilteringTestSetup(c) 3998 defer s.resetContext(c, ctx) 3999 4000 // Given the address for machine "1" is "localhost" 4001 setAddresses{"1", network.NewAddresses("localhost")}.step(c, ctx) 4002 // And the address for machine "2" is "10.0.0.1" 4003 setAddresses{"2", network.NewAddresses("10.0.0.1")}.step(c, ctx) 4004 openUnitPort{"wordpress/0", "tcp", 80}.step(c, ctx) 4005 // When I run juju status --format oneline 80/tcp 4006 _, stdout, stderr := runStatus(c, "--format", "oneline", "80/tcp") 4007 c.Assert(stderr, gc.IsNil) 4008 // Then I should receive output prefixed with: 4009 const expected = ` 4010 4011 - wordpress/0: localhost (agent:idle, workload:active) 80/tcp 4012 - logging/0: localhost (agent:idle, workload:active) 4013 ` 4014 c.Assert(string(stdout), gc.Equals, expected[1:]) 4015 } 4016 4017 // Scenario: User filters out a parent, but not its subordinate 4018 func (s *StatusSuite) TestFilterParentButNotSubordinate(c *gc.C) { 4019 ctx := s.FilteringTestSetup(c) 4020 defer s.resetContext(c, ctx) 4021 4022 // When I run juju status --format oneline 80/tcp 4023 _, stdout, stderr := runStatus(c, "--format", "oneline", "logging") 4024 c.Assert(stderr, gc.IsNil) 4025 // Then I should receive output prefixed with: 4026 const expected = ` 4027 4028 - mysql/0: controller-2.dns (agent:idle, workload:active) 4029 - logging/1: controller-2.dns (agent:idle, workload:active) 4030 - wordpress/0: controller-1.dns (agent:idle, workload:active) 4031 - logging/0: controller-1.dns (agent:idle, workload:active) 4032 ` 4033 c.Assert(string(stdout), gc.Equals, expected[1:]) 4034 } 4035 4036 // Scenario: User filters out a subordinate, but not its parent 4037 func (s *StatusSuite) TestFilterSubordinateButNotParent(c *gc.C) { 4038 ctx := s.FilteringTestSetup(c) 4039 defer s.resetContext(c, ctx) 4040 4041 // Given the wordpress service is exposed 4042 setServiceExposed{"wordpress", true}.step(c, ctx) 4043 // When I run juju status --format oneline not exposed 4044 _, stdout, stderr := runStatus(c, "--format", "oneline", "not", "exposed") 4045 c.Assert(stderr, gc.IsNil) 4046 // Then I should receive output prefixed with: 4047 const expected = ` 4048 4049 - mysql/0: controller-2.dns (agent:idle, workload:active) 4050 - logging/1: controller-2.dns (agent:idle, workload:active) 4051 ` 4052 c.Assert(string(stdout), gc.Equals, expected[1:]) 4053 } 4054 4055 func (s *StatusSuite) TestFilterMultipleHomogenousPatterns(c *gc.C) { 4056 ctx := s.FilteringTestSetup(c) 4057 defer s.resetContext(c, ctx) 4058 4059 _, stdout, stderr := runStatus(c, "--format", "oneline", "wordpress/0", "mysql/0") 4060 c.Assert(stderr, gc.IsNil) 4061 // Then I should receive output prefixed with: 4062 const expected = ` 4063 4064 - mysql/0: controller-2.dns (agent:idle, workload:active) 4065 - logging/1: controller-2.dns (agent:idle, workload:active) 4066 - wordpress/0: controller-1.dns (agent:idle, workload:active) 4067 - logging/0: controller-1.dns (agent:idle, workload:active) 4068 ` 4069 c.Assert(string(stdout), gc.Equals, expected[1:]) 4070 } 4071 4072 func (s *StatusSuite) TestFilterMultipleHeterogenousPatterns(c *gc.C) { 4073 ctx := s.FilteringTestSetup(c) 4074 defer s.resetContext(c, ctx) 4075 4076 _, stdout, stderr := runStatus(c, "--format", "oneline", "wordpress/0", "active") 4077 c.Assert(stderr, gc.IsNil) 4078 // Then I should receive output prefixed with: 4079 const expected = ` 4080 4081 - mysql/0: controller-2.dns (agent:idle, workload:active) 4082 - logging/1: controller-2.dns (agent:idle, workload:active) 4083 - wordpress/0: controller-1.dns (agent:idle, workload:active) 4084 - logging/0: controller-1.dns (agent:idle, workload:active) 4085 ` 4086 c.Assert(string(stdout), gc.Equals, expected[1:]) 4087 } 4088 4089 // TestSummaryStatusWithUnresolvableDns is result of bug# 1410320. 4090 func (s *StatusSuite) TestSummaryStatusWithUnresolvableDns(c *gc.C) { 4091 formatter := &summaryFormatter{} 4092 formatter.resolveAndTrackIp("invalidDns") 4093 // Test should not panic. 4094 } 4095 4096 func initStatusCommand(args ...string) (*statusCommand, error) { 4097 com := &statusCommand{} 4098 return com, coretesting.InitCommand(modelcmd.Wrap(com), args) 4099 } 4100 4101 var statusInitTests = []struct { 4102 args []string 4103 envVar string 4104 isoTime bool 4105 err string 4106 }{ 4107 { 4108 isoTime: false, 4109 }, { 4110 args: []string{"--utc"}, 4111 isoTime: true, 4112 }, { 4113 envVar: "true", 4114 isoTime: true, 4115 }, { 4116 envVar: "foo", 4117 err: "invalid JUJU_STATUS_ISO_TIME env var, expected true|false.*", 4118 }, 4119 } 4120 4121 func (*StatusSuite) TestStatusCommandInit(c *gc.C) { 4122 defer os.Setenv(osenv.JujuStatusIsoTimeEnvKey, os.Getenv(osenv.JujuStatusIsoTimeEnvKey)) 4123 4124 for i, t := range statusInitTests { 4125 c.Logf("test %d", i) 4126 os.Setenv(osenv.JujuStatusIsoTimeEnvKey, t.envVar) 4127 com, err := initStatusCommand(t.args...) 4128 if t.err != "" { 4129 c.Check(err, gc.ErrorMatches, t.err) 4130 } else { 4131 c.Check(err, jc.ErrorIsNil) 4132 } 4133 c.Check(com.isoTime, gc.DeepEquals, t.isoTime) 4134 } 4135 } 4136 4137 var statusTimeTest = test( 4138 "status generates timestamps as UTC in ISO format", 4139 addMachine{machineId: "0", job: state.JobManageModel}, 4140 setAddresses{"0", network.NewAddresses("controller-0.dns")}, 4141 startAliveMachine{"0"}, 4142 setMachineStatus{"0", status.Started, ""}, 4143 addCharm{"dummy"}, 4144 addService{name: "dummy-application", charm: "dummy"}, 4145 4146 addMachine{machineId: "1", job: state.JobHostUnits}, 4147 startAliveMachine{"1"}, 4148 setAddresses{"1", network.NewAddresses("controller-1.dns")}, 4149 setMachineStatus{"1", status.Started, ""}, 4150 4151 addAliveUnit{"dummy-application", "1"}, 4152 expect{ 4153 "add two units, one alive (in error state), one started", 4154 M{ 4155 "model": M{ 4156 "name": "controller", 4157 "controller": "kontroll", 4158 "cloud": "dummy", 4159 "region": "dummy-region", 4160 "version": "1.2.3", 4161 }, 4162 "machines": M{ 4163 "0": machine0, 4164 "1": machine1, 4165 }, 4166 "applications": M{ 4167 "dummy-application": dummyCharm(M{ 4168 "application-status": M{ 4169 "current": "waiting", 4170 "message": "waiting for machine", 4171 "since": "01 Apr 15 01:23+10:00", 4172 }, 4173 "units": M{ 4174 "dummy-application/0": M{ 4175 "machine": "1", 4176 "workload-status": M{ 4177 "current": "waiting", 4178 "message": "waiting for machine", 4179 "since": "01 Apr 15 01:23+10:00", 4180 }, 4181 "juju-status": M{ 4182 "current": "allocating", 4183 "since": "01 Apr 15 01:23+10:00", 4184 }, 4185 "public-address": "controller-1.dns", 4186 }, 4187 }, 4188 }), 4189 }, 4190 }, 4191 }, 4192 ) 4193 4194 func (s *StatusSuite) TestIsoTimeFormat(c *gc.C) { 4195 func(t testCase) { 4196 // Prepare context and run all steps to setup. 4197 ctx := s.newContext(c) 4198 ctx.expectIsoTime = true 4199 defer s.resetContext(c, ctx) 4200 ctx.run(c, t.steps) 4201 }(statusTimeTest) 4202 } 4203 4204 func (s *StatusSuite) TestFormatProvisioningError(c *gc.C) { 4205 status := ¶ms.FullStatus{ 4206 Model: params.ModelStatusInfo{ 4207 CloudTag: "cloud-dummy", 4208 }, 4209 Machines: map[string]params.MachineStatus{ 4210 "1": { 4211 AgentStatus: params.DetailedStatus{ 4212 Status: "error", 4213 Info: "<error while provisioning>", 4214 }, 4215 InstanceId: "pending", 4216 InstanceStatus: params.DetailedStatus{}, 4217 Series: "trusty", 4218 Id: "1", 4219 Jobs: []multiwatcher.MachineJob{"JobHostUnits"}, 4220 }, 4221 }, 4222 } 4223 formatter := NewStatusFormatter(status, true) 4224 formatted, err := formatter.format() 4225 c.Assert(err, jc.ErrorIsNil) 4226 4227 c.Check(formatted, jc.DeepEquals, formattedStatus{ 4228 Model: modelStatus{ 4229 Cloud: "dummy", 4230 }, 4231 Machines: map[string]machineStatus{ 4232 "1": { 4233 JujuStatus: statusInfoContents{Current: "error", Message: "<error while provisioning>"}, 4234 InstanceId: "pending", 4235 Series: "trusty", 4236 Id: "1", 4237 Containers: map[string]machineStatus{}, 4238 }, 4239 }, 4240 Applications: map[string]applicationStatus{}, 4241 }) 4242 } 4243 4244 type tableSections map[string][]string 4245 4246 func sectionTitle(lines []string) string { 4247 return strings.SplitN(lines[0], " ", 2)[0] 4248 } 4249 4250 func splitTableSections(tableData []byte) (tableSections, error) { 4251 scanner := bufio.NewScanner(bytes.NewReader(tableData)) 4252 result := make(tableSections) 4253 var current []string 4254 for scanner.Scan() { 4255 if line := scanner.Text(); line == "" && current != nil { 4256 result[sectionTitle(current)] = current 4257 current = nil 4258 } else if line != "" { 4259 current = append(current, line) 4260 } 4261 } 4262 if scanner.Err() != nil { 4263 return nil, scanner.Err() 4264 } 4265 if current != nil { 4266 result[sectionTitle(current)] = current 4267 } 4268 return result, nil 4269 }