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