github.com/altoros/juju-vmware@v0.0.0-20150312064031-f19ae857ccca/cmd/juju/status_test.go (about) 1 // Copyright 2012, 2013 Canonical Ltd. 2 // Licensed under the AGPLv3, see LICENCE file for details. 3 4 package main 5 6 import ( 7 "bytes" 8 "encoding/json" 9 "fmt" 10 "strings" 11 12 "github.com/juju/cmd" 13 jc "github.com/juju/testing/checkers" 14 gc "gopkg.in/check.v1" 15 "gopkg.in/juju/charm.v4" 16 goyaml "gopkg.in/yaml.v1" 17 18 "github.com/juju/juju/api" 19 "github.com/juju/juju/cmd/envcmd" 20 "github.com/juju/juju/constraints" 21 "github.com/juju/juju/environs" 22 "github.com/juju/juju/instance" 23 "github.com/juju/juju/juju/testing" 24 "github.com/juju/juju/network" 25 "github.com/juju/juju/state" 26 "github.com/juju/juju/state/multiwatcher" 27 "github.com/juju/juju/state/presence" 28 "github.com/juju/juju/testcharms" 29 coretesting "github.com/juju/juju/testing" 30 "github.com/juju/juju/version" 31 ) 32 33 func runStatus(c *gc.C, args ...string) (code int, stdout, stderr []byte) { 34 ctx := coretesting.Context(c) 35 code = cmd.Main(envcmd.Wrap(&StatusCommand{}), ctx, args) 36 stdout = ctx.Stdout.(*bytes.Buffer).Bytes() 37 stderr = ctx.Stderr.(*bytes.Buffer).Bytes() 38 return 39 } 40 41 type StatusSuite struct { 42 testing.JujuConnSuite 43 } 44 45 var _ = gc.Suite(&StatusSuite{}) 46 47 type M map[string]interface{} 48 49 type L []interface{} 50 51 type testCase struct { 52 summary string 53 steps []stepper 54 } 55 56 func test(summary string, steps ...stepper) testCase { 57 return testCase{summary, steps} 58 } 59 60 type stepper interface { 61 step(c *gc.C, ctx *context) 62 } 63 64 // 65 // context 66 // 67 68 func newContext(c *gc.C, st *state.State, env environs.Environ, adminUserTag string) *context { 69 // We make changes in the API server's state so that 70 // our changes to presence are immediately noticed 71 // in the status. 72 return &context{ 73 st: st, 74 env: env, 75 charms: make(map[string]*state.Charm), 76 pingers: make(map[string]*presence.Pinger), 77 adminUserTag: adminUserTag, 78 } 79 } 80 81 type context struct { 82 st *state.State 83 env environs.Environ 84 charms map[string]*state.Charm 85 pingers map[string]*presence.Pinger 86 adminUserTag string // A string repr of the tag. 87 } 88 89 func (ctx *context) reset(c *gc.C) { 90 for _, up := range ctx.pingers { 91 err := up.Kill() 92 c.Check(err, jc.ErrorIsNil) 93 } 94 } 95 96 func (ctx *context) run(c *gc.C, steps []stepper) { 97 for i, s := range steps { 98 c.Logf("step %d", i) 99 c.Logf("%#v", s) 100 s.step(c, ctx) 101 } 102 } 103 104 func (ctx *context) setAgentPresence(c *gc.C, p presence.Presencer) *presence.Pinger { 105 pinger, err := p.SetAgentPresence() 106 c.Assert(err, jc.ErrorIsNil) 107 ctx.st.StartSync() 108 err = p.WaitAgentPresence(coretesting.LongWait) 109 c.Assert(err, jc.ErrorIsNil) 110 agentPresence, err := p.AgentPresence() 111 c.Assert(err, jc.ErrorIsNil) 112 c.Assert(agentPresence, jc.IsTrue) 113 return pinger 114 } 115 116 func (s *StatusSuite) newContext(c *gc.C) *context { 117 st := s.Environ.(testing.GetStater).GetStateInAPIServer() 118 // We make changes in the API server's state so that 119 // our changes to presence are immediately noticed 120 // in the status. 121 return newContext(c, st, s.Environ, s.AdminUserTag(c).String()) 122 } 123 124 func (s *StatusSuite) resetContext(c *gc.C, ctx *context) { 125 ctx.reset(c) 126 s.JujuConnSuite.Reset(c) 127 } 128 129 // shortcuts for expected output. 130 var ( 131 machine0 = M{ 132 "agent-state": "started", 133 "dns-name": "dummyenv-0.dns", 134 "instance-id": "dummyenv-0", 135 "series": "quantal", 136 "hardware": "arch=amd64 cpu-cores=1 mem=1024M root-disk=8192M", 137 "state-server-member-status": "adding-vote", 138 } 139 machine1 = M{ 140 "agent-state": "started", 141 "dns-name": "dummyenv-1.dns", 142 "instance-id": "dummyenv-1", 143 "series": "quantal", 144 "hardware": "arch=amd64 cpu-cores=1 mem=1024M root-disk=8192M", 145 } 146 machine2 = M{ 147 "agent-state": "started", 148 "dns-name": "dummyenv-2.dns", 149 "instance-id": "dummyenv-2", 150 "series": "quantal", 151 "hardware": "arch=amd64 cpu-cores=1 mem=1024M root-disk=8192M", 152 } 153 machine3 = M{ 154 "agent-state": "started", 155 "dns-name": "dummyenv-3.dns", 156 "instance-id": "dummyenv-3", 157 "series": "quantal", 158 "hardware": "arch=amd64 cpu-cores=1 mem=1024M root-disk=8192M", 159 } 160 machine4 = M{ 161 "agent-state": "started", 162 "dns-name": "dummyenv-4.dns", 163 "instance-id": "dummyenv-4", 164 "series": "quantal", 165 "hardware": "arch=amd64 cpu-cores=1 mem=1024M root-disk=8192M", 166 } 167 machine1WithContainers = M{ 168 "agent-state": "started", 169 "containers": M{ 170 "1/lxc/0": M{ 171 "agent-state": "started", 172 "containers": M{ 173 "1/lxc/0/lxc/0": M{ 174 "agent-state": "started", 175 "dns-name": "dummyenv-3.dns", 176 "instance-id": "dummyenv-3", 177 "series": "quantal", 178 }, 179 }, 180 "dns-name": "dummyenv-2.dns", 181 "instance-id": "dummyenv-2", 182 "series": "quantal", 183 }, 184 "1/lxc/1": M{ 185 "instance-id": "pending", 186 "series": "quantal", 187 }, 188 }, 189 "dns-name": "dummyenv-1.dns", 190 "instance-id": "dummyenv-1", 191 "series": "quantal", 192 "hardware": "arch=amd64 cpu-cores=1 mem=1024M root-disk=8192M", 193 } 194 machine1WithContainersScoped = M{ 195 "agent-state": "started", 196 "containers": M{ 197 "1/lxc/0": M{ 198 "agent-state": "started", 199 "dns-name": "dummyenv-2.dns", 200 "instance-id": "dummyenv-2", 201 "series": "quantal", 202 }, 203 }, 204 "dns-name": "dummyenv-1.dns", 205 "instance-id": "dummyenv-1", 206 "series": "quantal", 207 "hardware": "arch=amd64 cpu-cores=1 mem=1024M root-disk=8192M", 208 } 209 unexposedService = M{ 210 "charm": "cs:quantal/dummy-1", 211 "exposed": false, 212 } 213 exposedService = M{ 214 "charm": "cs:quantal/dummy-1", 215 "exposed": true, 216 } 217 ) 218 219 type outputFormat struct { 220 name string 221 marshal func(v interface{}) ([]byte, error) 222 unmarshal func(data []byte, v interface{}) error 223 } 224 225 // statusFormats list all output formats supported by status command. 226 var statusFormats = []outputFormat{ 227 {"yaml", goyaml.Marshal, goyaml.Unmarshal}, 228 {"json", json.Marshal, json.Unmarshal}, 229 } 230 231 var machineCons = constraints.MustParse("cpu-cores=2 mem=8G root-disk=8G") 232 233 var statusTests = []testCase{ 234 // Status tests 235 test( 236 "bootstrap and starting a single instance", 237 238 addMachine{machineId: "0", job: state.JobManageEnviron}, 239 expect{ 240 "simulate juju bootstrap by adding machine/0 to the state", 241 M{ 242 "environment": "dummyenv", 243 "machines": M{ 244 "0": M{ 245 "instance-id": "pending", 246 "series": "quantal", 247 "state-server-member-status": "adding-vote", 248 }, 249 }, 250 "services": M{}, 251 }, 252 }, 253 254 startAliveMachine{"0"}, 255 setAddresses{"0", []network.Address{ 256 network.NewAddress("10.0.0.1", network.ScopeUnknown), 257 network.NewAddress("dummyenv-0.dns", network.ScopePublic), 258 }}, 259 expect{ 260 "simulate the PA starting an instance in response to the state change", 261 M{ 262 "environment": "dummyenv", 263 "machines": M{ 264 "0": M{ 265 "agent-state": "pending", 266 "dns-name": "dummyenv-0.dns", 267 "instance-id": "dummyenv-0", 268 "series": "quantal", 269 "hardware": "arch=amd64 cpu-cores=1 mem=1024M root-disk=8192M", 270 "state-server-member-status": "adding-vote", 271 }, 272 }, 273 "services": M{}, 274 }, 275 }, 276 277 setMachineStatus{"0", state.StatusStarted, ""}, 278 expect{ 279 "simulate the MA started and set the machine status", 280 M{ 281 "environment": "dummyenv", 282 "machines": M{ 283 "0": machine0, 284 }, 285 "services": M{}, 286 }, 287 }, 288 289 setTools{"0", version.MustParseBinary("1.2.3-trusty-ppc")}, 290 expect{ 291 "simulate the MA setting the version", 292 M{ 293 "environment": "dummyenv", 294 "machines": M{ 295 "0": M{ 296 "dns-name": "dummyenv-0.dns", 297 "instance-id": "dummyenv-0", 298 "agent-version": "1.2.3", 299 "agent-state": "started", 300 "series": "quantal", 301 "hardware": "arch=amd64 cpu-cores=1 mem=1024M root-disk=8192M", 302 "state-server-member-status": "adding-vote", 303 }, 304 }, 305 "services": M{}, 306 }, 307 }, 308 ), test( 309 "deploy two services and two networks", 310 addMachine{machineId: "0", job: state.JobManageEnviron}, 311 startAliveMachine{"0"}, 312 setMachineStatus{"0", state.StatusStarted, ""}, 313 setAddresses{"0", []network.Address{ 314 network.NewAddress("10.0.0.1", network.ScopeUnknown), 315 network.NewAddress("dummyenv-0.dns", network.ScopePublic), 316 }}, 317 addCharm{"dummy"}, 318 addService{ 319 name: "networks-service", 320 charm: "dummy", 321 networks: []string{"net1", "net2"}, 322 cons: constraints.MustParse("networks=foo,bar,^no,^good"), 323 }, 324 addService{ 325 name: "no-networks-service", 326 charm: "dummy", 327 cons: constraints.MustParse("networks=^mynet"), 328 }, 329 addNetwork{ 330 name: "net1", 331 providerId: network.Id("provider-net1"), 332 cidr: "0.1.2.0/24", 333 vlanTag: 0, 334 }, 335 addNetwork{ 336 name: "net2", 337 providerId: network.Id("provider-vlan42"), 338 cidr: "0.42.1.0/24", 339 vlanTag: 42, 340 }, 341 342 expect{ 343 "simulate just the two services and a bootstrap node", 344 M{ 345 "environment": "dummyenv", 346 "machines": M{ 347 "0": machine0, 348 }, 349 "services": M{ 350 "networks-service": M{ 351 "charm": "cs:quantal/dummy-1", 352 "exposed": false, 353 "networks": M{ 354 "enabled": L{"net1", "net2"}, 355 "disabled": L{"foo", "bar", "no", "good"}, 356 }, 357 }, 358 "no-networks-service": M{ 359 "charm": "cs:quantal/dummy-1", 360 "exposed": false, 361 "networks": M{ 362 "disabled": L{"mynet"}, 363 }, 364 }, 365 }, 366 "networks": M{ 367 "net1": M{ 368 "provider-id": "provider-net1", 369 "cidr": "0.1.2.0/24", 370 }, 371 "net2": M{ 372 "provider-id": "provider-vlan42", 373 "cidr": "0.42.1.0/24", 374 "vlan-tag": 42, 375 }, 376 }, 377 }, 378 }, 379 ), test( 380 "instance with different hardware characteristics", 381 addMachine{machineId: "0", cons: machineCons, job: state.JobManageEnviron}, 382 setAddresses{"0", []network.Address{ 383 network.NewAddress("10.0.0.1", network.ScopeUnknown), 384 network.NewAddress("dummyenv-0.dns", network.ScopePublic), 385 }}, 386 startAliveMachine{"0"}, 387 setMachineStatus{"0", state.StatusStarted, ""}, 388 expect{ 389 "machine 0 has specific hardware characteristics", 390 M{ 391 "environment": "dummyenv", 392 "machines": M{ 393 "0": M{ 394 "agent-state": "started", 395 "dns-name": "dummyenv-0.dns", 396 "instance-id": "dummyenv-0", 397 "series": "quantal", 398 "hardware": "arch=amd64 cpu-cores=2 mem=8192M root-disk=8192M", 399 "state-server-member-status": "adding-vote", 400 }, 401 }, 402 "services": M{}, 403 }, 404 }, 405 ), test( 406 "instance without addresses", 407 addMachine{machineId: "0", cons: machineCons, job: state.JobManageEnviron}, 408 startAliveMachine{"0"}, 409 setMachineStatus{"0", state.StatusStarted, ""}, 410 expect{ 411 "machine 0 has no dns-name", 412 M{ 413 "environment": "dummyenv", 414 "machines": M{ 415 "0": M{ 416 "agent-state": "started", 417 "instance-id": "dummyenv-0", 418 "series": "quantal", 419 "hardware": "arch=amd64 cpu-cores=2 mem=8192M root-disk=8192M", 420 "state-server-member-status": "adding-vote", 421 }, 422 }, 423 "services": M{}, 424 }, 425 }, 426 ), test( 427 "test pending and missing machines", 428 addMachine{machineId: "0", job: state.JobManageEnviron}, 429 expect{ 430 "machine 0 reports pending", 431 M{ 432 "environment": "dummyenv", 433 "machines": M{ 434 "0": M{ 435 "instance-id": "pending", 436 "series": "quantal", 437 "state-server-member-status": "adding-vote", 438 }, 439 }, 440 "services": M{}, 441 }, 442 }, 443 444 startMissingMachine{"0"}, 445 expect{ 446 "machine 0 reports missing", 447 M{ 448 "environment": "dummyenv", 449 "machines": M{ 450 "0": M{ 451 "instance-state": "missing", 452 "instance-id": "i-missing", 453 "agent-state": "pending", 454 "series": "quantal", 455 "hardware": "arch=amd64 cpu-cores=1 mem=1024M root-disk=8192M", 456 "state-server-member-status": "adding-vote", 457 }, 458 }, 459 "services": M{}, 460 }, 461 }, 462 ), test( 463 "add two services and expose one, then add 2 more machines and some units", 464 addMachine{machineId: "0", job: state.JobManageEnviron}, 465 setAddresses{"0", []network.Address{network.NewAddress("dummyenv-0.dns", network.ScopeUnknown)}}, 466 startAliveMachine{"0"}, 467 setMachineStatus{"0", state.StatusStarted, ""}, 468 addCharm{"dummy"}, 469 addService{name: "dummy-service", charm: "dummy"}, 470 addService{name: "exposed-service", charm: "dummy"}, 471 expect{ 472 "no services exposed yet", 473 M{ 474 "environment": "dummyenv", 475 "machines": M{ 476 "0": machine0, 477 }, 478 "services": M{ 479 "dummy-service": unexposedService, 480 "exposed-service": unexposedService, 481 }, 482 }, 483 }, 484 485 setServiceExposed{"exposed-service", true}, 486 expect{ 487 "one exposed service", 488 M{ 489 "environment": "dummyenv", 490 "machines": M{ 491 "0": machine0, 492 }, 493 "services": M{ 494 "dummy-service": unexposedService, 495 "exposed-service": exposedService, 496 }, 497 }, 498 }, 499 500 addMachine{machineId: "1", job: state.JobHostUnits}, 501 setAddresses{"1", []network.Address{network.NewAddress("dummyenv-1.dns", network.ScopeUnknown)}}, 502 startAliveMachine{"1"}, 503 setMachineStatus{"1", state.StatusStarted, ""}, 504 addMachine{machineId: "2", job: state.JobHostUnits}, 505 setAddresses{"2", []network.Address{network.NewAddress("dummyenv-2.dns", network.ScopeUnknown)}}, 506 startAliveMachine{"2"}, 507 setMachineStatus{"2", state.StatusStarted, ""}, 508 expect{ 509 "two more machines added", 510 M{ 511 "environment": "dummyenv", 512 "machines": M{ 513 "0": machine0, 514 "1": machine1, 515 "2": machine2, 516 }, 517 "services": M{ 518 "dummy-service": unexposedService, 519 "exposed-service": exposedService, 520 }, 521 }, 522 }, 523 524 addUnit{"dummy-service", "1"}, 525 addAliveUnit{"exposed-service", "2"}, 526 setUnitStatus{"exposed-service/0", state.StatusError, "You Require More Vespene Gas", nil}, 527 // Open multiple ports with different protocols, 528 // ensure they're sorted on protocol, then number. 529 openUnitPort{"exposed-service/0", "udp", 10}, 530 openUnitPort{"exposed-service/0", "udp", 2}, 531 openUnitPort{"exposed-service/0", "tcp", 3}, 532 openUnitPort{"exposed-service/0", "tcp", 2}, 533 // Simulate some status with no info, while the agent is down. 534 setUnitStatus{"dummy-service/0", state.StatusActive, "", nil}, 535 expect{ 536 "add two units, one alive (in error state), one down", 537 M{ 538 "environment": "dummyenv", 539 "machines": M{ 540 "0": machine0, 541 "1": machine1, 542 "2": machine2, 543 }, 544 "services": M{ 545 "exposed-service": M{ 546 "charm": "cs:quantal/dummy-1", 547 "exposed": true, 548 "units": M{ 549 "exposed-service/0": M{ 550 "machine": "2", 551 "agent-state": "error", 552 "agent-state-info": "You Require More Vespene Gas", 553 "open-ports": L{ 554 "2/tcp", "3/tcp", "2/udp", "10/udp", 555 }, 556 "public-address": "dummyenv-2.dns", 557 }, 558 }, 559 }, 560 "dummy-service": M{ 561 "charm": "cs:quantal/dummy-1", 562 "exposed": false, 563 "units": M{ 564 "dummy-service/0": M{ 565 "machine": "1", 566 "agent-state": "down", 567 "agent-state-info": "(started)", 568 "public-address": "dummyenv-1.dns", 569 }, 570 }, 571 }, 572 }, 573 }, 574 }, 575 576 addMachine{machineId: "3", job: state.JobHostUnits}, 577 startMachine{"3"}, 578 // Simulate some status with info, while the agent is down. 579 setAddresses{"3", []network.Address{network.NewAddress("dummyenv-3.dns", network.ScopeUnknown)}}, 580 setMachineStatus{"3", state.StatusStopped, "Really?"}, 581 addMachine{machineId: "4", job: state.JobHostUnits}, 582 setAddresses{"4", []network.Address{network.NewAddress("dummyenv-4.dns", network.ScopeUnknown)}}, 583 startAliveMachine{"4"}, 584 setMachineStatus{"4", state.StatusError, "Beware the red toys"}, 585 ensureDyingUnit{"dummy-service/0"}, 586 addMachine{machineId: "5", job: state.JobHostUnits}, 587 ensureDeadMachine{"5"}, 588 expect{ 589 "add three more machine, one with a dead agent, one in error state and one dead itself; also one dying unit", 590 M{ 591 "environment": "dummyenv", 592 "machines": M{ 593 "0": machine0, 594 "1": machine1, 595 "2": machine2, 596 "3": M{ 597 "dns-name": "dummyenv-3.dns", 598 "instance-id": "dummyenv-3", 599 "agent-state": "down", 600 "agent-state-info": "(stopped: Really?)", 601 "series": "quantal", 602 "hardware": "arch=amd64 cpu-cores=1 mem=1024M root-disk=8192M", 603 }, 604 "4": M{ 605 "dns-name": "dummyenv-4.dns", 606 "instance-id": "dummyenv-4", 607 "agent-state": "error", 608 "agent-state-info": "Beware the red toys", 609 "series": "quantal", 610 "hardware": "arch=amd64 cpu-cores=1 mem=1024M root-disk=8192M", 611 }, 612 "5": M{ 613 "life": "dead", 614 "instance-id": "pending", 615 "series": "quantal", 616 }, 617 }, 618 "services": M{ 619 "exposed-service": M{ 620 "charm": "cs:quantal/dummy-1", 621 "exposed": true, 622 "units": M{ 623 "exposed-service/0": M{ 624 "machine": "2", 625 "agent-state": "error", 626 "agent-state-info": "You Require More Vespene Gas", 627 "open-ports": L{ 628 "2/tcp", "3/tcp", "2/udp", "10/udp", 629 }, 630 "public-address": "dummyenv-2.dns", 631 }, 632 }, 633 }, 634 "dummy-service": M{ 635 "charm": "cs:quantal/dummy-1", 636 "exposed": false, 637 "units": M{ 638 "dummy-service/0": M{ 639 "machine": "1", 640 "life": "dying", 641 "agent-state": "down", 642 "agent-state-info": "(started)", 643 "public-address": "dummyenv-1.dns", 644 }, 645 }, 646 }, 647 }, 648 }, 649 }, 650 651 scopedExpect{ 652 "scope status on dummy-service/0 unit", 653 []string{"dummy-service/0"}, 654 M{ 655 "environment": "dummyenv", 656 "machines": M{ 657 "1": machine1, 658 }, 659 "services": M{ 660 "dummy-service": M{ 661 "charm": "cs:quantal/dummy-1", 662 "exposed": false, 663 "units": M{ 664 "dummy-service/0": M{ 665 "machine": "1", 666 "life": "dying", 667 "agent-state": "down", 668 "agent-state-info": "(started)", 669 "public-address": "dummyenv-1.dns", 670 }, 671 }, 672 }, 673 }, 674 }, 675 }, 676 scopedExpect{ 677 "scope status on exposed-service service", 678 []string{"exposed-service"}, 679 M{ 680 "environment": "dummyenv", 681 "machines": M{ 682 "2": machine2, 683 }, 684 "services": M{ 685 "exposed-service": M{ 686 "charm": "cs:quantal/dummy-1", 687 "exposed": true, 688 "units": M{ 689 "exposed-service/0": M{ 690 "machine": "2", 691 "agent-state": "error", 692 "agent-state-info": "You Require More Vespene Gas", 693 "open-ports": L{ 694 "2/tcp", "3/tcp", "2/udp", "10/udp", 695 }, 696 "public-address": "dummyenv-2.dns", 697 }, 698 }, 699 }, 700 }, 701 }, 702 }, 703 scopedExpect{ 704 "scope status on service pattern", 705 []string{"d*-service"}, 706 M{ 707 "environment": "dummyenv", 708 "machines": M{ 709 "1": machine1, 710 }, 711 "services": M{ 712 "dummy-service": M{ 713 "charm": "cs:quantal/dummy-1", 714 "exposed": false, 715 "units": M{ 716 "dummy-service/0": M{ 717 "machine": "1", 718 "life": "dying", 719 "agent-state": "down", 720 "agent-state-info": "(started)", 721 "public-address": "dummyenv-1.dns", 722 }, 723 }, 724 }, 725 }, 726 }, 727 }, 728 scopedExpect{ 729 "scope status on unit pattern", 730 []string{"e*posed-service/*"}, 731 M{ 732 "environment": "dummyenv", 733 "machines": M{ 734 "2": machine2, 735 }, 736 "services": M{ 737 "exposed-service": M{ 738 "charm": "cs:quantal/dummy-1", 739 "exposed": true, 740 "units": M{ 741 "exposed-service/0": M{ 742 "machine": "2", 743 "agent-state": "error", 744 "agent-state-info": "You Require More Vespene Gas", 745 "open-ports": L{ 746 "2/tcp", "3/tcp", "2/udp", "10/udp", 747 }, 748 "public-address": "dummyenv-2.dns", 749 }, 750 }, 751 }, 752 }, 753 }, 754 }, 755 scopedExpect{ 756 "scope status on combination of service and unit patterns", 757 []string{"exposed-service", "dummy-service", "e*posed-service/*", "dummy-service/*"}, 758 M{ 759 "environment": "dummyenv", 760 "machines": M{ 761 "1": machine1, 762 "2": machine2, 763 }, 764 "services": M{ 765 "dummy-service": M{ 766 "charm": "cs:quantal/dummy-1", 767 "exposed": false, 768 "units": M{ 769 "dummy-service/0": M{ 770 "machine": "1", 771 "life": "dying", 772 "agent-state": "down", 773 "agent-state-info": "(started)", 774 "public-address": "dummyenv-1.dns", 775 }, 776 }, 777 }, 778 "exposed-service": M{ 779 "charm": "cs:quantal/dummy-1", 780 "exposed": true, 781 "units": M{ 782 "exposed-service/0": M{ 783 "machine": "2", 784 "agent-state": "error", 785 "agent-state-info": "You Require More Vespene Gas", 786 "open-ports": L{ 787 "2/tcp", "3/tcp", "2/udp", "10/udp", 788 }, 789 "public-address": "dummyenv-2.dns", 790 }, 791 }, 792 }, 793 }, 794 }, 795 }, 796 ), test( 797 "a unit with a hook relation error", 798 addMachine{machineId: "0", job: state.JobManageEnviron}, 799 setAddresses{"0", []network.Address{network.NewAddress("dummyenv-0.dns", network.ScopeUnknown)}}, 800 startAliveMachine{"0"}, 801 setMachineStatus{"0", state.StatusStarted, ""}, 802 803 addMachine{machineId: "1", job: state.JobHostUnits}, 804 setAddresses{"1", []network.Address{network.NewAddress("dummyenv-1.dns", network.ScopeUnknown)}}, 805 startAliveMachine{"1"}, 806 setMachineStatus{"1", state.StatusStarted, ""}, 807 808 addCharm{"wordpress"}, 809 addService{name: "wordpress", charm: "wordpress"}, 810 addAliveUnit{"wordpress", "1"}, 811 812 addCharm{"mysql"}, 813 addService{name: "mysql", charm: "mysql"}, 814 addAliveUnit{"mysql", "1"}, 815 816 relateServices{"wordpress", "mysql"}, 817 818 setUnitStatus{"wordpress/0", state.StatusError, 819 "hook failed: some-relation-changed", 820 map[string]interface{}{"relation-id": 0}}, 821 822 expect{ 823 "a unit with a hook relation error", 824 M{ 825 "environment": "dummyenv", 826 "machines": M{ 827 "0": machine0, 828 "1": machine1, 829 }, 830 "services": M{ 831 "wordpress": M{ 832 "charm": "cs:quantal/wordpress-3", 833 "exposed": false, 834 "relations": M{ 835 "db": L{"mysql"}, 836 }, 837 "units": M{ 838 "wordpress/0": M{ 839 "machine": "1", 840 "agent-state": "error", 841 "agent-state-info": "hook failed: some-relation-changed for mysql:server", 842 "public-address": "dummyenv-1.dns", 843 }, 844 }, 845 }, 846 "mysql": M{ 847 "charm": "cs:quantal/mysql-1", 848 "exposed": false, 849 "relations": M{ 850 "server": L{"wordpress"}, 851 }, 852 "units": M{ 853 "mysql/0": M{ 854 "machine": "1", 855 "agent-state": "allocating", 856 "public-address": "dummyenv-1.dns", 857 }, 858 }, 859 }, 860 }, 861 }, 862 }, 863 ), test( 864 "a unit with a hook relation error when the agent is down", 865 addMachine{machineId: "0", job: state.JobManageEnviron}, 866 setAddresses{"0", []network.Address{network.NewAddress("dummyenv-0.dns", network.ScopeUnknown)}}, 867 startAliveMachine{"0"}, 868 setMachineStatus{"0", state.StatusStarted, ""}, 869 870 addMachine{machineId: "1", job: state.JobHostUnits}, 871 setAddresses{"1", []network.Address{network.NewAddress("dummyenv-1.dns", network.ScopeUnknown)}}, 872 startAliveMachine{"1"}, 873 setMachineStatus{"1", state.StatusStarted, ""}, 874 875 addCharm{"wordpress"}, 876 addService{name: "wordpress", charm: "wordpress"}, 877 addUnit{"wordpress", "1"}, 878 879 addCharm{"mysql"}, 880 addService{name: "mysql", charm: "mysql"}, 881 addAliveUnit{"mysql", "1"}, 882 883 relateServices{"wordpress", "mysql"}, 884 885 setUnitStatus{"wordpress/0", state.StatusError, 886 "hook failed: some-relation-changed", 887 map[string]interface{}{"relation-id": 0}}, 888 889 expect{ 890 "a unit with a hook relation error when the agent is down", 891 M{ 892 "environment": "dummyenv", 893 "machines": M{ 894 "0": machine0, 895 "1": machine1, 896 }, 897 "services": M{ 898 "wordpress": M{ 899 "charm": "cs:quantal/wordpress-3", 900 "exposed": false, 901 "relations": M{ 902 "db": L{"mysql"}, 903 }, 904 "units": M{ 905 "wordpress/0": M{ 906 "machine": "1", 907 "agent-state": "down", 908 "agent-state-info": "(error: hook failed: some-relation-changed for mysql:server)", 909 "public-address": "dummyenv-1.dns", 910 }, 911 }, 912 }, 913 "mysql": M{ 914 "charm": "cs:quantal/mysql-1", 915 "exposed": false, 916 "relations": M{ 917 "server": L{"wordpress"}, 918 }, 919 "units": M{ 920 "mysql/0": M{ 921 "machine": "1", 922 "agent-state": "allocating", 923 "public-address": "dummyenv-1.dns", 924 }, 925 }, 926 }, 927 }, 928 }, 929 }, 930 ), test( 931 "add a dying service", 932 addCharm{"dummy"}, 933 addService{name: "dummy-service", charm: "dummy"}, 934 addMachine{machineId: "0", job: state.JobHostUnits}, 935 addUnit{"dummy-service", "0"}, 936 ensureDyingService{"dummy-service"}, 937 expect{ 938 "service shows life==dying", 939 M{ 940 "environment": "dummyenv", 941 "machines": M{ 942 "0": M{ 943 "instance-id": "pending", 944 "series": "quantal", 945 }, 946 }, 947 "services": M{ 948 "dummy-service": M{ 949 "charm": "cs:quantal/dummy-1", 950 "exposed": false, 951 "life": "dying", 952 "units": M{ 953 "dummy-service/0": M{ 954 "machine": "0", 955 "agent-state": "allocating", 956 }, 957 }, 958 }, 959 }, 960 }, 961 }, 962 ), 963 964 // Relation tests 965 test( 966 "complex scenario with multiple related services", 967 addMachine{machineId: "0", job: state.JobManageEnviron}, 968 setAddresses{"0", []network.Address{network.NewAddress("dummyenv-0.dns", network.ScopeUnknown)}}, 969 startAliveMachine{"0"}, 970 setMachineStatus{"0", state.StatusStarted, ""}, 971 addCharm{"wordpress"}, 972 addCharm{"mysql"}, 973 addCharm{"varnish"}, 974 975 addService{name: "project", charm: "wordpress"}, 976 setServiceExposed{"project", true}, 977 addMachine{machineId: "1", job: state.JobHostUnits}, 978 setAddresses{"1", []network.Address{network.NewAddress("dummyenv-1.dns", network.ScopeUnknown)}}, 979 startAliveMachine{"1"}, 980 setMachineStatus{"1", state.StatusStarted, ""}, 981 addAliveUnit{"project", "1"}, 982 setUnitStatus{"project/0", state.StatusActive, "", nil}, 983 984 addService{name: "mysql", charm: "mysql"}, 985 setServiceExposed{"mysql", true}, 986 addMachine{machineId: "2", job: state.JobHostUnits}, 987 setAddresses{"2", []network.Address{network.NewAddress("dummyenv-2.dns", network.ScopeUnknown)}}, 988 startAliveMachine{"2"}, 989 setMachineStatus{"2", state.StatusStarted, ""}, 990 addAliveUnit{"mysql", "2"}, 991 setUnitStatus{"mysql/0", state.StatusActive, "", nil}, 992 993 addService{name: "varnish", charm: "varnish"}, 994 setServiceExposed{"varnish", true}, 995 addMachine{machineId: "3", job: state.JobHostUnits}, 996 setAddresses{"3", []network.Address{network.NewAddress("dummyenv-3.dns", network.ScopeUnknown)}}, 997 startAliveMachine{"3"}, 998 setMachineStatus{"3", state.StatusStarted, ""}, 999 addUnit{"varnish", "3"}, 1000 1001 addService{name: "private", charm: "wordpress"}, 1002 setServiceExposed{"private", true}, 1003 addMachine{machineId: "4", job: state.JobHostUnits}, 1004 setAddresses{"4", []network.Address{network.NewAddress("dummyenv-4.dns", network.ScopeUnknown)}}, 1005 startAliveMachine{"4"}, 1006 setMachineStatus{"4", state.StatusStarted, ""}, 1007 addUnit{"private", "4"}, 1008 1009 relateServices{"project", "mysql"}, 1010 relateServices{"project", "varnish"}, 1011 relateServices{"private", "mysql"}, 1012 1013 expect{ 1014 "multiples services with relations between some of them", 1015 M{ 1016 "environment": "dummyenv", 1017 "machines": M{ 1018 "0": machine0, 1019 "1": machine1, 1020 "2": machine2, 1021 "3": machine3, 1022 "4": machine4, 1023 }, 1024 "services": M{ 1025 "project": M{ 1026 "charm": "cs:quantal/wordpress-3", 1027 "exposed": true, 1028 "units": M{ 1029 "project/0": M{ 1030 "machine": "1", 1031 "agent-state": "started", 1032 "public-address": "dummyenv-1.dns", 1033 }, 1034 }, 1035 "relations": M{ 1036 "db": L{"mysql"}, 1037 "cache": L{"varnish"}, 1038 }, 1039 }, 1040 "mysql": M{ 1041 "charm": "cs:quantal/mysql-1", 1042 "exposed": true, 1043 "units": M{ 1044 "mysql/0": M{ 1045 "machine": "2", 1046 "agent-state": "started", 1047 "public-address": "dummyenv-2.dns", 1048 }, 1049 }, 1050 "relations": M{ 1051 "server": L{"private", "project"}, 1052 }, 1053 }, 1054 "varnish": M{ 1055 "charm": "cs:quantal/varnish-1", 1056 "exposed": true, 1057 "units": M{ 1058 "varnish/0": M{ 1059 "machine": "3", 1060 "agent-state": "allocating", 1061 "public-address": "dummyenv-3.dns", 1062 }, 1063 }, 1064 "relations": M{ 1065 "webcache": L{"project"}, 1066 }, 1067 }, 1068 "private": M{ 1069 "charm": "cs:quantal/wordpress-3", 1070 "exposed": true, 1071 "units": M{ 1072 "private/0": M{ 1073 "machine": "4", 1074 "agent-state": "allocating", 1075 "public-address": "dummyenv-4.dns", 1076 }, 1077 }, 1078 "relations": M{ 1079 "db": L{"mysql"}, 1080 }, 1081 }, 1082 }, 1083 }, 1084 }, 1085 ), test( 1086 "simple peer scenario", 1087 addMachine{machineId: "0", job: state.JobManageEnviron}, 1088 setAddresses{"0", []network.Address{network.NewAddress("dummyenv-0.dns", network.ScopeUnknown)}}, 1089 startAliveMachine{"0"}, 1090 setMachineStatus{"0", state.StatusStarted, ""}, 1091 addCharm{"riak"}, 1092 addCharm{"wordpress"}, 1093 1094 addService{name: "riak", charm: "riak"}, 1095 setServiceExposed{"riak", true}, 1096 addMachine{machineId: "1", job: state.JobHostUnits}, 1097 setAddresses{"1", []network.Address{network.NewAddress("dummyenv-1.dns", network.ScopeUnknown)}}, 1098 startAliveMachine{"1"}, 1099 setMachineStatus{"1", state.StatusStarted, ""}, 1100 addAliveUnit{"riak", "1"}, 1101 setUnitStatus{"riak/0", state.StatusActive, "", nil}, 1102 addMachine{machineId: "2", job: state.JobHostUnits}, 1103 setAddresses{"2", []network.Address{network.NewAddress("dummyenv-2.dns", network.ScopeUnknown)}}, 1104 startAliveMachine{"2"}, 1105 setMachineStatus{"2", state.StatusStarted, ""}, 1106 addAliveUnit{"riak", "2"}, 1107 setUnitStatus{"riak/1", state.StatusActive, "", nil}, 1108 addMachine{machineId: "3", job: state.JobHostUnits}, 1109 setAddresses{"3", []network.Address{network.NewAddress("dummyenv-3.dns", network.ScopeUnknown)}}, 1110 startAliveMachine{"3"}, 1111 setMachineStatus{"3", state.StatusStarted, ""}, 1112 addAliveUnit{"riak", "3"}, 1113 setUnitStatus{"riak/2", state.StatusActive, "", nil}, 1114 1115 expect{ 1116 "multiples related peer units", 1117 M{ 1118 "environment": "dummyenv", 1119 "machines": M{ 1120 "0": machine0, 1121 "1": machine1, 1122 "2": machine2, 1123 "3": machine3, 1124 }, 1125 "services": M{ 1126 "riak": M{ 1127 "charm": "cs:quantal/riak-7", 1128 "exposed": true, 1129 "units": M{ 1130 "riak/0": M{ 1131 "machine": "1", 1132 "agent-state": "started", 1133 "public-address": "dummyenv-1.dns", 1134 }, 1135 "riak/1": M{ 1136 "machine": "2", 1137 "agent-state": "started", 1138 "public-address": "dummyenv-2.dns", 1139 }, 1140 "riak/2": M{ 1141 "machine": "3", 1142 "agent-state": "started", 1143 "public-address": "dummyenv-3.dns", 1144 }, 1145 }, 1146 "relations": M{ 1147 "ring": L{"riak"}, 1148 }, 1149 }, 1150 }, 1151 }, 1152 }, 1153 ), 1154 1155 // Subordinate tests 1156 test( 1157 "one service with one subordinate service", 1158 addMachine{machineId: "0", job: state.JobManageEnviron}, 1159 setAddresses{"0", []network.Address{network.NewAddress("dummyenv-0.dns", network.ScopeUnknown)}}, 1160 startAliveMachine{"0"}, 1161 setMachineStatus{"0", state.StatusStarted, ""}, 1162 addCharm{"wordpress"}, 1163 addCharm{"mysql"}, 1164 addCharm{"logging"}, 1165 1166 addService{name: "wordpress", charm: "wordpress"}, 1167 setServiceExposed{"wordpress", true}, 1168 addMachine{machineId: "1", job: state.JobHostUnits}, 1169 setAddresses{"1", []network.Address{network.NewAddress("dummyenv-1.dns", network.ScopeUnknown)}}, 1170 startAliveMachine{"1"}, 1171 setMachineStatus{"1", state.StatusStarted, ""}, 1172 addAliveUnit{"wordpress", "1"}, 1173 setUnitStatus{"wordpress/0", state.StatusActive, "", nil}, 1174 1175 addService{name: "mysql", charm: "mysql"}, 1176 setServiceExposed{"mysql", true}, 1177 addMachine{machineId: "2", job: state.JobHostUnits}, 1178 setAddresses{"2", []network.Address{network.NewAddress("dummyenv-2.dns", network.ScopeUnknown)}}, 1179 startAliveMachine{"2"}, 1180 setMachineStatus{"2", state.StatusStarted, ""}, 1181 addAliveUnit{"mysql", "2"}, 1182 setUnitStatus{"mysql/0", state.StatusActive, "", nil}, 1183 1184 addService{name: "logging", charm: "logging"}, 1185 setServiceExposed{"logging", true}, 1186 1187 relateServices{"wordpress", "mysql"}, 1188 relateServices{"wordpress", "logging"}, 1189 relateServices{"mysql", "logging"}, 1190 1191 addSubordinate{"wordpress/0", "logging"}, 1192 addSubordinate{"mysql/0", "logging"}, 1193 1194 setUnitsAlive{"logging"}, 1195 setUnitStatus{"logging/0", state.StatusActive, "", nil}, 1196 setUnitStatus{"logging/1", state.StatusError, "somehow lost in all those logs", nil}, 1197 1198 expect{ 1199 "multiples related peer units", 1200 M{ 1201 "environment": "dummyenv", 1202 "machines": M{ 1203 "0": machine0, 1204 "1": machine1, 1205 "2": machine2, 1206 }, 1207 "services": M{ 1208 "wordpress": M{ 1209 "charm": "cs:quantal/wordpress-3", 1210 "exposed": true, 1211 "units": M{ 1212 "wordpress/0": M{ 1213 "machine": "1", 1214 "agent-state": "started", 1215 "subordinates": M{ 1216 "logging/0": M{ 1217 "agent-state": "started", 1218 "public-address": "dummyenv-1.dns", 1219 }, 1220 }, 1221 "public-address": "dummyenv-1.dns", 1222 }, 1223 }, 1224 "relations": M{ 1225 "db": L{"mysql"}, 1226 "logging-dir": L{"logging"}, 1227 }, 1228 }, 1229 "mysql": M{ 1230 "charm": "cs:quantal/mysql-1", 1231 "exposed": true, 1232 "units": M{ 1233 "mysql/0": M{ 1234 "machine": "2", 1235 "agent-state": "started", 1236 "subordinates": M{ 1237 "logging/1": M{ 1238 "agent-state": "error", 1239 "agent-state-info": "somehow lost in all those logs", 1240 "public-address": "dummyenv-2.dns", 1241 }, 1242 }, 1243 "public-address": "dummyenv-2.dns", 1244 }, 1245 }, 1246 "relations": M{ 1247 "server": L{"wordpress"}, 1248 "juju-info": L{"logging"}, 1249 }, 1250 }, 1251 "logging": M{ 1252 "charm": "cs:quantal/logging-1", 1253 "exposed": true, 1254 "relations": M{ 1255 "logging-directory": L{"wordpress"}, 1256 "info": L{"mysql"}, 1257 }, 1258 "subordinate-to": L{"mysql", "wordpress"}, 1259 }, 1260 }, 1261 }, 1262 }, 1263 1264 // scoped on 'logging' 1265 scopedExpect{ 1266 "subordinates scoped on logging", 1267 []string{"logging"}, 1268 M{ 1269 "environment": "dummyenv", 1270 "machines": M{ 1271 "1": machine1, 1272 "2": machine2, 1273 }, 1274 "services": M{ 1275 "wordpress": M{ 1276 "charm": "cs:quantal/wordpress-3", 1277 "exposed": true, 1278 "units": M{ 1279 "wordpress/0": M{ 1280 "machine": "1", 1281 "agent-state": "started", 1282 "subordinates": M{ 1283 "logging/0": M{ 1284 "agent-state": "started", 1285 "public-address": "dummyenv-1.dns", 1286 }, 1287 }, 1288 "public-address": "dummyenv-1.dns", 1289 }, 1290 }, 1291 "relations": M{ 1292 "db": L{"mysql"}, 1293 "logging-dir": L{"logging"}, 1294 }, 1295 }, 1296 "mysql": M{ 1297 "charm": "cs:quantal/mysql-1", 1298 "exposed": true, 1299 "units": M{ 1300 "mysql/0": M{ 1301 "machine": "2", 1302 "agent-state": "started", 1303 "subordinates": M{ 1304 "logging/1": M{ 1305 "agent-state": "error", 1306 "agent-state-info": "somehow lost in all those logs", 1307 "public-address": "dummyenv-2.dns", 1308 }, 1309 }, 1310 "public-address": "dummyenv-2.dns", 1311 }, 1312 }, 1313 "relations": M{ 1314 "server": L{"wordpress"}, 1315 "juju-info": L{"logging"}, 1316 }, 1317 }, 1318 "logging": M{ 1319 "charm": "cs:quantal/logging-1", 1320 "exposed": true, 1321 "relations": M{ 1322 "logging-directory": L{"wordpress"}, 1323 "info": L{"mysql"}, 1324 }, 1325 "subordinate-to": L{"mysql", "wordpress"}, 1326 }, 1327 }, 1328 }, 1329 }, 1330 1331 // scoped on wordpress/0 1332 scopedExpect{ 1333 "subordinates scoped on logging", 1334 []string{"wordpress/0"}, 1335 M{ 1336 "environment": "dummyenv", 1337 "machines": M{ 1338 "1": machine1, 1339 }, 1340 "services": M{ 1341 "wordpress": M{ 1342 "charm": "cs:quantal/wordpress-3", 1343 "exposed": true, 1344 "units": M{ 1345 "wordpress/0": M{ 1346 "machine": "1", 1347 "agent-state": "started", 1348 "subordinates": M{ 1349 "logging/0": M{ 1350 "agent-state": "started", 1351 "public-address": "dummyenv-1.dns", 1352 }, 1353 }, 1354 "public-address": "dummyenv-1.dns", 1355 }, 1356 }, 1357 "relations": M{ 1358 "db": L{"mysql"}, 1359 "logging-dir": L{"logging"}, 1360 }, 1361 }, 1362 "logging": M{ 1363 "charm": "cs:quantal/logging-1", 1364 "exposed": true, 1365 "relations": M{ 1366 "logging-directory": L{"wordpress"}, 1367 "info": L{"mysql"}, 1368 }, 1369 "subordinate-to": L{"mysql", "wordpress"}, 1370 }, 1371 }, 1372 }, 1373 }, 1374 ), 1375 test( 1376 "machines with containers", 1377 addMachine{machineId: "0", job: state.JobManageEnviron}, 1378 setAddresses{"0", []network.Address{network.NewAddress("dummyenv-0.dns", network.ScopeUnknown)}}, 1379 startAliveMachine{"0"}, 1380 setMachineStatus{"0", state.StatusStarted, ""}, 1381 addCharm{"mysql"}, 1382 addService{name: "mysql", charm: "mysql"}, 1383 setServiceExposed{"mysql", true}, 1384 1385 addMachine{machineId: "1", job: state.JobHostUnits}, 1386 setAddresses{"1", []network.Address{network.NewAddress("dummyenv-1.dns", network.ScopeUnknown)}}, 1387 startAliveMachine{"1"}, 1388 setMachineStatus{"1", state.StatusStarted, ""}, 1389 addAliveUnit{"mysql", "1"}, 1390 setUnitStatus{"mysql/0", state.StatusActive, "", nil}, 1391 1392 // A container on machine 1. 1393 addContainer{"1", "1/lxc/0", state.JobHostUnits}, 1394 setAddresses{"1/lxc/0", []network.Address{network.NewAddress("dummyenv-2.dns", network.ScopeUnknown)}}, 1395 startAliveMachine{"1/lxc/0"}, 1396 setMachineStatus{"1/lxc/0", state.StatusStarted, ""}, 1397 addAliveUnit{"mysql", "1/lxc/0"}, 1398 setUnitStatus{"mysql/1", state.StatusActive, "", nil}, 1399 addContainer{"1", "1/lxc/1", state.JobHostUnits}, 1400 1401 // A nested container. 1402 addContainer{"1/lxc/0", "1/lxc/0/lxc/0", state.JobHostUnits}, 1403 setAddresses{"1/lxc/0/lxc/0", []network.Address{network.NewAddress("dummyenv-3.dns", network.ScopeUnknown)}}, 1404 startAliveMachine{"1/lxc/0/lxc/0"}, 1405 setMachineStatus{"1/lxc/0/lxc/0", state.StatusStarted, ""}, 1406 1407 expect{ 1408 "machines with nested containers", 1409 M{ 1410 "environment": "dummyenv", 1411 "machines": M{ 1412 "0": machine0, 1413 "1": machine1WithContainers, 1414 }, 1415 "services": M{ 1416 "mysql": M{ 1417 "charm": "cs:quantal/mysql-1", 1418 "exposed": true, 1419 "units": M{ 1420 "mysql/0": M{ 1421 "machine": "1", 1422 "agent-state": "started", 1423 "public-address": "dummyenv-1.dns", 1424 }, 1425 "mysql/1": M{ 1426 "machine": "1/lxc/0", 1427 "agent-state": "started", 1428 "public-address": "dummyenv-2.dns", 1429 }, 1430 }, 1431 }, 1432 }, 1433 }, 1434 }, 1435 1436 // once again, with a scope on mysql/1 1437 scopedExpect{ 1438 "machines with nested containers", 1439 []string{"mysql/1"}, 1440 M{ 1441 "environment": "dummyenv", 1442 "machines": M{ 1443 "1": M{ 1444 "agent-state": "started", 1445 "containers": M{ 1446 "1/lxc/0": M{ 1447 "agent-state": "started", 1448 "dns-name": "dummyenv-2.dns", 1449 "instance-id": "dummyenv-2", 1450 "series": "quantal", 1451 }, 1452 }, 1453 "dns-name": "dummyenv-1.dns", 1454 "instance-id": "dummyenv-1", 1455 "series": "quantal", 1456 "hardware": "arch=amd64 cpu-cores=1 mem=1024M root-disk=8192M", 1457 }, 1458 }, 1459 "services": M{ 1460 "mysql": M{ 1461 "charm": "cs:quantal/mysql-1", 1462 "exposed": true, 1463 "units": M{ 1464 "mysql/1": M{ 1465 "machine": "1/lxc/0", 1466 "agent-state": "started", 1467 "public-address": "dummyenv-2.dns", 1468 }, 1469 }, 1470 }, 1471 }, 1472 }, 1473 }, 1474 ), test( 1475 "service with out of date charm", 1476 addMachine{machineId: "0", job: state.JobManageEnviron}, 1477 setAddresses{"0", []network.Address{network.NewAddress("dummyenv-0.dns", network.ScopeUnknown)}}, 1478 startAliveMachine{"0"}, 1479 setMachineStatus{"0", state.StatusStarted, ""}, 1480 addMachine{machineId: "1", job: state.JobHostUnits}, 1481 setAddresses{"1", []network.Address{network.NewAddress("dummyenv-1.dns", network.ScopeUnknown)}}, 1482 startAliveMachine{"1"}, 1483 setMachineStatus{"1", state.StatusStarted, ""}, 1484 addCharm{"mysql"}, 1485 addService{name: "mysql", charm: "mysql"}, 1486 setServiceExposed{"mysql", true}, 1487 addCharmPlaceholder{"mysql", 23}, 1488 addAliveUnit{"mysql", "1"}, 1489 1490 expect{ 1491 "services and units with correct charm status", 1492 M{ 1493 "environment": "dummyenv", 1494 "machines": M{ 1495 "0": machine0, 1496 "1": machine1, 1497 }, 1498 "services": M{ 1499 "mysql": M{ 1500 "charm": "cs:quantal/mysql-1", 1501 "can-upgrade-to": "cs:quantal/mysql-23", 1502 "exposed": true, 1503 "units": M{ 1504 "mysql/0": M{ 1505 "machine": "1", 1506 "agent-state": "allocating", 1507 "public-address": "dummyenv-1.dns", 1508 }, 1509 }, 1510 }, 1511 }, 1512 }, 1513 }, 1514 ), test( 1515 "unit with out of date charm", 1516 addMachine{machineId: "0", job: state.JobManageEnviron}, 1517 setAddresses{"0", []network.Address{network.NewAddress("dummyenv-0.dns", network.ScopeUnknown)}}, 1518 startAliveMachine{"0"}, 1519 setMachineStatus{"0", state.StatusStarted, ""}, 1520 addMachine{machineId: "1", job: state.JobHostUnits}, 1521 setAddresses{"1", []network.Address{network.NewAddress("dummyenv-1.dns", network.ScopeUnknown)}}, 1522 startAliveMachine{"1"}, 1523 setMachineStatus{"1", state.StatusStarted, ""}, 1524 addCharm{"mysql"}, 1525 addService{name: "mysql", charm: "mysql"}, 1526 setServiceExposed{"mysql", true}, 1527 addAliveUnit{"mysql", "1"}, 1528 setUnitCharmURL{"mysql/0", "cs:quantal/mysql-1"}, 1529 addCharmWithRevision{addCharm{"mysql"}, "local", 1}, 1530 setServiceCharm{"mysql", "local:quantal/mysql-1"}, 1531 1532 expect{ 1533 "services and units with correct charm status", 1534 M{ 1535 "environment": "dummyenv", 1536 "machines": M{ 1537 "0": machine0, 1538 "1": machine1, 1539 }, 1540 "services": M{ 1541 "mysql": M{ 1542 "charm": "local:quantal/mysql-1", 1543 "exposed": true, 1544 "units": M{ 1545 "mysql/0": M{ 1546 "machine": "1", 1547 "agent-state": "started", 1548 "upgrading-from": "cs:quantal/mysql-1", 1549 "public-address": "dummyenv-1.dns", 1550 }, 1551 }, 1552 }, 1553 }, 1554 }, 1555 }, 1556 ), test( 1557 "service and unit with out of date charms", 1558 addMachine{machineId: "0", job: state.JobManageEnviron}, 1559 setAddresses{"0", []network.Address{network.NewAddress("dummyenv-0.dns", network.ScopeUnknown)}}, 1560 startAliveMachine{"0"}, 1561 setMachineStatus{"0", state.StatusStarted, ""}, 1562 addMachine{machineId: "1", job: state.JobHostUnits}, 1563 setAddresses{"1", []network.Address{network.NewAddress("dummyenv-1.dns", network.ScopeUnknown)}}, 1564 startAliveMachine{"1"}, 1565 setMachineStatus{"1", state.StatusStarted, ""}, 1566 addCharm{"mysql"}, 1567 addService{name: "mysql", charm: "mysql"}, 1568 setServiceExposed{"mysql", true}, 1569 addAliveUnit{"mysql", "1"}, 1570 setUnitCharmURL{"mysql/0", "cs:quantal/mysql-1"}, 1571 addCharmWithRevision{addCharm{"mysql"}, "cs", 2}, 1572 setServiceCharm{"mysql", "cs:quantal/mysql-2"}, 1573 addCharmPlaceholder{"mysql", 23}, 1574 1575 expect{ 1576 "services and units with correct charm status", 1577 M{ 1578 "environment": "dummyenv", 1579 "machines": M{ 1580 "0": machine0, 1581 "1": machine1, 1582 }, 1583 "services": M{ 1584 "mysql": M{ 1585 "charm": "cs:quantal/mysql-2", 1586 "can-upgrade-to": "cs:quantal/mysql-23", 1587 "exposed": true, 1588 "units": M{ 1589 "mysql/0": M{ 1590 "machine": "1", 1591 "agent-state": "started", 1592 "upgrading-from": "cs:quantal/mysql-1", 1593 "public-address": "dummyenv-1.dns", 1594 }, 1595 }, 1596 }, 1597 }, 1598 }, 1599 }, 1600 ), test( 1601 "service with local charm not shown as out of date", 1602 addMachine{machineId: "0", job: state.JobManageEnviron}, 1603 setAddresses{"0", []network.Address{network.NewAddress("dummyenv-0.dns", network.ScopeUnknown)}}, 1604 startAliveMachine{"0"}, 1605 setMachineStatus{"0", state.StatusStarted, ""}, 1606 addMachine{machineId: "1", job: state.JobHostUnits}, 1607 setAddresses{"1", []network.Address{network.NewAddress("dummyenv-1.dns", network.ScopeUnknown)}}, 1608 startAliveMachine{"1"}, 1609 setMachineStatus{"1", state.StatusStarted, ""}, 1610 addCharm{"mysql"}, 1611 addService{name: "mysql", charm: "mysql"}, 1612 setServiceExposed{"mysql", true}, 1613 addAliveUnit{"mysql", "1"}, 1614 setUnitCharmURL{"mysql/0", "cs:quantal/mysql-1"}, 1615 addCharmWithRevision{addCharm{"mysql"}, "local", 1}, 1616 setServiceCharm{"mysql", "local:quantal/mysql-1"}, 1617 addCharmPlaceholder{"mysql", 23}, 1618 1619 expect{ 1620 "services and units with correct charm status", 1621 M{ 1622 "environment": "dummyenv", 1623 "machines": M{ 1624 "0": machine0, 1625 "1": machine1, 1626 }, 1627 "services": M{ 1628 "mysql": M{ 1629 "charm": "local:quantal/mysql-1", 1630 "exposed": true, 1631 "units": M{ 1632 "mysql/0": M{ 1633 "machine": "1", 1634 "agent-state": "started", 1635 "upgrading-from": "cs:quantal/mysql-1", 1636 "public-address": "dummyenv-1.dns", 1637 }, 1638 }, 1639 }, 1640 }, 1641 }, 1642 }, 1643 ), 1644 } 1645 1646 // TODO(dfc) test failing components by destructively mutating the state under the hood 1647 1648 type addMachine struct { 1649 machineId string 1650 cons constraints.Value 1651 job state.MachineJob 1652 } 1653 1654 func (am addMachine) step(c *gc.C, ctx *context) { 1655 m, err := ctx.st.AddOneMachine(state.MachineTemplate{ 1656 Series: "quantal", 1657 Constraints: am.cons, 1658 Jobs: []state.MachineJob{am.job}, 1659 }) 1660 c.Assert(err, jc.ErrorIsNil) 1661 c.Assert(m.Id(), gc.Equals, am.machineId) 1662 } 1663 1664 type addNetwork struct { 1665 name string 1666 providerId network.Id 1667 cidr string 1668 vlanTag int 1669 } 1670 1671 func (an addNetwork) step(c *gc.C, ctx *context) { 1672 n, err := ctx.st.AddNetwork(state.NetworkInfo{ 1673 Name: an.name, 1674 ProviderId: an.providerId, 1675 CIDR: an.cidr, 1676 VLANTag: an.vlanTag, 1677 }) 1678 c.Assert(err, jc.ErrorIsNil) 1679 c.Assert(n.Name(), gc.Equals, an.name) 1680 } 1681 1682 type addContainer struct { 1683 parentId string 1684 machineId string 1685 job state.MachineJob 1686 } 1687 1688 func (ac addContainer) step(c *gc.C, ctx *context) { 1689 template := state.MachineTemplate{ 1690 Series: "quantal", 1691 Jobs: []state.MachineJob{ac.job}, 1692 } 1693 m, err := ctx.st.AddMachineInsideMachine(template, ac.parentId, instance.LXC) 1694 c.Assert(err, jc.ErrorIsNil) 1695 c.Assert(m.Id(), gc.Equals, ac.machineId) 1696 } 1697 1698 type startMachine struct { 1699 machineId string 1700 } 1701 1702 func (sm startMachine) step(c *gc.C, ctx *context) { 1703 m, err := ctx.st.Machine(sm.machineId) 1704 c.Assert(err, jc.ErrorIsNil) 1705 cons, err := m.Constraints() 1706 c.Assert(err, jc.ErrorIsNil) 1707 inst, hc := testing.AssertStartInstanceWithConstraints(c, ctx.env, m.Id(), cons) 1708 err = m.SetProvisioned(inst.Id(), "fake_nonce", hc) 1709 c.Assert(err, jc.ErrorIsNil) 1710 } 1711 1712 type startMissingMachine struct { 1713 machineId string 1714 } 1715 1716 func (sm startMissingMachine) step(c *gc.C, ctx *context) { 1717 m, err := ctx.st.Machine(sm.machineId) 1718 c.Assert(err, jc.ErrorIsNil) 1719 cons, err := m.Constraints() 1720 c.Assert(err, jc.ErrorIsNil) 1721 _, hc := testing.AssertStartInstanceWithConstraints(c, ctx.env, m.Id(), cons) 1722 err = m.SetProvisioned("i-missing", "fake_nonce", hc) 1723 c.Assert(err, jc.ErrorIsNil) 1724 err = m.SetInstanceStatus("missing") 1725 c.Assert(err, jc.ErrorIsNil) 1726 } 1727 1728 type startAliveMachine struct { 1729 machineId string 1730 } 1731 1732 func (sam startAliveMachine) step(c *gc.C, ctx *context) { 1733 m, err := ctx.st.Machine(sam.machineId) 1734 c.Assert(err, jc.ErrorIsNil) 1735 pinger := ctx.setAgentPresence(c, m) 1736 cons, err := m.Constraints() 1737 c.Assert(err, jc.ErrorIsNil) 1738 inst, hc := testing.AssertStartInstanceWithConstraints(c, ctx.env, m.Id(), cons) 1739 err = m.SetProvisioned(inst.Id(), "fake_nonce", hc) 1740 c.Assert(err, jc.ErrorIsNil) 1741 ctx.pingers[m.Id()] = pinger 1742 } 1743 1744 type setAddresses struct { 1745 machineId string 1746 addresses []network.Address 1747 } 1748 1749 func (sa setAddresses) step(c *gc.C, ctx *context) { 1750 m, err := ctx.st.Machine(sa.machineId) 1751 c.Assert(err, jc.ErrorIsNil) 1752 err = m.SetAddresses(sa.addresses...) 1753 c.Assert(err, jc.ErrorIsNil) 1754 } 1755 1756 type setTools struct { 1757 machineId string 1758 version version.Binary 1759 } 1760 1761 func (st setTools) step(c *gc.C, ctx *context) { 1762 m, err := ctx.st.Machine(st.machineId) 1763 c.Assert(err, jc.ErrorIsNil) 1764 err = m.SetAgentVersion(st.version) 1765 c.Assert(err, jc.ErrorIsNil) 1766 } 1767 1768 type addCharm struct { 1769 name string 1770 } 1771 1772 func (ac addCharm) addCharmStep(c *gc.C, ctx *context, scheme string, rev int) { 1773 ch := testcharms.Repo.CharmDir(ac.name) 1774 name := ch.Meta().Name 1775 curl := charm.MustParseURL(fmt.Sprintf("%s:quantal/%s-%d", scheme, name, rev)) 1776 dummy, err := ctx.st.AddCharm(ch, curl, "dummy-path", fmt.Sprintf("%s-%d-sha256", name, rev)) 1777 c.Assert(err, jc.ErrorIsNil) 1778 ctx.charms[ac.name] = dummy 1779 } 1780 1781 func (ac addCharm) step(c *gc.C, ctx *context) { 1782 ch := testcharms.Repo.CharmDir(ac.name) 1783 ac.addCharmStep(c, ctx, "cs", ch.Revision()) 1784 } 1785 1786 type addCharmWithRevision struct { 1787 addCharm 1788 scheme string 1789 rev int 1790 } 1791 1792 func (ac addCharmWithRevision) step(c *gc.C, ctx *context) { 1793 ac.addCharmStep(c, ctx, ac.scheme, ac.rev) 1794 } 1795 1796 type addService struct { 1797 name string 1798 charm string 1799 networks []string 1800 cons constraints.Value 1801 } 1802 1803 func (as addService) step(c *gc.C, ctx *context) { 1804 ch, ok := ctx.charms[as.charm] 1805 c.Assert(ok, jc.IsTrue) 1806 svc, err := ctx.st.AddService(as.name, ctx.adminUserTag, ch, as.networks, nil) 1807 c.Assert(err, jc.ErrorIsNil) 1808 if svc.IsPrincipal() { 1809 err = svc.SetConstraints(as.cons) 1810 c.Assert(err, jc.ErrorIsNil) 1811 } 1812 } 1813 1814 type setServiceExposed struct { 1815 name string 1816 exposed bool 1817 } 1818 1819 func (sse setServiceExposed) step(c *gc.C, ctx *context) { 1820 s, err := ctx.st.Service(sse.name) 1821 c.Assert(err, jc.ErrorIsNil) 1822 err = s.ClearExposed() 1823 c.Assert(err, jc.ErrorIsNil) 1824 if sse.exposed { 1825 err = s.SetExposed() 1826 c.Assert(err, jc.ErrorIsNil) 1827 } 1828 } 1829 1830 type setServiceCharm struct { 1831 name string 1832 charm string 1833 } 1834 1835 func (ssc setServiceCharm) step(c *gc.C, ctx *context) { 1836 ch, err := ctx.st.Charm(charm.MustParseURL(ssc.charm)) 1837 c.Assert(err, jc.ErrorIsNil) 1838 s, err := ctx.st.Service(ssc.name) 1839 c.Assert(err, jc.ErrorIsNil) 1840 err = s.SetCharm(ch, false) 1841 c.Assert(err, jc.ErrorIsNil) 1842 } 1843 1844 type addCharmPlaceholder struct { 1845 name string 1846 rev int 1847 } 1848 1849 func (ac addCharmPlaceholder) step(c *gc.C, ctx *context) { 1850 ch := testcharms.Repo.CharmDir(ac.name) 1851 name := ch.Meta().Name 1852 curl := charm.MustParseURL(fmt.Sprintf("cs:quantal/%s-%d", name, ac.rev)) 1853 err := ctx.st.AddStoreCharmPlaceholder(curl) 1854 c.Assert(err, jc.ErrorIsNil) 1855 } 1856 1857 type addUnit struct { 1858 serviceName string 1859 machineId string 1860 } 1861 1862 func (au addUnit) step(c *gc.C, ctx *context) { 1863 s, err := ctx.st.Service(au.serviceName) 1864 c.Assert(err, jc.ErrorIsNil) 1865 u, err := s.AddUnit() 1866 c.Assert(err, jc.ErrorIsNil) 1867 m, err := ctx.st.Machine(au.machineId) 1868 c.Assert(err, jc.ErrorIsNil) 1869 err = u.AssignToMachine(m) 1870 c.Assert(err, jc.ErrorIsNil) 1871 } 1872 1873 type addAliveUnit struct { 1874 serviceName string 1875 machineId string 1876 } 1877 1878 func (aau addAliveUnit) step(c *gc.C, ctx *context) { 1879 s, err := ctx.st.Service(aau.serviceName) 1880 c.Assert(err, jc.ErrorIsNil) 1881 u, err := s.AddUnit() 1882 c.Assert(err, jc.ErrorIsNil) 1883 pinger := ctx.setAgentPresence(c, u) 1884 m, err := ctx.st.Machine(aau.machineId) 1885 c.Assert(err, jc.ErrorIsNil) 1886 err = u.AssignToMachine(m) 1887 c.Assert(err, jc.ErrorIsNil) 1888 ctx.pingers[u.Name()] = pinger 1889 } 1890 1891 type setUnitsAlive struct { 1892 serviceName string 1893 } 1894 1895 func (sua setUnitsAlive) step(c *gc.C, ctx *context) { 1896 s, err := ctx.st.Service(sua.serviceName) 1897 c.Assert(err, jc.ErrorIsNil) 1898 us, err := s.AllUnits() 1899 c.Assert(err, jc.ErrorIsNil) 1900 for _, u := range us { 1901 ctx.pingers[u.Name()] = ctx.setAgentPresence(c, u) 1902 } 1903 } 1904 1905 type setUnitStatus struct { 1906 unitName string 1907 status state.Status 1908 statusInfo string 1909 statusData map[string]interface{} 1910 } 1911 1912 func (sus setUnitStatus) step(c *gc.C, ctx *context) { 1913 u, err := ctx.st.Unit(sus.unitName) 1914 c.Assert(err, jc.ErrorIsNil) 1915 err = u.SetStatus(sus.status, sus.statusInfo, sus.statusData) 1916 c.Assert(err, jc.ErrorIsNil) 1917 } 1918 1919 type setUnitCharmURL struct { 1920 unitName string 1921 charm string 1922 } 1923 1924 func (uc setUnitCharmURL) step(c *gc.C, ctx *context) { 1925 u, err := ctx.st.Unit(uc.unitName) 1926 c.Assert(err, jc.ErrorIsNil) 1927 curl := charm.MustParseURL(uc.charm) 1928 err = u.SetCharmURL(curl) 1929 c.Assert(err, jc.ErrorIsNil) 1930 err = u.SetStatus(state.StatusActive, "", nil) 1931 c.Assert(err, jc.ErrorIsNil) 1932 } 1933 1934 type openUnitPort struct { 1935 unitName string 1936 protocol string 1937 number int 1938 } 1939 1940 func (oup openUnitPort) step(c *gc.C, ctx *context) { 1941 u, err := ctx.st.Unit(oup.unitName) 1942 c.Assert(err, jc.ErrorIsNil) 1943 err = u.OpenPort(oup.protocol, oup.number) 1944 c.Assert(err, jc.ErrorIsNil) 1945 } 1946 1947 type ensureDyingUnit struct { 1948 unitName string 1949 } 1950 1951 func (e ensureDyingUnit) step(c *gc.C, ctx *context) { 1952 u, err := ctx.st.Unit(e.unitName) 1953 c.Assert(err, jc.ErrorIsNil) 1954 err = u.Destroy() 1955 c.Assert(err, jc.ErrorIsNil) 1956 c.Assert(u.Life(), gc.Equals, state.Dying) 1957 } 1958 1959 type ensureDyingService struct { 1960 serviceName string 1961 } 1962 1963 func (e ensureDyingService) step(c *gc.C, ctx *context) { 1964 svc, err := ctx.st.Service(e.serviceName) 1965 c.Assert(err, jc.ErrorIsNil) 1966 err = svc.Destroy() 1967 c.Assert(err, jc.ErrorIsNil) 1968 err = svc.Refresh() 1969 c.Assert(err, jc.ErrorIsNil) 1970 c.Assert(svc.Life(), gc.Equals, state.Dying) 1971 } 1972 1973 type ensureDeadMachine struct { 1974 machineId string 1975 } 1976 1977 func (e ensureDeadMachine) step(c *gc.C, ctx *context) { 1978 m, err := ctx.st.Machine(e.machineId) 1979 c.Assert(err, jc.ErrorIsNil) 1980 err = m.EnsureDead() 1981 c.Assert(err, jc.ErrorIsNil) 1982 c.Assert(m.Life(), gc.Equals, state.Dead) 1983 } 1984 1985 type setMachineStatus struct { 1986 machineId string 1987 status state.Status 1988 statusInfo string 1989 } 1990 1991 func (sms setMachineStatus) step(c *gc.C, ctx *context) { 1992 m, err := ctx.st.Machine(sms.machineId) 1993 c.Assert(err, jc.ErrorIsNil) 1994 err = m.SetStatus(sms.status, sms.statusInfo, nil) 1995 c.Assert(err, jc.ErrorIsNil) 1996 } 1997 1998 type relateServices struct { 1999 ep1, ep2 string 2000 } 2001 2002 func (rs relateServices) step(c *gc.C, ctx *context) { 2003 eps, err := ctx.st.InferEndpoints(rs.ep1, rs.ep2) 2004 c.Assert(err, jc.ErrorIsNil) 2005 _, err = ctx.st.AddRelation(eps...) 2006 c.Assert(err, jc.ErrorIsNil) 2007 } 2008 2009 type addSubordinate struct { 2010 prinUnit string 2011 subService string 2012 } 2013 2014 func (as addSubordinate) step(c *gc.C, ctx *context) { 2015 u, err := ctx.st.Unit(as.prinUnit) 2016 c.Assert(err, jc.ErrorIsNil) 2017 eps, err := ctx.st.InferEndpoints(u.ServiceName(), as.subService) 2018 c.Assert(err, jc.ErrorIsNil) 2019 rel, err := ctx.st.EndpointsRelation(eps...) 2020 c.Assert(err, jc.ErrorIsNil) 2021 ru, err := rel.Unit(u) 2022 c.Assert(err, jc.ErrorIsNil) 2023 err = ru.EnterScope(nil) 2024 c.Assert(err, jc.ErrorIsNil) 2025 } 2026 2027 type scopedExpect struct { 2028 what string 2029 scope []string 2030 output M 2031 } 2032 2033 type expect struct { 2034 what string 2035 output M 2036 } 2037 2038 func (e scopedExpect) step(c *gc.C, ctx *context) { 2039 c.Logf("\nexpect: %s %s\n", e.what, strings.Join(e.scope, " ")) 2040 2041 // Now execute the command for each format. 2042 for _, format := range statusFormats { 2043 c.Logf("format %q", format.name) 2044 // Run command with the required format. 2045 args := append([]string{"--format", format.name}, e.scope...) 2046 c.Logf("running status %s", strings.Join(args, " ")) 2047 code, stdout, stderr := runStatus(c, args...) 2048 c.Assert(code, gc.Equals, 0) 2049 if !c.Check(stderr, gc.HasLen, 0) { 2050 c.Fatalf("status failed: %s", string(stderr)) 2051 } 2052 2053 // Prepare the output in the same format. 2054 buf, err := format.marshal(e.output) 2055 c.Assert(err, jc.ErrorIsNil) 2056 expected := make(M) 2057 err = format.unmarshal(buf, &expected) 2058 c.Assert(err, jc.ErrorIsNil) 2059 2060 // Check the output is as expected. 2061 actual := make(M) 2062 err = format.unmarshal(stdout, &actual) 2063 c.Assert(err, jc.ErrorIsNil) 2064 c.Assert(actual, jc.DeepEquals, expected) 2065 } 2066 } 2067 2068 func (e expect) step(c *gc.C, ctx *context) { 2069 scopedExpect{e.what, nil, e.output}.step(c, ctx) 2070 } 2071 2072 func (s *StatusSuite) TestStatusAllFormats(c *gc.C) { 2073 for i, t := range statusTests { 2074 c.Logf("test %d: %s", i, t.summary) 2075 func(t testCase) { 2076 // Prepare context and run all steps to setup. 2077 ctx := s.newContext(c) 2078 defer s.resetContext(c, ctx) 2079 ctx.run(c, t.steps) 2080 }(t) 2081 } 2082 } 2083 2084 type fakeApiClient struct { 2085 statusReturn *api.Status 2086 patternsUsed []string 2087 closeCalled bool 2088 } 2089 2090 func newFakeApiClient(statusReturn *api.Status) fakeApiClient { 2091 return fakeApiClient{ 2092 statusReturn: statusReturn, 2093 } 2094 } 2095 2096 func (a *fakeApiClient) Status(patterns []string) (*api.Status, error) { 2097 a.patternsUsed = patterns 2098 return a.statusReturn, nil 2099 } 2100 2101 func (a *fakeApiClient) Close() error { 2102 a.closeCalled = true 2103 return nil 2104 } 2105 2106 // Check that the client works with an older server which doesn't 2107 // return the top level Relations field nor the unit and machine level 2108 // Agent field (they were introduced at the same time). 2109 func (s *StatusSuite) TestStatusWithPreRelationsServer(c *gc.C) { 2110 // Construct an older style status response 2111 client := newFakeApiClient(&api.Status{ 2112 EnvironmentName: "dummyenv", 2113 Machines: map[string]api.MachineStatus{ 2114 "0": { 2115 // Agent field intentionally not set 2116 Id: "0", 2117 InstanceId: instance.Id("dummyenv-0"), 2118 AgentState: "down", 2119 AgentStateInfo: "(started)", 2120 Series: "quantal", 2121 Containers: map[string]api.MachineStatus{}, 2122 Jobs: []multiwatcher.MachineJob{multiwatcher.JobManageEnviron}, 2123 HasVote: false, 2124 WantsVote: true, 2125 }, 2126 "1": { 2127 // Agent field intentionally not set 2128 Id: "1", 2129 InstanceId: instance.Id("dummyenv-1"), 2130 AgentState: "started", 2131 AgentStateInfo: "hello", 2132 Series: "quantal", 2133 Containers: map[string]api.MachineStatus{}, 2134 Jobs: []multiwatcher.MachineJob{multiwatcher.JobHostUnits}, 2135 HasVote: false, 2136 WantsVote: false, 2137 }, 2138 }, 2139 Services: map[string]api.ServiceStatus{ 2140 "mysql": { 2141 Charm: "local:quantal/mysql-1", 2142 Relations: map[string][]string{ 2143 "server": {"wordpress"}, 2144 }, 2145 Units: map[string]api.UnitStatus{ 2146 "mysql/0": { 2147 // Agent field intentionally not set 2148 Machine: "1", 2149 AgentState: "allocating", 2150 }, 2151 }, 2152 }, 2153 "wordpress": { 2154 Charm: "local:quantal/wordpress-3", 2155 Relations: map[string][]string{ 2156 "db": {"mysql"}, 2157 }, 2158 Units: map[string]api.UnitStatus{ 2159 "wordpress/0": { 2160 // Agent field intentionally not set 2161 AgentState: "error", 2162 AgentStateInfo: "blam", 2163 Machine: "1", 2164 }, 2165 }, 2166 }, 2167 }, 2168 Networks: map[string]api.NetworkStatus{}, 2169 // Relations field intentionally not set 2170 }) 2171 s.PatchValue(&newApiClientForStatus, func(_ *StatusCommand) (statusAPI, error) { 2172 return &client, nil 2173 }) 2174 2175 expected := expect{ 2176 "sane output with an older client that doesn't return Agent or Relations fields", 2177 M{ 2178 "environment": "dummyenv", 2179 "machines": M{ 2180 "0": M{ 2181 "agent-state": "down", 2182 "agent-state-info": "(started)", 2183 "instance-id": "dummyenv-0", 2184 "series": "quantal", 2185 "state-server-member-status": "adding-vote", 2186 }, 2187 "1": M{ 2188 "agent-state": "started", 2189 "agent-state-info": "hello", 2190 "instance-id": "dummyenv-1", 2191 "series": "quantal", 2192 }, 2193 }, 2194 "services": M{ 2195 "mysql": M{ 2196 "charm": "local:quantal/mysql-1", 2197 "exposed": false, 2198 "relations": M{ 2199 "server": L{"wordpress"}, 2200 }, 2201 "units": M{ 2202 "mysql/0": M{ 2203 "machine": "1", 2204 "agent-state": "allocating", 2205 }, 2206 }, 2207 }, 2208 "wordpress": M{ 2209 "charm": "local:quantal/wordpress-3", 2210 "exposed": false, 2211 "relations": M{ 2212 "db": L{"mysql"}, 2213 }, 2214 "units": M{ 2215 "wordpress/0": M{ 2216 "machine": "1", 2217 "agent-state": "error", 2218 "agent-state-info": "blam", 2219 }, 2220 }, 2221 }, 2222 }, 2223 }, 2224 } 2225 ctx := s.newContext(c) 2226 defer s.resetContext(c, ctx) 2227 ctx.run(c, []stepper{expected}) 2228 } 2229 2230 func (s *StatusSuite) TestStatusWithFormatSummary(c *gc.C) { 2231 ctx := s.newContext(c) 2232 defer s.resetContext(c, ctx) 2233 steps := []stepper{ 2234 addMachine{machineId: "0", job: state.JobManageEnviron}, 2235 setAddresses{"0", []network.Address{network.NewAddress("localhost", network.ScopeUnknown)}}, 2236 startAliveMachine{"0"}, 2237 setMachineStatus{"0", state.StatusStarted, ""}, 2238 addCharm{"wordpress"}, 2239 addCharm{"mysql"}, 2240 addCharm{"logging"}, 2241 addService{name: "wordpress", charm: "wordpress"}, 2242 setServiceExposed{"wordpress", true}, 2243 addMachine{machineId: "1", job: state.JobHostUnits}, 2244 setAddresses{"1", []network.Address{network.NewAddress("localhost", network.ScopeUnknown)}}, 2245 startAliveMachine{"1"}, 2246 setMachineStatus{"1", state.StatusStarted, ""}, 2247 addAliveUnit{"wordpress", "1"}, 2248 setUnitStatus{"wordpress/0", state.StatusActive, "", nil}, 2249 addService{name: "mysql", charm: "mysql"}, 2250 setServiceExposed{"mysql", true}, 2251 addMachine{machineId: "2", job: state.JobHostUnits}, 2252 setAddresses{"2", []network.Address{network.NewAddress("10.0.0.1", network.ScopeUnknown)}}, 2253 startAliveMachine{"2"}, 2254 setMachineStatus{"2", state.StatusStarted, ""}, 2255 addAliveUnit{"mysql", "2"}, 2256 setUnitStatus{"mysql/0", state.StatusActive, "", nil}, 2257 addService{name: "logging", charm: "logging"}, 2258 setServiceExposed{"logging", true}, 2259 relateServices{"wordpress", "mysql"}, 2260 relateServices{"wordpress", "logging"}, 2261 relateServices{"mysql", "logging"}, 2262 addSubordinate{"wordpress/0", "logging"}, 2263 addSubordinate{"mysql/0", "logging"}, 2264 setUnitsAlive{"logging"}, 2265 setUnitStatus{"logging/0", state.StatusActive, "", nil}, 2266 setUnitStatus{"logging/1", state.StatusError, "somehow lost in all those logs", nil}, 2267 } 2268 for _, s := range steps { 2269 s.step(c, ctx) 2270 } 2271 code, stdout, stderr := runStatus(c, "--format", "summary") 2272 c.Check(code, gc.Equals, 0) 2273 c.Check(string(stderr), gc.Equals, "") 2274 c.Assert( 2275 string(stdout), 2276 gc.Equals, 2277 "Running on subnets: 127.0.0.1/8, 10.0.0.1/8 \n"+ 2278 "Utilizing ports: \n"+ 2279 " # MACHINES: (3)\n"+ 2280 " started: 3 \n"+ 2281 " \n"+ 2282 " # UNITS: (4)\n"+ 2283 " error: 1 \n"+ 2284 " started: 3 \n"+ 2285 " \n"+ 2286 " # SERVICES: (3)\n"+ 2287 " logging 1/1 exposed\n"+ 2288 " mysql 1/1 exposed\n"+ 2289 " wordpress 1/1 exposed\n"+ 2290 "\n", 2291 ) 2292 } 2293 func (s *StatusSuite) TestStatusWithFormatOneline(c *gc.C) { 2294 ctx := s.newContext(c) 2295 defer s.resetContext(c, ctx) 2296 steps := []stepper{ 2297 addMachine{machineId: "0", job: state.JobManageEnviron}, 2298 setAddresses{"0", []network.Address{network.NewAddress("dummyenv-0.dns", network.ScopeUnknown)}}, 2299 startAliveMachine{"0"}, 2300 setMachineStatus{"0", state.StatusStarted, ""}, 2301 addCharm{"wordpress"}, 2302 addCharm{"mysql"}, 2303 addCharm{"logging"}, 2304 2305 addService{name: "wordpress", charm: "wordpress"}, 2306 setServiceExposed{"wordpress", true}, 2307 addMachine{machineId: "1", job: state.JobHostUnits}, 2308 setAddresses{"1", []network.Address{network.NewAddress("dummyenv-1.dns", network.ScopeUnknown)}}, 2309 startAliveMachine{"1"}, 2310 setMachineStatus{"1", state.StatusStarted, ""}, 2311 addAliveUnit{"wordpress", "1"}, 2312 setUnitStatus{"wordpress/0", state.StatusActive, "", nil}, 2313 2314 addService{name: "mysql", charm: "mysql"}, 2315 setServiceExposed{"mysql", true}, 2316 addMachine{machineId: "2", job: state.JobHostUnits}, 2317 setAddresses{"2", []network.Address{network.NewAddress("dummyenv-2.dns", network.ScopeUnknown)}}, 2318 startAliveMachine{"2"}, 2319 setMachineStatus{"2", state.StatusStarted, ""}, 2320 addAliveUnit{"mysql", "2"}, 2321 setUnitStatus{"mysql/0", state.StatusActive, "", nil}, 2322 2323 addService{name: "logging", charm: "logging"}, 2324 setServiceExposed{"logging", true}, 2325 2326 relateServices{"wordpress", "mysql"}, 2327 relateServices{"wordpress", "logging"}, 2328 relateServices{"mysql", "logging"}, 2329 2330 addSubordinate{"wordpress/0", "logging"}, 2331 addSubordinate{"mysql/0", "logging"}, 2332 2333 setUnitsAlive{"logging"}, 2334 setUnitStatus{"logging/0", state.StatusActive, "", nil}, 2335 setUnitStatus{"logging/1", state.StatusError, "somehow lost in all those logs", nil}, 2336 } 2337 2338 ctx.run(c, steps) 2339 2340 const expected = ` 2341 - mysql/0: dummyenv-2.dns (started) 2342 - logging/1: dummyenv-2.dns (error) 2343 - wordpress/0: dummyenv-1.dns (started) 2344 - logging/0: dummyenv-1.dns (started) 2345 ` 2346 2347 code, stdout, stderr := runStatus(c, "--format", "oneline") 2348 c.Check(code, gc.Equals, 0) 2349 c.Check(string(stderr), gc.Equals, "") 2350 c.Assert(string(stdout), gc.Equals, expected) 2351 2352 c.Log(`Check that "short" is an alias for oneline.`) 2353 code, stdout, stderr = runStatus(c, "--format", "short") 2354 c.Check(code, gc.Equals, 0) 2355 c.Check(string(stderr), gc.Equals, "") 2356 c.Assert(string(stdout), gc.Equals, expected) 2357 2358 c.Log(`Check that "line" is an alias for oneline.`) 2359 code, stdout, stderr = runStatus(c, "--format", "line") 2360 c.Check(code, gc.Equals, 0) 2361 c.Check(string(stderr), gc.Equals, "") 2362 c.Assert(string(stdout), gc.Equals, expected) 2363 } 2364 func (s *StatusSuite) TestStatusWithFormatTabular(c *gc.C) { 2365 ctx := s.newContext(c) 2366 defer s.resetContext(c, ctx) 2367 steps := []stepper{ 2368 addMachine{machineId: "0", job: state.JobManageEnviron}, 2369 setAddresses{"0", []network.Address{network.NewAddress("dummyenv-0.dns", network.ScopeUnknown)}}, 2370 startAliveMachine{"0"}, 2371 setMachineStatus{"0", state.StatusStarted, ""}, 2372 addCharm{"wordpress"}, 2373 addCharm{"mysql"}, 2374 addCharm{"logging"}, 2375 addService{name: "wordpress", charm: "wordpress"}, 2376 setServiceExposed{"wordpress", true}, 2377 addMachine{machineId: "1", job: state.JobHostUnits}, 2378 setAddresses{"1", []network.Address{network.NewAddress("dummyenv-1.dns", network.ScopeUnknown)}}, 2379 startAliveMachine{"1"}, 2380 setMachineStatus{"1", state.StatusStarted, ""}, 2381 addAliveUnit{"wordpress", "1"}, 2382 setUnitStatus{"wordpress/0", state.StatusActive, "", nil}, 2383 addService{name: "mysql", charm: "mysql"}, 2384 setServiceExposed{"mysql", true}, 2385 addMachine{machineId: "2", job: state.JobHostUnits}, 2386 setAddresses{"2", []network.Address{network.NewAddress("dummyenv-2.dns", network.ScopeUnknown)}}, 2387 startAliveMachine{"2"}, 2388 setMachineStatus{"2", state.StatusStarted, ""}, 2389 addAliveUnit{"mysql", "2"}, 2390 setUnitStatus{"mysql/0", state.StatusActive, "", nil}, 2391 addService{name: "logging", charm: "logging"}, 2392 setServiceExposed{"logging", true}, 2393 relateServices{"wordpress", "mysql"}, 2394 relateServices{"wordpress", "logging"}, 2395 relateServices{"mysql", "logging"}, 2396 addSubordinate{"wordpress/0", "logging"}, 2397 addSubordinate{"mysql/0", "logging"}, 2398 setUnitsAlive{"logging"}, 2399 setUnitStatus{"logging/0", state.StatusActive, "", nil}, 2400 setUnitStatus{"logging/1", state.StatusError, "somehow lost in all those logs", nil}, 2401 } 2402 for _, s := range steps { 2403 s.step(c, ctx) 2404 } 2405 code, stdout, stderr := runStatus(c, "--format", "tabular") 2406 c.Check(code, gc.Equals, 0) 2407 c.Check(string(stderr), gc.Equals, "") 2408 c.Assert( 2409 string(stdout), 2410 gc.Equals, 2411 "[Machines] \n"+ 2412 "ID STATE VERSION DNS INS-ID SERIES HARDWARE \n"+ 2413 "0 started dummyenv-0.dns dummyenv-0 quantal arch=amd64 cpu-cores=1 mem=1024M root-disk=8192M \n"+ 2414 "1 started dummyenv-1.dns dummyenv-1 quantal arch=amd64 cpu-cores=1 mem=1024M root-disk=8192M \n"+ 2415 "2 started dummyenv-2.dns dummyenv-2 quantal arch=amd64 cpu-cores=1 mem=1024M root-disk=8192M \n"+ 2416 "\n"+ 2417 "[Services] \n"+ 2418 "NAME EXPOSED CHARM \n"+ 2419 "logging true cs:quantal/logging-1 \n"+ 2420 "mysql true cs:quantal/mysql-1 \n"+ 2421 "wordpress true cs:quantal/wordpress-3 \n"+ 2422 "\n"+ 2423 "[Units] \n"+ 2424 "ID STATE VERSION MACHINE PORTS PUBLIC-ADDRESS \n"+ 2425 "mysql/0 started 2 dummyenv-2.dns \n"+ 2426 " logging/1 error dummyenv-2.dns \n"+ 2427 "wordpress/0 started 1 dummyenv-1.dns \n"+ 2428 " logging/0 started dummyenv-1.dns \n"+ 2429 "\n", 2430 ) 2431 } 2432 2433 func (s *StatusSuite) TestStatusWithNilStatusApi(c *gc.C) { 2434 ctx := s.newContext(c) 2435 defer s.resetContext(c, ctx) 2436 steps := []stepper{ 2437 addMachine{machineId: "0", job: state.JobManageEnviron}, 2438 setAddresses{"0", []network.Address{network.NewAddress("dummyenv-0.dns", network.ScopeUnknown)}}, 2439 startAliveMachine{"0"}, 2440 setMachineStatus{"0", state.StatusStarted, ""}, 2441 } 2442 2443 for _, s := range steps { 2444 s.step(c, ctx) 2445 } 2446 2447 client := fakeApiClient{} 2448 var status = client.Status 2449 s.PatchValue(&status, func(_ []string) (*api.Status, error) { 2450 return nil, nil 2451 }) 2452 s.PatchValue(&newApiClientForStatus, func(_ *StatusCommand) (statusAPI, error) { 2453 return &client, nil 2454 }) 2455 2456 code, _, stderr := runStatus(c, "--format", "tabular") 2457 c.Check(code, gc.Equals, 1) 2458 c.Check(string(stderr), gc.Equals, "error: unable to obtain the current status\n") 2459 } 2460 2461 // 2462 // Filtering Feature 2463 // 2464 2465 func (s *StatusSuite) FilteringTestSetup(c *gc.C) *context { 2466 ctx := s.newContext(c) 2467 2468 steps := []stepper{ 2469 // Given a machine is started 2470 // And the machine's ID is "0" 2471 // And the machine's job is to manage the environment 2472 addMachine{machineId: "0", job: state.JobManageEnviron}, 2473 startAliveMachine{"0"}, 2474 setMachineStatus{"0", state.StatusStarted, ""}, 2475 // And the machine's address is "dummyenv-0.dns" 2476 setAddresses{"0", []network.Address{network.NewAddress("dummyenv-0.dns", network.ScopeUnknown)}}, 2477 // And the "wordpress" charm is available 2478 addCharm{"wordpress"}, 2479 addService{name: "wordpress", charm: "wordpress"}, 2480 // And the "mysql" charm is available 2481 addCharm{"mysql"}, 2482 addService{name: "mysql", charm: "mysql"}, 2483 // And the "logging" charm is available 2484 addCharm{"logging"}, 2485 // And a machine is started 2486 // And the machine's ID is "1" 2487 // And the machine's job is to host units 2488 addMachine{machineId: "1", job: state.JobHostUnits}, 2489 startAliveMachine{"1"}, 2490 setMachineStatus{"1", state.StatusStarted, ""}, 2491 // And the machine's address is "dummyenv-1.dns" 2492 setAddresses{"1", []network.Address{network.NewAddress("dummyenv-1.dns", network.ScopeUnknown)}}, 2493 // And a unit of "wordpress" is deployed to machine "1" 2494 addAliveUnit{"wordpress", "1"}, 2495 // And the unit is started 2496 setUnitStatus{"wordpress/0", state.StatusActive, "", nil}, 2497 // And a machine is started 2498 2499 // And the machine's ID is "2" 2500 // And the machine's job is to host units 2501 addMachine{machineId: "2", job: state.JobHostUnits}, 2502 startAliveMachine{"2"}, 2503 setMachineStatus{"2", state.StatusStarted, ""}, 2504 // And the machine's address is "dummyenv-2.dns" 2505 setAddresses{"2", []network.Address{network.NewAddress("dummyenv-2.dns", network.ScopeUnknown)}}, 2506 // And a unit of "mysql" is deployed to machine "2" 2507 addAliveUnit{"mysql", "2"}, 2508 // And the unit is started 2509 setUnitStatus{"mysql/0", state.StatusActive, "", nil}, 2510 // And the "logging" service is added 2511 addService{name: "logging", charm: "logging"}, 2512 // And the service is exposed 2513 setServiceExposed{"logging", true}, 2514 // And the "wordpress" service is related to the "mysql" service 2515 relateServices{"wordpress", "mysql"}, 2516 // And the "wordpress" service is related to the "logging" service 2517 relateServices{"wordpress", "logging"}, 2518 // And the "mysql" service is related to the "logging" service 2519 relateServices{"mysql", "logging"}, 2520 // And the "logging" service is a subordinate to unit 0 of the "wordpress" service 2521 addSubordinate{"wordpress/0", "logging"}, 2522 setUnitStatus{"logging/0", state.StatusActive, "", nil}, 2523 // And the "logging" service is a subordinate to unit 0 of the "mysql" service 2524 addSubordinate{"mysql/0", "logging"}, 2525 setUnitStatus{"logging/1", state.StatusActive, "", nil}, 2526 setUnitsAlive{"logging"}, 2527 } 2528 2529 ctx.run(c, steps) 2530 return ctx 2531 } 2532 2533 // Scenario: One unit is in an errored state and user filters to started 2534 func (s *StatusSuite) TestFilterToStarted(c *gc.C) { 2535 ctx := s.FilteringTestSetup(c) 2536 defer s.resetContext(c, ctx) 2537 2538 // Given unit 1 of the "logging" service has an error 2539 setUnitStatus{"logging/1", state.StatusError, "mock error", nil}.step(c, ctx) 2540 // And unit 0 of the "mysql" service has an error 2541 setUnitStatus{"mysql/0", state.StatusError, "mock error", nil}.step(c, ctx) 2542 // When I run juju status --format oneline started 2543 _, stdout, stderr := runStatus(c, "--format", "oneline", "started") 2544 c.Assert(string(stderr), gc.Equals, "") 2545 // Then I should receive output prefixed with: 2546 const expected = ` 2547 2548 - wordpress/0: dummyenv-1.dns (started) 2549 - logging/0: dummyenv-1.dns (started) 2550 ` 2551 2552 c.Assert(string(stdout), gc.Equals, expected[1:]) 2553 } 2554 2555 // Scenario: One unit is in an errored state and user filters to errored 2556 func (s *StatusSuite) TestFilterToErrored(c *gc.C) { 2557 ctx := s.FilteringTestSetup(c) 2558 defer s.resetContext(c, ctx) 2559 2560 // Given unit 1 of the "logging" service has an error 2561 setUnitStatus{"logging/1", state.StatusError, "mock error", nil}.step(c, ctx) 2562 // When I run juju status --format oneline error 2563 _, stdout, stderr := runStatus(c, "--format", "oneline", "error") 2564 c.Assert(stderr, gc.IsNil) 2565 // Then I should receive output prefixed with: 2566 const expected = ` 2567 2568 - mysql/0: dummyenv-2.dns (started) 2569 - logging/1: dummyenv-2.dns (error) 2570 ` 2571 2572 c.Assert(string(stdout), gc.Equals, expected[1:]) 2573 } 2574 2575 // Scenario: User filters to mysql service 2576 func (s *StatusSuite) TestFilterToService(c *gc.C) { 2577 ctx := s.FilteringTestSetup(c) 2578 defer s.resetContext(c, ctx) 2579 2580 // When I run juju status --format oneline error 2581 _, stdout, stderr := runStatus(c, "--format", "oneline", "mysql") 2582 c.Assert(stderr, gc.IsNil) 2583 // Then I should receive output prefixed with: 2584 const expected = ` 2585 2586 - mysql/0: dummyenv-2.dns (started) 2587 - logging/1: dummyenv-2.dns (started) 2588 ` 2589 2590 c.Assert(string(stdout), gc.Equals, expected[1:]) 2591 } 2592 2593 // Scenario: User filters to exposed services 2594 func (s *StatusSuite) TestFilterToExposedService(c *gc.C) { 2595 ctx := s.FilteringTestSetup(c) 2596 defer s.resetContext(c, ctx) 2597 2598 // Given unit 1 of the "mysql" service is exposed 2599 setServiceExposed{"mysql", true}.step(c, ctx) 2600 // And the logging service is not exposed 2601 setServiceExposed{"logging", false}.step(c, ctx) 2602 // And the wordpress service is not exposed 2603 setServiceExposed{"wordpress", false}.step(c, ctx) 2604 // When I run juju status --format oneline exposed 2605 _, stdout, stderr := runStatus(c, "--format", "oneline", "exposed") 2606 c.Assert(stderr, gc.IsNil) 2607 // Then I should receive output prefixed with: 2608 const expected = ` 2609 2610 - mysql/0: dummyenv-2.dns (started) 2611 - logging/1: dummyenv-2.dns (started) 2612 ` 2613 2614 c.Assert(string(stdout), gc.Equals, expected[1:]) 2615 } 2616 2617 // Scenario: User filters to non-exposed services 2618 func (s *StatusSuite) TestFilterToNotExposedService(c *gc.C) { 2619 ctx := s.FilteringTestSetup(c) 2620 defer s.resetContext(c, ctx) 2621 2622 setServiceExposed{"mysql", true}.step(c, ctx) 2623 // When I run juju status --format oneline not exposed 2624 _, stdout, stderr := runStatus(c, "--format", "oneline", "not", "exposed") 2625 c.Assert(stderr, gc.IsNil) 2626 // Then I should receive output prefixed with: 2627 const expected = ` 2628 2629 - wordpress/0: dummyenv-1.dns (started) 2630 - logging/0: dummyenv-1.dns (started) 2631 ` 2632 2633 c.Assert(string(stdout), gc.Equals, expected[1:]) 2634 } 2635 2636 // Scenario: Filtering on Subnets 2637 func (s *StatusSuite) TestFilterOnSubnet(c *gc.C) { 2638 ctx := s.FilteringTestSetup(c) 2639 defer s.resetContext(c, ctx) 2640 2641 // Given the address for machine "1" is "localhost" 2642 setAddresses{"1", []network.Address{network.NewAddress("localhost", network.ScopeUnknown)}}.step(c, ctx) 2643 // And the address for machine "2" is "10.0.0.1" 2644 setAddresses{"2", []network.Address{network.NewAddress("10.0.0.1", network.ScopeUnknown)}}.step(c, ctx) 2645 // When I run juju status --format oneline 127.0.0.1 2646 _, stdout, stderr := runStatus(c, "--format", "oneline", "127.0.0.1") 2647 c.Assert(stderr, gc.IsNil) 2648 // Then I should receive output prefixed with: 2649 const expected = ` 2650 2651 - wordpress/0: localhost (started) 2652 - logging/0: localhost (started) 2653 ` 2654 2655 c.Assert(string(stdout), gc.Equals, expected[1:]) 2656 } 2657 2658 // Scenario: Filtering on Ports 2659 func (s *StatusSuite) TestFilterOnPorts(c *gc.C) { 2660 ctx := s.FilteringTestSetup(c) 2661 defer s.resetContext(c, ctx) 2662 2663 // Given the address for machine "1" is "localhost" 2664 setAddresses{"1", []network.Address{network.NewAddress("localhost", network.ScopeUnknown)}}.step(c, ctx) 2665 // And the address for machine "2" is "10.0.0.1" 2666 setAddresses{"2", []network.Address{network.NewAddress("10.0.0.1", network.ScopeUnknown)}}.step(c, ctx) 2667 openUnitPort{"wordpress/0", "tcp", 80}.step(c, ctx) 2668 // When I run juju status --format oneline 80/tcp 2669 _, stdout, stderr := runStatus(c, "--format", "oneline", "80/tcp") 2670 c.Assert(stderr, gc.IsNil) 2671 // Then I should receive output prefixed with: 2672 const expected = ` 2673 2674 - wordpress/0: localhost (started) 80/tcp 2675 - logging/0: localhost (started) 2676 ` 2677 2678 c.Assert(string(stdout), gc.Equals, expected[1:]) 2679 } 2680 2681 // Scenario: User filters out a parent, but not its subordinate 2682 func (s *StatusSuite) TestFilterParentButNotSubordinate(c *gc.C) { 2683 ctx := s.FilteringTestSetup(c) 2684 defer s.resetContext(c, ctx) 2685 2686 // When I run juju status --format oneline 80/tcp 2687 _, stdout, stderr := runStatus(c, "--format", "oneline", "logging") 2688 c.Assert(stderr, gc.IsNil) 2689 // Then I should receive output prefixed with: 2690 const expected = ` 2691 2692 - mysql/0: dummyenv-2.dns (started) 2693 - logging/1: dummyenv-2.dns (started) 2694 - wordpress/0: dummyenv-1.dns (started) 2695 - logging/0: dummyenv-1.dns (started) 2696 ` 2697 2698 c.Assert(string(stdout), gc.Equals, expected[1:]) 2699 } 2700 2701 // Scenario: User filters out a subordinate, but not its parent 2702 func (s *StatusSuite) TestFilterSubordinateButNotParent(c *gc.C) { 2703 ctx := s.FilteringTestSetup(c) 2704 defer s.resetContext(c, ctx) 2705 2706 // Given the wordpress service is exposed 2707 setServiceExposed{"wordpress", true}.step(c, ctx) 2708 // When I run juju status --format oneline not exposed 2709 _, stdout, stderr := runStatus(c, "--format", "oneline", "not", "exposed") 2710 c.Assert(stderr, gc.IsNil) 2711 // Then I should receive output prefixed with: 2712 const expected = ` 2713 2714 - mysql/0: dummyenv-2.dns (started) 2715 - logging/1: dummyenv-2.dns (started) 2716 ` 2717 2718 c.Assert(string(stdout), gc.Equals, expected[1:]) 2719 } 2720 2721 func (s *StatusSuite) TestFilterMultipleHomogenousPatterns(c *gc.C) { 2722 ctx := s.FilteringTestSetup(c) 2723 defer s.resetContext(c, ctx) 2724 2725 _, stdout, stderr := runStatus(c, "--format", "oneline", "wordpress/0", "mysql/0") 2726 c.Assert(stderr, gc.IsNil) 2727 // Then I should receive output prefixed with: 2728 const expected = ` 2729 2730 - mysql/0: dummyenv-2.dns (started) 2731 - logging/1: dummyenv-2.dns (started) 2732 - wordpress/0: dummyenv-1.dns (started) 2733 - logging/0: dummyenv-1.dns (started) 2734 ` 2735 2736 c.Assert(string(stdout), gc.Equals, expected[1:]) 2737 } 2738 2739 func (s *StatusSuite) TestFilterMultipleHeterogenousPatterns(c *gc.C) { 2740 ctx := s.FilteringTestSetup(c) 2741 defer s.resetContext(c, ctx) 2742 2743 _, stdout, stderr := runStatus(c, "--format", "oneline", "wordpress/0", "started") 2744 c.Assert(stderr, gc.IsNil) 2745 // Then I should receive output prefixed with: 2746 const expected = ` 2747 2748 - mysql/0: dummyenv-2.dns (started) 2749 - logging/1: dummyenv-2.dns (started) 2750 - wordpress/0: dummyenv-1.dns (started) 2751 - logging/0: dummyenv-1.dns (started) 2752 ` 2753 2754 c.Assert(string(stdout), gc.Equals, expected[1:]) 2755 } 2756 2757 // TestSummaryStatusWithUnresolvableDns is result of bug# 1410320. 2758 func (s *StatusSuite) TestSummaryStatusWithUnresolvableDns(c *gc.C) { 2759 formatter := &summaryFormatter{} 2760 formatter.resolveAndTrackIp("invalidDns") 2761 // Test should not panic. 2762 }