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