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