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