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