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