github.com/mattyw/juju@v0.0.0-20140610034352-732aecd63861/cmd/juju/status_test.go (about) 1 // Copyright 2012, 2013 Canonical Ltd. 2 // Licensed under the AGPLv3, see LICENCE file for details. 3 4 package main 5 6 import ( 7 "bytes" 8 "encoding/json" 9 "fmt" 10 "net/url" 11 "strings" 12 "time" 13 14 jc "github.com/juju/testing/checkers" 15 gc "launchpad.net/gocheck" 16 "launchpad.net/goyaml" 17 18 "github.com/juju/juju/charm" 19 charmtesting "github.com/juju/juju/charm/testing" 20 "github.com/juju/juju/cmd" 21 "github.com/juju/juju/cmd/envcmd" 22 "github.com/juju/juju/constraints" 23 "github.com/juju/juju/environs/network" 24 "github.com/juju/juju/instance" 25 "github.com/juju/juju/juju" 26 "github.com/juju/juju/juju/testing" 27 "github.com/juju/juju/state" 28 "github.com/juju/juju/state/api/params" 29 "github.com/juju/juju/state/presence" 30 coretesting "github.com/juju/juju/testing" 31 "github.com/juju/juju/version" 32 ) 33 34 func runStatus(c *gc.C, args ...string) (code int, stdout, stderr []byte) { 35 ctx := coretesting.Context(c) 36 code = cmd.Main(envcmd.Wrap(&StatusCommand{}), ctx, args) 37 stdout = ctx.Stdout.(*bytes.Buffer).Bytes() 38 stderr = ctx.Stderr.(*bytes.Buffer).Bytes() 39 return 40 } 41 42 type StatusSuite struct { 43 testing.JujuConnSuite 44 } 45 46 var _ = gc.Suite(&StatusSuite{}) 47 48 type M map[string]interface{} 49 50 type L []interface{} 51 52 type testCase struct { 53 summary string 54 steps []stepper 55 } 56 57 func test(summary string, steps ...stepper) testCase { 58 return testCase{summary, steps} 59 } 60 61 type stepper interface { 62 step(c *gc.C, ctx *context) 63 } 64 65 type context struct { 66 st *state.State 67 conn *juju.Conn 68 charms map[string]*state.Charm 69 pingers map[string]*presence.Pinger 70 } 71 72 func (s *StatusSuite) newContext() *context { 73 st := s.Conn.Environ.(testing.GetStater).GetStateInAPIServer() 74 // We make changes in the API server's state so that 75 // our changes to presence are immediately noticed 76 // in the status. 77 return &context{ 78 st: st, 79 conn: s.Conn, 80 charms: make(map[string]*state.Charm), 81 pingers: make(map[string]*presence.Pinger), 82 } 83 } 84 85 func (s *StatusSuite) resetContext(c *gc.C, ctx *context) { 86 for _, up := range ctx.pingers { 87 err := up.Kill() 88 c.Check(err, gc.IsNil) 89 } 90 s.JujuConnSuite.Reset(c) 91 } 92 93 func (ctx *context) run(c *gc.C, steps []stepper) { 94 for i, s := range steps { 95 c.Logf("step %d", i) 96 c.Logf("%#v", s) 97 s.step(c, ctx) 98 } 99 } 100 101 type aliver interface { 102 AgentAlive() (bool, error) 103 SetAgentAlive() (*presence.Pinger, error) 104 WaitAgentAlive(time.Duration) error 105 } 106 107 func (ctx *context) setAgentAlive(c *gc.C, a aliver) *presence.Pinger { 108 pinger, err := a.SetAgentAlive() 109 c.Assert(err, gc.IsNil) 110 ctx.st.StartSync() 111 err = a.WaitAgentAlive(coretesting.LongWait) 112 c.Assert(err, gc.IsNil) 113 agentAlive, err := a.AgentAlive() 114 c.Assert(err, gc.IsNil) 115 c.Assert(agentAlive, gc.Equals, true) 116 return pinger 117 } 118 119 // shortcuts for expected output. 120 var ( 121 machine0 = M{ 122 "agent-state": "started", 123 "dns-name": "dummyenv-0.dns", 124 "instance-id": "dummyenv-0", 125 "series": "quantal", 126 "hardware": "arch=amd64 cpu-cores=1 mem=1024M root-disk=8192M", 127 "state-server-member-status": "adding-vote", 128 } 129 machine1 = M{ 130 "agent-state": "started", 131 "dns-name": "dummyenv-1.dns", 132 "instance-id": "dummyenv-1", 133 "series": "quantal", 134 "hardware": "arch=amd64 cpu-cores=1 mem=1024M root-disk=8192M", 135 } 136 machine2 = M{ 137 "agent-state": "started", 138 "dns-name": "dummyenv-2.dns", 139 "instance-id": "dummyenv-2", 140 "series": "quantal", 141 "hardware": "arch=amd64 cpu-cores=1 mem=1024M root-disk=8192M", 142 } 143 machine3 = M{ 144 "agent-state": "started", 145 "dns-name": "dummyenv-3.dns", 146 "instance-id": "dummyenv-3", 147 "series": "quantal", 148 "hardware": "arch=amd64 cpu-cores=1 mem=1024M root-disk=8192M", 149 } 150 machine4 = M{ 151 "agent-state": "started", 152 "dns-name": "dummyenv-4.dns", 153 "instance-id": "dummyenv-4", 154 "series": "quantal", 155 "hardware": "arch=amd64 cpu-cores=1 mem=1024M root-disk=8192M", 156 } 157 machine1WithContainers = M{ 158 "agent-state": "started", 159 "containers": M{ 160 "1/lxc/0": M{ 161 "agent-state": "started", 162 "containers": M{ 163 "1/lxc/0/lxc/0": M{ 164 "agent-state": "started", 165 "dns-name": "dummyenv-3.dns", 166 "instance-id": "dummyenv-3", 167 "series": "quantal", 168 }, 169 }, 170 "dns-name": "dummyenv-2.dns", 171 "instance-id": "dummyenv-2", 172 "series": "quantal", 173 }, 174 "1/lxc/1": M{ 175 "instance-id": "pending", 176 "series": "quantal", 177 }, 178 }, 179 "dns-name": "dummyenv-1.dns", 180 "instance-id": "dummyenv-1", 181 "series": "quantal", 182 "hardware": "arch=amd64 cpu-cores=1 mem=1024M root-disk=8192M", 183 } 184 machine1WithContainersScoped = M{ 185 "agent-state": "started", 186 "containers": M{ 187 "1/lxc/0": M{ 188 "agent-state": "started", 189 "dns-name": "dummyenv-2.dns", 190 "instance-id": "dummyenv-2", 191 "series": "quantal", 192 }, 193 }, 194 "dns-name": "dummyenv-1.dns", 195 "instance-id": "dummyenv-1", 196 "series": "quantal", 197 "hardware": "arch=amd64 cpu-cores=1 mem=1024M root-disk=8192M", 198 } 199 unexposedService = M{ 200 "charm": "cs:quantal/dummy-1", 201 "exposed": false, 202 } 203 exposedService = M{ 204 "charm": "cs:quantal/dummy-1", 205 "exposed": true, 206 } 207 ) 208 209 type outputFormat struct { 210 name string 211 marshal func(v interface{}) ([]byte, error) 212 unmarshal func(data []byte, v interface{}) error 213 } 214 215 // statusFormats list all output formats supported by status command. 216 var statusFormats = []outputFormat{ 217 {"yaml", goyaml.Marshal, goyaml.Unmarshal}, 218 {"json", json.Marshal, json.Unmarshal}, 219 } 220 221 var machineCons = constraints.MustParse("cpu-cores=2 mem=8G root-disk=8G") 222 223 var statusTests = []testCase{ 224 // Status tests 225 test( 226 "bootstrap and starting a single instance", 227 228 // unlikely, as you can't run juju status in real life without 229 // machine/0 bootstrapped. 230 expect{ 231 "empty state", 232 M{ 233 "environment": "dummyenv", 234 "machines": M{}, 235 "services": M{}, 236 }, 237 }, 238 239 addMachine{machineId: "0", job: state.JobManageEnviron}, 240 expect{ 241 "simulate juju bootstrap by adding machine/0 to the state", 242 M{ 243 "environment": "dummyenv", 244 "machines": M{ 245 "0": M{ 246 "instance-id": "pending", 247 "series": "quantal", 248 "state-server-member-status": "adding-vote", 249 }, 250 }, 251 "services": M{}, 252 }, 253 }, 254 255 startAliveMachine{"0"}, 256 setAddresses{"0", []instance.Address{ 257 instance.NewAddress("10.0.0.1", instance.NetworkUnknown), 258 instance.NewAddress("dummyenv-0.dns", instance.NetworkPublic), 259 }}, 260 expect{ 261 "simulate the PA starting an instance in response to the state change", 262 M{ 263 "environment": "dummyenv", 264 "machines": M{ 265 "0": M{ 266 "agent-state": "pending", 267 "dns-name": "dummyenv-0.dns", 268 "instance-id": "dummyenv-0", 269 "series": "quantal", 270 "hardware": "arch=amd64 cpu-cores=1 mem=1024M root-disk=8192M", 271 "state-server-member-status": "adding-vote", 272 }, 273 }, 274 "services": M{}, 275 }, 276 }, 277 278 setMachineStatus{"0", params.StatusStarted, ""}, 279 expect{ 280 "simulate the MA started and set the machine status", 281 M{ 282 "environment": "dummyenv", 283 "machines": M{ 284 "0": machine0, 285 }, 286 "services": M{}, 287 }, 288 }, 289 290 setTools{"0", version.MustParseBinary("1.2.3-gutsy-ppc")}, 291 expect{ 292 "simulate the MA setting the version", 293 M{ 294 "environment": "dummyenv", 295 "machines": M{ 296 "0": M{ 297 "dns-name": "dummyenv-0.dns", 298 "instance-id": "dummyenv-0", 299 "agent-version": "1.2.3", 300 "agent-state": "started", 301 "series": "quantal", 302 "hardware": "arch=amd64 cpu-cores=1 mem=1024M root-disk=8192M", 303 "state-server-member-status": "adding-vote", 304 }, 305 }, 306 "services": M{}, 307 }, 308 }, 309 ), test( 310 "deploy two services and two networks", 311 addMachine{machineId: "0", job: state.JobManageEnviron}, 312 startAliveMachine{"0"}, 313 setMachineStatus{"0", params.StatusStarted, ""}, 314 setAddresses{"0", []instance.Address{ 315 instance.NewAddress("10.0.0.1", instance.NetworkUnknown), 316 instance.NewAddress("dummyenv-0.dns", instance.NetworkPublic), 317 }}, 318 addCharm{"dummy"}, 319 addService{ 320 name: "networks-service", 321 charm: "dummy", 322 networks: []string{"net1", "net2"}, 323 cons: constraints.MustParse("networks=foo,bar,^no,^good"), 324 }, 325 addService{ 326 name: "no-networks-service", 327 charm: "dummy", 328 cons: constraints.MustParse("networks=^mynet"), 329 }, 330 addNetwork{ 331 name: "net1", 332 providerId: network.Id("provider-net1"), 333 cidr: "0.1.2.0/24", 334 vlanTag: 0, 335 }, 336 addNetwork{ 337 name: "net2", 338 providerId: network.Id("provider-vlan42"), 339 cidr: "0.42.1.0/24", 340 vlanTag: 42, 341 }, 342 343 expect{ 344 "simulate just the two services and a bootstrap node", 345 M{ 346 "environment": "dummyenv", 347 "machines": M{ 348 "0": machine0, 349 }, 350 "services": M{ 351 "networks-service": M{ 352 "charm": "cs:quantal/dummy-1", 353 "exposed": false, 354 "networks": M{ 355 "enabled": L{"net1", "net2"}, 356 "disabled": L{"foo", "bar", "no", "good"}, 357 }, 358 }, 359 "no-networks-service": M{ 360 "charm": "cs:quantal/dummy-1", 361 "exposed": false, 362 "networks": M{ 363 "disabled": L{"mynet"}, 364 }, 365 }, 366 }, 367 "networks": M{ 368 "net1": M{ 369 "provider-id": "provider-net1", 370 "cidr": "0.1.2.0/24", 371 }, 372 "net2": M{ 373 "provider-id": "provider-vlan42", 374 "cidr": "0.42.1.0/24", 375 "vlan-tag": 42, 376 }, 377 }, 378 }, 379 }, 380 ), test( 381 "instance with different hardware characteristics", 382 addMachine{machineId: "0", cons: machineCons, job: state.JobManageEnviron}, 383 setAddresses{"0", []instance.Address{ 384 instance.NewAddress("10.0.0.1", instance.NetworkUnknown), 385 instance.NewAddress("dummyenv-0.dns", instance.NetworkPublic), 386 }}, 387 startAliveMachine{"0"}, 388 setMachineStatus{"0", params.StatusStarted, ""}, 389 expect{ 390 "machine 0 has specific hardware characteristics", 391 M{ 392 "environment": "dummyenv", 393 "machines": M{ 394 "0": M{ 395 "agent-state": "started", 396 "dns-name": "dummyenv-0.dns", 397 "instance-id": "dummyenv-0", 398 "series": "quantal", 399 "hardware": "arch=amd64 cpu-cores=2 mem=8192M root-disk=8192M", 400 "state-server-member-status": "adding-vote", 401 }, 402 }, 403 "services": M{}, 404 }, 405 }, 406 ), test( 407 "instance without addresses", 408 addMachine{machineId: "0", cons: machineCons, job: state.JobManageEnviron}, 409 startAliveMachine{"0"}, 410 setMachineStatus{"0", params.StatusStarted, ""}, 411 expect{ 412 "machine 0 has no dns-name", 413 M{ 414 "environment": "dummyenv", 415 "machines": M{ 416 "0": M{ 417 "agent-state": "started", 418 "instance-id": "dummyenv-0", 419 "series": "quantal", 420 "hardware": "arch=amd64 cpu-cores=2 mem=8192M root-disk=8192M", 421 "state-server-member-status": "adding-vote", 422 }, 423 }, 424 "services": M{}, 425 }, 426 }, 427 ), test( 428 "test pending and missing machines", 429 addMachine{machineId: "0", job: state.JobManageEnviron}, 430 expect{ 431 "machine 0 reports pending", 432 M{ 433 "environment": "dummyenv", 434 "machines": M{ 435 "0": M{ 436 "instance-id": "pending", 437 "series": "quantal", 438 "state-server-member-status": "adding-vote", 439 }, 440 }, 441 "services": M{}, 442 }, 443 }, 444 445 startMissingMachine{"0"}, 446 expect{ 447 "machine 0 reports missing", 448 M{ 449 "environment": "dummyenv", 450 "machines": M{ 451 "0": M{ 452 "instance-state": "missing", 453 "instance-id": "i-missing", 454 "agent-state": "pending", 455 "series": "quantal", 456 "hardware": "arch=amd64 cpu-cores=1 mem=1024M root-disk=8192M", 457 "state-server-member-status": "adding-vote", 458 }, 459 }, 460 "services": M{}, 461 }, 462 }, 463 ), test( 464 "add two services and expose one, then add 2 more machines and some units", 465 addMachine{machineId: "0", job: state.JobManageEnviron}, 466 setAddresses{"0", []instance.Address{instance.NewAddress("dummyenv-0.dns", instance.NetworkUnknown)}}, 467 startAliveMachine{"0"}, 468 setMachineStatus{"0", params.StatusStarted, ""}, 469 addCharm{"dummy"}, 470 addService{name: "dummy-service", charm: "dummy"}, 471 addService{name: "exposed-service", charm: "dummy"}, 472 expect{ 473 "no services exposed yet", 474 M{ 475 "environment": "dummyenv", 476 "machines": M{ 477 "0": machine0, 478 }, 479 "services": M{ 480 "dummy-service": unexposedService, 481 "exposed-service": unexposedService, 482 }, 483 }, 484 }, 485 486 setServiceExposed{"exposed-service", true}, 487 expect{ 488 "one exposed service", 489 M{ 490 "environment": "dummyenv", 491 "machines": M{ 492 "0": machine0, 493 }, 494 "services": M{ 495 "dummy-service": unexposedService, 496 "exposed-service": exposedService, 497 }, 498 }, 499 }, 500 501 addMachine{machineId: "1", job: state.JobHostUnits}, 502 setAddresses{"1", []instance.Address{instance.NewAddress("dummyenv-1.dns", instance.NetworkUnknown)}}, 503 startAliveMachine{"1"}, 504 setMachineStatus{"1", params.StatusStarted, ""}, 505 addMachine{machineId: "2", job: state.JobHostUnits}, 506 setAddresses{"2", []instance.Address{instance.NewAddress("dummyenv-2.dns", instance.NetworkUnknown)}}, 507 startAliveMachine{"2"}, 508 setMachineStatus{"2", params.StatusStarted, ""}, 509 expect{ 510 "two more machines added", 511 M{ 512 "environment": "dummyenv", 513 "machines": M{ 514 "0": machine0, 515 "1": machine1, 516 "2": machine2, 517 }, 518 "services": M{ 519 "dummy-service": unexposedService, 520 "exposed-service": exposedService, 521 }, 522 }, 523 }, 524 525 addUnit{"dummy-service", "1"}, 526 addAliveUnit{"exposed-service", "2"}, 527 setUnitStatus{"exposed-service/0", params.StatusError, "You Require More Vespene Gas"}, 528 // Open multiple ports with different protocols, 529 // ensure they're sorted on protocol, then number. 530 openUnitPort{"exposed-service/0", "udp", 10}, 531 openUnitPort{"exposed-service/0", "udp", 2}, 532 openUnitPort{"exposed-service/0", "tcp", 3}, 533 openUnitPort{"exposed-service/0", "tcp", 2}, 534 // Simulate some status with no info, while the agent is down. 535 setUnitStatus{"dummy-service/0", params.StatusStarted, ""}, 536 expect{ 537 "add two units, one alive (in error state), one down", 538 M{ 539 "environment": "dummyenv", 540 "machines": M{ 541 "0": machine0, 542 "1": machine1, 543 "2": machine2, 544 }, 545 "services": M{ 546 "exposed-service": M{ 547 "charm": "cs:quantal/dummy-1", 548 "exposed": true, 549 "units": M{ 550 "exposed-service/0": M{ 551 "machine": "2", 552 "agent-state": "error", 553 "agent-state-info": "You Require More Vespene Gas", 554 "open-ports": L{ 555 "2/tcp", "3/tcp", "2/udp", "10/udp", 556 }, 557 "public-address": "dummyenv-2.dns", 558 }, 559 }, 560 }, 561 "dummy-service": M{ 562 "charm": "cs:quantal/dummy-1", 563 "exposed": false, 564 "units": M{ 565 "dummy-service/0": M{ 566 "machine": "1", 567 "agent-state": "down", 568 "agent-state-info": "(started)", 569 "public-address": "dummyenv-1.dns", 570 }, 571 }, 572 }, 573 }, 574 }, 575 }, 576 577 addMachine{machineId: "3", job: state.JobHostUnits}, 578 startMachine{"3"}, 579 // Simulate some status with info, while the agent is down. 580 setAddresses{"3", []instance.Address{instance.NewAddress("dummyenv-3.dns", instance.NetworkUnknown)}}, 581 setMachineStatus{"3", params.StatusStopped, "Really?"}, 582 addMachine{machineId: "4", job: state.JobHostUnits}, 583 setAddresses{"4", []instance.Address{instance.NewAddress("dummyenv-4.dns", instance.NetworkUnknown)}}, 584 startAliveMachine{"4"}, 585 setMachineStatus{"4", params.StatusError, "Beware the red toys"}, 586 ensureDyingUnit{"dummy-service/0"}, 587 addMachine{machineId: "5", job: state.JobHostUnits}, 588 ensureDeadMachine{"5"}, 589 expect{ 590 "add three more machine, one with a dead agent, one in error state and one dead itself; also one dying unit", 591 M{ 592 "environment": "dummyenv", 593 "machines": M{ 594 "0": machine0, 595 "1": machine1, 596 "2": machine2, 597 "3": M{ 598 "dns-name": "dummyenv-3.dns", 599 "instance-id": "dummyenv-3", 600 "agent-state": "down", 601 "agent-state-info": "(stopped: Really?)", 602 "series": "quantal", 603 "hardware": "arch=amd64 cpu-cores=1 mem=1024M root-disk=8192M", 604 }, 605 "4": M{ 606 "dns-name": "dummyenv-4.dns", 607 "instance-id": "dummyenv-4", 608 "agent-state": "error", 609 "agent-state-info": "Beware the red toys", 610 "series": "quantal", 611 "hardware": "arch=amd64 cpu-cores=1 mem=1024M root-disk=8192M", 612 }, 613 "5": M{ 614 "life": "dead", 615 "instance-id": "pending", 616 "series": "quantal", 617 }, 618 }, 619 "services": M{ 620 "exposed-service": M{ 621 "charm": "cs:quantal/dummy-1", 622 "exposed": true, 623 "units": M{ 624 "exposed-service/0": M{ 625 "machine": "2", 626 "agent-state": "error", 627 "agent-state-info": "You Require More Vespene Gas", 628 "open-ports": L{ 629 "2/tcp", "3/tcp", "2/udp", "10/udp", 630 }, 631 "public-address": "dummyenv-2.dns", 632 }, 633 }, 634 }, 635 "dummy-service": M{ 636 "charm": "cs:quantal/dummy-1", 637 "exposed": false, 638 "units": M{ 639 "dummy-service/0": M{ 640 "machine": "1", 641 "life": "dying", 642 "agent-state": "down", 643 "agent-state-info": "(started)", 644 "public-address": "dummyenv-1.dns", 645 }, 646 }, 647 }, 648 }, 649 }, 650 }, 651 652 scopedExpect{ 653 "scope status on dummy-service/0 unit", 654 []string{"dummy-service/0"}, 655 M{ 656 "environment": "dummyenv", 657 "machines": M{ 658 "1": machine1, 659 }, 660 "services": M{ 661 "dummy-service": M{ 662 "charm": "cs:quantal/dummy-1", 663 "exposed": false, 664 "units": M{ 665 "dummy-service/0": M{ 666 "machine": "1", 667 "life": "dying", 668 "agent-state": "down", 669 "agent-state-info": "(started)", 670 "public-address": "dummyenv-1.dns", 671 }, 672 }, 673 }, 674 }, 675 }, 676 }, 677 scopedExpect{ 678 "scope status on exposed-service service", 679 []string{"exposed-service"}, 680 M{ 681 "environment": "dummyenv", 682 "machines": M{ 683 "2": machine2, 684 }, 685 "services": M{ 686 "exposed-service": M{ 687 "charm": "cs:quantal/dummy-1", 688 "exposed": true, 689 "units": M{ 690 "exposed-service/0": M{ 691 "machine": "2", 692 "agent-state": "error", 693 "agent-state-info": "You Require More Vespene Gas", 694 "open-ports": L{ 695 "2/tcp", "3/tcp", "2/udp", "10/udp", 696 }, 697 "public-address": "dummyenv-2.dns", 698 }, 699 }, 700 }, 701 }, 702 }, 703 }, 704 scopedExpect{ 705 "scope status on service pattern", 706 []string{"d*-service"}, 707 M{ 708 "environment": "dummyenv", 709 "machines": M{ 710 "1": machine1, 711 }, 712 "services": M{ 713 "dummy-service": M{ 714 "charm": "cs:quantal/dummy-1", 715 "exposed": false, 716 "units": M{ 717 "dummy-service/0": M{ 718 "machine": "1", 719 "life": "dying", 720 "agent-state": "down", 721 "agent-state-info": "(started)", 722 "public-address": "dummyenv-1.dns", 723 }, 724 }, 725 }, 726 }, 727 }, 728 }, 729 scopedExpect{ 730 "scope status on unit pattern", 731 []string{"e*posed-service/*"}, 732 M{ 733 "environment": "dummyenv", 734 "machines": M{ 735 "2": machine2, 736 }, 737 "services": M{ 738 "exposed-service": M{ 739 "charm": "cs:quantal/dummy-1", 740 "exposed": true, 741 "units": M{ 742 "exposed-service/0": M{ 743 "machine": "2", 744 "agent-state": "error", 745 "agent-state-info": "You Require More Vespene Gas", 746 "open-ports": L{ 747 "2/tcp", "3/tcp", "2/udp", "10/udp", 748 }, 749 "public-address": "dummyenv-2.dns", 750 }, 751 }, 752 }, 753 }, 754 }, 755 }, 756 scopedExpect{ 757 "scope status on combination of service and unit patterns", 758 []string{"exposed-service", "dummy-service", "e*posed-service/*", "dummy-service/*"}, 759 M{ 760 "environment": "dummyenv", 761 "machines": M{ 762 "1": machine1, 763 "2": machine2, 764 }, 765 "services": M{ 766 "dummy-service": M{ 767 "charm": "cs:quantal/dummy-1", 768 "exposed": false, 769 "units": M{ 770 "dummy-service/0": M{ 771 "machine": "1", 772 "life": "dying", 773 "agent-state": "down", 774 "agent-state-info": "(started)", 775 "public-address": "dummyenv-1.dns", 776 }, 777 }, 778 }, 779 "exposed-service": M{ 780 "charm": "cs:quantal/dummy-1", 781 "exposed": true, 782 "units": M{ 783 "exposed-service/0": M{ 784 "machine": "2", 785 "agent-state": "error", 786 "agent-state-info": "You Require More Vespene Gas", 787 "open-ports": L{ 788 "2/tcp", "3/tcp", "2/udp", "10/udp", 789 }, 790 "public-address": "dummyenv-2.dns", 791 }, 792 }, 793 }, 794 }, 795 }, 796 }, 797 ), test( 798 "add a dying service", 799 addCharm{"dummy"}, 800 addService{name: "dummy-service", charm: "dummy"}, 801 addMachine{machineId: "0", job: state.JobHostUnits}, 802 addUnit{"dummy-service", "0"}, 803 ensureDyingService{"dummy-service"}, 804 expect{ 805 "service shows life==dying", 806 M{ 807 "environment": "dummyenv", 808 "machines": M{ 809 "0": M{ 810 "instance-id": "pending", 811 "series": "quantal", 812 }, 813 }, 814 "services": M{ 815 "dummy-service": M{ 816 "charm": "cs:quantal/dummy-1", 817 "exposed": false, 818 "life": "dying", 819 "units": M{ 820 "dummy-service/0": M{ 821 "machine": "0", 822 "agent-state": "pending", 823 }, 824 }, 825 }, 826 }, 827 }, 828 }, 829 ), 830 831 // Relation tests 832 test( 833 "complex scenario with multiple related services", 834 addMachine{machineId: "0", job: state.JobManageEnviron}, 835 setAddresses{"0", []instance.Address{instance.NewAddress("dummyenv-0.dns", instance.NetworkUnknown)}}, 836 startAliveMachine{"0"}, 837 setMachineStatus{"0", params.StatusStarted, ""}, 838 addCharm{"wordpress"}, 839 addCharm{"mysql"}, 840 addCharm{"varnish"}, 841 842 addService{name: "project", charm: "wordpress"}, 843 setServiceExposed{"project", true}, 844 addMachine{machineId: "1", job: state.JobHostUnits}, 845 setAddresses{"1", []instance.Address{instance.NewAddress("dummyenv-1.dns", instance.NetworkUnknown)}}, 846 startAliveMachine{"1"}, 847 setMachineStatus{"1", params.StatusStarted, ""}, 848 addAliveUnit{"project", "1"}, 849 setUnitStatus{"project/0", params.StatusStarted, ""}, 850 851 addService{name: "mysql", charm: "mysql"}, 852 setServiceExposed{"mysql", true}, 853 addMachine{machineId: "2", job: state.JobHostUnits}, 854 setAddresses{"2", []instance.Address{instance.NewAddress("dummyenv-2.dns", instance.NetworkUnknown)}}, 855 startAliveMachine{"2"}, 856 setMachineStatus{"2", params.StatusStarted, ""}, 857 addAliveUnit{"mysql", "2"}, 858 setUnitStatus{"mysql/0", params.StatusStarted, ""}, 859 860 addService{name: "varnish", charm: "varnish"}, 861 setServiceExposed{"varnish", true}, 862 addMachine{machineId: "3", job: state.JobHostUnits}, 863 setAddresses{"3", []instance.Address{instance.NewAddress("dummyenv-3.dns", instance.NetworkUnknown)}}, 864 startAliveMachine{"3"}, 865 setMachineStatus{"3", params.StatusStarted, ""}, 866 addUnit{"varnish", "3"}, 867 868 addService{name: "private", charm: "wordpress"}, 869 setServiceExposed{"private", true}, 870 addMachine{machineId: "4", job: state.JobHostUnits}, 871 setAddresses{"4", []instance.Address{instance.NewAddress("dummyenv-4.dns", instance.NetworkUnknown)}}, 872 startAliveMachine{"4"}, 873 setMachineStatus{"4", params.StatusStarted, ""}, 874 addUnit{"private", "4"}, 875 876 relateServices{"project", "mysql"}, 877 relateServices{"project", "varnish"}, 878 relateServices{"private", "mysql"}, 879 880 expect{ 881 "multiples services with relations between some of them", 882 M{ 883 "environment": "dummyenv", 884 "machines": M{ 885 "0": machine0, 886 "1": machine1, 887 "2": machine2, 888 "3": machine3, 889 "4": machine4, 890 }, 891 "services": M{ 892 "project": M{ 893 "charm": "cs:quantal/wordpress-3", 894 "exposed": true, 895 "units": M{ 896 "project/0": M{ 897 "machine": "1", 898 "agent-state": "started", 899 "public-address": "dummyenv-1.dns", 900 }, 901 }, 902 "relations": M{ 903 "db": L{"mysql"}, 904 "cache": L{"varnish"}, 905 }, 906 }, 907 "mysql": M{ 908 "charm": "cs:quantal/mysql-1", 909 "exposed": true, 910 "units": M{ 911 "mysql/0": M{ 912 "machine": "2", 913 "agent-state": "started", 914 "public-address": "dummyenv-2.dns", 915 }, 916 }, 917 "relations": M{ 918 "server": L{"private", "project"}, 919 }, 920 }, 921 "varnish": M{ 922 "charm": "cs:quantal/varnish-1", 923 "exposed": true, 924 "units": M{ 925 "varnish/0": M{ 926 "machine": "3", 927 "agent-state": "pending", 928 "public-address": "dummyenv-3.dns", 929 }, 930 }, 931 "relations": M{ 932 "webcache": L{"project"}, 933 }, 934 }, 935 "private": M{ 936 "charm": "cs:quantal/wordpress-3", 937 "exposed": true, 938 "units": M{ 939 "private/0": M{ 940 "machine": "4", 941 "agent-state": "pending", 942 "public-address": "dummyenv-4.dns", 943 }, 944 }, 945 "relations": M{ 946 "db": L{"mysql"}, 947 }, 948 }, 949 }, 950 }, 951 }, 952 ), test( 953 "simple peer scenario", 954 addMachine{machineId: "0", job: state.JobManageEnviron}, 955 setAddresses{"0", []instance.Address{instance.NewAddress("dummyenv-0.dns", instance.NetworkUnknown)}}, 956 startAliveMachine{"0"}, 957 setMachineStatus{"0", params.StatusStarted, ""}, 958 addCharm{"riak"}, 959 addCharm{"wordpress"}, 960 961 addService{name: "riak", charm: "riak"}, 962 setServiceExposed{"riak", true}, 963 addMachine{machineId: "1", job: state.JobHostUnits}, 964 setAddresses{"1", []instance.Address{instance.NewAddress("dummyenv-1.dns", instance.NetworkUnknown)}}, 965 startAliveMachine{"1"}, 966 setMachineStatus{"1", params.StatusStarted, ""}, 967 addAliveUnit{"riak", "1"}, 968 setUnitStatus{"riak/0", params.StatusStarted, ""}, 969 addMachine{machineId: "2", job: state.JobHostUnits}, 970 setAddresses{"2", []instance.Address{instance.NewAddress("dummyenv-2.dns", instance.NetworkUnknown)}}, 971 startAliveMachine{"2"}, 972 setMachineStatus{"2", params.StatusStarted, ""}, 973 addAliveUnit{"riak", "2"}, 974 setUnitStatus{"riak/1", params.StatusStarted, ""}, 975 addMachine{machineId: "3", job: state.JobHostUnits}, 976 setAddresses{"3", []instance.Address{instance.NewAddress("dummyenv-3.dns", instance.NetworkUnknown)}}, 977 startAliveMachine{"3"}, 978 setMachineStatus{"3", params.StatusStarted, ""}, 979 addAliveUnit{"riak", "3"}, 980 setUnitStatus{"riak/2", params.StatusStarted, ""}, 981 982 expect{ 983 "multiples related peer units", 984 M{ 985 "environment": "dummyenv", 986 "machines": M{ 987 "0": machine0, 988 "1": machine1, 989 "2": machine2, 990 "3": machine3, 991 }, 992 "services": M{ 993 "riak": M{ 994 "charm": "cs:quantal/riak-7", 995 "exposed": true, 996 "units": M{ 997 "riak/0": M{ 998 "machine": "1", 999 "agent-state": "started", 1000 "public-address": "dummyenv-1.dns", 1001 }, 1002 "riak/1": M{ 1003 "machine": "2", 1004 "agent-state": "started", 1005 "public-address": "dummyenv-2.dns", 1006 }, 1007 "riak/2": M{ 1008 "machine": "3", 1009 "agent-state": "started", 1010 "public-address": "dummyenv-3.dns", 1011 }, 1012 }, 1013 "relations": M{ 1014 "ring": L{"riak"}, 1015 }, 1016 }, 1017 }, 1018 }, 1019 }, 1020 ), 1021 1022 // Subordinate tests 1023 test( 1024 "one service with one subordinate service", 1025 addMachine{machineId: "0", job: state.JobManageEnviron}, 1026 setAddresses{"0", []instance.Address{instance.NewAddress("dummyenv-0.dns", instance.NetworkUnknown)}}, 1027 startAliveMachine{"0"}, 1028 setMachineStatus{"0", params.StatusStarted, ""}, 1029 addCharm{"wordpress"}, 1030 addCharm{"mysql"}, 1031 addCharm{"logging"}, 1032 1033 addService{name: "wordpress", charm: "wordpress"}, 1034 setServiceExposed{"wordpress", true}, 1035 addMachine{machineId: "1", job: state.JobHostUnits}, 1036 setAddresses{"1", []instance.Address{instance.NewAddress("dummyenv-1.dns", instance.NetworkUnknown)}}, 1037 startAliveMachine{"1"}, 1038 setMachineStatus{"1", params.StatusStarted, ""}, 1039 addAliveUnit{"wordpress", "1"}, 1040 setUnitStatus{"wordpress/0", params.StatusStarted, ""}, 1041 1042 addService{name: "mysql", charm: "mysql"}, 1043 setServiceExposed{"mysql", true}, 1044 addMachine{machineId: "2", job: state.JobHostUnits}, 1045 setAddresses{"2", []instance.Address{instance.NewAddress("dummyenv-2.dns", instance.NetworkUnknown)}}, 1046 startAliveMachine{"2"}, 1047 setMachineStatus{"2", params.StatusStarted, ""}, 1048 addAliveUnit{"mysql", "2"}, 1049 setUnitStatus{"mysql/0", params.StatusStarted, ""}, 1050 1051 addService{name: "logging", charm: "logging"}, 1052 setServiceExposed{"logging", true}, 1053 1054 relateServices{"wordpress", "mysql"}, 1055 relateServices{"wordpress", "logging"}, 1056 relateServices{"mysql", "logging"}, 1057 1058 addSubordinate{"wordpress/0", "logging"}, 1059 addSubordinate{"mysql/0", "logging"}, 1060 1061 setUnitsAlive{"logging"}, 1062 setUnitStatus{"logging/0", params.StatusStarted, ""}, 1063 setUnitStatus{"logging/1", params.StatusError, "somehow lost in all those logs"}, 1064 1065 expect{ 1066 "multiples related peer units", 1067 M{ 1068 "environment": "dummyenv", 1069 "machines": M{ 1070 "0": machine0, 1071 "1": machine1, 1072 "2": machine2, 1073 }, 1074 "services": M{ 1075 "wordpress": M{ 1076 "charm": "cs:quantal/wordpress-3", 1077 "exposed": true, 1078 "units": M{ 1079 "wordpress/0": M{ 1080 "machine": "1", 1081 "agent-state": "started", 1082 "subordinates": M{ 1083 "logging/0": M{ 1084 "agent-state": "started", 1085 "public-address": "dummyenv-1.dns", 1086 }, 1087 }, 1088 "public-address": "dummyenv-1.dns", 1089 }, 1090 }, 1091 "relations": M{ 1092 "db": L{"mysql"}, 1093 "logging-dir": L{"logging"}, 1094 }, 1095 }, 1096 "mysql": M{ 1097 "charm": "cs:quantal/mysql-1", 1098 "exposed": true, 1099 "units": M{ 1100 "mysql/0": M{ 1101 "machine": "2", 1102 "agent-state": "started", 1103 "subordinates": M{ 1104 "logging/1": M{ 1105 "agent-state": "error", 1106 "agent-state-info": "somehow lost in all those logs", 1107 "public-address": "dummyenv-2.dns", 1108 }, 1109 }, 1110 "public-address": "dummyenv-2.dns", 1111 }, 1112 }, 1113 "relations": M{ 1114 "server": L{"wordpress"}, 1115 "juju-info": L{"logging"}, 1116 }, 1117 }, 1118 "logging": M{ 1119 "charm": "cs:quantal/logging-1", 1120 "exposed": true, 1121 "relations": M{ 1122 "logging-directory": L{"wordpress"}, 1123 "info": L{"mysql"}, 1124 }, 1125 "subordinate-to": L{"mysql", "wordpress"}, 1126 }, 1127 }, 1128 }, 1129 }, 1130 1131 // scoped on 'logging' 1132 scopedExpect{ 1133 "subordinates scoped on logging", 1134 []string{"logging"}, 1135 M{ 1136 "environment": "dummyenv", 1137 "machines": M{ 1138 "1": machine1, 1139 "2": machine2, 1140 }, 1141 "services": M{ 1142 "wordpress": M{ 1143 "charm": "cs:quantal/wordpress-3", 1144 "exposed": true, 1145 "units": M{ 1146 "wordpress/0": M{ 1147 "machine": "1", 1148 "agent-state": "started", 1149 "subordinates": M{ 1150 "logging/0": M{ 1151 "agent-state": "started", 1152 "public-address": "dummyenv-1.dns", 1153 }, 1154 }, 1155 "public-address": "dummyenv-1.dns", 1156 }, 1157 }, 1158 "relations": M{ 1159 "db": L{"mysql"}, 1160 "logging-dir": L{"logging"}, 1161 }, 1162 }, 1163 "mysql": M{ 1164 "charm": "cs:quantal/mysql-1", 1165 "exposed": true, 1166 "units": M{ 1167 "mysql/0": M{ 1168 "machine": "2", 1169 "agent-state": "started", 1170 "subordinates": M{ 1171 "logging/1": M{ 1172 "agent-state": "error", 1173 "agent-state-info": "somehow lost in all those logs", 1174 "public-address": "dummyenv-2.dns", 1175 }, 1176 }, 1177 "public-address": "dummyenv-2.dns", 1178 }, 1179 }, 1180 "relations": M{ 1181 "server": L{"wordpress"}, 1182 "juju-info": L{"logging"}, 1183 }, 1184 }, 1185 "logging": M{ 1186 "charm": "cs:quantal/logging-1", 1187 "exposed": true, 1188 "relations": M{ 1189 "logging-directory": L{"wordpress"}, 1190 "info": L{"mysql"}, 1191 }, 1192 "subordinate-to": L{"mysql", "wordpress"}, 1193 }, 1194 }, 1195 }, 1196 }, 1197 1198 // scoped on wordpress/0 1199 scopedExpect{ 1200 "subordinates scoped on logging", 1201 []string{"wordpress/0"}, 1202 M{ 1203 "environment": "dummyenv", 1204 "machines": M{ 1205 "1": machine1, 1206 }, 1207 "services": M{ 1208 "wordpress": M{ 1209 "charm": "cs:quantal/wordpress-3", 1210 "exposed": true, 1211 "units": M{ 1212 "wordpress/0": M{ 1213 "machine": "1", 1214 "agent-state": "started", 1215 "subordinates": M{ 1216 "logging/0": M{ 1217 "agent-state": "started", 1218 "public-address": "dummyenv-1.dns", 1219 }, 1220 }, 1221 "public-address": "dummyenv-1.dns", 1222 }, 1223 }, 1224 "relations": M{ 1225 "db": L{"mysql"}, 1226 "logging-dir": L{"logging"}, 1227 }, 1228 }, 1229 "logging": M{ 1230 "charm": "cs:quantal/logging-1", 1231 "exposed": true, 1232 "relations": M{ 1233 "logging-directory": L{"wordpress"}, 1234 "info": L{"mysql"}, 1235 }, 1236 "subordinate-to": L{"mysql", "wordpress"}, 1237 }, 1238 }, 1239 }, 1240 }, 1241 ), test( 1242 "one service with two subordinate services", 1243 addMachine{machineId: "0", job: state.JobManageEnviron}, 1244 startAliveMachine{"0"}, 1245 setMachineStatus{"0", params.StatusStarted, ""}, 1246 addCharm{"wordpress"}, 1247 addCharm{"logging"}, 1248 addCharm{"monitoring"}, 1249 1250 addService{name: "wordpress", charm: "wordpress"}, 1251 setServiceExposed{"wordpress", true}, 1252 addMachine{machineId: "1", job: state.JobHostUnits}, 1253 setAddresses{"1", []instance.Address{instance.NewAddress("dummyenv-1.dns", instance.NetworkUnknown)}}, 1254 startAliveMachine{"1"}, 1255 setMachineStatus{"1", params.StatusStarted, ""}, 1256 addAliveUnit{"wordpress", "1"}, 1257 setUnitStatus{"wordpress/0", params.StatusStarted, ""}, 1258 1259 addService{name: "logging", charm: "logging"}, 1260 setServiceExposed{"logging", true}, 1261 addService{name: "monitoring", charm: "monitoring"}, 1262 setServiceExposed{"monitoring", true}, 1263 1264 relateServices{"wordpress", "logging"}, 1265 relateServices{"wordpress", "monitoring"}, 1266 1267 addSubordinate{"wordpress/0", "logging"}, 1268 addSubordinate{"wordpress/0", "monitoring"}, 1269 1270 setUnitsAlive{"logging"}, 1271 setUnitStatus{"logging/0", params.StatusStarted, ""}, 1272 1273 setUnitsAlive{"monitoring"}, 1274 setUnitStatus{"monitoring/0", params.StatusStarted, ""}, 1275 1276 // scoped on monitoring; make sure logging doesn't show up. 1277 scopedExpect{ 1278 "subordinates scoped on:", 1279 []string{"monitoring"}, 1280 M{ 1281 "environment": "dummyenv", 1282 "machines": M{ 1283 "1": machine1, 1284 }, 1285 "services": M{ 1286 "wordpress": M{ 1287 "charm": "cs:quantal/wordpress-3", 1288 "exposed": true, 1289 "units": M{ 1290 "wordpress/0": M{ 1291 "machine": "1", 1292 "agent-state": "started", 1293 "subordinates": M{ 1294 "monitoring/0": M{ 1295 "agent-state": "started", 1296 "public-address": "dummyenv-1.dns", 1297 }, 1298 }, 1299 "public-address": "dummyenv-1.dns", 1300 }, 1301 }, 1302 "relations": M{ 1303 "logging-dir": L{"logging"}, 1304 "monitoring-port": L{"monitoring"}, 1305 }, 1306 }, 1307 "monitoring": M{ 1308 "charm": "cs:quantal/monitoring-0", 1309 "exposed": true, 1310 "relations": M{ 1311 "monitoring-port": L{"wordpress"}, 1312 }, 1313 "subordinate-to": L{"wordpress"}, 1314 }, 1315 }, 1316 }, 1317 }, 1318 ), test( 1319 "machines with containers", 1320 addMachine{machineId: "0", job: state.JobManageEnviron}, 1321 setAddresses{"0", []instance.Address{instance.NewAddress("dummyenv-0.dns", instance.NetworkUnknown)}}, 1322 startAliveMachine{"0"}, 1323 setMachineStatus{"0", params.StatusStarted, ""}, 1324 addCharm{"mysql"}, 1325 addService{name: "mysql", charm: "mysql"}, 1326 setServiceExposed{"mysql", true}, 1327 1328 addMachine{machineId: "1", job: state.JobHostUnits}, 1329 setAddresses{"1", []instance.Address{instance.NewAddress("dummyenv-1.dns", instance.NetworkUnknown)}}, 1330 startAliveMachine{"1"}, 1331 setMachineStatus{"1", params.StatusStarted, ""}, 1332 addAliveUnit{"mysql", "1"}, 1333 setUnitStatus{"mysql/0", params.StatusStarted, ""}, 1334 1335 // A container on machine 1. 1336 addContainer{"1", "1/lxc/0", state.JobHostUnits}, 1337 setAddresses{"1/lxc/0", []instance.Address{instance.NewAddress("dummyenv-2.dns", instance.NetworkUnknown)}}, 1338 startAliveMachine{"1/lxc/0"}, 1339 setMachineStatus{"1/lxc/0", params.StatusStarted, ""}, 1340 addAliveUnit{"mysql", "1/lxc/0"}, 1341 setUnitStatus{"mysql/1", params.StatusStarted, ""}, 1342 addContainer{"1", "1/lxc/1", state.JobHostUnits}, 1343 1344 // A nested container. 1345 addContainer{"1/lxc/0", "1/lxc/0/lxc/0", state.JobHostUnits}, 1346 setAddresses{"1/lxc/0/lxc/0", []instance.Address{instance.NewAddress("dummyenv-3.dns", instance.NetworkUnknown)}}, 1347 startAliveMachine{"1/lxc/0/lxc/0"}, 1348 setMachineStatus{"1/lxc/0/lxc/0", params.StatusStarted, ""}, 1349 1350 expect{ 1351 "machines with nested containers", 1352 M{ 1353 "environment": "dummyenv", 1354 "machines": M{ 1355 "0": machine0, 1356 "1": machine1WithContainers, 1357 }, 1358 "services": M{ 1359 "mysql": M{ 1360 "charm": "cs:quantal/mysql-1", 1361 "exposed": true, 1362 "units": M{ 1363 "mysql/0": M{ 1364 "machine": "1", 1365 "agent-state": "started", 1366 "public-address": "dummyenv-1.dns", 1367 }, 1368 "mysql/1": M{ 1369 "machine": "1/lxc/0", 1370 "agent-state": "started", 1371 "public-address": "dummyenv-2.dns", 1372 }, 1373 }, 1374 }, 1375 }, 1376 }, 1377 }, 1378 1379 // once again, with a scope on mysql/1 1380 scopedExpect{ 1381 "machines with nested containers", 1382 []string{"mysql/1"}, 1383 M{ 1384 "environment": "dummyenv", 1385 "machines": M{ 1386 "1": machine1WithContainersScoped, 1387 }, 1388 "services": M{ 1389 "mysql": M{ 1390 "charm": "cs:quantal/mysql-1", 1391 "exposed": true, 1392 "units": M{ 1393 "mysql/1": M{ 1394 "machine": "1/lxc/0", 1395 "agent-state": "started", 1396 "public-address": "dummyenv-2.dns", 1397 }, 1398 }, 1399 }, 1400 }, 1401 }, 1402 }, 1403 ), test( 1404 "service with out of date charm", 1405 addMachine{machineId: "0", job: state.JobManageEnviron}, 1406 setAddresses{"0", []instance.Address{instance.NewAddress("dummyenv-0.dns", instance.NetworkUnknown)}}, 1407 startAliveMachine{"0"}, 1408 setMachineStatus{"0", params.StatusStarted, ""}, 1409 addMachine{machineId: "1", job: state.JobHostUnits}, 1410 setAddresses{"1", []instance.Address{instance.NewAddress("dummyenv-1.dns", instance.NetworkUnknown)}}, 1411 startAliveMachine{"1"}, 1412 setMachineStatus{"1", params.StatusStarted, ""}, 1413 addCharm{"mysql"}, 1414 addService{name: "mysql", charm: "mysql"}, 1415 setServiceExposed{"mysql", true}, 1416 addCharmPlaceholder{"mysql", 23}, 1417 addAliveUnit{"mysql", "1"}, 1418 1419 expect{ 1420 "services and units with correct charm status", 1421 M{ 1422 "environment": "dummyenv", 1423 "machines": M{ 1424 "0": machine0, 1425 "1": machine1, 1426 }, 1427 "services": M{ 1428 "mysql": M{ 1429 "charm": "cs:quantal/mysql-1", 1430 "can-upgrade-to": "cs:quantal/mysql-23", 1431 "exposed": true, 1432 "units": M{ 1433 "mysql/0": M{ 1434 "machine": "1", 1435 "agent-state": "pending", 1436 "public-address": "dummyenv-1.dns", 1437 }, 1438 }, 1439 }, 1440 }, 1441 }, 1442 }, 1443 ), test( 1444 "unit with out of date charm", 1445 addMachine{machineId: "0", job: state.JobManageEnviron}, 1446 setAddresses{"0", []instance.Address{instance.NewAddress("dummyenv-0.dns", instance.NetworkUnknown)}}, 1447 startAliveMachine{"0"}, 1448 setMachineStatus{"0", params.StatusStarted, ""}, 1449 addMachine{machineId: "1", job: state.JobHostUnits}, 1450 setAddresses{"1", []instance.Address{instance.NewAddress("dummyenv-1.dns", instance.NetworkUnknown)}}, 1451 startAliveMachine{"1"}, 1452 setMachineStatus{"1", params.StatusStarted, ""}, 1453 addCharm{"mysql"}, 1454 addService{name: "mysql", charm: "mysql"}, 1455 setServiceExposed{"mysql", true}, 1456 addAliveUnit{"mysql", "1"}, 1457 setUnitCharmURL{"mysql/0", "cs:quantal/mysql-1"}, 1458 addCharmWithRevision{addCharm{"mysql"}, "local", 1}, 1459 setServiceCharm{"mysql", "local:quantal/mysql-1"}, 1460 1461 expect{ 1462 "services and units with correct charm status", 1463 M{ 1464 "environment": "dummyenv", 1465 "machines": M{ 1466 "0": machine0, 1467 "1": machine1, 1468 }, 1469 "services": M{ 1470 "mysql": M{ 1471 "charm": "local:quantal/mysql-1", 1472 "exposed": true, 1473 "units": M{ 1474 "mysql/0": M{ 1475 "machine": "1", 1476 "agent-state": "started", 1477 "upgrading-from": "cs:quantal/mysql-1", 1478 "public-address": "dummyenv-1.dns", 1479 }, 1480 }, 1481 }, 1482 }, 1483 }, 1484 }, 1485 ), test( 1486 "service and unit with out of date charms", 1487 addMachine{machineId: "0", job: state.JobManageEnviron}, 1488 setAddresses{"0", []instance.Address{instance.NewAddress("dummyenv-0.dns", instance.NetworkUnknown)}}, 1489 startAliveMachine{"0"}, 1490 setMachineStatus{"0", params.StatusStarted, ""}, 1491 addMachine{machineId: "1", job: state.JobHostUnits}, 1492 setAddresses{"1", []instance.Address{instance.NewAddress("dummyenv-1.dns", instance.NetworkUnknown)}}, 1493 startAliveMachine{"1"}, 1494 setMachineStatus{"1", params.StatusStarted, ""}, 1495 addCharm{"mysql"}, 1496 addService{name: "mysql", charm: "mysql"}, 1497 setServiceExposed{"mysql", true}, 1498 addAliveUnit{"mysql", "1"}, 1499 setUnitCharmURL{"mysql/0", "cs:quantal/mysql-1"}, 1500 addCharmWithRevision{addCharm{"mysql"}, "cs", 2}, 1501 setServiceCharm{"mysql", "cs:quantal/mysql-2"}, 1502 addCharmPlaceholder{"mysql", 23}, 1503 1504 expect{ 1505 "services and units with correct charm status", 1506 M{ 1507 "environment": "dummyenv", 1508 "machines": M{ 1509 "0": machine0, 1510 "1": machine1, 1511 }, 1512 "services": M{ 1513 "mysql": M{ 1514 "charm": "cs:quantal/mysql-2", 1515 "can-upgrade-to": "cs:quantal/mysql-23", 1516 "exposed": true, 1517 "units": M{ 1518 "mysql/0": M{ 1519 "machine": "1", 1520 "agent-state": "started", 1521 "upgrading-from": "cs:quantal/mysql-1", 1522 "public-address": "dummyenv-1.dns", 1523 }, 1524 }, 1525 }, 1526 }, 1527 }, 1528 }, 1529 ), test( 1530 "service with local charm not shown as out of date", 1531 addMachine{machineId: "0", job: state.JobManageEnviron}, 1532 setAddresses{"0", []instance.Address{instance.NewAddress("dummyenv-0.dns", instance.NetworkUnknown)}}, 1533 startAliveMachine{"0"}, 1534 setMachineStatus{"0", params.StatusStarted, ""}, 1535 addMachine{machineId: "1", job: state.JobHostUnits}, 1536 setAddresses{"1", []instance.Address{instance.NewAddress("dummyenv-1.dns", instance.NetworkUnknown)}}, 1537 startAliveMachine{"1"}, 1538 setMachineStatus{"1", params.StatusStarted, ""}, 1539 addCharm{"mysql"}, 1540 addService{name: "mysql", charm: "mysql"}, 1541 setServiceExposed{"mysql", true}, 1542 addAliveUnit{"mysql", "1"}, 1543 setUnitCharmURL{"mysql/0", "cs:quantal/mysql-1"}, 1544 addCharmWithRevision{addCharm{"mysql"}, "local", 1}, 1545 setServiceCharm{"mysql", "local:quantal/mysql-1"}, 1546 addCharmPlaceholder{"mysql", 23}, 1547 1548 expect{ 1549 "services and units with correct charm status", 1550 M{ 1551 "environment": "dummyenv", 1552 "machines": M{ 1553 "0": machine0, 1554 "1": machine1, 1555 }, 1556 "services": M{ 1557 "mysql": M{ 1558 "charm": "local:quantal/mysql-1", 1559 "exposed": true, 1560 "units": M{ 1561 "mysql/0": M{ 1562 "machine": "1", 1563 "agent-state": "started", 1564 "upgrading-from": "cs:quantal/mysql-1", 1565 "public-address": "dummyenv-1.dns", 1566 }, 1567 }, 1568 }, 1569 }, 1570 }, 1571 }, 1572 ), 1573 } 1574 1575 // TODO(dfc) test failing components by destructively mutating the state under the hood 1576 1577 type addMachine struct { 1578 machineId string 1579 cons constraints.Value 1580 job state.MachineJob 1581 } 1582 1583 func (am addMachine) step(c *gc.C, ctx *context) { 1584 m, err := ctx.st.AddOneMachine(state.MachineTemplate{ 1585 Series: "quantal", 1586 Constraints: am.cons, 1587 Jobs: []state.MachineJob{am.job}, 1588 }) 1589 c.Assert(err, gc.IsNil) 1590 c.Assert(m.Id(), gc.Equals, am.machineId) 1591 } 1592 1593 type addNetwork struct { 1594 name string 1595 providerId network.Id 1596 cidr string 1597 vlanTag int 1598 } 1599 1600 func (an addNetwork) step(c *gc.C, ctx *context) { 1601 n, err := ctx.st.AddNetwork(state.NetworkInfo{ 1602 Name: an.name, 1603 ProviderId: an.providerId, 1604 CIDR: an.cidr, 1605 VLANTag: an.vlanTag, 1606 }) 1607 c.Assert(err, gc.IsNil) 1608 c.Assert(n.Name(), gc.Equals, an.name) 1609 } 1610 1611 type addContainer struct { 1612 parentId string 1613 machineId string 1614 job state.MachineJob 1615 } 1616 1617 func (ac addContainer) step(c *gc.C, ctx *context) { 1618 template := state.MachineTemplate{ 1619 Series: "quantal", 1620 Jobs: []state.MachineJob{ac.job}, 1621 } 1622 m, err := ctx.st.AddMachineInsideMachine(template, ac.parentId, instance.LXC) 1623 c.Assert(err, gc.IsNil) 1624 c.Assert(m.Id(), gc.Equals, ac.machineId) 1625 } 1626 1627 type startMachine struct { 1628 machineId string 1629 } 1630 1631 func (sm startMachine) step(c *gc.C, ctx *context) { 1632 m, err := ctx.st.Machine(sm.machineId) 1633 c.Assert(err, gc.IsNil) 1634 cons, err := m.Constraints() 1635 c.Assert(err, gc.IsNil) 1636 inst, hc := testing.AssertStartInstanceWithConstraints(c, ctx.conn.Environ, m.Id(), cons) 1637 err = m.SetProvisioned(inst.Id(), "fake_nonce", hc) 1638 c.Assert(err, gc.IsNil) 1639 } 1640 1641 type startMissingMachine struct { 1642 machineId string 1643 } 1644 1645 func (sm startMissingMachine) step(c *gc.C, ctx *context) { 1646 m, err := ctx.st.Machine(sm.machineId) 1647 c.Assert(err, gc.IsNil) 1648 cons, err := m.Constraints() 1649 c.Assert(err, gc.IsNil) 1650 _, hc := testing.AssertStartInstanceWithConstraints(c, ctx.conn.Environ, m.Id(), cons) 1651 err = m.SetProvisioned("i-missing", "fake_nonce", hc) 1652 c.Assert(err, gc.IsNil) 1653 err = m.SetInstanceStatus("missing") 1654 c.Assert(err, gc.IsNil) 1655 } 1656 1657 type startAliveMachine struct { 1658 machineId string 1659 } 1660 1661 func (sam startAliveMachine) step(c *gc.C, ctx *context) { 1662 m, err := ctx.st.Machine(sam.machineId) 1663 c.Assert(err, gc.IsNil) 1664 pinger := ctx.setAgentAlive(c, m) 1665 cons, err := m.Constraints() 1666 c.Assert(err, gc.IsNil) 1667 inst, hc := testing.AssertStartInstanceWithConstraints(c, ctx.conn.Environ, m.Id(), cons) 1668 err = m.SetProvisioned(inst.Id(), "fake_nonce", hc) 1669 c.Assert(err, gc.IsNil) 1670 ctx.pingers[m.Id()] = pinger 1671 } 1672 1673 type setAddresses struct { 1674 machineId string 1675 addresses []instance.Address 1676 } 1677 1678 func (sa setAddresses) step(c *gc.C, ctx *context) { 1679 m, err := ctx.st.Machine(sa.machineId) 1680 c.Assert(err, gc.IsNil) 1681 err = m.SetAddresses(sa.addresses...) 1682 c.Assert(err, gc.IsNil) 1683 } 1684 1685 type setTools struct { 1686 machineId string 1687 version version.Binary 1688 } 1689 1690 func (st setTools) step(c *gc.C, ctx *context) { 1691 m, err := ctx.st.Machine(st.machineId) 1692 c.Assert(err, gc.IsNil) 1693 err = m.SetAgentVersion(st.version) 1694 c.Assert(err, gc.IsNil) 1695 } 1696 1697 type addCharm struct { 1698 name string 1699 } 1700 1701 func (ac addCharm) addCharmStep(c *gc.C, ctx *context, scheme string, rev int) { 1702 ch := charmtesting.Charms.Dir(ac.name) 1703 name := ch.Meta().Name 1704 curl := charm.MustParseURL(fmt.Sprintf("%s:quantal/%s-%d", scheme, name, rev)) 1705 bundleURL, err := url.Parse(fmt.Sprintf("http://bundles.testing.invalid/%s-%d", name, rev)) 1706 c.Assert(err, gc.IsNil) 1707 dummy, err := ctx.st.AddCharm(ch, curl, bundleURL, fmt.Sprintf("%s-%d-sha256", name, rev)) 1708 c.Assert(err, gc.IsNil) 1709 ctx.charms[ac.name] = dummy 1710 } 1711 1712 func (ac addCharm) step(c *gc.C, ctx *context) { 1713 ch := charmtesting.Charms.Dir(ac.name) 1714 ac.addCharmStep(c, ctx, "cs", ch.Revision()) 1715 } 1716 1717 type addCharmWithRevision struct { 1718 addCharm 1719 scheme string 1720 rev int 1721 } 1722 1723 func (ac addCharmWithRevision) step(c *gc.C, ctx *context) { 1724 ac.addCharmStep(c, ctx, ac.scheme, ac.rev) 1725 } 1726 1727 type addService struct { 1728 name string 1729 charm string 1730 networks []string 1731 cons constraints.Value 1732 } 1733 1734 func (as addService) step(c *gc.C, ctx *context) { 1735 ch, ok := ctx.charms[as.charm] 1736 c.Assert(ok, gc.Equals, true) 1737 svc, err := ctx.st.AddService(as.name, "user-admin", ch, as.networks) 1738 c.Assert(err, gc.IsNil) 1739 if svc.IsPrincipal() { 1740 err = svc.SetConstraints(as.cons) 1741 c.Assert(err, gc.IsNil) 1742 } 1743 } 1744 1745 type setServiceExposed struct { 1746 name string 1747 exposed bool 1748 } 1749 1750 func (sse setServiceExposed) step(c *gc.C, ctx *context) { 1751 s, err := ctx.st.Service(sse.name) 1752 c.Assert(err, gc.IsNil) 1753 if sse.exposed { 1754 err = s.SetExposed() 1755 c.Assert(err, gc.IsNil) 1756 } 1757 } 1758 1759 type setServiceCharm struct { 1760 name string 1761 charm string 1762 } 1763 1764 func (ssc setServiceCharm) step(c *gc.C, ctx *context) { 1765 ch, err := ctx.st.Charm(charm.MustParseURL(ssc.charm)) 1766 c.Assert(err, gc.IsNil) 1767 s, err := ctx.st.Service(ssc.name) 1768 c.Assert(err, gc.IsNil) 1769 err = s.SetCharm(ch, false) 1770 c.Assert(err, gc.IsNil) 1771 } 1772 1773 type addCharmPlaceholder struct { 1774 name string 1775 rev int 1776 } 1777 1778 func (ac addCharmPlaceholder) step(c *gc.C, ctx *context) { 1779 ch := charmtesting.Charms.Dir(ac.name) 1780 name := ch.Meta().Name 1781 curl := charm.MustParseURL(fmt.Sprintf("cs:quantal/%s-%d", name, ac.rev)) 1782 err := ctx.st.AddStoreCharmPlaceholder(curl) 1783 c.Assert(err, gc.IsNil) 1784 } 1785 1786 type addUnit struct { 1787 serviceName string 1788 machineId string 1789 } 1790 1791 func (au addUnit) step(c *gc.C, ctx *context) { 1792 s, err := ctx.st.Service(au.serviceName) 1793 c.Assert(err, gc.IsNil) 1794 u, err := s.AddUnit() 1795 c.Assert(err, gc.IsNil) 1796 m, err := ctx.st.Machine(au.machineId) 1797 c.Assert(err, gc.IsNil) 1798 err = u.AssignToMachine(m) 1799 c.Assert(err, gc.IsNil) 1800 } 1801 1802 type addAliveUnit struct { 1803 serviceName string 1804 machineId string 1805 } 1806 1807 func (aau addAliveUnit) step(c *gc.C, ctx *context) { 1808 s, err := ctx.st.Service(aau.serviceName) 1809 c.Assert(err, gc.IsNil) 1810 u, err := s.AddUnit() 1811 c.Assert(err, gc.IsNil) 1812 pinger := ctx.setAgentAlive(c, u) 1813 m, err := ctx.st.Machine(aau.machineId) 1814 c.Assert(err, gc.IsNil) 1815 err = u.AssignToMachine(m) 1816 c.Assert(err, gc.IsNil) 1817 ctx.pingers[u.Name()] = pinger 1818 } 1819 1820 type setUnitsAlive struct { 1821 serviceName string 1822 } 1823 1824 func (sua setUnitsAlive) step(c *gc.C, ctx *context) { 1825 s, err := ctx.st.Service(sua.serviceName) 1826 c.Assert(err, gc.IsNil) 1827 us, err := s.AllUnits() 1828 c.Assert(err, gc.IsNil) 1829 for _, u := range us { 1830 ctx.pingers[u.Name()] = ctx.setAgentAlive(c, u) 1831 } 1832 } 1833 1834 type setUnitStatus struct { 1835 unitName string 1836 status params.Status 1837 statusInfo string 1838 } 1839 1840 func (sus setUnitStatus) step(c *gc.C, ctx *context) { 1841 u, err := ctx.st.Unit(sus.unitName) 1842 c.Assert(err, gc.IsNil) 1843 err = u.SetStatus(sus.status, sus.statusInfo, nil) 1844 c.Assert(err, gc.IsNil) 1845 } 1846 1847 type setUnitCharmURL struct { 1848 unitName string 1849 charm string 1850 } 1851 1852 func (uc setUnitCharmURL) step(c *gc.C, ctx *context) { 1853 u, err := ctx.st.Unit(uc.unitName) 1854 c.Assert(err, gc.IsNil) 1855 curl := charm.MustParseURL(uc.charm) 1856 err = u.SetCharmURL(curl) 1857 c.Assert(err, gc.IsNil) 1858 err = u.SetStatus(params.StatusStarted, "", nil) 1859 c.Assert(err, gc.IsNil) 1860 } 1861 1862 type openUnitPort struct { 1863 unitName string 1864 protocol string 1865 number int 1866 } 1867 1868 func (oup openUnitPort) step(c *gc.C, ctx *context) { 1869 u, err := ctx.st.Unit(oup.unitName) 1870 c.Assert(err, gc.IsNil) 1871 err = u.OpenPort(oup.protocol, oup.number) 1872 c.Assert(err, gc.IsNil) 1873 } 1874 1875 type ensureDyingUnit struct { 1876 unitName string 1877 } 1878 1879 func (e ensureDyingUnit) step(c *gc.C, ctx *context) { 1880 u, err := ctx.st.Unit(e.unitName) 1881 c.Assert(err, gc.IsNil) 1882 err = u.Destroy() 1883 c.Assert(err, gc.IsNil) 1884 c.Assert(u.Life(), gc.Equals, state.Dying) 1885 } 1886 1887 type ensureDyingService struct { 1888 serviceName string 1889 } 1890 1891 func (e ensureDyingService) step(c *gc.C, ctx *context) { 1892 svc, err := ctx.st.Service(e.serviceName) 1893 c.Assert(err, gc.IsNil) 1894 err = svc.Destroy() 1895 c.Assert(err, gc.IsNil) 1896 err = svc.Refresh() 1897 c.Assert(err, gc.IsNil) 1898 c.Assert(svc.Life(), gc.Equals, state.Dying) 1899 } 1900 1901 type ensureDeadMachine struct { 1902 machineId string 1903 } 1904 1905 func (e ensureDeadMachine) step(c *gc.C, ctx *context) { 1906 m, err := ctx.st.Machine(e.machineId) 1907 c.Assert(err, gc.IsNil) 1908 err = m.EnsureDead() 1909 c.Assert(err, gc.IsNil) 1910 c.Assert(m.Life(), gc.Equals, state.Dead) 1911 } 1912 1913 type setMachineStatus struct { 1914 machineId string 1915 status params.Status 1916 statusInfo string 1917 } 1918 1919 func (sms setMachineStatus) step(c *gc.C, ctx *context) { 1920 m, err := ctx.st.Machine(sms.machineId) 1921 c.Assert(err, gc.IsNil) 1922 err = m.SetStatus(sms.status, sms.statusInfo, nil) 1923 c.Assert(err, gc.IsNil) 1924 } 1925 1926 type relateServices struct { 1927 ep1, ep2 string 1928 } 1929 1930 func (rs relateServices) step(c *gc.C, ctx *context) { 1931 eps, err := ctx.st.InferEndpoints([]string{rs.ep1, rs.ep2}) 1932 c.Assert(err, gc.IsNil) 1933 _, err = ctx.st.AddRelation(eps...) 1934 c.Assert(err, gc.IsNil) 1935 } 1936 1937 type addSubordinate struct { 1938 prinUnit string 1939 subService string 1940 } 1941 1942 func (as addSubordinate) step(c *gc.C, ctx *context) { 1943 u, err := ctx.st.Unit(as.prinUnit) 1944 c.Assert(err, gc.IsNil) 1945 eps, err := ctx.st.InferEndpoints([]string{u.ServiceName(), as.subService}) 1946 c.Assert(err, gc.IsNil) 1947 rel, err := ctx.st.EndpointsRelation(eps...) 1948 c.Assert(err, gc.IsNil) 1949 ru, err := rel.Unit(u) 1950 c.Assert(err, gc.IsNil) 1951 err = ru.EnterScope(nil) 1952 c.Assert(err, gc.IsNil) 1953 } 1954 1955 type scopedExpect struct { 1956 what string 1957 scope []string 1958 output M 1959 } 1960 1961 type expect struct { 1962 what string 1963 output M 1964 } 1965 1966 func (e scopedExpect) step(c *gc.C, ctx *context) { 1967 c.Logf("\nexpect: %s %s\n", e.what, strings.Join(e.scope, " ")) 1968 1969 // Now execute the command for each format. 1970 for _, format := range statusFormats { 1971 c.Logf("format %q", format.name) 1972 // Run command with the required format. 1973 args := append([]string{"--format", format.name}, e.scope...) 1974 code, stdout, stderr := runStatus(c, args...) 1975 c.Assert(code, gc.Equals, 0) 1976 if !c.Check(stderr, gc.HasLen, 0) { 1977 c.Fatalf("status failed: %s", string(stderr)) 1978 } 1979 1980 // Prepare the output in the same format. 1981 buf, err := format.marshal(e.output) 1982 c.Assert(err, gc.IsNil) 1983 expected := make(M) 1984 err = format.unmarshal(buf, &expected) 1985 c.Assert(err, gc.IsNil) 1986 1987 // Check the output is as expected. 1988 actual := make(M) 1989 err = format.unmarshal(stdout, &actual) 1990 c.Assert(err, gc.IsNil) 1991 c.Assert(actual, jc.DeepEquals, expected) 1992 } 1993 } 1994 1995 func (e expect) step(c *gc.C, ctx *context) { 1996 scopedExpect{e.what, nil, e.output}.step(c, ctx) 1997 } 1998 1999 func (s *StatusSuite) TestStatusAllFormats(c *gc.C) { 2000 for i, t := range statusTests { 2001 c.Logf("test %d: %s", i, t.summary) 2002 func() { 2003 // Prepare context and run all steps to setup. 2004 ctx := s.newContext() 2005 defer s.resetContext(c, ctx) 2006 ctx.run(c, t.steps) 2007 }() 2008 } 2009 } 2010 2011 func (s *StatusSuite) TestStatusFilterErrors(c *gc.C) { 2012 steps := []stepper{ 2013 addMachine{machineId: "0", job: state.JobManageEnviron}, 2014 addMachine{machineId: "1", job: state.JobHostUnits}, 2015 addCharm{"mysql"}, 2016 addService{name: "mysql", charm: "mysql"}, 2017 addAliveUnit{"mysql", "1"}, 2018 } 2019 ctx := s.newContext() 2020 defer s.resetContext(c, ctx) 2021 ctx.run(c, steps) 2022 2023 // Status filters can only fail if the patterns are invalid. 2024 code, _, stderr := runStatus(c, "[*") 2025 c.Assert(code, gc.Not(gc.Equals), 0) 2026 c.Assert(string(stderr), gc.Equals, `error: pattern "[*" contains invalid characters`+"\n") 2027 2028 code, _, stderr = runStatus(c, "//") 2029 c.Assert(code, gc.Not(gc.Equals), 0) 2030 c.Assert(string(stderr), gc.Equals, `error: pattern "//" contains too many '/' characters`+"\n") 2031 2032 // Pattern validity is checked eagerly; if a bad pattern 2033 // proceeds a valid, matching pattern, then the bad pattern 2034 // will still cause an error. 2035 code, _, stderr = runStatus(c, "*", "[*") 2036 c.Assert(code, gc.Not(gc.Equals), 0) 2037 c.Assert(string(stderr), gc.Equals, `error: pattern "[*" contains invalid characters`+"\n") 2038 }