github.com/niedbalski/juju@v0.0.0-20190215020005-8ff100488e47/cmd/juju/status/status_internal_test.go (about) 1 // Copyright 2015 Canonical Ltd. 2 // Licensed under the AGPLv3, see LICENCE file for details. 3 4 package status 5 6 import ( 7 "bytes" 8 "encoding/json" 9 "fmt" 10 "io/ioutil" 11 "os" 12 "regexp" 13 "runtime" 14 "strings" 15 "time" 16 17 "github.com/juju/cmd" 18 "github.com/juju/cmd/cmdtesting" 19 "github.com/juju/loggo" 20 jc "github.com/juju/testing/checkers" 21 "github.com/juju/utils" 22 "github.com/juju/version" 23 "github.com/kr/pretty" 24 gc "gopkg.in/check.v1" 25 "gopkg.in/juju/charm.v6" 26 "gopkg.in/juju/names.v2" 27 goyaml "gopkg.in/yaml.v2" 28 29 "github.com/juju/juju/apiserver/params" 30 "github.com/juju/juju/cmd/juju/common" 31 "github.com/juju/juju/cmd/modelcmd" 32 "github.com/juju/juju/core/constraints" 33 "github.com/juju/juju/core/crossmodel" 34 "github.com/juju/juju/core/instance" 35 "github.com/juju/juju/core/lease" 36 "github.com/juju/juju/core/migration" 37 corepresence "github.com/juju/juju/core/presence" 38 "github.com/juju/juju/core/status" 39 "github.com/juju/juju/environs" 40 environscontext "github.com/juju/juju/environs/context" 41 "github.com/juju/juju/juju/osenv" 42 "github.com/juju/juju/juju/testing" 43 "github.com/juju/juju/network" 44 "github.com/juju/juju/state" 45 "github.com/juju/juju/state/multiwatcher" 46 "github.com/juju/juju/state/presence" 47 "github.com/juju/juju/testcharms" 48 coretesting "github.com/juju/juju/testing" 49 "github.com/juju/juju/testing/factory" 50 coreversion "github.com/juju/juju/version" 51 ) 52 53 var ( 54 currentVersion = version.Number{Major: 1, Minor: 2, Patch: 3} 55 nextVersion = version.Number{Major: 1, Minor: 2, Patch: 4} 56 ) 57 58 func runStatus(c *gc.C, args ...string) (code int, stdout, stderr []byte) { 59 ctx := cmdtesting.Context(c) 60 code = cmd.Main(NewStatusCommand(), ctx, args) 61 stdout = ctx.Stdout.(*bytes.Buffer).Bytes() 62 stderr = ctx.Stderr.(*bytes.Buffer).Bytes() 63 return 64 } 65 66 type StatusSuite struct { 67 testing.JujuConnSuite 68 } 69 70 var _ = gc.Suite(&StatusSuite{}) 71 72 func (s *StatusSuite) SetUpSuite(c *gc.C) { 73 s.JujuConnSuite.SetUpSuite(c) 74 s.PatchValue(&coreversion.Current, currentVersion) 75 } 76 77 func (s *StatusSuite) SetUpTest(c *gc.C) { 78 s.ConfigAttrs = map[string]interface{}{ 79 "agent-version": currentVersion.String(), 80 } 81 s.JujuConnSuite.SetUpTest(c) 82 } 83 84 type M map[string]interface{} 85 86 type L []interface{} 87 88 type testCase struct { 89 summary string 90 steps []stepper 91 } 92 93 func test(summary string, steps ...stepper) testCase { 94 return testCase{summary, steps} 95 } 96 97 type stepper interface { 98 step(c *gc.C, ctx *context) 99 } 100 101 // 102 // context 103 // 104 105 func newContext(st *state.State, pool *state.StatePool, env environs.Environ, adminUserTag string) *context { 106 // We make changes in the API server's state so that 107 // our changes to presence are immediately noticed 108 // in the status. 109 return &context{ 110 st: st, 111 pool: pool, 112 env: env, 113 statusSetter: env.(agentStatusSetter), 114 charms: make(map[string]*state.Charm), 115 pingers: make(map[string]*presence.Pinger), 116 adminUserTag: adminUserTag, 117 } 118 } 119 120 type agentStatusSetter interface { 121 SetAgentStatus(agent string, status corepresence.Status) 122 } 123 124 type context struct { 125 st *state.State 126 pool *state.StatePool 127 env environs.Environ 128 statusSetter agentStatusSetter 129 charms map[string]*state.Charm 130 pingers map[string]*presence.Pinger 131 adminUserTag string // A string repr of the tag. 132 expectIsoTime bool 133 skipTest bool 134 } 135 136 func (ctx *context) reset(c *gc.C) { 137 for _, up := range ctx.pingers { 138 err := up.KillForTesting() 139 c.Check(err, jc.ErrorIsNil) 140 } 141 } 142 143 func (ctx *context) run(c *gc.C, steps []stepper) { 144 for i, s := range steps { 145 if ctx.skipTest { 146 c.Logf("skipping test %d", i) 147 return 148 } 149 c.Logf("step %d", i) 150 c.Logf("%#v", s) 151 s.step(c, ctx) 152 } 153 } 154 155 func (ctx *context) setAgentPresence(c *gc.C, p presence.Agent) *presence.Pinger { 156 pinger, err := p.SetAgentPresence() 157 c.Assert(err, jc.ErrorIsNil) 158 ctx.st.StartSync() 159 err = p.WaitAgentPresence(coretesting.LongWait) 160 c.Assert(err, jc.ErrorIsNil) 161 agentPresence, err := p.AgentPresence() 162 c.Assert(err, jc.ErrorIsNil) 163 c.Assert(agentPresence, jc.IsTrue) 164 return pinger 165 } 166 167 func (s *StatusSuite) newContext(c *gc.C) *context { 168 st := s.Environ.(testing.GetStater).GetStateInAPIServer() 169 170 // We make changes in the API server's state so that 171 // our changes to presence are immediately noticed 172 // in the status. 173 return newContext(st, s.StatePool, s.Environ, s.AdminUserTag(c).String()) 174 } 175 176 func (s *StatusSuite) resetContext(c *gc.C, ctx *context) { 177 ctx.reset(c) 178 s.JujuConnSuite.Reset(c) 179 } 180 181 // shortcuts for expected output. 182 var ( 183 model = M{ 184 "name": "controller", 185 "type": "iaas", 186 "controller": "kontroll", 187 "cloud": "dummy", 188 "region": "dummy-region", 189 "version": "1.2.3", 190 "model-status": M{ 191 "current": "available", 192 "since": "01 Apr 15 01:23+10:00", 193 }, 194 "sla": "unsupported", 195 } 196 197 machine0 = M{ 198 "juju-status": M{ 199 "current": "started", 200 "since": "01 Apr 15 01:23+10:00", 201 }, 202 "dns-name": "10.0.0.1", 203 "ip-addresses": []string{"10.0.0.1"}, 204 "instance-id": "controller-0", 205 "machine-status": M{ 206 "current": "pending", 207 "since": "01 Apr 15 01:23+10:00", 208 }, 209 "series": "quantal", 210 "network-interfaces": M{ 211 "eth0": M{ 212 "ip-addresses": []string{"10.0.0.1"}, 213 "mac-address": "aa:bb:cc:dd:ee:ff", 214 "is-up": true, 215 }, 216 }, 217 "hardware": "arch=amd64 cores=1 mem=1024M root-disk=8192M", 218 "controller-member-status": "adding-vote", 219 } 220 machine1 = M{ 221 "juju-status": M{ 222 "current": "started", 223 "since": "01 Apr 15 01:23+10:00", 224 }, 225 "dns-name": "10.0.1.1", 226 "ip-addresses": []string{"10.0.1.1"}, 227 "instance-id": "controller-1", 228 "machine-status": M{ 229 "current": "pending", 230 "since": "01 Apr 15 01:23+10:00", 231 }, 232 "series": "quantal", 233 "network-interfaces": M{ 234 "eth0": M{ 235 "ip-addresses": []string{"10.0.1.1"}, 236 "mac-address": "aa:bb:cc:dd:ee:ff", 237 "is-up": true, 238 }, 239 }, 240 "hardware": "arch=amd64 cores=1 mem=1024M root-disk=8192M", 241 } 242 machine1WithLXDProfile = M{ 243 "juju-status": M{ 244 "current": "started", 245 "since": "01 Apr 15 01:23+10:00", 246 }, 247 "dns-name": "10.0.1.1", 248 "ip-addresses": []string{"10.0.1.1"}, 249 "instance-id": "controller-1", 250 "machine-status": M{ 251 "current": "pending", 252 "since": "01 Apr 15 01:23+10:00", 253 }, 254 "series": "quantal", 255 "network-interfaces": M{ 256 "eth0": M{ 257 "ip-addresses": []string{"10.0.1.1"}, 258 "mac-address": "aa:bb:cc:dd:ee:ff", 259 "is-up": true, 260 }, 261 }, 262 "hardware": "arch=amd64 cores=1 mem=1024M root-disk=8192M", 263 "lxd-profiles": M{ 264 "juju-controller-lxd-profile-1": M{ 265 "config": M{ 266 "environment.http_proxy": "", 267 "linux.kernel_modules": "openvswitch,nbd,ip_tables,ip6_tables", 268 "security.nesting": "true", 269 "security.privileged": "true", 270 }, 271 "description": "lxd profile for testing, will pass validation", 272 "devices": M{ 273 "bdisk": M{ 274 "source": "/dev/loop0", 275 "type": "unix-block", 276 }, 277 "gpu": M{ 278 "type": "gpu", 279 }, 280 "sony": M{ 281 "productid": "51da", 282 "type": "usb", 283 "vendorid": "0fce", 284 }, 285 "tun": M{ 286 "path": "/dev/net/tun", 287 "type": "unix-char", 288 }, 289 }, 290 }, 291 }, 292 } 293 machine2 = M{ 294 "juju-status": M{ 295 "current": "started", 296 "since": "01 Apr 15 01:23+10:00", 297 }, 298 "dns-name": "10.0.2.1", 299 "ip-addresses": []string{"10.0.2.1"}, 300 "instance-id": "controller-2", 301 "machine-status": M{ 302 "current": "pending", 303 "since": "01 Apr 15 01:23+10:00", 304 }, 305 "series": "quantal", 306 "network-interfaces": M{ 307 "eth0": M{ 308 "ip-addresses": []string{"10.0.2.1"}, 309 "mac-address": "aa:bb:cc:dd:ee:ff", 310 "is-up": true, 311 }, 312 }, 313 "hardware": "arch=amd64 cores=1 mem=1024M root-disk=8192M", 314 } 315 machine3 = M{ 316 "juju-status": M{ 317 "current": "started", 318 "since": "01 Apr 15 01:23+10:00", 319 }, 320 "dns-name": "10.0.3.1", 321 "ip-addresses": []string{"10.0.3.1"}, 322 "instance-id": "controller-3", 323 "machine-status": M{ 324 "current": "started", 325 "message": "I am number three", 326 "since": "01 Apr 15 01:23+10:00", 327 }, 328 "series": "quantal", 329 "network-interfaces": M{ 330 "eth0": M{ 331 "ip-addresses": []string{"10.0.3.1"}, 332 "mac-address": "aa:bb:cc:dd:ee:ff", 333 "is-up": true, 334 }, 335 }, 336 "hardware": "arch=amd64 cores=1 mem=1024M root-disk=8192M", 337 } 338 machine4 = M{ 339 "juju-status": M{ 340 "current": "started", 341 "since": "01 Apr 15 01:23+10:00", 342 }, 343 "dns-name": "10.0.4.1", 344 "ip-addresses": []string{"10.0.4.1"}, 345 "instance-id": "controller-4", 346 "machine-status": M{ 347 "current": "pending", 348 "since": "01 Apr 15 01:23+10:00", 349 }, 350 "series": "quantal", 351 "network-interfaces": M{ 352 "eth0": M{ 353 "ip-addresses": []string{"10.0.4.1"}, 354 "mac-address": "aa:bb:cc:dd:ee:ff", 355 "is-up": true, 356 }, 357 }, 358 "hardware": "arch=amd64 cores=1 mem=1024M root-disk=8192M", 359 } 360 machine1WithContainers = M{ 361 "juju-status": M{ 362 "current": "started", 363 "since": "01 Apr 15 01:23+10:00", 364 }, 365 "containers": M{ 366 "1/lxd/0": M{ 367 "juju-status": M{ 368 "current": "started", 369 "since": "01 Apr 15 01:23+10:00", 370 }, 371 "containers": M{ 372 "1/lxd/0/lxd/0": M{ 373 "juju-status": M{ 374 "current": "started", 375 "since": "01 Apr 15 01:23+10:00", 376 }, 377 "dns-name": "10.0.3.1", 378 "ip-addresses": []string{"10.0.3.1"}, 379 "instance-id": "controller-3", 380 "machine-status": M{ 381 "current": "pending", 382 "since": "01 Apr 15 01:23+10:00", 383 }, 384 "series": "quantal", 385 "network-interfaces": M{ 386 "eth0": M{ 387 "ip-addresses": []string{"10.0.3.1"}, 388 "mac-address": "aa:bb:cc:dd:ee:ff", 389 "is-up": true, 390 }, 391 }, 392 }, 393 }, 394 "dns-name": "10.0.2.1", 395 "ip-addresses": []string{"10.0.2.1"}, 396 "instance-id": "controller-2", 397 "machine-status": M{ 398 "current": "pending", 399 "since": "01 Apr 15 01:23+10:00", 400 }, 401 "series": "quantal", 402 "network-interfaces": M{ 403 "eth0": M{ 404 "ip-addresses": []string{"10.0.2.1"}, 405 "mac-address": "aa:bb:cc:dd:ee:ff", 406 "is-up": true, 407 }, 408 }, 409 }, 410 "1/lxd/1": M{ 411 "juju-status": M{ 412 "current": "pending", 413 "since": "01 Apr 15 01:23+10:00", 414 }, 415 "instance-id": "pending", 416 "machine-status": M{ 417 "current": "pending", 418 "since": "01 Apr 15 01:23+10:00", 419 }, 420 "series": "quantal", 421 }, 422 }, 423 "dns-name": "10.0.1.1", 424 "ip-addresses": []string{"10.0.1.1"}, 425 "instance-id": "controller-1", 426 "machine-status": M{ 427 "current": "pending", 428 "since": "01 Apr 15 01:23+10:00", 429 }, 430 "series": "quantal", 431 "network-interfaces": M{ 432 "eth0": M{ 433 "ip-addresses": []string{"10.0.1.1"}, 434 "mac-address": "aa:bb:cc:dd:ee:ff", 435 "is-up": true, 436 }, 437 }, 438 "hardware": "arch=amd64 cores=1 mem=1024M root-disk=8192M", 439 } 440 unexposedApplication = dummyCharm(M{ 441 "application-status": M{ 442 "current": "waiting", 443 "message": "waiting for machine", 444 "since": "01 Apr 15 01:23+10:00", 445 }, 446 }) 447 exposedApplication = dummyCharm(M{ 448 "application-status": M{ 449 "current": "waiting", 450 "message": "waiting for machine", 451 "since": "01 Apr 15 01:23+10:00", 452 }, 453 "exposed": true, 454 }) 455 loggingCharm = M{ 456 "charm": "cs:quantal/logging-1", 457 "charm-origin": "jujucharms", 458 "charm-name": "logging", 459 "charm-rev": 1, 460 "series": "quantal", 461 "os": "ubuntu", 462 "exposed": true, 463 "application-status": M{ 464 "current": "error", 465 "message": "somehow lost in all those logs", 466 "since": "01 Apr 15 01:23+10:00", 467 }, 468 "relations": M{ 469 "logging-directory": L{"wordpress"}, 470 "info": L{"mysql"}, 471 }, 472 "endpoint-bindings": M{ 473 "info": "", 474 "logging-client": "", 475 "logging-directory": "", 476 }, 477 "subordinate-to": L{"mysql", "wordpress"}, 478 } 479 ) 480 481 type outputFormat struct { 482 name string 483 marshal func(v interface{}) ([]byte, error) 484 unmarshal func(data []byte, v interface{}) error 485 } 486 487 // statusFormats list all output formats that can be marshalled as structured data, 488 // supported by status command. 489 var statusFormats = []outputFormat{ 490 {"yaml", goyaml.Marshal, goyaml.Unmarshal}, 491 {"json", json.Marshal, json.Unmarshal}, 492 } 493 494 var machineCons = constraints.MustParse("cores=2 mem=8G root-disk=8G") 495 496 var statusTests = []testCase{ 497 // Status tests 498 test( // 0 499 "bootstrap and starting a single instance", 500 501 addMachine{machineId: "0", job: state.JobManageModel}, 502 expect{ 503 what: "simulate juju bootstrap by adding machine/0 to the state", 504 output: M{ 505 "model": model, 506 "machines": M{ 507 "0": M{ 508 "juju-status": M{ 509 "current": "pending", 510 "since": "01 Apr 15 01:23+10:00", 511 }, 512 "instance-id": "pending", 513 "machine-status": M{ 514 "current": "pending", 515 "since": "01 Apr 15 01:23+10:00", 516 }, 517 "series": "quantal", 518 "controller-member-status": "adding-vote", 519 }, 520 }, 521 "applications": M{}, 522 "storage": M{}, 523 "controller": M{ 524 "timestamp": "15:04:05+07:00", 525 }, 526 }, 527 }, 528 529 startAliveMachine{"0", ""}, 530 setAddresses{"0", []network.Address{ 531 network.NewScopedAddress("10.0.0.1", network.ScopePublic), 532 network.NewAddress("10.0.0.2"), 533 }}, 534 expect{ 535 what: "simulate the PA starting an instance in response to the state change", 536 output: M{ 537 "model": model, 538 "machines": M{ 539 "0": M{ 540 "juju-status": M{ 541 "current": "pending", 542 "since": "01 Apr 15 01:23+10:00", 543 }, 544 "dns-name": "10.0.0.1", 545 "ip-addresses": []string{"10.0.0.1", "10.0.0.2"}, 546 "instance-id": "controller-0", 547 "machine-status": M{ 548 "current": "pending", 549 "since": "01 Apr 15 01:23+10:00", 550 }, 551 "series": "quantal", 552 "network-interfaces": M{ 553 "eth0": M{ 554 "ip-addresses": []string{"10.0.0.1"}, 555 "mac-address": "aa:bb:cc:dd:ee:ff", 556 "is-up": true, 557 }, 558 "eth1": M{ 559 "ip-addresses": []string{"10.0.0.2"}, 560 "mac-address": "aa:bb:cc:dd:ee:ff", 561 "is-up": true, 562 }, 563 }, 564 "hardware": "arch=amd64 cores=1 mem=1024M root-disk=8192M", 565 "controller-member-status": "adding-vote", 566 }, 567 }, 568 "applications": M{}, 569 "storage": M{}, 570 "controller": M{ 571 "timestamp": "15:04:05+07:00", 572 }, 573 }, 574 }, 575 576 setMachineStatus{"0", status.Started, ""}, 577 expect{ 578 what: "simulate the MA started and set the machine status", 579 output: M{ 580 "model": model, 581 "machines": M{ 582 "0": M{ 583 "juju-status": M{ 584 "current": "started", 585 "since": "01 Apr 15 01:23+10:00", 586 }, 587 "dns-name": "10.0.0.1", 588 "ip-addresses": []string{"10.0.0.1", "10.0.0.2"}, 589 "instance-id": "controller-0", 590 "machine-status": M{ 591 "current": "pending", 592 "since": "01 Apr 15 01:23+10:00", 593 }, 594 "series": "quantal", 595 "network-interfaces": M{ 596 "eth0": M{ 597 "ip-addresses": []string{"10.0.0.1"}, 598 "mac-address": "aa:bb:cc:dd:ee:ff", 599 "is-up": true, 600 }, 601 "eth1": M{ 602 "ip-addresses": []string{"10.0.0.2"}, 603 "mac-address": "aa:bb:cc:dd:ee:ff", 604 "is-up": true, 605 }, 606 }, 607 "hardware": "arch=amd64 cores=1 mem=1024M root-disk=8192M", 608 "controller-member-status": "adding-vote", 609 }, 610 }, 611 "applications": M{}, 612 "storage": M{}, 613 "controller": M{ 614 "timestamp": "15:04:05+07:00", 615 }, 616 }, 617 }, 618 619 setTools{"0", version.MustParseBinary("1.2.3-trusty-ppc")}, 620 expect{ 621 what: "simulate the MA setting the version", 622 output: M{ 623 "model": model, 624 "machines": M{ 625 "0": M{ 626 "dns-name": "10.0.0.1", 627 "ip-addresses": []string{"10.0.0.1", "10.0.0.2"}, 628 "instance-id": "controller-0", 629 "machine-status": M{ 630 "current": "pending", 631 "since": "01 Apr 15 01:23+10:00", 632 }, 633 "juju-status": M{ 634 "current": "started", 635 "since": "01 Apr 15 01:23+10:00", 636 "version": "1.2.3", 637 }, 638 "series": "quantal", 639 "network-interfaces": M{ 640 "eth0": M{ 641 "ip-addresses": []string{"10.0.0.1"}, 642 "mac-address": "aa:bb:cc:dd:ee:ff", 643 "is-up": true, 644 }, 645 "eth1": M{ 646 "ip-addresses": []string{"10.0.0.2"}, 647 "mac-address": "aa:bb:cc:dd:ee:ff", 648 "is-up": true, 649 }, 650 }, 651 "hardware": "arch=amd64 cores=1 mem=1024M root-disk=8192M", 652 "controller-member-status": "adding-vote", 653 }, 654 }, 655 "applications": M{}, 656 "storage": M{}, 657 "controller": M{ 658 "timestamp": "15:04:05+07:00", 659 }, 660 }, 661 }, 662 ), 663 test( // 1 664 "instance with different hardware characteristics", 665 addMachine{machineId: "0", cons: machineCons, job: state.JobManageModel}, 666 setAddresses{"0", []network.Address{ 667 network.NewScopedAddress("10.0.0.1", network.ScopePublic), 668 network.NewAddress("10.0.0.2"), 669 }}, 670 startAliveMachine{"0", ""}, 671 setMachineStatus{"0", status.Started, ""}, 672 expect{ 673 what: "machine 0 has specific hardware characteristics", 674 output: M{ 675 "model": model, 676 "machines": M{ 677 "0": M{ 678 "juju-status": M{ 679 "current": "started", 680 "since": "01 Apr 15 01:23+10:00", 681 }, 682 "dns-name": "10.0.0.1", 683 "ip-addresses": []string{"10.0.0.1", "10.0.0.2"}, 684 "instance-id": "controller-0", 685 "machine-status": M{ 686 "current": "pending", 687 "since": "01 Apr 15 01:23+10:00", 688 }, 689 "series": "quantal", 690 "network-interfaces": M{ 691 "eth0": M{ 692 "ip-addresses": []string{"10.0.0.1"}, 693 "mac-address": "aa:bb:cc:dd:ee:ff", 694 "is-up": true, 695 }, 696 "eth1": M{ 697 "ip-addresses": []string{"10.0.0.2"}, 698 "mac-address": "aa:bb:cc:dd:ee:ff", 699 "is-up": true, 700 }, 701 }, 702 "constraints": "cores=2 mem=8192M root-disk=8192M", 703 "hardware": "arch=amd64 cores=2 mem=8192M root-disk=8192M", 704 "controller-member-status": "adding-vote", 705 }, 706 }, 707 "applications": M{}, 708 "storage": M{}, 709 "controller": M{ 710 "timestamp": "15:04:05+07:00", 711 }, 712 }, 713 }, 714 ), 715 test( // 2 716 "instance without addresses", 717 addMachine{machineId: "0", cons: machineCons, job: state.JobManageModel}, 718 startAliveMachine{"0", ""}, 719 setMachineStatus{"0", status.Started, ""}, 720 expect{ 721 what: "machine 0 has no dns-name", 722 output: M{ 723 "model": model, 724 "machines": M{ 725 "0": M{ 726 "juju-status": M{ 727 "current": "started", 728 "since": "01 Apr 15 01:23+10:00", 729 }, 730 "instance-id": "controller-0", 731 "machine-status": M{ 732 "current": "pending", 733 "since": "01 Apr 15 01:23+10:00", 734 }, 735 "series": "quantal", 736 "constraints": "cores=2 mem=8192M root-disk=8192M", 737 "hardware": "arch=amd64 cores=2 mem=8192M root-disk=8192M", 738 "controller-member-status": "adding-vote", 739 }, 740 }, 741 "applications": M{}, 742 "storage": M{}, 743 "controller": M{ 744 "timestamp": "15:04:05+07:00", 745 }, 746 }, 747 }, 748 ), 749 test( // 3 750 "test pending and missing machines", 751 addMachine{machineId: "0", job: state.JobManageModel}, 752 expect{ 753 what: "machine 0 reports pending", 754 output: M{ 755 "model": model, 756 "machines": M{ 757 "0": M{ 758 "juju-status": M{ 759 "current": "pending", 760 "since": "01 Apr 15 01:23+10:00", 761 }, 762 "instance-id": "pending", 763 "machine-status": M{ 764 "current": "pending", 765 "since": "01 Apr 15 01:23+10:00", 766 }, 767 "series": "quantal", 768 "controller-member-status": "adding-vote", 769 }, 770 }, 771 "applications": M{}, 772 "storage": M{}, 773 "controller": M{ 774 "timestamp": "15:04:05+07:00", 775 }, 776 }, 777 }, 778 779 startMissingMachine{"0"}, 780 expect{ 781 what: "machine 0 reports missing", 782 output: M{ 783 "model": model, 784 "machines": M{ 785 "0": M{ 786 "instance-id": "i-missing", 787 "juju-status": M{ 788 "current": "pending", 789 "since": "01 Apr 15 01:23+10:00", 790 }, 791 "machine-status": M{ 792 "current": "unknown", 793 "message": "missing", 794 "since": "01 Apr 15 01:23+10:00", 795 }, 796 "series": "quantal", 797 "hardware": "arch=amd64 cores=1 mem=1024M root-disk=8192M", 798 "controller-member-status": "adding-vote", 799 }, 800 }, 801 "applications": M{}, 802 "storage": M{}, 803 "controller": M{ 804 "timestamp": "15:04:05+07:00", 805 }, 806 }, 807 }, 808 ), 809 test( // 4 810 "add two applications and expose one, then add 2 more machines and some units", 811 // step 0 812 addMachine{machineId: "0", job: state.JobManageModel}, 813 setAddresses{"0", network.NewAddresses("10.0.0.1")}, 814 startAliveMachine{"0", ""}, 815 setMachineStatus{"0", status.Started, ""}, 816 addCharm{"dummy"}, 817 addApplication{name: "dummy-application", charm: "dummy"}, 818 addApplication{name: "exposed-application", charm: "dummy"}, 819 expect{ 820 what: "no applications exposed yet", 821 output: M{ 822 "model": model, 823 "machines": M{ 824 "0": machine0, 825 }, 826 "applications": M{ 827 "dummy-application": unexposedApplication, 828 "exposed-application": unexposedApplication, 829 }, 830 "storage": M{}, 831 "controller": M{ 832 "timestamp": "15:04:05+07:00", 833 }, 834 }, 835 }, 836 837 // step 8 838 setApplicationExposed{"exposed-application", true}, 839 expect{ 840 what: "one exposed application", 841 output: M{ 842 "model": model, 843 "machines": M{ 844 "0": machine0, 845 }, 846 "applications": M{ 847 "dummy-application": unexposedApplication, 848 "exposed-application": exposedApplication, 849 }, 850 "storage": M{}, 851 "controller": M{ 852 "timestamp": "15:04:05+07:00", 853 }, 854 }, 855 }, 856 857 // step 10 858 addMachine{machineId: "1", job: state.JobHostUnits}, 859 setAddresses{"1", network.NewAddresses("10.0.1.1")}, 860 startAliveMachine{"1", ""}, 861 setMachineStatus{"1", status.Started, ""}, 862 addMachine{machineId: "2", job: state.JobHostUnits}, 863 setAddresses{"2", network.NewAddresses("10.0.2.1")}, 864 startAliveMachine{"2", ""}, 865 setMachineStatus{"2", status.Started, ""}, 866 expect{ 867 what: "two more machines added", 868 output: M{ 869 "model": model, 870 "machines": M{ 871 "0": machine0, 872 "1": machine1, 873 "2": machine2, 874 }, 875 "applications": M{ 876 "dummy-application": unexposedApplication, 877 "exposed-application": exposedApplication, 878 }, 879 "storage": M{}, 880 "controller": M{ 881 "timestamp": "15:04:05+07:00", 882 }, 883 }, 884 }, 885 886 // step 19 887 addAliveUnit{"dummy-application", "1"}, 888 addAliveUnit{"exposed-application", "2"}, 889 setAgentStatus{"exposed-application/0", status.Error, "You Require More Vespene Gas", nil}, 890 // Open multiple ports with different protocols, 891 // ensure they're sorted on protocol, then number. 892 openUnitPort{"exposed-application/0", "udp", 10}, 893 openUnitPort{"exposed-application/0", "udp", 2}, 894 openUnitPort{"exposed-application/0", "tcp", 3}, 895 openUnitPort{"exposed-application/0", "tcp", 2}, 896 // Simulate some status with no info, while the agent is down. 897 // Status used to be down, we no longer support said state. 898 // now is one of: pending, started, error. 899 setUnitStatus{"dummy-application/0", status.Terminated, "", nil}, 900 setAgentStatus{"dummy-application/0", status.Idle, "", nil}, 901 902 expect{ 903 what: "add two units, one alive (in error state), one started", 904 output: M{ 905 "model": model, 906 "machines": M{ 907 "0": machine0, 908 "1": machine1, 909 "2": machine2, 910 }, 911 "applications": M{ 912 "exposed-application": dummyCharm(M{ 913 "exposed": true, 914 "application-status": M{ 915 "current": "error", 916 "message": "You Require More Vespene Gas", 917 "since": "01 Apr 15 01:23+10:00", 918 }, 919 "units": M{ 920 "exposed-application/0": M{ 921 "machine": "2", 922 "workload-status": M{ 923 "current": "error", 924 "message": "You Require More Vespene Gas", 925 "since": "01 Apr 15 01:23+10:00", 926 }, 927 "juju-status": M{ 928 "current": "idle", 929 "since": "01 Apr 15 01:23+10:00", 930 }, 931 "open-ports": L{ 932 "2/tcp", "3/tcp", "2/udp", "10/udp", 933 }, 934 "public-address": "10.0.2.1", 935 }, 936 }, 937 }), 938 "dummy-application": dummyCharm(M{ 939 "application-status": M{ 940 "current": "terminated", 941 "since": "01 Apr 15 01:23+10:00", 942 }, 943 "units": M{ 944 "dummy-application/0": M{ 945 "machine": "1", 946 "workload-status": M{ 947 "current": "terminated", 948 "since": "01 Apr 15 01:23+10:00", 949 }, 950 "juju-status": M{ 951 "current": "idle", 952 "since": "01 Apr 15 01:23+10:00", 953 }, 954 "public-address": "10.0.1.1", 955 }, 956 }, 957 }), 958 }, 959 "storage": M{}, 960 "controller": M{ 961 "timestamp": "15:04:05+07:00", 962 }, 963 }, 964 }, 965 966 // step 29 967 addMachine{machineId: "3", job: state.JobHostUnits}, 968 startMachine{"3"}, 969 // Simulate some status with info, while the agent is down. 970 setAddresses{"3", network.NewAddresses("10.0.3.1")}, 971 setMachineStatus{"3", status.Stopped, "Really?"}, 972 addMachine{machineId: "4", job: state.JobHostUnits}, 973 setAddresses{"4", network.NewAddresses("10.0.4.1")}, 974 startAliveMachine{"4", ""}, 975 setMachineStatus{"4", status.Error, "Beware the red toys"}, 976 ensureDyingUnit{"dummy-application/0"}, 977 addMachine{machineId: "5", job: state.JobHostUnits}, 978 ensureDeadMachine{"5"}, 979 expect{ 980 what: "add three more machine, one with a dead agent, one in error state and one dead itself; also one dying unit", 981 output: M{ 982 "model": model, 983 "machines": M{ 984 "0": machine0, 985 "1": machine1, 986 "2": machine2, 987 "3": M{ 988 "dns-name": "10.0.3.1", 989 "ip-addresses": []string{"10.0.3.1"}, 990 "instance-id": "controller-3", 991 "machine-status": M{ 992 "current": "pending", 993 "since": "01 Apr 15 01:23+10:00", 994 }, 995 "juju-status": M{ 996 "current": "stopped", 997 "message": "Really?", 998 "since": "01 Apr 15 01:23+10:00", 999 }, 1000 "series": "quantal", 1001 "network-interfaces": M{ 1002 "eth0": M{ 1003 "ip-addresses": []string{"10.0.3.1"}, 1004 "mac-address": "aa:bb:cc:dd:ee:ff", 1005 "is-up": true, 1006 }, 1007 }, 1008 "hardware": "arch=amd64 cores=1 mem=1024M root-disk=8192M", 1009 }, 1010 "4": M{ 1011 "dns-name": "10.0.4.1", 1012 "ip-addresses": []string{"10.0.4.1"}, 1013 "instance-id": "controller-4", 1014 "machine-status": M{ 1015 "current": "pending", 1016 "since": "01 Apr 15 01:23+10:00", 1017 }, 1018 "juju-status": M{ 1019 "current": "error", 1020 "message": "Beware the red toys", 1021 "since": "01 Apr 15 01:23+10:00", 1022 }, 1023 "series": "quantal", 1024 "network-interfaces": M{ 1025 "eth0": M{ 1026 "ip-addresses": []string{"10.0.4.1"}, 1027 "mac-address": "aa:bb:cc:dd:ee:ff", 1028 "is-up": true, 1029 }, 1030 }, 1031 "hardware": "arch=amd64 cores=1 mem=1024M root-disk=8192M", 1032 }, 1033 "5": M{ 1034 "juju-status": M{ 1035 "current": "pending", 1036 "since": "01 Apr 15 01:23+10:00", 1037 "life": "dead", 1038 }, 1039 "instance-id": "pending", 1040 "machine-status": M{ 1041 "current": "pending", 1042 "since": "01 Apr 15 01:23+10:00", 1043 }, 1044 "series": "quantal", 1045 }, 1046 }, 1047 "applications": M{ 1048 "exposed-application": dummyCharm(M{ 1049 "exposed": true, 1050 "application-status": M{ 1051 "current": "error", 1052 "message": "You Require More Vespene Gas", 1053 "since": "01 Apr 15 01:23+10:00", 1054 }, 1055 "units": M{ 1056 "exposed-application/0": M{ 1057 "machine": "2", 1058 "workload-status": M{ 1059 "current": "error", 1060 "message": "You Require More Vespene Gas", 1061 "since": "01 Apr 15 01:23+10:00", 1062 }, 1063 "juju-status": M{ 1064 "current": "idle", 1065 "since": "01 Apr 15 01:23+10:00", 1066 }, 1067 "open-ports": L{ 1068 "2/tcp", "3/tcp", "2/udp", "10/udp", 1069 }, 1070 "public-address": "10.0.2.1", 1071 }, 1072 }, 1073 }), 1074 "dummy-application": dummyCharm(M{ 1075 "application-status": M{ 1076 "current": "terminated", 1077 "since": "01 Apr 15 01:23+10:00", 1078 }, 1079 "units": M{ 1080 "dummy-application/0": M{ 1081 "machine": "1", 1082 "workload-status": M{ 1083 "current": "terminated", 1084 "since": "01 Apr 15 01:23+10:00", 1085 }, 1086 "juju-status": M{ 1087 "current": "idle", 1088 "since": "01 Apr 15 01:23+10:00", 1089 }, 1090 "public-address": "10.0.1.1", 1091 }, 1092 }, 1093 }), 1094 }, 1095 "storage": M{}, 1096 "controller": M{ 1097 "timestamp": "15:04:05+07:00", 1098 }, 1099 }, 1100 }, 1101 1102 // step 41 1103 scopedExpect{ 1104 what: "scope status on dummy-application/0 unit", 1105 scope: []string{"dummy-application/0"}, 1106 output: M{ 1107 "model": model, 1108 "machines": M{ 1109 "1": machine1, 1110 }, 1111 "applications": M{ 1112 "dummy-application": dummyCharm(M{ 1113 "application-status": M{ 1114 "current": "terminated", 1115 "since": "01 Apr 15 01:23+10:00", 1116 }, 1117 "units": M{ 1118 "dummy-application/0": M{ 1119 "machine": "1", 1120 "workload-status": M{ 1121 "current": "terminated", 1122 "since": "01 Apr 15 01:23+10:00", 1123 }, 1124 "juju-status": M{ 1125 "current": "idle", 1126 "since": "01 Apr 15 01:23+10:00", 1127 }, 1128 "public-address": "10.0.1.1", 1129 }, 1130 }, 1131 }), 1132 }, 1133 "storage": M{}, 1134 "controller": M{ 1135 "timestamp": "15:04:05+07:00", 1136 }, 1137 }, 1138 }, 1139 scopedExpect{ 1140 what: "scope status on exposed-application application", 1141 scope: []string{"exposed-application"}, 1142 output: M{ 1143 "model": model, 1144 "machines": M{ 1145 "2": machine2, 1146 }, 1147 "applications": M{ 1148 "exposed-application": dummyCharm(M{ 1149 "exposed": true, 1150 "application-status": M{ 1151 "current": "error", 1152 "message": "You Require More Vespene Gas", 1153 "since": "01 Apr 15 01:23+10:00", 1154 }, 1155 "units": M{ 1156 "exposed-application/0": M{ 1157 "machine": "2", 1158 "workload-status": M{ 1159 "current": "error", 1160 "message": "You Require More Vespene Gas", 1161 "since": "01 Apr 15 01:23+10:00", 1162 }, 1163 "juju-status": M{ 1164 "current": "idle", 1165 "since": "01 Apr 15 01:23+10:00", 1166 }, 1167 "open-ports": L{ 1168 "2/tcp", "3/tcp", "2/udp", "10/udp", 1169 }, 1170 "public-address": "10.0.2.1", 1171 }, 1172 }, 1173 }), 1174 }, 1175 "storage": M{}, 1176 "controller": M{ 1177 "timestamp": "15:04:05+07:00", 1178 }, 1179 }, 1180 }, 1181 scopedExpect{ 1182 what: "scope status on application pattern", 1183 scope: []string{"d*-application"}, 1184 output: M{ 1185 "model": model, 1186 "machines": M{ 1187 "1": machine1, 1188 }, 1189 "applications": M{ 1190 "dummy-application": dummyCharm(M{ 1191 "application-status": M{ 1192 "current": "terminated", 1193 "since": "01 Apr 15 01:23+10:00", 1194 }, 1195 "units": M{ 1196 "dummy-application/0": M{ 1197 "machine": "1", 1198 "workload-status": M{ 1199 "current": "terminated", 1200 "since": "01 Apr 15 01:23+10:00", 1201 }, 1202 "juju-status": M{ 1203 "current": "idle", 1204 "since": "01 Apr 15 01:23+10:00", 1205 }, 1206 "public-address": "10.0.1.1", 1207 }, 1208 }, 1209 }), 1210 }, 1211 "storage": M{}, 1212 "controller": M{ 1213 "timestamp": "15:04:05+07:00", 1214 }, 1215 }, 1216 }, 1217 scopedExpect{ 1218 what: "scope status on unit pattern", 1219 scope: []string{"e*posed-application/*"}, 1220 output: M{ 1221 "model": model, 1222 "machines": M{ 1223 "2": machine2, 1224 }, 1225 "applications": M{ 1226 "exposed-application": dummyCharm(M{ 1227 "exposed": true, 1228 "application-status": M{ 1229 "current": "error", 1230 "message": "You Require More Vespene Gas", 1231 "since": "01 Apr 15 01:23+10:00", 1232 }, 1233 "units": M{ 1234 "exposed-application/0": M{ 1235 "machine": "2", 1236 "workload-status": M{ 1237 "current": "error", 1238 "message": "You Require More Vespene Gas", 1239 "since": "01 Apr 15 01:23+10:00", 1240 }, 1241 "juju-status": M{ 1242 "current": "idle", 1243 "since": "01 Apr 15 01:23+10:00", 1244 }, 1245 "open-ports": L{ 1246 "2/tcp", "3/tcp", "2/udp", "10/udp", 1247 }, 1248 "public-address": "10.0.2.1", 1249 }, 1250 }, 1251 }), 1252 }, 1253 "storage": M{}, 1254 "controller": M{ 1255 "timestamp": "15:04:05+07:00", 1256 }, 1257 }, 1258 }, 1259 scopedExpect{ 1260 what: "scope status on combination of application and unit patterns", 1261 scope: []string{"exposed-application", "dummy-application", "e*posed-application/*", "dummy-application/*"}, 1262 output: M{ 1263 "model": model, 1264 "machines": M{ 1265 "1": machine1, 1266 "2": machine2, 1267 }, 1268 "applications": M{ 1269 "dummy-application": dummyCharm(M{ 1270 "application-status": M{ 1271 "current": "terminated", 1272 "since": "01 Apr 15 01:23+10:00", 1273 }, 1274 "units": M{ 1275 "dummy-application/0": M{ 1276 "machine": "1", 1277 "workload-status": M{ 1278 "current": "terminated", 1279 "since": "01 Apr 15 01:23+10:00", 1280 }, 1281 "juju-status": M{ 1282 "current": "idle", 1283 "since": "01 Apr 15 01:23+10:00", 1284 }, 1285 "public-address": "10.0.1.1", 1286 }, 1287 }, 1288 }), 1289 "exposed-application": dummyCharm(M{ 1290 "exposed": true, 1291 "application-status": M{ 1292 "current": "error", 1293 "message": "You Require More Vespene Gas", 1294 "since": "01 Apr 15 01:23+10:00", 1295 }, 1296 "units": M{ 1297 "exposed-application/0": M{ 1298 "machine": "2", 1299 "workload-status": M{ 1300 "current": "error", 1301 "message": "You Require More Vespene Gas", 1302 "since": "01 Apr 15 01:23+10:00", 1303 }, 1304 "juju-status": M{ 1305 "current": "idle", 1306 "since": "01 Apr 15 01:23+10:00", 1307 }, 1308 "open-ports": L{ 1309 "2/tcp", "3/tcp", "2/udp", "10/udp", 1310 }, 1311 "public-address": "10.0.2.1", 1312 }, 1313 }, 1314 }), 1315 }, 1316 "storage": M{}, 1317 "controller": M{ 1318 "timestamp": "15:04:05+07:00", 1319 }, 1320 }, 1321 }, 1322 ), 1323 test( // 5 1324 "a unit with a hook relation error", 1325 addMachine{machineId: "0", job: state.JobManageModel}, 1326 setAddresses{"0", network.NewAddresses("10.0.0.1")}, 1327 startAliveMachine{"0", ""}, 1328 setMachineStatus{"0", status.Started, ""}, 1329 1330 addMachine{machineId: "1", job: state.JobHostUnits}, 1331 setAddresses{"1", network.NewAddresses("10.0.1.1")}, 1332 startAliveMachine{"1", ""}, 1333 setMachineStatus{"1", status.Started, ""}, 1334 1335 addCharm{"wordpress"}, 1336 addApplication{name: "wordpress", charm: "wordpress"}, 1337 addAliveUnit{"wordpress", "1"}, 1338 1339 addCharm{"mysql"}, 1340 addApplication{name: "mysql", charm: "mysql"}, 1341 addAliveUnit{"mysql", "1"}, 1342 1343 relateApplications{"wordpress", "mysql", ""}, 1344 1345 setAgentStatus{"wordpress/0", status.Error, 1346 "hook failed: some-relation-changed", 1347 map[string]interface{}{"relation-id": 0}}, 1348 1349 expect{ 1350 what: "a unit with a hook relation error", 1351 output: M{ 1352 "model": model, 1353 "machines": M{ 1354 "0": machine0, 1355 "1": machine1, 1356 }, 1357 "applications": M{ 1358 "wordpress": wordpressCharm(M{ 1359 "relations": M{ 1360 "db": L{"mysql"}, 1361 }, 1362 "application-status": M{ 1363 "current": "error", 1364 "message": "hook failed: some-relation-changed", 1365 "since": "01 Apr 15 01:23+10:00", 1366 }, 1367 "units": M{ 1368 "wordpress/0": M{ 1369 "machine": "1", 1370 "workload-status": M{ 1371 "current": "error", 1372 "message": "hook failed: some-relation-changed for mysql:server", 1373 "since": "01 Apr 15 01:23+10:00", 1374 }, 1375 "juju-status": M{ 1376 "current": "idle", 1377 "since": "01 Apr 15 01:23+10:00", 1378 }, 1379 "public-address": "10.0.1.1", 1380 }, 1381 }, 1382 "endpoint-bindings": M{ 1383 "logging-dir": "", 1384 "monitoring-port": "", 1385 "url": "", 1386 "admin-api": "", 1387 "cache": "", 1388 "db": "", 1389 "db-client": "", 1390 "foo-bar": "", 1391 }, 1392 }), 1393 "mysql": mysqlCharm(M{ 1394 "relations": M{ 1395 "server": L{"wordpress"}, 1396 }, 1397 "application-status": M{ 1398 "current": "waiting", 1399 "message": "waiting for machine", 1400 "since": "01 Apr 15 01:23+10:00", 1401 }, 1402 "units": M{ 1403 "mysql/0": M{ 1404 "machine": "1", 1405 "workload-status": M{ 1406 "current": "waiting", 1407 "message": "waiting for machine", 1408 "since": "01 Apr 15 01:23+10:00", 1409 }, 1410 "juju-status": M{ 1411 "current": "allocating", 1412 "since": "01 Apr 15 01:23+10:00", 1413 }, 1414 "public-address": "10.0.1.1", 1415 }, 1416 }, 1417 "endpoint-bindings": M{ 1418 "server": "", 1419 "server-admin": "", 1420 "metrics-client": "", 1421 }, 1422 }), 1423 }, 1424 "storage": M{}, 1425 "controller": M{ 1426 "timestamp": "15:04:05+07:00", 1427 }, 1428 }, 1429 }, 1430 ), 1431 test( // 6 1432 "a unit with a hook relation error when the agent is down", 1433 addMachine{machineId: "0", job: state.JobManageModel}, 1434 setAddresses{"0", network.NewAddresses("10.0.0.1")}, 1435 startAliveMachine{"0", ""}, 1436 setMachineStatus{"0", status.Started, ""}, 1437 1438 addMachine{machineId: "1", job: state.JobHostUnits}, 1439 setAddresses{"1", network.NewAddresses("10.0.1.1")}, 1440 startAliveMachine{"1", ""}, 1441 setMachineStatus{"1", status.Started, ""}, 1442 1443 addCharm{"wordpress"}, 1444 addApplication{name: "wordpress", charm: "wordpress"}, 1445 addAliveUnit{"wordpress", "1"}, 1446 1447 addCharm{"mysql"}, 1448 addApplication{name: "mysql", charm: "mysql"}, 1449 addAliveUnit{"mysql", "1"}, 1450 1451 relateApplications{"wordpress", "mysql", ""}, 1452 1453 setAgentStatus{"wordpress/0", status.Error, 1454 "hook failed: some-relation-changed", 1455 map[string]interface{}{"relation-id": 0}}, 1456 1457 expect{ 1458 what: "a unit with a hook relation error when the agent is down", 1459 output: M{ 1460 "model": model, 1461 "machines": M{ 1462 "0": machine0, 1463 "1": machine1, 1464 }, 1465 "applications": M{ 1466 "wordpress": wordpressCharm(M{ 1467 "relations": M{ 1468 "db": L{"mysql"}, 1469 }, 1470 "application-status": M{ 1471 "current": "error", 1472 "message": "hook failed: some-relation-changed", 1473 "since": "01 Apr 15 01:23+10:00", 1474 }, 1475 "units": M{ 1476 "wordpress/0": M{ 1477 "machine": "1", 1478 "workload-status": M{ 1479 "current": "error", 1480 "message": "hook failed: some-relation-changed for mysql:server", 1481 "since": "01 Apr 15 01:23+10:00", 1482 }, 1483 "juju-status": M{ 1484 "current": "idle", 1485 "since": "01 Apr 15 01:23+10:00", 1486 }, 1487 "public-address": "10.0.1.1", 1488 }, 1489 }, 1490 "endpoint-bindings": M{ 1491 "admin-api": "", 1492 "cache": "", 1493 "db": "", 1494 "db-client": "", 1495 "foo-bar": "", 1496 "logging-dir": "", 1497 "monitoring-port": "", 1498 "url": "", 1499 }, 1500 }), 1501 "mysql": mysqlCharm(M{ 1502 "relations": M{ 1503 "server": L{"wordpress"}, 1504 }, 1505 "application-status": M{ 1506 "current": "waiting", 1507 "message": "waiting for machine", 1508 "since": "01 Apr 15 01:23+10:00", 1509 }, 1510 "units": M{ 1511 "mysql/0": M{ 1512 "machine": "1", 1513 "workload-status": M{ 1514 "current": "waiting", 1515 "message": "waiting for machine", 1516 "since": "01 Apr 15 01:23+10:00", 1517 }, 1518 "juju-status": M{ 1519 "current": "allocating", 1520 "since": "01 Apr 15 01:23+10:00", 1521 }, 1522 "public-address": "10.0.1.1", 1523 }, 1524 }, 1525 "endpoint-bindings": M{ 1526 "server": "", 1527 "server-admin": "", 1528 "metrics-client": "", 1529 }, 1530 }), 1531 }, 1532 "storage": M{}, 1533 "controller": M{ 1534 "timestamp": "15:04:05+07:00", 1535 }, 1536 }, 1537 }, 1538 ), 1539 test( // 7 1540 "add a dying application", 1541 addCharm{"dummy"}, 1542 addApplication{name: "dummy-application", charm: "dummy"}, 1543 addMachine{machineId: "0", job: state.JobHostUnits}, 1544 addAliveUnit{"dummy-application", "0"}, 1545 ensureDyingApplication{"dummy-application"}, 1546 expect{ 1547 what: "application shows life==dying", 1548 output: M{ 1549 "model": model, 1550 "machines": M{ 1551 "0": M{ 1552 "juju-status": M{ 1553 "current": "pending", 1554 "since": "01 Apr 15 01:23+10:00", 1555 }, 1556 "instance-id": "pending", 1557 "machine-status": M{ 1558 "current": "pending", 1559 "since": "01 Apr 15 01:23+10:00", 1560 }, 1561 1562 "series": "quantal", 1563 }, 1564 }, 1565 "applications": M{ 1566 "dummy-application": dummyCharm(M{ 1567 "life": "dying", 1568 "application-status": M{ 1569 "current": "waiting", 1570 "message": "waiting for machine", 1571 "since": "01 Apr 15 01:23+10:00", 1572 }, 1573 "units": M{ 1574 "dummy-application/0": M{ 1575 "machine": "0", 1576 "workload-status": M{ 1577 "current": "waiting", 1578 "message": "waiting for machine", 1579 "since": "01 Apr 15 01:23+10:00", 1580 }, 1581 "juju-status": M{ 1582 "current": "allocating", 1583 "since": "01 Apr 15 01:23+10:00", 1584 }, 1585 }, 1586 }, 1587 }), 1588 }, 1589 "storage": M{}, 1590 "controller": M{ 1591 "timestamp": "15:04:05+07:00", 1592 }, 1593 }, 1594 }, 1595 ), 1596 test( // 8 1597 "a unit where the agent is down shows as lost", 1598 addCharm{"dummy"}, 1599 addApplication{name: "dummy-application", charm: "dummy"}, 1600 addMachine{machineId: "0", job: state.JobHostUnits}, 1601 startAliveMachine{"0", ""}, 1602 setMachineStatus{"0", status.Started, ""}, 1603 addUnit{"dummy-application", "0"}, 1604 setAgentStatus{"dummy-application/0", status.Idle, "", nil}, 1605 setUnitStatus{"dummy-application/0", status.Active, "", nil}, 1606 expect{ 1607 what: "unit shows that agent is lost", 1608 output: M{ 1609 "model": model, 1610 "machines": M{ 1611 "0": M{ 1612 "juju-status": M{ 1613 "current": "started", 1614 "since": "01 Apr 15 01:23+10:00", 1615 }, 1616 "instance-id": "controller-0", 1617 "machine-status": M{ 1618 "current": "pending", 1619 "since": "01 Apr 15 01:23+10:00", 1620 }, 1621 1622 "series": "quantal", 1623 "hardware": "arch=amd64 cores=1 mem=1024M root-disk=8192M", 1624 }, 1625 }, 1626 "applications": M{ 1627 "dummy-application": dummyCharm(M{ 1628 "application-status": M{ 1629 "current": "active", 1630 "since": "01 Apr 15 01:23+10:00", 1631 }, 1632 "units": M{ 1633 "dummy-application/0": M{ 1634 "machine": "0", 1635 "workload-status": M{ 1636 "current": "unknown", 1637 "message": "agent lost, see 'juju show-status-log dummy-application/0'", 1638 "since": "01 Apr 15 01:23+10:00", 1639 }, 1640 "juju-status": M{ 1641 "current": "lost", 1642 "message": "agent is not communicating with the server", 1643 "since": "01 Apr 15 01:23+10:00", 1644 }, 1645 }, 1646 }, 1647 }), 1648 }, 1649 "storage": M{}, 1650 "controller": M{ 1651 "timestamp": "15:04:05+07:00", 1652 }, 1653 }, 1654 }, 1655 ), 1656 1657 // Relation tests 1658 test( // 9 1659 "complex scenario with multiple related applications", 1660 addMachine{machineId: "0", job: state.JobManageModel}, 1661 setAddresses{"0", network.NewAddresses("10.0.0.1")}, 1662 startAliveMachine{"0", ""}, 1663 setMachineStatus{"0", status.Started, ""}, 1664 addCharm{"wordpress"}, 1665 addCharm{"mysql"}, 1666 addCharm{"varnish"}, 1667 1668 addApplication{name: "project", charm: "wordpress"}, 1669 setApplicationExposed{"project", true}, 1670 addMachine{machineId: "1", job: state.JobHostUnits}, 1671 setAddresses{"1", network.NewAddresses("10.0.1.1")}, 1672 startAliveMachine{"1", ""}, 1673 setMachineStatus{"1", status.Started, ""}, 1674 addAliveUnit{"project", "1"}, 1675 setAgentStatus{"project/0", status.Idle, "", nil}, 1676 setUnitStatus{"project/0", status.Active, "", nil}, 1677 1678 addApplication{name: "mysql", charm: "mysql"}, 1679 setApplicationExposed{"mysql", true}, 1680 addMachine{machineId: "2", job: state.JobHostUnits}, 1681 setAddresses{"2", network.NewAddresses("10.0.2.1")}, 1682 startAliveMachine{"2", ""}, 1683 setMachineStatus{"2", status.Started, ""}, 1684 addAliveUnit{"mysql", "2"}, 1685 setAgentStatus{"mysql/0", status.Idle, "", nil}, 1686 setUnitStatus{"mysql/0", status.Active, "", nil}, 1687 1688 addApplication{name: "varnish", charm: "varnish"}, 1689 setApplicationExposed{"varnish", true}, 1690 addMachine{machineId: "3", job: state.JobHostUnits}, 1691 setAddresses{"3", network.NewAddresses("10.0.3.1")}, 1692 startAliveMachine{"3", ""}, 1693 setMachineStatus{"3", status.Started, ""}, 1694 setMachineInstanceStatus{"3", status.Started, "I am number three"}, 1695 addAliveUnit{"varnish", "3"}, 1696 1697 addApplication{name: "private", charm: "wordpress"}, 1698 setApplicationExposed{"private", true}, 1699 addMachine{machineId: "4", job: state.JobHostUnits}, 1700 setAddresses{"4", network.NewAddresses("10.0.4.1")}, 1701 startAliveMachine{"4", ""}, 1702 setMachineStatus{"4", status.Started, ""}, 1703 addAliveUnit{"private", "4"}, 1704 1705 relateApplications{"project", "mysql", ""}, 1706 relateApplications{"project", "varnish", ""}, 1707 relateApplications{"private", "mysql", ""}, 1708 1709 expect{ 1710 what: "multiples applications with relations between some of them", 1711 output: M{ 1712 "model": model, 1713 "machines": M{ 1714 "0": machine0, 1715 "1": machine1, 1716 "2": machine2, 1717 "3": machine3, 1718 "4": machine4, 1719 }, 1720 "applications": M{ 1721 "project": wordpressCharm(M{ 1722 "exposed": true, 1723 "application-status": M{ 1724 "current": "active", 1725 "since": "01 Apr 15 01:23+10:00", 1726 }, 1727 "units": M{ 1728 "project/0": M{ 1729 "machine": "1", 1730 "workload-status": M{ 1731 "current": "active", 1732 "since": "01 Apr 15 01:23+10:00", 1733 }, 1734 "juju-status": M{ 1735 "current": "idle", 1736 "since": "01 Apr 15 01:23+10:00", 1737 }, 1738 "public-address": "10.0.1.1", 1739 }, 1740 }, 1741 "endpoint-bindings": M{ 1742 "db": "", 1743 "db-client": "", 1744 "foo-bar": "", 1745 "logging-dir": "", 1746 "monitoring-port": "", 1747 "url": "", 1748 "admin-api": "", 1749 "cache": "", 1750 }, 1751 "relations": M{ 1752 "db": L{"mysql"}, 1753 "cache": L{"varnish"}, 1754 }, 1755 }), 1756 "mysql": mysqlCharm(M{ 1757 "exposed": true, 1758 "application-status": M{ 1759 "current": "active", 1760 "since": "01 Apr 15 01:23+10:00", 1761 }, 1762 "units": M{ 1763 "mysql/0": M{ 1764 "machine": "2", 1765 "workload-status": M{ 1766 "current": "active", 1767 "since": "01 Apr 15 01:23+10:00", 1768 }, 1769 "juju-status": M{ 1770 "current": "idle", 1771 "since": "01 Apr 15 01:23+10:00", 1772 }, 1773 "public-address": "10.0.2.1", 1774 }, 1775 }, 1776 "endpoint-bindings": M{ 1777 "server": "", 1778 "server-admin": "", 1779 "metrics-client": "", 1780 }, 1781 "relations": M{ 1782 "server": L{"private", "project"}, 1783 }, 1784 }), 1785 "varnish": M{ 1786 "charm": "cs:quantal/varnish-1", 1787 "charm-origin": "jujucharms", 1788 "charm-name": "varnish", 1789 "charm-rev": 1, 1790 "series": "quantal", 1791 "os": "ubuntu", 1792 "exposed": true, 1793 "application-status": M{ 1794 "current": "waiting", 1795 "message": "waiting for machine", 1796 "since": "01 Apr 15 01:23+10:00", 1797 }, 1798 "units": M{ 1799 "varnish/0": M{ 1800 "machine": "3", 1801 "workload-status": M{ 1802 "current": "waiting", 1803 "message": "waiting for machine", 1804 "since": "01 Apr 15 01:23+10:00", 1805 }, 1806 "juju-status": M{ 1807 "current": "allocating", 1808 "since": "01 Apr 15 01:23+10:00", 1809 }, 1810 "public-address": "10.0.3.1", 1811 }, 1812 }, 1813 "endpoint-bindings": M{ 1814 "webcache": "", 1815 }, 1816 "relations": M{ 1817 "webcache": L{"project"}, 1818 }, 1819 }, 1820 "private": wordpressCharm(M{ 1821 "exposed": true, 1822 "application-status": M{ 1823 "current": "waiting", 1824 "message": "waiting for machine", 1825 "since": "01 Apr 15 01:23+10:00", 1826 }, 1827 "units": M{ 1828 "private/0": M{ 1829 "machine": "4", 1830 "workload-status": M{ 1831 "current": "waiting", 1832 "message": "waiting for machine", 1833 "since": "01 Apr 15 01:23+10:00", 1834 }, 1835 "juju-status": M{ 1836 "current": "allocating", 1837 "since": "01 Apr 15 01:23+10:00", 1838 }, 1839 "public-address": "10.0.4.1", 1840 }, 1841 }, 1842 "endpoint-bindings": M{ 1843 "logging-dir": "", 1844 "monitoring-port": "", 1845 "url": "", 1846 "admin-api": "", 1847 "cache": "", 1848 "db": "", 1849 "db-client": "", 1850 "foo-bar": "", 1851 }, 1852 "relations": M{ 1853 "db": L{"mysql"}, 1854 }, 1855 }), 1856 }, 1857 "storage": M{}, 1858 "controller": M{ 1859 "timestamp": "15:04:05+07:00", 1860 }, 1861 }, 1862 }, 1863 ), 1864 test( // 10 1865 "simple peer scenario with leader", 1866 addMachine{machineId: "0", job: state.JobManageModel}, 1867 setAddresses{"0", network.NewAddresses("10.0.0.1")}, 1868 startAliveMachine{"0", ""}, 1869 setMachineStatus{"0", status.Started, ""}, 1870 addCharm{"riak"}, 1871 addCharm{"wordpress"}, 1872 1873 addApplication{name: "riak", charm: "riak"}, 1874 setApplicationExposed{"riak", true}, 1875 addMachine{machineId: "1", job: state.JobHostUnits}, 1876 setAddresses{"1", network.NewAddresses("10.0.1.1")}, 1877 startAliveMachine{"1", ""}, 1878 setMachineStatus{"1", status.Started, ""}, 1879 addAliveUnit{"riak", "1"}, 1880 setAgentStatus{"riak/0", status.Idle, "", nil}, 1881 setUnitStatus{"riak/0", status.Active, "", nil}, 1882 addMachine{machineId: "2", job: state.JobHostUnits}, 1883 setAddresses{"2", network.NewAddresses("10.0.2.1")}, 1884 startAliveMachine{"2", ""}, 1885 setMachineStatus{"2", status.Started, ""}, 1886 addAliveUnit{"riak", "2"}, 1887 setAgentStatus{"riak/1", status.Idle, "", nil}, 1888 setUnitStatus{"riak/1", status.Active, "", nil}, 1889 addMachine{machineId: "3", job: state.JobHostUnits}, 1890 setAddresses{"3", network.NewAddresses("10.0.3.1")}, 1891 startAliveMachine{"3", ""}, 1892 setMachineStatus{"3", status.Started, ""}, 1893 setMachineInstanceStatus{"3", status.Started, "I am number three"}, 1894 addAliveUnit{"riak", "3"}, 1895 setAgentStatus{"riak/2", status.Idle, "", nil}, 1896 setUnitStatus{"riak/2", status.Active, "", nil}, 1897 setUnitAsLeader{"riak/1"}, 1898 1899 expect{ 1900 what: "multiples related peer units", 1901 output: M{ 1902 "model": model, 1903 "machines": M{ 1904 "0": machine0, 1905 "1": machine1, 1906 "2": machine2, 1907 "3": machine3, 1908 }, 1909 "applications": M{ 1910 "riak": M{ 1911 "charm": "cs:quantal/riak-7", 1912 "charm-origin": "jujucharms", 1913 "charm-name": "riak", 1914 "charm-rev": 7, 1915 "series": "quantal", 1916 "os": "ubuntu", 1917 "exposed": true, 1918 "application-status": M{ 1919 "current": "active", 1920 "since": "01 Apr 15 01:23+10:00", 1921 }, 1922 "units": M{ 1923 "riak/0": M{ 1924 "machine": "1", 1925 "workload-status": M{ 1926 "current": "active", 1927 "since": "01 Apr 15 01:23+10:00", 1928 }, 1929 "juju-status": M{ 1930 "current": "idle", 1931 "since": "01 Apr 15 01:23+10:00", 1932 }, 1933 "public-address": "10.0.1.1", 1934 }, 1935 "riak/1": M{ 1936 "machine": "2", 1937 "workload-status": M{ 1938 "current": "active", 1939 "since": "01 Apr 15 01:23+10:00", 1940 }, 1941 "juju-status": M{ 1942 "current": "idle", 1943 "since": "01 Apr 15 01:23+10:00", 1944 }, 1945 "public-address": "10.0.2.1", 1946 "leader": true, 1947 }, 1948 "riak/2": M{ 1949 "machine": "3", 1950 "workload-status": M{ 1951 "current": "active", 1952 "since": "01 Apr 15 01:23+10:00", 1953 }, 1954 "juju-status": M{ 1955 "current": "idle", 1956 "since": "01 Apr 15 01:23+10:00", 1957 }, 1958 "public-address": "10.0.3.1", 1959 }, 1960 }, 1961 "endpoint-bindings": M{ 1962 "admin": "", 1963 "endpoint": "", 1964 "ring": "", 1965 }, 1966 "relations": M{ 1967 "ring": L{"riak"}, 1968 }, 1969 }, 1970 }, 1971 "storage": M{}, 1972 "controller": M{ 1973 "timestamp": "15:04:05+07:00", 1974 }, 1975 }, 1976 }, 1977 ), 1978 1979 // Subordinate tests 1980 test( // 11 1981 "one application with one subordinate application and leader", 1982 addMachine{machineId: "0", job: state.JobManageModel}, 1983 setAddresses{"0", network.NewAddresses("10.0.0.1")}, 1984 startAliveMachine{"0", ""}, 1985 setMachineStatus{"0", status.Started, ""}, 1986 addCharm{"wordpress"}, 1987 addCharm{"mysql"}, 1988 addCharm{"logging"}, 1989 1990 addApplication{name: "wordpress", charm: "wordpress"}, 1991 setApplicationExposed{"wordpress", true}, 1992 addMachine{machineId: "1", job: state.JobHostUnits}, 1993 setAddresses{"1", network.NewAddresses("10.0.1.1")}, 1994 startAliveMachine{"1", ""}, 1995 setMachineStatus{"1", status.Started, ""}, 1996 addAliveUnit{"wordpress", "1"}, 1997 setAgentStatus{"wordpress/0", status.Idle, "", nil}, 1998 setUnitStatus{"wordpress/0", status.Active, "", nil}, 1999 2000 addApplication{name: "mysql", charm: "mysql"}, 2001 setApplicationExposed{"mysql", true}, 2002 addMachine{machineId: "2", job: state.JobHostUnits}, 2003 setAddresses{"2", network.NewAddresses("10.0.2.1")}, 2004 startAliveMachine{"2", ""}, 2005 setMachineStatus{"2", status.Started, ""}, 2006 addAliveUnit{"mysql", "2"}, 2007 setAgentStatus{"mysql/0", status.Idle, "", nil}, 2008 setUnitStatus{"mysql/0", status.Active, "", nil}, 2009 2010 addApplication{name: "logging", charm: "logging"}, 2011 setApplicationExposed{"logging", true}, 2012 2013 relateApplications{"wordpress", "mysql", ""}, 2014 relateApplications{"wordpress", "logging", ""}, 2015 relateApplications{"mysql", "logging", ""}, 2016 2017 addSubordinate{"wordpress/0", "logging"}, 2018 addSubordinate{"mysql/0", "logging"}, 2019 2020 setUnitsAlive{"logging"}, 2021 setAgentStatus{"logging/0", status.Idle, "", nil}, 2022 setUnitStatus{"logging/0", status.Active, "", nil}, 2023 setAgentStatus{"logging/1", status.Error, "somehow lost in all those logs", nil}, 2024 2025 setUnitAsLeader{"mysql/0"}, 2026 setUnitAsLeader{"logging/1"}, 2027 setUnitAsLeader{"wordpress/0"}, 2028 2029 expect{ 2030 what: "multiples related peer units", 2031 output: M{ 2032 "model": model, 2033 "machines": M{ 2034 "0": machine0, 2035 "1": machine1, 2036 "2": machine2, 2037 }, 2038 "applications": M{ 2039 "wordpress": wordpressCharm(M{ 2040 "exposed": true, 2041 "application-status": M{ 2042 "current": "active", 2043 "since": "01 Apr 15 01:23+10:00", 2044 }, 2045 "units": M{ 2046 "wordpress/0": M{ 2047 "machine": "1", 2048 "workload-status": M{ 2049 "current": "active", 2050 "since": "01 Apr 15 01:23+10:00", 2051 }, 2052 "juju-status": M{ 2053 "current": "idle", 2054 "since": "01 Apr 15 01:23+10:00", 2055 }, 2056 "subordinates": M{ 2057 "logging/0": M{ 2058 "workload-status": M{ 2059 "current": "active", 2060 "since": "01 Apr 15 01:23+10:00", 2061 }, 2062 "juju-status": M{ 2063 "current": "idle", 2064 "since": "01 Apr 15 01:23+10:00", 2065 }, 2066 "public-address": "10.0.1.1", 2067 }, 2068 }, 2069 "public-address": "10.0.1.1", 2070 "leader": true, 2071 }, 2072 }, 2073 "endpoint-bindings": M{ 2074 "monitoring-port": "", 2075 "url": "", 2076 "admin-api": "", 2077 "cache": "", 2078 "db": "", 2079 "db-client": "", 2080 "foo-bar": "", 2081 "logging-dir": "", 2082 }, 2083 "relations": M{ 2084 "db": L{"mysql"}, 2085 "logging-dir": L{"logging"}, 2086 }, 2087 }), 2088 "mysql": mysqlCharm(M{ 2089 "exposed": true, 2090 "application-status": M{ 2091 "current": "active", 2092 "since": "01 Apr 15 01:23+10:00", 2093 }, 2094 "units": M{ 2095 "mysql/0": M{ 2096 "machine": "2", 2097 "workload-status": M{ 2098 "current": "active", 2099 "since": "01 Apr 15 01:23+10:00", 2100 }, 2101 "juju-status": M{ 2102 "current": "idle", 2103 "since": "01 Apr 15 01:23+10:00", 2104 }, 2105 "subordinates": M{ 2106 "logging/1": M{ 2107 "workload-status": M{ 2108 "current": "error", 2109 "message": "somehow lost in all those logs", 2110 "since": "01 Apr 15 01:23+10:00", 2111 }, 2112 "juju-status": M{ 2113 "current": "idle", 2114 "since": "01 Apr 15 01:23+10:00", 2115 }, 2116 "public-address": "10.0.2.1", 2117 "leader": true, 2118 }, 2119 }, 2120 "public-address": "10.0.2.1", 2121 "leader": true, 2122 }, 2123 }, 2124 "endpoint-bindings": M{ 2125 "server": "", 2126 "server-admin": "", 2127 "metrics-client": "", 2128 }, 2129 "relations": M{ 2130 "server": L{"wordpress"}, 2131 "juju-info": L{"logging"}, 2132 }, 2133 }), 2134 "logging": loggingCharm, 2135 }, 2136 "storage": M{}, 2137 "controller": M{ 2138 "timestamp": "15:04:05+07:00", 2139 }, 2140 }, 2141 }, 2142 2143 // scoped on 'logging' 2144 scopedExpect{ 2145 what: "subordinates scoped on logging", 2146 scope: []string{"logging"}, 2147 output: M{ 2148 "model": model, 2149 "machines": M{ 2150 "1": machine1, 2151 "2": machine2, 2152 }, 2153 "applications": M{ 2154 "wordpress": wordpressCharm(M{ 2155 "exposed": true, 2156 "application-status": M{ 2157 "current": "active", 2158 "since": "01 Apr 15 01:23+10:00", 2159 }, 2160 "units": M{ 2161 "wordpress/0": M{ 2162 "machine": "1", 2163 "workload-status": M{ 2164 "current": "active", 2165 "since": "01 Apr 15 01:23+10:00", 2166 }, 2167 "juju-status": M{ 2168 "current": "idle", 2169 "since": "01 Apr 15 01:23+10:00", 2170 }, 2171 "subordinates": M{ 2172 "logging/0": M{ 2173 "workload-status": M{ 2174 "current": "active", 2175 "since": "01 Apr 15 01:23+10:00", 2176 }, 2177 "juju-status": M{ 2178 "current": "idle", 2179 "since": "01 Apr 15 01:23+10:00", 2180 }, 2181 "public-address": "10.0.1.1", 2182 }, 2183 }, 2184 "public-address": "10.0.1.1", 2185 "leader": true, 2186 }, 2187 }, 2188 "endpoint-bindings": M{ 2189 "monitoring-port": "", 2190 "url": "", 2191 "admin-api": "", 2192 "cache": "", 2193 "db": "", 2194 "db-client": "", 2195 "foo-bar": "", 2196 "logging-dir": "", 2197 }, 2198 "relations": M{ 2199 "db": L{"mysql"}, 2200 "logging-dir": L{"logging"}, 2201 }, 2202 }), 2203 "mysql": mysqlCharm(M{ 2204 "exposed": true, 2205 "application-status": M{ 2206 "current": "active", 2207 "since": "01 Apr 15 01:23+10:00", 2208 }, 2209 "units": M{ 2210 "mysql/0": M{ 2211 "machine": "2", 2212 "workload-status": M{ 2213 "current": "active", 2214 "since": "01 Apr 15 01:23+10:00", 2215 }, 2216 "juju-status": M{ 2217 "current": "idle", 2218 "since": "01 Apr 15 01:23+10:00", 2219 }, 2220 "subordinates": M{ 2221 "logging/1": M{ 2222 "workload-status": M{ 2223 "current": "error", 2224 "message": "somehow lost in all those logs", 2225 "since": "01 Apr 15 01:23+10:00", 2226 }, 2227 "juju-status": M{ 2228 "current": "idle", 2229 "since": "01 Apr 15 01:23+10:00", 2230 }, 2231 "public-address": "10.0.2.1", 2232 "leader": true, 2233 }, 2234 }, 2235 "public-address": "10.0.2.1", 2236 "leader": true, 2237 }, 2238 }, 2239 "endpoint-bindings": M{ 2240 "server": "", 2241 "server-admin": "", 2242 "metrics-client": "", 2243 }, 2244 "relations": M{ 2245 "server": L{"wordpress"}, 2246 "juju-info": L{"logging"}, 2247 }, 2248 }), 2249 "logging": loggingCharm, 2250 }, 2251 "storage": M{}, 2252 "controller": M{ 2253 "timestamp": "15:04:05+07:00", 2254 }, 2255 }, 2256 }, 2257 2258 // scoped on wordpress/0 2259 scopedExpect{ 2260 what: "subordinates scoped on wordpress", 2261 scope: []string{"wordpress/0"}, 2262 output: M{ 2263 "model": model, 2264 "machines": M{ 2265 "1": machine1, 2266 }, 2267 "applications": M{ 2268 "wordpress": wordpressCharm(M{ 2269 "exposed": true, 2270 "application-status": M{ 2271 "current": "active", 2272 "since": "01 Apr 15 01:23+10:00", 2273 }, 2274 "units": M{ 2275 "wordpress/0": M{ 2276 "machine": "1", 2277 "workload-status": M{ 2278 "current": "active", 2279 "since": "01 Apr 15 01:23+10:00", 2280 }, 2281 "juju-status": M{ 2282 "current": "idle", 2283 "since": "01 Apr 15 01:23+10:00", 2284 }, 2285 "subordinates": M{ 2286 "logging/0": M{ 2287 "workload-status": M{ 2288 "current": "active", 2289 "since": "01 Apr 15 01:23+10:00", 2290 }, 2291 "juju-status": M{ 2292 "current": "idle", 2293 "since": "01 Apr 15 01:23+10:00", 2294 }, 2295 "public-address": "10.0.1.1", 2296 }, 2297 }, 2298 "public-address": "10.0.1.1", 2299 "leader": true, 2300 }, 2301 }, 2302 "endpoint-bindings": M{ 2303 "admin-api": "", 2304 "cache": "", 2305 "db": "", 2306 "db-client": "", 2307 "foo-bar": "", 2308 "logging-dir": "", 2309 "monitoring-port": "", 2310 "url": "", 2311 }, 2312 "relations": M{ 2313 "db": L{"mysql"}, 2314 "logging-dir": L{"logging"}, 2315 }, 2316 }), 2317 "logging": loggingCharm, 2318 }, 2319 "storage": M{}, 2320 "controller": M{ 2321 "timestamp": "15:04:05+07:00", 2322 }, 2323 }, 2324 }, 2325 ), 2326 test( // 12 2327 "machines with containers", 2328 // step 0 2329 addMachine{machineId: "0", job: state.JobManageModel}, 2330 setAddresses{"0", network.NewAddresses("10.0.0.1")}, 2331 startAliveMachine{"0", ""}, 2332 setMachineStatus{"0", status.Started, ""}, 2333 addCharm{"mysql"}, 2334 addApplication{name: "mysql", charm: "mysql"}, 2335 setApplicationExposed{"mysql", true}, 2336 2337 // step 7 2338 addMachine{machineId: "1", job: state.JobHostUnits}, 2339 setAddresses{"1", network.NewAddresses("10.0.1.1")}, 2340 startAliveMachine{"1", ""}, 2341 setMachineStatus{"1", status.Started, ""}, 2342 addAliveUnit{"mysql", "1"}, 2343 setAgentStatus{"mysql/0", status.Idle, "", nil}, 2344 setUnitStatus{"mysql/0", status.Active, "", nil}, 2345 2346 // step 14: A container on machine 1. 2347 addContainer{"1", "1/lxd/0", state.JobHostUnits}, 2348 setAddresses{"1/lxd/0", network.NewAddresses("10.0.2.1")}, 2349 startAliveMachine{"1/lxd/0", ""}, 2350 setMachineStatus{"1/lxd/0", status.Started, ""}, 2351 addAliveUnit{"mysql", "1/lxd/0"}, 2352 setAgentStatus{"mysql/1", status.Idle, "", nil}, 2353 setUnitStatus{"mysql/1", status.Active, "", nil}, 2354 addContainer{"1", "1/lxd/1", state.JobHostUnits}, 2355 2356 // step 22: A nested container. 2357 addContainer{"1/lxd/0", "1/lxd/0/lxd/0", state.JobHostUnits}, 2358 setAddresses{"1/lxd/0/lxd/0", network.NewAddresses("10.0.3.1")}, 2359 startAliveMachine{"1/lxd/0/lxd/0", ""}, 2360 setMachineStatus{"1/lxd/0/lxd/0", status.Started, ""}, 2361 2362 expect{ 2363 what: "machines with nested containers", 2364 output: M{ 2365 "model": model, 2366 "machines": M{ 2367 "0": machine0, 2368 "1": machine1WithContainers, 2369 }, 2370 "applications": M{ 2371 "mysql": mysqlCharm(M{ 2372 "exposed": true, 2373 "application-status": M{ 2374 "current": "active", 2375 "since": "01 Apr 15 01:23+10:00", 2376 }, 2377 "units": M{ 2378 "mysql/0": M{ 2379 "machine": "1", 2380 "workload-status": M{ 2381 "current": "active", 2382 "since": "01 Apr 15 01:23+10:00", 2383 }, 2384 "juju-status": M{ 2385 "current": "idle", 2386 "since": "01 Apr 15 01:23+10:00", 2387 }, 2388 "public-address": "10.0.1.1", 2389 }, 2390 "mysql/1": M{ 2391 "machine": "1/lxd/0", 2392 "workload-status": M{ 2393 "current": "active", 2394 "since": "01 Apr 15 01:23+10:00", 2395 }, 2396 "juju-status": M{ 2397 "current": "idle", 2398 "since": "01 Apr 15 01:23+10:00", 2399 }, 2400 "public-address": "10.0.2.1", 2401 }, 2402 }, 2403 "endpoint-bindings": M{ 2404 "server": "", 2405 "server-admin": "", 2406 "metrics-client": "", 2407 }, 2408 }), 2409 }, 2410 "storage": M{}, 2411 "controller": M{ 2412 "timestamp": "15:04:05+07:00", 2413 }, 2414 }, 2415 }, 2416 2417 // step 27: once again, with a scope on mysql/1 2418 scopedExpect{ 2419 what: "machines with nested containers 2", 2420 scope: []string{"mysql/1"}, 2421 output: M{ 2422 "model": model, 2423 "machines": M{ 2424 "1": M{ 2425 "juju-status": M{ 2426 "current": "started", 2427 "since": "01 Apr 15 01:23+10:00", 2428 }, 2429 "containers": M{ 2430 "1/lxd/0": M{ 2431 "juju-status": M{ 2432 "current": "started", 2433 "since": "01 Apr 15 01:23+10:00", 2434 }, 2435 "dns-name": "10.0.2.1", 2436 "ip-addresses": []string{"10.0.2.1"}, 2437 "instance-id": "controller-2", 2438 "machine-status": M{ 2439 "current": "pending", 2440 "since": "01 Apr 15 01:23+10:00", 2441 }, 2442 2443 "series": "quantal", 2444 "network-interfaces": M{ 2445 "eth0": M{ 2446 "ip-addresses": []string{"10.0.2.1"}, 2447 "mac-address": "aa:bb:cc:dd:ee:ff", 2448 "is-up": true, 2449 }, 2450 }, 2451 }, 2452 }, 2453 "dns-name": "10.0.1.1", 2454 "ip-addresses": []string{"10.0.1.1"}, 2455 "instance-id": "controller-1", 2456 "machine-status": M{ 2457 "current": "pending", 2458 "since": "01 Apr 15 01:23+10:00", 2459 }, 2460 2461 "series": "quantal", 2462 "network-interfaces": M{ 2463 "eth0": M{ 2464 "ip-addresses": []string{"10.0.1.1"}, 2465 "mac-address": "aa:bb:cc:dd:ee:ff", 2466 "is-up": true, 2467 }, 2468 }, 2469 "hardware": "arch=amd64 cores=1 mem=1024M root-disk=8192M", 2470 }, 2471 }, 2472 "applications": M{ 2473 "mysql": mysqlCharm(M{ 2474 "exposed": true, 2475 "application-status": M{ 2476 "current": "active", 2477 "since": "01 Apr 15 01:23+10:00", 2478 }, 2479 "units": M{ 2480 "mysql/1": M{ 2481 "machine": "1/lxd/0", 2482 "workload-status": M{ 2483 "current": "active", 2484 "since": "01 Apr 15 01:23+10:00", 2485 }, 2486 "juju-status": M{ 2487 "current": "idle", 2488 "since": "01 Apr 15 01:23+10:00", 2489 }, 2490 "public-address": "10.0.2.1", 2491 }, 2492 }, 2493 "endpoint-bindings": M{ 2494 "server": "", 2495 "server-admin": "", 2496 "metrics-client": "", 2497 }, 2498 }), 2499 }, 2500 "storage": M{}, 2501 "controller": M{ 2502 "timestamp": "15:04:05+07:00", 2503 }, 2504 }, 2505 }, 2506 ), 2507 test( // 13 2508 "application with out of date charm", 2509 addMachine{machineId: "0", job: state.JobManageModel}, 2510 setAddresses{"0", network.NewAddresses("10.0.0.1")}, 2511 startAliveMachine{"0", ""}, 2512 setMachineStatus{"0", status.Started, ""}, 2513 addMachine{machineId: "1", job: state.JobHostUnits}, 2514 setAddresses{"1", network.NewAddresses("10.0.1.1")}, 2515 startAliveMachine{"1", ""}, 2516 setMachineStatus{"1", status.Started, ""}, 2517 addCharm{"mysql"}, 2518 addApplication{name: "mysql", charm: "mysql"}, 2519 setApplicationExposed{"mysql", true}, 2520 addCharmPlaceholder{"mysql", 23}, 2521 addAliveUnit{"mysql", "1"}, 2522 2523 expect{ 2524 what: "applications and units with correct charm status", 2525 output: M{ 2526 "model": model, 2527 "machines": M{ 2528 "0": machine0, 2529 "1": machine1, 2530 }, 2531 "applications": M{ 2532 "mysql": mysqlCharm(M{ 2533 "can-upgrade-to": "cs:quantal/mysql-23", 2534 "exposed": true, 2535 "application-status": M{ 2536 "current": "waiting", 2537 "message": "waiting for machine", 2538 "since": "01 Apr 15 01:23+10:00", 2539 }, 2540 "units": M{ 2541 "mysql/0": M{ 2542 "machine": "1", 2543 "workload-status": M{ 2544 "current": "waiting", 2545 "message": "waiting for machine", 2546 "since": "01 Apr 15 01:23+10:00", 2547 }, 2548 "juju-status": M{ 2549 "current": "allocating", 2550 "since": "01 Apr 15 01:23+10:00", 2551 }, 2552 "public-address": "10.0.1.1", 2553 }, 2554 }, 2555 "endpoint-bindings": M{ 2556 "server": "", 2557 "server-admin": "", 2558 "metrics-client": "", 2559 }, 2560 }), 2561 }, 2562 "storage": M{}, 2563 "controller": M{ 2564 "timestamp": "15:04:05+07:00", 2565 }, 2566 }, 2567 }, 2568 ), 2569 test( // 14 2570 "unit with out of date charm", 2571 addMachine{machineId: "0", job: state.JobManageModel}, 2572 setAddresses{"0", network.NewAddresses("10.0.0.1")}, 2573 startAliveMachine{"0", ""}, 2574 setMachineStatus{"0", status.Started, ""}, 2575 addMachine{machineId: "1", job: state.JobHostUnits}, 2576 setAddresses{"1", network.NewAddresses("10.0.1.1")}, 2577 startAliveMachine{"1", ""}, 2578 setMachineStatus{"1", status.Started, ""}, 2579 addCharm{"mysql"}, 2580 addApplication{name: "mysql", charm: "mysql"}, 2581 setApplicationExposed{"mysql", true}, 2582 addAliveUnit{"mysql", "1"}, 2583 setUnitCharmURL{"mysql/0", "cs:quantal/mysql-1"}, 2584 addCharmWithRevision{addCharm{"mysql"}, "local", 1}, 2585 setApplicationCharm{"mysql", "local:quantal/mysql-1"}, 2586 2587 expect{ 2588 what: "applications and units with correct charm status", 2589 output: M{ 2590 "model": model, 2591 "machines": M{ 2592 "0": machine0, 2593 "1": machine1, 2594 }, 2595 "applications": M{ 2596 "mysql": mysqlCharm(M{ 2597 "charm": "local:quantal/mysql-1", 2598 "charm-origin": "local", 2599 "exposed": true, 2600 "application-status": M{ 2601 "current": "active", 2602 "since": "01 Apr 15 01:23+10:00", 2603 }, 2604 "units": M{ 2605 "mysql/0": M{ 2606 "machine": "1", 2607 "workload-status": M{ 2608 "current": "active", 2609 "since": "01 Apr 15 01:23+10:00", 2610 }, 2611 "juju-status": M{ 2612 "current": "idle", 2613 "since": "01 Apr 15 01:23+10:00", 2614 }, 2615 "upgrading-from": "cs:quantal/mysql-1", 2616 "public-address": "10.0.1.1", 2617 }, 2618 }, 2619 "endpoint-bindings": M{ 2620 "server": "", 2621 "server-admin": "", 2622 "metrics-client": "", 2623 }, 2624 }), 2625 }, 2626 "storage": M{}, 2627 "controller": M{ 2628 "timestamp": "15:04:05+07:00", 2629 }, 2630 }, 2631 }, 2632 ), 2633 test( // 15 2634 "application and unit with out of date charms", 2635 addMachine{machineId: "0", job: state.JobManageModel}, 2636 setAddresses{"0", network.NewAddresses("10.0.0.1")}, 2637 startAliveMachine{"0", ""}, 2638 setMachineStatus{"0", status.Started, ""}, 2639 addMachine{machineId: "1", job: state.JobHostUnits}, 2640 setAddresses{"1", network.NewAddresses("10.0.1.1")}, 2641 startAliveMachine{"1", ""}, 2642 setMachineStatus{"1", status.Started, ""}, 2643 addCharm{"mysql"}, 2644 addApplication{name: "mysql", charm: "mysql"}, 2645 setApplicationExposed{"mysql", true}, 2646 addAliveUnit{"mysql", "1"}, 2647 setUnitCharmURL{"mysql/0", "cs:quantal/mysql-1"}, 2648 addCharmWithRevision{addCharm{"mysql"}, "cs", 2}, 2649 setApplicationCharm{"mysql", "cs:quantal/mysql-2"}, 2650 addCharmPlaceholder{"mysql", 23}, 2651 2652 expect{ 2653 what: "applications and units with correct charm status", 2654 output: M{ 2655 "model": model, 2656 "machines": M{ 2657 "0": machine0, 2658 "1": machine1, 2659 }, 2660 "applications": M{ 2661 "mysql": mysqlCharm(M{ 2662 "charm": "cs:quantal/mysql-2", 2663 "charm-rev": 2, 2664 "can-upgrade-to": "cs:quantal/mysql-23", 2665 "exposed": true, 2666 "application-status": M{ 2667 "current": "active", 2668 "since": "01 Apr 15 01:23+10:00", 2669 }, 2670 "units": M{ 2671 "mysql/0": M{ 2672 "machine": "1", 2673 "workload-status": M{ 2674 "current": "active", 2675 "since": "01 Apr 15 01:23+10:00", 2676 }, 2677 "juju-status": M{ 2678 "current": "idle", 2679 "since": "01 Apr 15 01:23+10:00", 2680 }, 2681 "upgrading-from": "cs:quantal/mysql-1", 2682 "public-address": "10.0.1.1", 2683 }, 2684 }, 2685 "endpoint-bindings": M{ 2686 "server": "", 2687 "server-admin": "", 2688 "metrics-client": "", 2689 }, 2690 }), 2691 }, 2692 "storage": M{}, 2693 "controller": M{ 2694 "timestamp": "15:04:05+07:00", 2695 }, 2696 }, 2697 }, 2698 ), 2699 test( // 16 2700 "application with local charm not shown as out of date", 2701 addMachine{machineId: "0", job: state.JobManageModel}, 2702 setAddresses{"0", network.NewAddresses("10.0.0.1")}, 2703 startAliveMachine{"0", ""}, 2704 setMachineStatus{"0", status.Started, ""}, 2705 addMachine{machineId: "1", job: state.JobHostUnits}, 2706 setAddresses{"1", network.NewAddresses("10.0.1.1")}, 2707 startAliveMachine{"1", ""}, 2708 setMachineStatus{"1", status.Started, ""}, 2709 addCharm{"mysql"}, 2710 addApplication{name: "mysql", charm: "mysql"}, 2711 setApplicationExposed{"mysql", true}, 2712 addAliveUnit{"mysql", "1"}, 2713 setUnitCharmURL{"mysql/0", "cs:quantal/mysql-1"}, 2714 addCharmWithRevision{addCharm{"mysql"}, "local", 1}, 2715 setApplicationCharm{"mysql", "local:quantal/mysql-1"}, 2716 addCharmPlaceholder{"mysql", 23}, 2717 2718 expect{ 2719 what: "applications and units with correct charm status", 2720 output: M{ 2721 "model": model, 2722 "machines": M{ 2723 "0": machine0, 2724 "1": machine1, 2725 }, 2726 "applications": M{ 2727 "mysql": mysqlCharm(M{ 2728 "charm": "local:quantal/mysql-1", 2729 "charm-origin": "local", 2730 "exposed": true, 2731 "application-status": M{ 2732 "current": "active", 2733 "since": "01 Apr 15 01:23+10:00", 2734 }, 2735 "units": M{ 2736 "mysql/0": M{ 2737 "machine": "1", 2738 "workload-status": M{ 2739 "current": "active", 2740 "since": "01 Apr 15 01:23+10:00", 2741 }, 2742 "juju-status": M{ 2743 "current": "idle", 2744 "since": "01 Apr 15 01:23+10:00", 2745 }, 2746 "upgrading-from": "cs:quantal/mysql-1", 2747 "public-address": "10.0.1.1", 2748 }, 2749 }, 2750 "endpoint-bindings": M{ 2751 "server": "", 2752 "server-admin": "", 2753 "metrics-client": "", 2754 }, 2755 }), 2756 }, 2757 "storage": M{}, 2758 "controller": M{ 2759 "timestamp": "15:04:05+07:00", 2760 }, 2761 }, 2762 }, 2763 ), 2764 test( // 17 2765 "deploy two applications; set meter statuses on one", 2766 addMachine{machineId: "0", job: state.JobManageModel}, 2767 setAddresses{"0", network.NewAddresses("10.0.0.1")}, 2768 startAliveMachine{"0", ""}, 2769 setMachineStatus{"0", status.Started, ""}, 2770 2771 addMachine{machineId: "1", job: state.JobHostUnits}, 2772 setAddresses{"1", network.NewAddresses("10.0.1.1")}, 2773 startAliveMachine{"1", ""}, 2774 setMachineStatus{"1", status.Started, ""}, 2775 2776 addMachine{machineId: "2", job: state.JobHostUnits}, 2777 setAddresses{"2", network.NewAddresses("10.0.2.1")}, 2778 startAliveMachine{"2", ""}, 2779 setMachineStatus{"2", status.Started, ""}, 2780 2781 addMachine{machineId: "3", job: state.JobHostUnits}, 2782 setAddresses{"3", network.NewAddresses("10.0.3.1")}, 2783 startAliveMachine{"3", ""}, 2784 setMachineStatus{"3", status.Started, ""}, 2785 setMachineInstanceStatus{"3", status.Started, "I am number three"}, 2786 2787 addMachine{machineId: "4", job: state.JobHostUnits}, 2788 setAddresses{"4", network.NewAddresses("10.0.4.1")}, 2789 startAliveMachine{"4", ""}, 2790 setMachineStatus{"4", status.Started, ""}, 2791 2792 addCharm{"mysql"}, 2793 addApplication{name: "mysql", charm: "mysql"}, 2794 setApplicationExposed{"mysql", true}, 2795 2796 addCharm{"metered"}, 2797 addApplication{name: "applicationwithmeterstatus", charm: "metered"}, 2798 2799 addAliveUnit{"mysql", "1"}, 2800 addAliveUnit{"applicationwithmeterstatus", "2"}, 2801 addAliveUnit{"applicationwithmeterstatus", "3"}, 2802 addAliveUnit{"applicationwithmeterstatus", "4"}, 2803 2804 setApplicationExposed{"mysql", true}, 2805 2806 setAgentStatus{"mysql/0", status.Idle, "", nil}, 2807 setUnitStatus{"mysql/0", status.Active, "", nil}, 2808 setAgentStatus{"applicationwithmeterstatus/0", status.Idle, "", nil}, 2809 setUnitStatus{"applicationwithmeterstatus/0", status.Active, "", nil}, 2810 setAgentStatus{"applicationwithmeterstatus/1", status.Idle, "", nil}, 2811 setUnitStatus{"applicationwithmeterstatus/1", status.Active, "", nil}, 2812 setAgentStatus{"applicationwithmeterstatus/2", status.Idle, "", nil}, 2813 setUnitStatus{"applicationwithmeterstatus/2", status.Active, "", nil}, 2814 2815 setUnitMeterStatus{"applicationwithmeterstatus/1", "GREEN", "test green status"}, 2816 setUnitMeterStatus{"applicationwithmeterstatus/2", "RED", "test red status"}, 2817 2818 expect{ 2819 what: "simulate just the two applications and a bootstrap node", 2820 output: M{ 2821 "model": model, 2822 "machines": M{ 2823 "0": machine0, 2824 "1": machine1, 2825 "2": machine2, 2826 "3": machine3, 2827 "4": machine4, 2828 }, 2829 "applications": M{ 2830 "mysql": mysqlCharm(M{ 2831 "exposed": true, 2832 "application-status": M{ 2833 "current": "active", 2834 "since": "01 Apr 15 01:23+10:00", 2835 }, 2836 "units": M{ 2837 "mysql/0": M{ 2838 "machine": "1", 2839 "workload-status": M{ 2840 "current": "active", 2841 "since": "01 Apr 15 01:23+10:00", 2842 }, 2843 "juju-status": M{ 2844 "current": "idle", 2845 "since": "01 Apr 15 01:23+10:00", 2846 }, 2847 "public-address": "10.0.1.1", 2848 }, 2849 }, 2850 "endpoint-bindings": M{ 2851 "server": "", 2852 "server-admin": "", 2853 "metrics-client": "", 2854 }, 2855 }), 2856 2857 "applicationwithmeterstatus": meteredCharm(M{ 2858 "application-status": M{ 2859 "current": "active", 2860 "since": "01 Apr 15 01:23+10:00", 2861 }, 2862 "units": M{ 2863 "applicationwithmeterstatus/0": M{ 2864 "machine": "2", 2865 "workload-status": M{ 2866 "current": "active", 2867 "since": "01 Apr 15 01:23+10:00", 2868 }, 2869 "juju-status": M{ 2870 "current": "idle", 2871 "since": "01 Apr 15 01:23+10:00", 2872 }, 2873 "public-address": "10.0.2.1", 2874 }, 2875 "applicationwithmeterstatus/1": M{ 2876 "machine": "3", 2877 "workload-status": M{ 2878 "current": "active", 2879 "since": "01 Apr 15 01:23+10:00", 2880 }, 2881 "juju-status": M{ 2882 "current": "idle", 2883 "since": "01 Apr 15 01:23+10:00", 2884 }, 2885 "meter-status": M{ 2886 "color": "green", 2887 "message": "test green status", 2888 }, 2889 "public-address": "10.0.3.1", 2890 }, 2891 "applicationwithmeterstatus/2": M{ 2892 "machine": "4", 2893 "workload-status": M{ 2894 "current": "active", 2895 "since": "01 Apr 15 01:23+10:00", 2896 }, 2897 "juju-status": M{ 2898 "current": "idle", 2899 "since": "01 Apr 15 01:23+10:00", 2900 }, 2901 "meter-status": M{ 2902 "color": "red", 2903 "message": "test red status", 2904 }, 2905 "public-address": "10.0.4.1", 2906 }, 2907 }, 2908 }), 2909 }, 2910 "storage": M{}, 2911 "controller": M{ 2912 "timestamp": "15:04:05+07:00", 2913 }, 2914 }, 2915 }, 2916 ), 2917 test( // 18 2918 "upgrade available", 2919 setToolsUpgradeAvailable{}, 2920 expect{ 2921 what: "upgrade availability should be shown in model-status", 2922 output: M{ 2923 "model": M{ 2924 "name": "controller", 2925 "type": "iaas", 2926 "controller": "kontroll", 2927 "cloud": "dummy", 2928 "region": "dummy-region", 2929 "version": "1.2.3", 2930 "upgrade-available": "1.2.4", 2931 "model-status": M{ 2932 "current": "available", 2933 "since": "01 Apr 15 01:23+10:00", 2934 }, 2935 "sla": "unsupported", 2936 }, 2937 "machines": M{}, 2938 "applications": M{}, 2939 "storage": M{}, 2940 "controller": M{ 2941 "timestamp": "15:04:05+07:00", 2942 }, 2943 }, 2944 stderr: "Model \"controller\" is empty.\n", 2945 }, 2946 ), 2947 test( // 19 2948 "consistent workload version", 2949 addMachine{machineId: "0", job: state.JobManageModel}, 2950 setAddresses{"0", network.NewAddresses("10.0.0.1")}, 2951 startAliveMachine{"0", ""}, 2952 setMachineStatus{"0", status.Started, ""}, 2953 2954 addCharm{"mysql"}, 2955 addApplication{name: "mysql", charm: "mysql"}, 2956 2957 addMachine{machineId: "1", job: state.JobHostUnits}, 2958 setAddresses{"1", network.NewAddresses("10.0.1.1")}, 2959 startAliveMachine{"1", ""}, 2960 setMachineStatus{"1", status.Started, ""}, 2961 addAliveUnit{"mysql", "1"}, 2962 setUnitWorkloadVersion{"mysql/0", "the best!"}, 2963 2964 expect{ 2965 what: "application and unit with correct workload version", 2966 output: M{ 2967 "model": model, 2968 "machines": M{ 2969 "0": machine0, 2970 "1": machine1, 2971 }, 2972 "applications": M{ 2973 "mysql": mysqlCharm(M{ 2974 "version": "the best!", 2975 "application-status": M{ 2976 "current": "waiting", 2977 "message": "waiting for machine", 2978 "since": "01 Apr 15 01:23+10:00", 2979 }, 2980 "units": M{ 2981 "mysql/0": M{ 2982 "machine": "1", 2983 "workload-status": M{ 2984 "current": "waiting", 2985 "message": "waiting for machine", 2986 "since": "01 Apr 15 01:23+10:00", 2987 }, 2988 "juju-status": M{ 2989 "current": "allocating", 2990 "since": "01 Apr 15 01:23+10:00", 2991 }, 2992 "public-address": "10.0.1.1", 2993 }, 2994 }, 2995 "endpoint-bindings": M{ 2996 "server": "", 2997 "server-admin": "", 2998 "metrics-client": "", 2999 }, 3000 }), 3001 }, 3002 "storage": M{}, 3003 "controller": M{ 3004 "timestamp": "15:04:05+07:00", 3005 }, 3006 }, 3007 }, 3008 ), 3009 test( // 20 3010 "mixed workload version", 3011 addMachine{machineId: "0", job: state.JobManageModel}, 3012 setAddresses{"0", network.NewAddresses("10.0.0.1")}, 3013 startAliveMachine{"0", ""}, 3014 setMachineStatus{"0", status.Started, ""}, 3015 3016 addCharm{"mysql"}, 3017 addApplication{name: "mysql", charm: "mysql"}, 3018 3019 addMachine{machineId: "1", job: state.JobHostUnits}, 3020 setAddresses{"1", network.NewAddresses("10.0.1.1")}, 3021 startAliveMachine{"1", ""}, 3022 setMachineStatus{"1", status.Started, ""}, 3023 addAliveUnit{"mysql", "1"}, 3024 setUnitWorkloadVersion{"mysql/0", "the best!"}, 3025 3026 addMachine{machineId: "2", job: state.JobHostUnits}, 3027 setAddresses{"2", network.NewAddresses("10.0.2.1")}, 3028 startAliveMachine{"2", ""}, 3029 setMachineStatus{"2", status.Started, ""}, 3030 addAliveUnit{"mysql", "2"}, 3031 setUnitWorkloadVersion{"mysql/1", "not as good"}, 3032 3033 expect{ 3034 what: "application and unit with correct workload version", 3035 output: M{ 3036 "model": model, 3037 "machines": M{ 3038 "0": machine0, 3039 "1": machine1, 3040 "2": machine2, 3041 }, 3042 "applications": M{ 3043 "mysql": mysqlCharm(M{ 3044 "version": "not as good", 3045 "application-status": M{ 3046 "current": "waiting", 3047 "message": "waiting for machine", 3048 "since": "01 Apr 15 01:23+10:00", 3049 }, 3050 "units": M{ 3051 "mysql/0": M{ 3052 "machine": "1", 3053 "workload-status": M{ 3054 "current": "waiting", 3055 "message": "waiting for machine", 3056 "since": "01 Apr 15 01:23+10:00", 3057 }, 3058 "juju-status": M{ 3059 "current": "allocating", 3060 "since": "01 Apr 15 01:23+10:00", 3061 }, 3062 "public-address": "10.0.1.1", 3063 }, 3064 "mysql/1": M{ 3065 "machine": "2", 3066 "workload-status": M{ 3067 "current": "waiting", 3068 "message": "waiting for machine", 3069 "since": "01 Apr 15 01:23+10:00", 3070 }, 3071 "juju-status": M{ 3072 "current": "allocating", 3073 "since": "01 Apr 15 01:23+10:00", 3074 }, 3075 "public-address": "10.0.2.1", 3076 }, 3077 }, 3078 "endpoint-bindings": M{ 3079 "server": "", 3080 "server-admin": "", 3081 "metrics-client": "", 3082 }, 3083 }), 3084 }, 3085 "storage": M{}, 3086 "controller": M{ 3087 "timestamp": "15:04:05+07:00", 3088 }, 3089 }, 3090 }, 3091 ), 3092 test( // 21 3093 "instance with localhost addresses", 3094 addMachine{machineId: "0", job: state.JobManageModel}, 3095 setAddresses{"0", []network.Address{ 3096 network.NewScopedAddress("10.0.0.1", network.ScopeCloudLocal), 3097 network.NewScopedAddress("127.0.0.1", network.ScopeMachineLocal), 3098 // TODO(macgreagoir) setAddresses step method needs to 3099 // set netmask correctly before we can test IPv6 3100 // loopback. 3101 // network.NewScopedAddress("::1", network.ScopeMachineLocal), 3102 }}, 3103 startAliveMachine{"0", ""}, 3104 setMachineStatus{"0", status.Started, ""}, 3105 expect{ 3106 what: "machine 0 has localhost addresses that should not display", 3107 output: M{ 3108 "model": model, 3109 "machines": M{ 3110 "0": machine0, 3111 }, 3112 "applications": M{}, 3113 "storage": M{}, 3114 "controller": M{ 3115 "timestamp": "15:04:05+07:00", 3116 }, 3117 }, 3118 }, 3119 ), 3120 test( // 22 3121 "instance with IPv6 addresses", 3122 addMachine{machineId: "0", cons: machineCons, job: state.JobManageModel}, 3123 setAddresses{"0", []network.Address{ 3124 network.NewScopedAddress("2001:db8::1", network.ScopeCloudLocal), 3125 // TODO(macgreagoir) setAddresses step method needs to 3126 // set netmask correctly before we can test IPv6 3127 // loopback. 3128 // network.NewScopedAddress("::1", network.ScopeMachineLocal), 3129 }}, 3130 startAliveMachine{"0", ""}, 3131 setMachineStatus{"0", status.Started, ""}, 3132 expect{ 3133 what: "machine 0 has an IPv6 address", 3134 output: M{ 3135 "model": model, 3136 "machines": M{ 3137 "0": M{ 3138 "juju-status": M{ 3139 "current": "started", 3140 "since": "01 Apr 15 01:23+10:00", 3141 }, 3142 "dns-name": "2001:db8::1", 3143 "ip-addresses": []string{"2001:db8::1"}, 3144 "instance-id": "controller-0", 3145 "machine-status": M{ 3146 "current": "pending", 3147 "since": "01 Apr 15 01:23+10:00", 3148 }, 3149 "series": "quantal", 3150 "network-interfaces": M{ 3151 "eth0": M{ 3152 "ip-addresses": []string{"2001:db8::1"}, 3153 "mac-address": "aa:bb:cc:dd:ee:ff", 3154 "is-up": true, 3155 }, 3156 }, 3157 "constraints": "cores=2 mem=8192M root-disk=8192M", 3158 "hardware": "arch=amd64 cores=2 mem=8192M root-disk=8192M", 3159 "controller-member-status": "adding-vote", 3160 }, 3161 }, 3162 "applications": M{}, 3163 "storage": M{}, 3164 "controller": M{ 3165 "timestamp": "15:04:05+07:00", 3166 }, 3167 }, 3168 }, 3169 ), 3170 test( // 23 3171 "a remote application", 3172 addMachine{machineId: "0", job: state.JobManageModel}, 3173 setAddresses{"0", network.NewAddresses("10.0.0.1")}, 3174 startAliveMachine{"0", ""}, 3175 setMachineStatus{"0", status.Started, ""}, 3176 addMachine{machineId: "1", job: state.JobHostUnits}, 3177 setAddresses{"1", network.NewAddresses("10.0.1.1")}, 3178 startAliveMachine{"1", ""}, 3179 setMachineStatus{"1", status.Started, ""}, 3180 3181 addCharm{"wordpress"}, 3182 addApplication{name: "wordpress", charm: "wordpress"}, 3183 addAliveUnit{"wordpress", "1"}, 3184 3185 addCharm{"mysql"}, 3186 addRemoteApplication{name: "hosted-mysql", url: "me/model.mysql", charm: "mysql", endpoints: []string{"server"}}, 3187 relateApplications{"wordpress", "hosted-mysql", ""}, 3188 3189 expect{ 3190 what: "a remote application", 3191 output: M{ 3192 "model": model, 3193 "machines": M{ 3194 "0": machine0, 3195 "1": machine1, 3196 }, 3197 "application-endpoints": M{ 3198 "hosted-mysql": M{ 3199 "url": "me/model.mysql", 3200 "endpoints": M{ 3201 "server": M{ 3202 "interface": "mysql", 3203 "role": "provider", 3204 }, 3205 }, 3206 "application-status": M{ 3207 "current": "unknown", 3208 "since": "01 Apr 15 01:23+10:00", 3209 }, 3210 "relations": M{ 3211 "server": L{"wordpress"}, 3212 }, 3213 }, 3214 }, 3215 "applications": M{ 3216 "wordpress": wordpressCharm(M{ 3217 "application-status": M{ 3218 "current": "waiting", 3219 "message": "waiting for machine", 3220 "since": "01 Apr 15 01:23+10:00", 3221 }, 3222 "relations": M{ 3223 "db": L{"hosted-mysql"}, 3224 }, 3225 "units": M{ 3226 "wordpress/0": M{ 3227 "machine": "1", 3228 "workload-status": M{ 3229 "current": "waiting", 3230 "message": "waiting for machine", 3231 "since": "01 Apr 15 01:23+10:00", 3232 }, 3233 "juju-status": M{ 3234 "current": "allocating", 3235 "since": "01 Apr 15 01:23+10:00", 3236 }, 3237 "public-address": "10.0.1.1", 3238 }, 3239 }, 3240 "endpoint-bindings": M{ 3241 "monitoring-port": "", 3242 "url": "", 3243 "admin-api": "", 3244 "cache": "", 3245 "db": "", 3246 "db-client": "", 3247 "foo-bar": "", 3248 "logging-dir": "", 3249 }, 3250 }), 3251 }, 3252 "storage": M{}, 3253 "controller": M{ 3254 "timestamp": "15:04:05+07:00", 3255 }, 3256 }, 3257 }, 3258 ), 3259 test( // 24 3260 "set meter status on the model", 3261 setSLA{"advanced"}, 3262 setModelMeterStatus{"RED", "status message"}, 3263 expect{ 3264 what: "simulate just the two applications and a bootstrap node", 3265 output: M{ 3266 "model": M{ 3267 "name": "controller", 3268 "type": "iaas", 3269 "controller": "kontroll", 3270 "cloud": "dummy", 3271 "region": "dummy-region", 3272 "version": "1.2.3", 3273 "model-status": M{ 3274 "current": "available", 3275 "since": "01 Apr 15 01:23+10:00", 3276 }, 3277 "meter-status": M{ 3278 "color": "red", 3279 "message": "status message", 3280 }, 3281 "sla": "advanced", 3282 }, 3283 "machines": M{}, 3284 "applications": M{}, 3285 "storage": M{}, 3286 "controller": M{ 3287 "timestamp": "15:04:05+07:00", 3288 }, 3289 }, 3290 stderr: "Model \"controller\" is empty.\n", 3291 }, 3292 ), 3293 test( // 25 3294 "set sla on the model", 3295 setSLA{"advanced"}, 3296 expect{ 3297 what: "set sla on the model", 3298 output: M{ 3299 "model": M{ 3300 "name": "controller", 3301 "type": "iaas", 3302 "controller": "kontroll", 3303 "cloud": "dummy", 3304 "region": "dummy-region", 3305 "version": "1.2.3", 3306 "model-status": M{ 3307 "current": "available", 3308 "since": "01 Apr 15 01:23+10:00", 3309 }, 3310 "sla": "advanced", 3311 }, 3312 "machines": M{}, 3313 "applications": M{}, 3314 "storage": M{}, 3315 "controller": M{ 3316 "timestamp": "15:04:05+07:00", 3317 }, 3318 }, 3319 stderr: "Model \"controller\" is empty.\n", 3320 }, 3321 ), 3322 test( //26 3323 "deploy application with endpoint bound to space", 3324 addMachine{machineId: "0", job: state.JobManageModel}, 3325 setAddresses{"0", network.NewAddresses("10.0.0.1")}, 3326 startAliveMachine{"0", ""}, 3327 setMachineStatus{"0", status.Started, ""}, 3328 addMachine{machineId: "1", job: state.JobHostUnits}, 3329 setAddresses{"1", network.NewAddresses("10.0.1.1")}, 3330 startAliveMachine{"1", ""}, 3331 setMachineStatus{"1", status.Started, ""}, 3332 3333 addSpace{"myspace1"}, 3334 3335 addCharm{"wordpress"}, 3336 addApplication{name: "wordpress", charm: "wordpress", binding: map[string]string{"db-client": "", "logging-dir": "", "cache": "", "db": "myspace1", "monitoring-port": "", "url": "", "admin-api": "", "foo-bar": ""}}, 3337 addAliveUnit{"wordpress", "1"}, 3338 3339 scopedExpect{ 3340 output: M{ 3341 "model": M{ 3342 "region": "dummy-region", 3343 "version": "1.2.3", 3344 "model-status": M{ 3345 "current": "available", 3346 "since": "01 Apr 15 01:23+10:00", 3347 }, 3348 "type": "iaas", 3349 "sla": "unsupported", 3350 "name": "controller", 3351 "controller": "kontroll", 3352 "cloud": "dummy", 3353 }, 3354 "machines": M{ 3355 "1": M{ 3356 "juju-status": M{ 3357 "current": "started", 3358 "since": "01 Apr 15 01:23+10:00", 3359 }, 3360 "dns-name": "10.0.1.1", 3361 "ip-addresses": []string{"10.0.1.1"}, 3362 "instance-id": "controller-1", 3363 "machine-status": M{ 3364 "current": "pending", 3365 "since": "01 Apr 15 01:23+10:00", 3366 }, 3367 "series": "quantal", 3368 "network-interfaces": M{ 3369 "eth0": M{ 3370 "ip-addresses": []string{"10.0.1.1"}, 3371 "mac-address": "aa:bb:cc:dd:ee:ff", 3372 "is-up": bool(true), 3373 }, 3374 }, 3375 "hardware": "arch=amd64 cores=1 mem=1024M root-disk=8192M", 3376 }, 3377 "0": M{ 3378 "series": "quantal", 3379 "network-interfaces": M{ 3380 "eth0": M{ 3381 "ip-addresses": []string{"10.0.0.1"}, 3382 "mac-address": "aa:bb:cc:dd:ee:ff", 3383 "is-up": bool(true), 3384 }, 3385 }, 3386 "controller-member-status": "adding-vote", 3387 "dns-name": "10.0.0.1", 3388 "ip-addresses": []string{"10.0.0.1"}, 3389 "instance-id": "controller-0", 3390 "machine-status": M{ 3391 "current": "pending", 3392 "since": "01 Apr 15 01:23+10:00", 3393 }, 3394 "juju-status": M{ 3395 "current": "started", 3396 "since": "01 Apr 15 01:23+10:00", 3397 }, 3398 "hardware": "arch=amd64 cores=1 mem=1024M root-disk=8192M", 3399 }, 3400 }, 3401 "applications": M{ 3402 "wordpress": M{ 3403 "series": "quantal", 3404 "os": "ubuntu", 3405 "charm-name": "wordpress", 3406 "exposed": bool(false), 3407 "units": M{ 3408 "wordpress/0": M{ 3409 "public-address": "10.0.1.1", 3410 "workload-status": M{ 3411 "current": "waiting", 3412 "message": "waiting for machine", 3413 "since": "01 Apr 15 01:23+10:00", 3414 }, 3415 "juju-status": M{ 3416 "current": "allocating", 3417 "since": "01 Apr 15 01:23+10:00", 3418 }, 3419 "machine": "1", 3420 }, 3421 }, 3422 "charm": "cs:quantal/wordpress-3", 3423 "charm-origin": "jujucharms", 3424 "charm-rev": int(3), 3425 "application-status": M{ 3426 "current": "waiting", 3427 "message": "waiting for machine", 3428 "since": "01 Apr 15 01:23+10:00", 3429 }, 3430 "endpoint-bindings": M{ 3431 "cache": "", 3432 "db": "myspace1", 3433 "db-client": "", 3434 "foo-bar": "", 3435 "logging-dir": "", 3436 "monitoring-port": "", 3437 "url": "", 3438 "admin-api": "", 3439 }, 3440 }, 3441 }, 3442 "storage": M{}, 3443 "controller": M{ 3444 "timestamp": "15:04:05+07:00", 3445 }, 3446 }, 3447 }, 3448 ), 3449 test( // 27 3450 "application with lxd profiles", 3451 addMachine{machineId: "0", job: state.JobManageModel}, 3452 setAddresses{"0", network.NewAddresses("10.0.0.1")}, 3453 startAliveMachine{"0", ""}, 3454 setMachineStatus{"0", status.Started, ""}, 3455 addMachine{machineId: "1", job: state.JobHostUnits}, 3456 setAddresses{"1", network.NewAddresses("10.0.1.1")}, 3457 startAliveMachine{"1", ""}, 3458 setMachineStatus{"1", status.Started, ""}, 3459 setCharmProfiles{"1", []string{"juju-controller-lxd-profile-1"}}, 3460 addCharm{"lxd-profile"}, 3461 addApplication{name: "lxd-profile", charm: "lxd-profile"}, 3462 setApplicationExposed{"lxd-profile", true}, 3463 addAliveUnit{"lxd-profile", "1"}, 3464 setUnitCharmURL{"lxd-profile/0", "cs:quantal/lxd-profile-0"}, 3465 addCharmWithRevision{addCharm{"lxd-profile"}, "local", 1}, 3466 setApplicationCharm{"lxd-profile", "local:quantal/lxd-profile-1"}, 3467 addCharmPlaceholder{"lxd-profile", 23}, 3468 expect{ 3469 what: "applications and units with correct lxd profile charm status", 3470 output: M{ 3471 "model": model, 3472 "machines": M{ 3473 "0": machine0, 3474 "1": machine1WithLXDProfile, 3475 }, 3476 "applications": M{ 3477 "lxd-profile": lxdProfileCharm(M{ 3478 "charm": "local:quantal/lxd-profile-1", 3479 "charm-origin": "local", 3480 "exposed": true, 3481 "application-status": M{ 3482 "current": "active", 3483 "since": "01 Apr 15 01:23+10:00", 3484 }, 3485 "units": M{ 3486 "lxd-profile/0": M{ 3487 "machine": "1", 3488 "workload-status": M{ 3489 "current": "active", 3490 "since": "01 Apr 15 01:23+10:00", 3491 }, 3492 "juju-status": M{ 3493 "current": "idle", 3494 "since": "01 Apr 15 01:23+10:00", 3495 }, 3496 "upgrading-from": "cs:quantal/lxd-profile-0", 3497 "public-address": "10.0.1.1", 3498 }, 3499 }, 3500 "endpoint-bindings": M{ 3501 "another": "", 3502 "ubuntu": "", 3503 }, 3504 }), 3505 }, 3506 "storage": M{}, 3507 "controller": M{ 3508 "timestamp": "15:04:05+07:00", 3509 }, 3510 }, 3511 }, 3512 ), 3513 } 3514 3515 func mysqlCharm(extras M) M { 3516 charm := M{ 3517 "charm": "cs:quantal/mysql-1", 3518 "charm-origin": "jujucharms", 3519 "charm-name": "mysql", 3520 "charm-rev": 1, 3521 "series": "quantal", 3522 "os": "ubuntu", 3523 "exposed": false, 3524 } 3525 for key, value := range extras { 3526 charm[key] = value 3527 } 3528 return charm 3529 } 3530 3531 func lxdProfileCharm(extras M) M { 3532 charm := M{ 3533 "charm": "cs:quantal/lxd-profile-0", 3534 "charm-origin": "jujucharms", 3535 "charm-name": "lxd-profile", 3536 "charm-rev": 1, 3537 "series": "quantal", 3538 "os": "ubuntu", 3539 "exposed": false, 3540 } 3541 for key, value := range extras { 3542 charm[key] = value 3543 } 3544 return charm 3545 } 3546 3547 func meteredCharm(extras M) M { 3548 charm := M{ 3549 "charm": "cs:quantal/metered-1", 3550 "charm-origin": "jujucharms", 3551 "charm-name": "metered", 3552 "charm-rev": 1, 3553 "series": "quantal", 3554 "os": "ubuntu", 3555 "exposed": false, 3556 } 3557 for key, value := range extras { 3558 charm[key] = value 3559 } 3560 return charm 3561 } 3562 3563 func dummyCharm(extras M) M { 3564 charm := M{ 3565 "charm": "cs:quantal/dummy-1", 3566 "charm-origin": "jujucharms", 3567 "charm-name": "dummy", 3568 "charm-rev": 1, 3569 "series": "quantal", 3570 "os": "ubuntu", 3571 "exposed": false, 3572 } 3573 for key, value := range extras { 3574 charm[key] = value 3575 } 3576 return charm 3577 } 3578 3579 func wordpressCharm(extras M) M { 3580 charm := M{ 3581 "charm": "cs:quantal/wordpress-3", 3582 "charm-origin": "jujucharms", 3583 "charm-name": "wordpress", 3584 "charm-rev": 3, 3585 "series": "quantal", 3586 "os": "ubuntu", 3587 "exposed": false, 3588 } 3589 for key, value := range extras { 3590 charm[key] = value 3591 } 3592 return charm 3593 } 3594 3595 // TODO(dfc) test failing components by destructively mutating the state under the hood 3596 3597 // sometimes you just need to skip the tests for windows (environment variables etc) 3598 type skipTestOnWindows struct{} 3599 3600 func (skipTestOnWindows) step(c *gc.C, ctx *context) { 3601 if runtime.GOOS == "windows" { 3602 ctx.skipTest = true 3603 } 3604 } 3605 3606 type setSLA struct { 3607 level string 3608 } 3609 3610 func (s setSLA) step(c *gc.C, ctx *context) { 3611 err := ctx.st.SetSLA(s.level, "test-user", []byte("")) 3612 c.Assert(err, jc.ErrorIsNil) 3613 } 3614 3615 type addMachine struct { 3616 machineId string 3617 cons constraints.Value 3618 job state.MachineJob 3619 } 3620 3621 func (am addMachine) step(c *gc.C, ctx *context) { 3622 m, err := ctx.st.AddOneMachine(state.MachineTemplate{ 3623 Series: "quantal", 3624 Constraints: am.cons, 3625 Jobs: []state.MachineJob{am.job}, 3626 }) 3627 c.Assert(err, jc.ErrorIsNil) 3628 c.Assert(m.Id(), gc.Equals, am.machineId) 3629 } 3630 3631 type addContainer struct { 3632 parentId string 3633 machineId string 3634 job state.MachineJob 3635 } 3636 3637 func (ac addContainer) step(c *gc.C, ctx *context) { 3638 template := state.MachineTemplate{ 3639 Series: "quantal", 3640 Jobs: []state.MachineJob{ac.job}, 3641 } 3642 m, err := ctx.st.AddMachineInsideMachine(template, ac.parentId, instance.LXD) 3643 c.Assert(err, jc.ErrorIsNil) 3644 c.Assert(m.Id(), gc.Equals, ac.machineId) 3645 } 3646 3647 type startMachine struct { 3648 machineId string 3649 } 3650 3651 func (sm startMachine) step(c *gc.C, ctx *context) { 3652 m, err := ctx.st.Machine(sm.machineId) 3653 c.Assert(err, jc.ErrorIsNil) 3654 cons, err := m.Constraints() 3655 c.Assert(err, jc.ErrorIsNil) 3656 cfg, err := ctx.st.ControllerConfig() 3657 c.Assert(err, jc.ErrorIsNil) 3658 inst, hc := testing.AssertStartInstanceWithConstraints(c, ctx.env, environscontext.NewCloudCallContext(), cfg.ControllerUUID(), m.Id(), cons) 3659 err = m.SetProvisioned(inst.Id(), "", "fake_nonce", hc) 3660 c.Assert(err, jc.ErrorIsNil) 3661 } 3662 3663 type startMissingMachine struct { 3664 machineId string 3665 } 3666 3667 func (sm startMissingMachine) step(c *gc.C, ctx *context) { 3668 m, err := ctx.st.Machine(sm.machineId) 3669 c.Assert(err, jc.ErrorIsNil) 3670 cons, err := m.Constraints() 3671 c.Assert(err, jc.ErrorIsNil) 3672 cfg, err := ctx.st.ControllerConfig() 3673 c.Assert(err, jc.ErrorIsNil) 3674 _, hc := testing.AssertStartInstanceWithConstraints(c, ctx.env, environscontext.NewCloudCallContext(), cfg.ControllerUUID(), m.Id(), cons) 3675 err = m.SetProvisioned("i-missing", "", "fake_nonce", hc) 3676 c.Assert(err, jc.ErrorIsNil) 3677 // lp:1558657 3678 now := time.Now() 3679 s := status.StatusInfo{ 3680 Status: status.Unknown, 3681 Message: "missing", 3682 Since: &now, 3683 } 3684 err = m.SetInstanceStatus(s) 3685 c.Assert(err, jc.ErrorIsNil) 3686 } 3687 3688 type startAliveMachine struct { 3689 machineId string 3690 displayName string 3691 } 3692 3693 func (sam startAliveMachine) step(c *gc.C, ctx *context) { 3694 m, err := ctx.st.Machine(sam.machineId) 3695 c.Assert(err, jc.ErrorIsNil) 3696 pinger := ctx.setAgentPresence(c, m) 3697 cons, err := m.Constraints() 3698 c.Assert(err, jc.ErrorIsNil) 3699 cfg, err := ctx.st.ControllerConfig() 3700 c.Assert(err, jc.ErrorIsNil) 3701 inst, hc := testing.AssertStartInstanceWithConstraints(c, ctx.env, environscontext.NewCloudCallContext(), cfg.ControllerUUID(), m.Id(), cons) 3702 err = m.SetProvisioned(inst.Id(), sam.displayName, "fake_nonce", hc) 3703 c.Assert(err, jc.ErrorIsNil) 3704 ctx.pingers[m.Id()] = pinger 3705 } 3706 3707 type startMachineWithHardware struct { 3708 machineId string 3709 hc instance.HardwareCharacteristics 3710 } 3711 3712 func (sm startMachineWithHardware) step(c *gc.C, ctx *context) { 3713 m, err := ctx.st.Machine(sm.machineId) 3714 c.Assert(err, jc.ErrorIsNil) 3715 pinger := ctx.setAgentPresence(c, m) 3716 cons, err := m.Constraints() 3717 c.Assert(err, jc.ErrorIsNil) 3718 cfg, err := ctx.st.ControllerConfig() 3719 c.Assert(err, jc.ErrorIsNil) 3720 inst, _ := testing.AssertStartInstanceWithConstraints(c, ctx.env, environscontext.NewCloudCallContext(), cfg.ControllerUUID(), m.Id(), cons) 3721 err = m.SetProvisioned(inst.Id(), "", "fake_nonce", &sm.hc) 3722 c.Assert(err, jc.ErrorIsNil) 3723 ctx.pingers[m.Id()] = pinger 3724 } 3725 3726 type startAliveMachineWithDisplayName struct { 3727 machineId string 3728 displayName string 3729 } 3730 3731 func (sm startAliveMachineWithDisplayName) step(c *gc.C, ctx *context) { 3732 m, err := ctx.st.Machine(sm.machineId) 3733 c.Assert(err, jc.ErrorIsNil) 3734 pinger := ctx.setAgentPresence(c, m) 3735 cons, err := m.Constraints() 3736 c.Assert(err, jc.ErrorIsNil) 3737 cfg, err := ctx.st.ControllerConfig() 3738 c.Assert(err, jc.ErrorIsNil) 3739 inst, hc := testing.AssertStartInstanceWithConstraints(c, ctx.env, environscontext.NewCloudCallContext(), cfg.ControllerUUID(), m.Id(), cons) 3740 err = m.SetProvisioned(inst.Id(), sm.displayName, "fake_nonce", hc) 3741 c.Assert(err, jc.ErrorIsNil) 3742 ctx.pingers[m.Id()] = pinger 3743 _, displayName, err := m.InstanceNames() 3744 c.Assert(err, jc.ErrorIsNil) 3745 c.Assert(displayName, gc.Equals, sm.displayName) 3746 } 3747 3748 type setMachineInstanceStatus struct { 3749 machineId string 3750 Status status.Status 3751 Message string 3752 } 3753 3754 func (sm setMachineInstanceStatus) step(c *gc.C, ctx *context) { 3755 m, err := ctx.st.Machine(sm.machineId) 3756 c.Assert(err, jc.ErrorIsNil) 3757 now := time.Now() 3758 s := status.StatusInfo{ 3759 Status: sm.Status, 3760 Message: sm.Message, 3761 Since: &now, 3762 } 3763 err = m.SetInstanceStatus(s) 3764 c.Assert(err, jc.ErrorIsNil) 3765 } 3766 3767 type addSpace struct { 3768 spaceName string 3769 } 3770 3771 func (sp addSpace) step(c *gc.C, ctx *context) { 3772 f := factory.NewFactory(ctx.st, ctx.pool) 3773 f.MakeSpace(c, &factory.SpaceParams{ 3774 Name: sp.spaceName, ProviderID: network.Id("provider"), IsPublic: true}) 3775 } 3776 3777 type setAddresses struct { 3778 machineId string 3779 addresses []network.Address 3780 } 3781 3782 func (sa setAddresses) step(c *gc.C, ctx *context) { 3783 m, err := ctx.st.Machine(sa.machineId) 3784 c.Assert(err, jc.ErrorIsNil) 3785 err = m.SetProviderAddresses(sa.addresses...) 3786 c.Assert(err, jc.ErrorIsNil) 3787 addrs := make([]state.LinkLayerDeviceAddress, len(sa.addresses)) 3788 lldevs := make([]state.LinkLayerDeviceArgs, len(sa.addresses)) 3789 for i, address := range sa.addresses { 3790 devName := fmt.Sprintf("eth%d", i) 3791 macAddr := "aa:bb:cc:dd:ee:ff" 3792 configMethod := state.StaticAddress 3793 devType := state.EthernetDevice 3794 if address.Scope == network.ScopeMachineLocal || 3795 address.Value == "localhost" { 3796 devName = "lo" 3797 macAddr = "00:00:00:00:00:00" 3798 configMethod = state.LoopbackAddress 3799 devType = state.LoopbackDevice 3800 } 3801 lldevs[i] = state.LinkLayerDeviceArgs{ 3802 Name: devName, 3803 MACAddress: macAddr, // TODO(macgreagoir) Enough for first pass 3804 IsUp: true, 3805 Type: devType, 3806 } 3807 addrs[i] = state.LinkLayerDeviceAddress{ 3808 DeviceName: devName, 3809 ConfigMethod: configMethod, 3810 // TODO(macgreagoir) Enough for first pass, but 3811 // incorrect for IPv4 loopback, and breaks IPv6 3812 // loopback. 3813 CIDRAddress: fmt.Sprintf("%s/24", address.Value)} 3814 } 3815 // TODO(macgreagoir) Let these go for now, before this turns into a test for setting lldevs and addrs. 3816 // err = m.SetLinkLayerDevices(lldevs...) 3817 // c.Assert(err, jc.ErrorIsNil) 3818 _ = m.SetLinkLayerDevices(lldevs...) 3819 // err = m.SetDevicesAddresses(addrs...) 3820 // c.Assert(err, jc.ErrorIsNil) 3821 _ = m.SetDevicesAddresses(addrs...) 3822 } 3823 3824 type setTools struct { 3825 machineId string 3826 version version.Binary 3827 } 3828 3829 func (st setTools) step(c *gc.C, ctx *context) { 3830 m, err := ctx.st.Machine(st.machineId) 3831 c.Assert(err, jc.ErrorIsNil) 3832 err = m.SetAgentVersion(st.version) 3833 c.Assert(err, jc.ErrorIsNil) 3834 } 3835 3836 type setUnitTools struct { 3837 unitName string 3838 version version.Binary 3839 } 3840 3841 func (st setUnitTools) step(c *gc.C, ctx *context) { 3842 m, err := ctx.st.Unit(st.unitName) 3843 c.Assert(err, jc.ErrorIsNil) 3844 err = m.SetAgentVersion(st.version) 3845 c.Assert(err, jc.ErrorIsNil) 3846 } 3847 3848 type addCharm struct { 3849 name string 3850 } 3851 3852 func (ac addCharm) addCharmStep(c *gc.C, ctx *context, scheme string, rev int) { 3853 ch := testcharms.Repo.CharmDir(ac.name) 3854 name := ch.Meta().Name 3855 curl := charm.MustParseURL(fmt.Sprintf("%s:quantal/%s-%d", scheme, name, rev)) 3856 info := state.CharmInfo{ 3857 Charm: ch, 3858 ID: curl, 3859 StoragePath: "dummy-path", 3860 SHA256: fmt.Sprintf("%s-%d-sha256", name, rev), 3861 } 3862 dummy, err := ctx.st.AddCharm(info) 3863 c.Assert(err, jc.ErrorIsNil) 3864 ctx.charms[ac.name] = dummy 3865 } 3866 3867 func (ac addCharm) step(c *gc.C, ctx *context) { 3868 ch := testcharms.Repo.CharmDir(ac.name) 3869 ac.addCharmStep(c, ctx, "cs", ch.Revision()) 3870 } 3871 3872 type addCharmWithRevision struct { 3873 addCharm 3874 scheme string 3875 rev int 3876 } 3877 3878 func (ac addCharmWithRevision) step(c *gc.C, ctx *context) { 3879 ac.addCharmStep(c, ctx, ac.scheme, ac.rev) 3880 } 3881 3882 type addApplication struct { 3883 name string 3884 charm string 3885 binding map[string]string 3886 cons constraints.Value 3887 } 3888 3889 func (as addApplication) step(c *gc.C, ctx *context) { 3890 ch, ok := ctx.charms[as.charm] 3891 c.Assert(ok, jc.IsTrue) 3892 app, err := ctx.st.AddApplication(state.AddApplicationArgs{Name: as.name, Charm: ch, EndpointBindings: as.binding}) 3893 c.Assert(err, jc.ErrorIsNil) 3894 if app.IsPrincipal() { 3895 err = app.SetConstraints(as.cons) 3896 c.Assert(err, jc.ErrorIsNil) 3897 } 3898 } 3899 3900 type addRemoteApplication struct { 3901 name string 3902 url string 3903 charm string 3904 endpoints []string 3905 isConsumerProxy bool 3906 } 3907 3908 func (as addRemoteApplication) step(c *gc.C, ctx *context) { 3909 ch, ok := ctx.charms[as.charm] 3910 c.Assert(ok, jc.IsTrue) 3911 var endpoints []charm.Relation 3912 for _, ep := range as.endpoints { 3913 r, ok := ch.Meta().Requires[ep] 3914 if !ok { 3915 r, ok = ch.Meta().Provides[ep] 3916 } 3917 c.Assert(ok, jc.IsTrue) 3918 endpoints = append(endpoints, r) 3919 } 3920 _, err := ctx.st.AddRemoteApplication(state.AddRemoteApplicationParams{ 3921 Name: as.name, 3922 URL: as.url, 3923 SourceModel: coretesting.ModelTag, 3924 Endpoints: endpoints, 3925 IsConsumerProxy: as.isConsumerProxy, 3926 }) 3927 c.Assert(err, jc.ErrorIsNil) 3928 } 3929 3930 type addApplicationOffer struct { 3931 name string 3932 owner string 3933 applicationName string 3934 endpoints []string 3935 } 3936 3937 func (ao addApplicationOffer) step(c *gc.C, ctx *context) { 3938 endpoints := make(map[string]string) 3939 for _, ep := range ao.endpoints { 3940 endpoints[ep] = ep 3941 } 3942 offers := state.NewApplicationOffers(ctx.st) 3943 _, err := offers.AddOffer(crossmodel.AddApplicationOfferArgs{ 3944 OfferName: ao.name, 3945 Owner: ao.owner, 3946 ApplicationName: ao.applicationName, 3947 Endpoints: endpoints, 3948 }) 3949 c.Assert(err, jc.ErrorIsNil) 3950 } 3951 3952 type addOfferConnection struct { 3953 sourceModelUUID string 3954 name string 3955 username string 3956 relationKey string 3957 } 3958 3959 func (oc addOfferConnection) step(c *gc.C, ctx *context) { 3960 rel, err := ctx.st.KeyRelation(oc.relationKey) 3961 c.Assert(err, jc.ErrorIsNil) 3962 offer, err := state.NewApplicationOffers(ctx.st).ApplicationOffer(oc.name) 3963 c.Assert(err, jc.ErrorIsNil) 3964 _, err = ctx.st.AddOfferConnection(state.AddOfferConnectionParams{ 3965 SourceModelUUID: oc.sourceModelUUID, 3966 OfferUUID: offer.OfferUUID, 3967 Username: oc.username, 3968 RelationId: rel.Id(), 3969 RelationKey: rel.Tag().Id(), 3970 }) 3971 c.Assert(err, jc.ErrorIsNil) 3972 } 3973 3974 type setApplicationExposed struct { 3975 name string 3976 exposed bool 3977 } 3978 3979 func (sse setApplicationExposed) step(c *gc.C, ctx *context) { 3980 s, err := ctx.st.Application(sse.name) 3981 c.Assert(err, jc.ErrorIsNil) 3982 err = s.ClearExposed() 3983 c.Assert(err, jc.ErrorIsNil) 3984 if sse.exposed { 3985 err = s.SetExposed() 3986 c.Assert(err, jc.ErrorIsNil) 3987 } 3988 } 3989 3990 type setApplicationCharm struct { 3991 name string 3992 charm string 3993 } 3994 3995 func (ssc setApplicationCharm) step(c *gc.C, ctx *context) { 3996 fmt.Println("HERE") 3997 ch, err := ctx.st.Charm(charm.MustParseURL(ssc.charm)) 3998 c.Assert(err, jc.ErrorIsNil) 3999 s, err := ctx.st.Application(ssc.name) 4000 c.Assert(err, jc.ErrorIsNil) 4001 cfg := state.SetCharmConfig{Charm: ch} 4002 err = s.SetCharm(cfg) 4003 c.Assert(err, jc.ErrorIsNil) 4004 } 4005 4006 type addCharmPlaceholder struct { 4007 name string 4008 rev int 4009 } 4010 4011 func (ac addCharmPlaceholder) step(c *gc.C, ctx *context) { 4012 ch := testcharms.Repo.CharmDir(ac.name) 4013 name := ch.Meta().Name 4014 curl := charm.MustParseURL(fmt.Sprintf("cs:quantal/%s-%d", name, ac.rev)) 4015 err := ctx.st.AddStoreCharmPlaceholder(curl) 4016 c.Assert(err, jc.ErrorIsNil) 4017 } 4018 4019 type addUnit struct { 4020 applicationName string 4021 machineId string 4022 } 4023 4024 func (au addUnit) step(c *gc.C, ctx *context) { 4025 s, err := ctx.st.Application(au.applicationName) 4026 c.Assert(err, jc.ErrorIsNil) 4027 u, err := s.AddUnit(state.AddUnitParams{}) 4028 c.Assert(err, jc.ErrorIsNil) 4029 m, err := ctx.st.Machine(au.machineId) 4030 c.Assert(err, jc.ErrorIsNil) 4031 err = u.AssignToMachine(m) 4032 c.Assert(err, jc.ErrorIsNil) 4033 ctx.statusSetter.SetAgentStatus(u.Tag().String(), corepresence.Missing) 4034 } 4035 4036 type addAliveUnit struct { 4037 applicationName string 4038 machineId string 4039 } 4040 4041 func (aau addAliveUnit) step(c *gc.C, ctx *context) { 4042 s, err := ctx.st.Application(aau.applicationName) 4043 c.Assert(err, jc.ErrorIsNil) 4044 u, err := s.AddUnit(state.AddUnitParams{}) 4045 c.Assert(err, jc.ErrorIsNil) 4046 pinger := ctx.setAgentPresence(c, u) 4047 m, err := ctx.st.Machine(aau.machineId) 4048 c.Assert(err, jc.ErrorIsNil) 4049 err = u.AssignToMachine(m) 4050 c.Assert(err, jc.ErrorIsNil) 4051 ctx.pingers[u.Name()] = pinger 4052 } 4053 4054 type setUnitsAlive struct { 4055 applicationName string 4056 } 4057 4058 func (sua setUnitsAlive) step(c *gc.C, ctx *context) { 4059 s, err := ctx.st.Application(sua.applicationName) 4060 c.Assert(err, jc.ErrorIsNil) 4061 us, err := s.AllUnits() 4062 c.Assert(err, jc.ErrorIsNil) 4063 for _, u := range us { 4064 ctx.pingers[u.Name()] = ctx.setAgentPresence(c, u) 4065 } 4066 } 4067 4068 type setUnitMeterStatus struct { 4069 unitName string 4070 color string 4071 message string 4072 } 4073 4074 func (s setUnitMeterStatus) step(c *gc.C, ctx *context) { 4075 u, err := ctx.st.Unit(s.unitName) 4076 c.Assert(err, jc.ErrorIsNil) 4077 err = u.SetMeterStatus(s.color, s.message) 4078 c.Assert(err, jc.ErrorIsNil) 4079 } 4080 4081 type setModelMeterStatus struct { 4082 color string 4083 message string 4084 } 4085 4086 func (s setModelMeterStatus) step(c *gc.C, ctx *context) { 4087 m, err := ctx.st.Model() 4088 c.Assert(err, jc.ErrorIsNil) 4089 err = m.SetMeterStatus(s.color, s.message) 4090 c.Assert(err, jc.ErrorIsNil) 4091 } 4092 4093 type setUnitAsLeader struct { 4094 unitName string 4095 } 4096 4097 func (s setUnitAsLeader) step(c *gc.C, ctx *context) { 4098 u, err := ctx.st.Unit(s.unitName) 4099 c.Assert(err, jc.ErrorIsNil) 4100 target := ctx.st.LeaseNotifyTarget( 4101 ioutil.Discard, 4102 loggo.GetLogger("status_internal_test"), 4103 ) 4104 target.Claimed( 4105 lease.Key{"application-leadership", ctx.st.ModelUUID(), u.ApplicationName()}, 4106 u.Name(), 4107 ) 4108 } 4109 4110 type setUnitStatus struct { 4111 unitName string 4112 status status.Status 4113 statusInfo string 4114 statusData map[string]interface{} 4115 } 4116 4117 func (sus setUnitStatus) step(c *gc.C, ctx *context) { 4118 u, err := ctx.st.Unit(sus.unitName) 4119 c.Assert(err, jc.ErrorIsNil) 4120 // lp:1558657 4121 now := time.Now() 4122 s := status.StatusInfo{ 4123 Status: sus.status, 4124 Message: sus.statusInfo, 4125 Data: sus.statusData, 4126 Since: &now, 4127 } 4128 err = u.SetStatus(s) 4129 c.Assert(err, jc.ErrorIsNil) 4130 } 4131 4132 type setAgentStatus struct { 4133 unitName string 4134 status status.Status 4135 statusInfo string 4136 statusData map[string]interface{} 4137 } 4138 4139 func (sus setAgentStatus) step(c *gc.C, ctx *context) { 4140 u, err := ctx.st.Unit(sus.unitName) 4141 c.Assert(err, jc.ErrorIsNil) 4142 // lp:1558657 4143 now := time.Now() 4144 sInfo := status.StatusInfo{ 4145 Status: sus.status, 4146 Message: sus.statusInfo, 4147 Data: sus.statusData, 4148 Since: &now, 4149 } 4150 err = u.SetAgentStatus(sInfo) 4151 c.Assert(err, jc.ErrorIsNil) 4152 } 4153 4154 type setUnitCharmURL struct { 4155 unitName string 4156 charm string 4157 } 4158 4159 func (uc setUnitCharmURL) step(c *gc.C, ctx *context) { 4160 u, err := ctx.st.Unit(uc.unitName) 4161 c.Assert(err, jc.ErrorIsNil) 4162 curl := charm.MustParseURL(uc.charm) 4163 err = u.SetCharmURL(curl) 4164 c.Assert(err, jc.ErrorIsNil) 4165 // lp:1558657 4166 now := time.Now() 4167 s := status.StatusInfo{ 4168 Status: status.Active, 4169 Message: "", 4170 Since: &now, 4171 } 4172 err = u.SetStatus(s) 4173 c.Assert(err, jc.ErrorIsNil) 4174 sInfo := status.StatusInfo{ 4175 Status: status.Idle, 4176 Message: "", 4177 Since: &now, 4178 } 4179 err = u.SetAgentStatus(sInfo) 4180 c.Assert(err, jc.ErrorIsNil) 4181 4182 } 4183 4184 type setUnitWorkloadVersion struct { 4185 unitName string 4186 version string 4187 } 4188 4189 func (wv setUnitWorkloadVersion) step(c *gc.C, ctx *context) { 4190 u, err := ctx.st.Unit(wv.unitName) 4191 c.Assert(err, jc.ErrorIsNil) 4192 err = u.SetWorkloadVersion(wv.version) 4193 c.Assert(err, jc.ErrorIsNil) 4194 } 4195 4196 type openUnitPort struct { 4197 unitName string 4198 protocol string 4199 number int 4200 } 4201 4202 func (oup openUnitPort) step(c *gc.C, ctx *context) { 4203 u, err := ctx.st.Unit(oup.unitName) 4204 c.Assert(err, jc.ErrorIsNil) 4205 err = u.OpenPort(oup.protocol, oup.number) 4206 c.Assert(err, jc.ErrorIsNil) 4207 } 4208 4209 type ensureDyingUnit struct { 4210 unitName string 4211 } 4212 4213 func (e ensureDyingUnit) step(c *gc.C, ctx *context) { 4214 u, err := ctx.st.Unit(e.unitName) 4215 c.Assert(err, jc.ErrorIsNil) 4216 err = u.Destroy() 4217 c.Assert(err, jc.ErrorIsNil) 4218 c.Assert(u.Life(), gc.Equals, state.Dying) 4219 } 4220 4221 type ensureDyingApplication struct { 4222 applicationName string 4223 } 4224 4225 func (e ensureDyingApplication) step(c *gc.C, ctx *context) { 4226 svc, err := ctx.st.Application(e.applicationName) 4227 c.Assert(err, jc.ErrorIsNil) 4228 err = svc.Destroy() 4229 c.Assert(err, jc.ErrorIsNil) 4230 err = svc.Refresh() 4231 c.Assert(err, jc.ErrorIsNil) 4232 c.Assert(svc.Life(), gc.Equals, state.Dying) 4233 } 4234 4235 type ensureDeadMachine struct { 4236 machineId string 4237 } 4238 4239 func (e ensureDeadMachine) step(c *gc.C, ctx *context) { 4240 m, err := ctx.st.Machine(e.machineId) 4241 c.Assert(err, jc.ErrorIsNil) 4242 err = m.EnsureDead() 4243 c.Assert(err, jc.ErrorIsNil) 4244 c.Assert(m.Life(), gc.Equals, state.Dead) 4245 } 4246 4247 type setMachineStatus struct { 4248 machineId string 4249 status status.Status 4250 statusInfo string 4251 } 4252 4253 func (sms setMachineStatus) step(c *gc.C, ctx *context) { 4254 // lp:1558657 4255 now := time.Now() 4256 m, err := ctx.st.Machine(sms.machineId) 4257 c.Assert(err, jc.ErrorIsNil) 4258 sInfo := status.StatusInfo{ 4259 Status: sms.status, 4260 Message: sms.statusInfo, 4261 Since: &now, 4262 } 4263 err = m.SetStatus(sInfo) 4264 c.Assert(err, jc.ErrorIsNil) 4265 } 4266 4267 type relateApplications struct { 4268 ep1, ep2 string 4269 status string 4270 } 4271 4272 func (rs relateApplications) step(c *gc.C, ctx *context) { 4273 eps, err := ctx.st.InferEndpoints(rs.ep1, rs.ep2) 4274 c.Assert(err, jc.ErrorIsNil) 4275 rel, err := ctx.st.AddRelation(eps...) 4276 c.Assert(err, jc.ErrorIsNil) 4277 s := rs.status 4278 if s == "" { 4279 s = "joined" 4280 } 4281 err = rel.SetStatus(status.StatusInfo{Status: status.Status(s)}) 4282 c.Assert(err, jc.ErrorIsNil) 4283 } 4284 4285 type addSubordinate struct { 4286 prinUnit string 4287 subApplication string 4288 } 4289 4290 func (as addSubordinate) step(c *gc.C, ctx *context) { 4291 u, err := ctx.st.Unit(as.prinUnit) 4292 c.Assert(err, jc.ErrorIsNil) 4293 eps, err := ctx.st.InferEndpoints(u.ApplicationName(), as.subApplication) 4294 c.Assert(err, jc.ErrorIsNil) 4295 rel, err := ctx.st.EndpointsRelation(eps...) 4296 c.Assert(err, jc.ErrorIsNil) 4297 ru, err := rel.Unit(u) 4298 c.Assert(err, jc.ErrorIsNil) 4299 err = ru.EnterScope(nil) 4300 c.Assert(err, jc.ErrorIsNil) 4301 } 4302 4303 type setCharmProfiles struct { 4304 machineId string 4305 profiles []string 4306 } 4307 4308 func (s setCharmProfiles) step(c *gc.C, ctx *context) { 4309 m, err := ctx.st.Machine(s.machineId) 4310 c.Assert(err, jc.ErrorIsNil) 4311 err = m.SetCharmProfiles(s.profiles) 4312 c.Assert(err, jc.ErrorIsNil) 4313 } 4314 4315 type scopedExpect struct { 4316 what string 4317 scope []string 4318 output M 4319 stderr string 4320 } 4321 4322 type expect struct { 4323 what string 4324 output M 4325 stderr string 4326 } 4327 4328 // substituteFakeTime replaces all key values 4329 // in actual status output with a known fake value. 4330 func substituteFakeTime(c *gc.C, key string, in []byte, expectIsoTime bool) []byte { 4331 // This regexp will work for yaml and json. 4332 exp := regexp.MustCompile(`(?P<key>"?` + key + `"?:\ ?)(?P<quote>"?)(?P<timestamp>[^("|\n)]*)*"?`) 4333 // Before the substitution is done, check that the timestamp produced 4334 // by status is in the correct format. 4335 if matches := exp.FindStringSubmatch(string(in)); matches != nil { 4336 for i, name := range exp.SubexpNames() { 4337 if name != "timestamp" { 4338 continue 4339 } 4340 timeFormat := "02 Jan 2006 15:04:05Z07:00" 4341 if expectIsoTime { 4342 timeFormat = "2006-01-02 15:04:05Z" 4343 } 4344 _, err := time.Parse(timeFormat, matches[i]) 4345 c.Assert(err, jc.ErrorIsNil) 4346 } 4347 } 4348 4349 out := exp.ReplaceAllString(string(in), `$key$quote<timestamp>$quote`) 4350 // Substitute a made up time used in our expected output. 4351 out = strings.Replace(out, "<timestamp>", "01 Apr 15 01:23+10:00", -1) 4352 return []byte(out) 4353 } 4354 4355 // substituteFakeTimestamp replaces all key values for a given timestamp 4356 // in actual status output with a known fake value. 4357 func substituteFakeTimestamp(c *gc.C, in []byte, expectIsoTime bool) []byte { 4358 timeFormat := "15:04:05Z07:00" 4359 output := strings.Replace(timeFormat, "Z", "+", -1) 4360 if expectIsoTime { 4361 timeFormat = "15:04:05Z" 4362 output = "15:04:05" 4363 } 4364 // This regexp will work for any input type 4365 exp := regexp.MustCompile(`(?P<timestamp>[0-9]{2}:[0-9]{2}:[0-9]{2}((Z|\+|\-)([0-9]{2}:[0-9]{2})?)?)`) 4366 if matches := exp.FindStringSubmatch(string(in)); matches != nil { 4367 for i, name := range exp.SubexpNames() { 4368 if name != "timestamp" { 4369 continue 4370 } 4371 value := matches[i] 4372 if num := len(value); num == 8 { 4373 value = fmt.Sprintf("%sZ", value) 4374 } else if num == 14 && (expectIsoTime || value[8] == 'Z') { 4375 value = fmt.Sprintf("%sZ", value[:8]) 4376 } 4377 _, err := time.Parse(timeFormat, value) 4378 c.Assert(err, jc.ErrorIsNil) 4379 } 4380 } 4381 4382 out := exp.ReplaceAllString(string(in), `<timestamp>`) 4383 // Substitute a made up time used in our expected output. 4384 out = strings.Replace(out, "<timestamp>", output, -1) 4385 return []byte(out) 4386 } 4387 4388 // substituteSpacingBetweenTimestampAndNotes forces the spacing between the 4389 // headers Timestamp and Notes to be consistent regardless of the time. This 4390 // happens because we're dealing with the result of the strings of stdout and 4391 // not with any useable AST 4392 func substituteSpacingBetweenTimestampAndNotes(c *gc.C, in []byte) []byte { 4393 exp := regexp.MustCompile(`Timestamp(?P<spacing>\s+)Notes`) 4394 result := exp.ReplaceAllString(string(in), fmt.Sprintf("Timestamp%sNotes", strings.Repeat(" ", 7))) 4395 return []byte(result) 4396 } 4397 4398 func (e scopedExpect) step(c *gc.C, ctx *context) { 4399 c.Logf("\nexpect: %s %s\n", e.what, strings.Join(e.scope, " ")) 4400 4401 // Now execute the command for each format. 4402 for _, format := range statusFormats { 4403 c.Logf("format %q", format.name) 4404 // Run command with the required format. 4405 args := []string{"--format", format.name} 4406 if ctx.expectIsoTime { 4407 args = append(args, "--utc") 4408 } 4409 args = append(args, e.scope...) 4410 c.Logf("running status %s", strings.Join(args, " ")) 4411 code, stdout, stderr := runStatus(c, args...) 4412 c.Assert(code, gc.Equals, 0) 4413 c.Assert(string(stderr), gc.Equals, e.stderr) 4414 4415 // Prepare the output in the same format. 4416 buf, err := format.marshal(e.output) 4417 c.Assert(err, jc.ErrorIsNil) 4418 4419 // we have to force the timestamp into the correct format as the model 4420 // is in string. 4421 buf = substituteFakeTimestamp(c, buf, ctx.expectIsoTime) 4422 4423 expected := make(M) 4424 err = format.unmarshal(buf, &expected) 4425 c.Assert(err, jc.ErrorIsNil) 4426 4427 // Check the output is as expected. 4428 actual := make(M) 4429 out := substituteFakeTime(c, "since", stdout, ctx.expectIsoTime) 4430 out = substituteFakeTimestamp(c, out, ctx.expectIsoTime) 4431 err = format.unmarshal(out, &actual) 4432 c.Assert(err, jc.ErrorIsNil) 4433 pretty.Ldiff(c, actual, expected) 4434 c.Assert(actual, jc.DeepEquals, expected) 4435 } 4436 } 4437 4438 func (e expect) step(c *gc.C, ctx *context) { 4439 scopedExpect{e.what, nil, e.output, e.stderr}.step(c, ctx) 4440 } 4441 4442 type setToolsUpgradeAvailable struct{} 4443 4444 func (ua setToolsUpgradeAvailable) step(c *gc.C, ctx *context) { 4445 model, err := ctx.st.Model() 4446 c.Assert(err, jc.ErrorIsNil) 4447 err = model.UpdateLatestToolsVersion(nextVersion) 4448 c.Assert(err, jc.ErrorIsNil) 4449 } 4450 4451 func (s *StatusSuite) TestStatusAllFormats(c *gc.C) { 4452 for i, t := range statusTests { 4453 c.Logf("test %d: %s", i, t.summary) 4454 func(t testCase) { 4455 // Prepare context and run all steps to setup. 4456 ctx := s.newContext(c) 4457 defer s.resetContext(c, ctx) 4458 ctx.run(c, t.steps) 4459 }(t) 4460 } 4461 } 4462 4463 func (s *StatusSuite) TestMigrationInProgress(c *gc.C) { 4464 // This test isn't part of statusTests because migrations can't be 4465 // run on controller models. 4466 st := s.setupMigrationTest(c) 4467 defer st.Close() 4468 4469 expected := M{ 4470 "model": M{ 4471 "name": "hosted", 4472 "type": "iaas", 4473 "controller": "kontroll", 4474 "cloud": "dummy", 4475 "region": "dummy-region", 4476 "version": "1.2.3", 4477 "model-status": M{ 4478 "current": "busy", 4479 "since": "01 Apr 15 01:23+10:00", 4480 "message": "migrating: foo bar", 4481 }, 4482 "sla": "unsupported", 4483 }, 4484 "machines": M{}, 4485 "applications": M{}, 4486 "storage": M{}, 4487 "controller": M{ 4488 "timestamp": "15:04:05+07:00", 4489 }, 4490 } 4491 4492 for _, format := range statusFormats { 4493 code, stdout, stderr := runStatus(c, "-m", "hosted", "--format", format.name) 4494 c.Check(code, gc.Equals, 0) 4495 c.Assert(string(stderr), gc.Equals, "Model \"hosted\" is empty.\n") 4496 4497 stdout = substituteFakeTime(c, "since", stdout, false) 4498 stdout = substituteFakeTimestamp(c, stdout, false) 4499 4500 // Roundtrip expected through format so that types will match. 4501 buf, err := format.marshal(expected) 4502 c.Assert(err, jc.ErrorIsNil) 4503 var expectedForFormat M 4504 err = format.unmarshal(buf, &expectedForFormat) 4505 c.Assert(err, jc.ErrorIsNil) 4506 4507 var actual M 4508 c.Assert(format.unmarshal(stdout, &actual), jc.ErrorIsNil) 4509 c.Check(actual, jc.DeepEquals, expectedForFormat) 4510 } 4511 } 4512 4513 func (s *StatusSuite) TestMigrationInProgressTabular(c *gc.C) { 4514 expected := ` 4515 Model Controller Cloud/Region Version SLA Timestamp Notes 4516 hosted kontroll dummy/dummy-region 1.2.3 unsupported 15:04:05+07:00 migrating: foo bar 4517 4518 `[1:] 4519 4520 st := s.setupMigrationTest(c) 4521 defer st.Close() 4522 code, stdout, stderr := runStatus(c, "-m", "hosted", "--format", "tabular") 4523 c.Assert(code, gc.Equals, 0) 4524 c.Assert(string(stderr), gc.Equals, "Model \"hosted\" is empty.\n") 4525 4526 output := substituteFakeTimestamp(c, stdout, false) 4527 output = substituteSpacingBetweenTimestampAndNotes(c, output) 4528 c.Assert(string(output), gc.Equals, expected) 4529 } 4530 4531 func (s *StatusSuite) TestMigrationInProgressAndUpgradeAvailable(c *gc.C) { 4532 expected := ` 4533 Model Controller Cloud/Region Version SLA Timestamp Notes 4534 hosted kontroll dummy/dummy-region 1.2.3 unsupported 15:04:05+07:00 migrating: foo bar 4535 4536 `[1:] 4537 4538 st := s.setupMigrationTest(c) 4539 defer st.Close() 4540 4541 model, err := st.Model() 4542 c.Assert(err, jc.ErrorIsNil) 4543 err = model.UpdateLatestToolsVersion(nextVersion) 4544 c.Assert(err, jc.ErrorIsNil) 4545 4546 code, stdout, stderr := runStatus(c, "-m", "hosted", "--format", "tabular") 4547 c.Assert(code, gc.Equals, 0) 4548 c.Assert(string(stderr), gc.Equals, "Model \"hosted\" is empty.\n") 4549 4550 output := substituteFakeTimestamp(c, stdout, false) 4551 output = substituteSpacingBetweenTimestampAndNotes(c, output) 4552 c.Assert(string(output), gc.Equals, expected) 4553 } 4554 4555 func (s *StatusSuite) setupMigrationTest(c *gc.C) *state.State { 4556 const hostedModelName = "hosted" 4557 const statusText = "foo bar" 4558 4559 f := factory.NewFactory(s.BackingState, s.StatePool) 4560 hostedSt := f.MakeModel(c, &factory.ModelParams{ 4561 Name: hostedModelName, 4562 }) 4563 4564 mig, err := hostedSt.CreateMigration(state.MigrationSpec{ 4565 InitiatedBy: names.NewUserTag("admin"), 4566 TargetInfo: migration.TargetInfo{ 4567 ControllerTag: names.NewControllerTag(utils.MustNewUUID().String()), 4568 Addrs: []string{"1.2.3.4:5555", "4.3.2.1:6666"}, 4569 CACert: "cert", 4570 AuthTag: names.NewUserTag("user"), 4571 Password: "password", 4572 }, 4573 }) 4574 c.Assert(err, jc.ErrorIsNil) 4575 err = mig.SetStatusMessage(statusText) 4576 c.Assert(err, jc.ErrorIsNil) 4577 4578 return hostedSt 4579 } 4580 4581 type fakeAPIClient struct { 4582 statusReturn *params.FullStatus 4583 patternsUsed []string 4584 closeCalled bool 4585 } 4586 4587 func (a *fakeAPIClient) Status(patterns []string) (*params.FullStatus, error) { 4588 a.patternsUsed = patterns 4589 return a.statusReturn, nil 4590 } 4591 4592 func (a *fakeAPIClient) Close() error { 4593 a.closeCalled = true 4594 return nil 4595 } 4596 4597 func (s *StatusSuite) TestStatusWithFormatSummary(c *gc.C) { 4598 ctx := s.newContext(c) 4599 defer s.resetContext(c, ctx) 4600 steps := []stepper{ 4601 addMachine{machineId: "0", job: state.JobManageModel}, 4602 setAddresses{"0", network.NewAddresses("localhost")}, 4603 startAliveMachine{"0", "snowflake"}, 4604 setMachineStatus{"0", status.Started, ""}, 4605 addCharm{"wordpress"}, 4606 addCharm{"mysql"}, 4607 addCharm{"logging"}, 4608 addCharm{"riak"}, 4609 addRemoteApplication{name: "hosted-riak", url: "me/model.riak", charm: "riak", endpoints: []string{"endpoint"}}, 4610 addApplication{name: "wordpress", charm: "wordpress"}, 4611 setApplicationExposed{"wordpress", true}, 4612 addMachine{machineId: "1", job: state.JobHostUnits}, 4613 setAddresses{"1", network.NewAddresses("localhost")}, 4614 startAliveMachine{"1", ""}, 4615 setMachineStatus{"1", status.Started, ""}, 4616 addAliveUnit{"wordpress", "1"}, 4617 setAgentStatus{"wordpress/0", status.Idle, "", nil}, 4618 setUnitStatus{"wordpress/0", status.Active, "", nil}, 4619 addApplication{name: "mysql", charm: "mysql"}, 4620 setApplicationExposed{"mysql", true}, 4621 addMachine{machineId: "2", job: state.JobHostUnits}, 4622 setAddresses{"2", network.NewAddresses("10.0.2.1")}, 4623 startAliveMachine{"2", ""}, 4624 setMachineStatus{"2", status.Started, ""}, 4625 addAliveUnit{"mysql", "2"}, 4626 setAgentStatus{"mysql/0", status.Idle, "", nil}, 4627 setUnitStatus{"mysql/0", status.Active, "", nil}, 4628 addApplication{name: "logging", charm: "logging"}, 4629 setApplicationExposed{"logging", true}, 4630 relateApplications{"wordpress", "mysql", ""}, 4631 relateApplications{"wordpress", "logging", ""}, 4632 relateApplications{"mysql", "logging", ""}, 4633 addSubordinate{"wordpress/0", "logging"}, 4634 addSubordinate{"mysql/0", "logging"}, 4635 setUnitsAlive{"logging"}, 4636 setAgentStatus{"logging/0", status.Idle, "", nil}, 4637 setUnitStatus{"logging/0", status.Active, "", nil}, 4638 setAgentStatus{"logging/1", status.Error, "somehow lost in all those logs", nil}, 4639 } 4640 for _, s := range steps { 4641 s.step(c, ctx) 4642 } 4643 code, stdout, stderr := runStatus(c, "--format", "summary") 4644 c.Check(code, gc.Equals, 0) 4645 c.Check(string(stderr), gc.Equals, "") 4646 c.Assert(string(stdout), gc.Equals, ` 4647 Running on subnets: 127.0.0.1/8, 10.0.2.1/8 4648 Utilizing ports: 4649 # Machines: (3) 4650 started: 3 4651 4652 # Units: (4) 4653 active: 3 4654 error: 1 4655 4656 # Applications: (3) 4657 logging 1/1 exposed 4658 mysql 1/1 exposed 4659 wordpress 1/1 exposed 4660 4661 # Remote: (1) 4662 hosted-riak me/model.riak 4663 4664 `[1:]) 4665 } 4666 func (s *StatusSuite) TestStatusWithFormatOneline(c *gc.C) { 4667 ctx := s.newContext(c) 4668 defer s.resetContext(c, ctx) 4669 steps := []stepper{ 4670 addMachine{machineId: "0", job: state.JobManageModel}, 4671 setAddresses{"0", network.NewAddresses("10.0.0.1")}, 4672 startAliveMachine{"0", "snowflake"}, 4673 setMachineStatus{"0", status.Started, ""}, 4674 addCharm{"wordpress"}, 4675 addCharm{"mysql"}, 4676 addCharm{"logging"}, 4677 4678 addApplication{name: "wordpress", charm: "wordpress"}, 4679 setApplicationExposed{"wordpress", true}, 4680 addMachine{machineId: "1", job: state.JobHostUnits}, 4681 setAddresses{"1", network.NewAddresses("10.0.1.1")}, 4682 startAliveMachine{"1", ""}, 4683 setMachineStatus{"1", status.Started, ""}, 4684 addAliveUnit{"wordpress", "1"}, 4685 setAgentStatus{"wordpress/0", status.Idle, "", nil}, 4686 setUnitStatus{"wordpress/0", status.Active, "", nil}, 4687 4688 addApplication{name: "mysql", charm: "mysql"}, 4689 setApplicationExposed{"mysql", true}, 4690 addMachine{machineId: "2", job: state.JobHostUnits}, 4691 setAddresses{"2", network.NewAddresses("10.0.2.1")}, 4692 startAliveMachine{"2", ""}, 4693 setMachineStatus{"2", status.Started, ""}, 4694 addAliveUnit{"mysql", "2"}, 4695 setAgentStatus{"mysql/0", status.Idle, "", nil}, 4696 setUnitStatus{"mysql/0", status.Active, "", nil}, 4697 4698 addApplication{name: "logging", charm: "logging"}, 4699 setApplicationExposed{"logging", true}, 4700 4701 relateApplications{"wordpress", "mysql", ""}, 4702 relateApplications{"wordpress", "logging", ""}, 4703 relateApplications{"mysql", "logging", ""}, 4704 4705 addSubordinate{"wordpress/0", "logging"}, 4706 addSubordinate{"mysql/0", "logging"}, 4707 4708 setUnitsAlive{"logging"}, 4709 setAgentStatus{"logging/0", status.Idle, "", nil}, 4710 setUnitStatus{"logging/0", status.Active, "", nil}, 4711 setAgentStatus{"logging/1", status.Error, "somehow lost in all those logs", nil}, 4712 } 4713 4714 ctx.run(c, steps) 4715 4716 const expected = ` 4717 - mysql/0: 10.0.2.1 (agent:idle, workload:active) 4718 - logging/1: 10.0.2.1 (agent:idle, workload:error) 4719 - wordpress/0: 10.0.1.1 (agent:idle, workload:active) 4720 - logging/0: 10.0.1.1 (agent:idle, workload:active) 4721 ` 4722 assertOneLineStatus(c, expected) 4723 } 4724 4725 func assertOneLineStatus(c *gc.C, expected string) { 4726 code, stdout, stderr := runStatus(c, "--format", "oneline") 4727 c.Check(code, gc.Equals, 0) 4728 c.Check(string(stderr), gc.Equals, "") 4729 c.Assert(string(stdout), gc.Equals, expected) 4730 4731 c.Log(`Check that "short" is an alias for oneline.`) 4732 code, stdout, stderr = runStatus(c, "--format", "short") 4733 c.Check(code, gc.Equals, 0) 4734 c.Check(string(stderr), gc.Equals, "") 4735 c.Assert(string(stdout), gc.Equals, expected) 4736 4737 c.Log(`Check that "line" is an alias for oneline.`) 4738 code, stdout, stderr = runStatus(c, "--format", "line") 4739 c.Check(code, gc.Equals, 0) 4740 c.Check(string(stderr), gc.Equals, "") 4741 c.Assert(string(stdout), gc.Equals, expected) 4742 } 4743 4744 func (s *StatusSuite) prepareTabularData(c *gc.C) *context { 4745 ctx := s.newContext(c) 4746 steps := []stepper{ 4747 setToolsUpgradeAvailable{}, 4748 addMachine{machineId: "0", job: state.JobManageModel}, 4749 setAddresses{"0", network.NewAddresses("10.0.0.1")}, 4750 startMachineWithHardware{"0", instance.MustParseHardware("availability-zone=us-east-1a")}, 4751 setMachineStatus{"0", status.Started, ""}, 4752 addCharm{"wordpress"}, 4753 addCharm{"mysql"}, 4754 addCharm{"logging"}, 4755 addCharm{"riak"}, 4756 addRemoteApplication{name: "hosted-riak", url: "me/model.riak", charm: "riak", endpoints: []string{"endpoint"}}, 4757 addApplication{name: "wordpress", charm: "wordpress"}, 4758 setApplicationExposed{"wordpress", true}, 4759 addMachine{machineId: "1", job: state.JobHostUnits}, 4760 setAddresses{"1", network.NewAddresses("10.0.1.1")}, 4761 startAliveMachine{"1", "snowflake"}, 4762 setMachineStatus{"1", status.Started, ""}, 4763 addAliveUnit{"wordpress", "1"}, 4764 setAgentStatus{"wordpress/0", status.Idle, "", nil}, 4765 setUnitStatus{"wordpress/0", status.Active, "", nil}, 4766 setUnitTools{"wordpress/0", version.MustParseBinary("1.2.3-trusty-ppc")}, 4767 addApplication{name: "mysql", charm: "mysql"}, 4768 setApplicationExposed{"mysql", true}, 4769 addMachine{machineId: "2", job: state.JobHostUnits}, 4770 setAddresses{"2", network.NewAddresses("10.0.2.1")}, 4771 startAliveMachine{"2", ""}, 4772 setMachineStatus{"2", status.Started, ""}, 4773 addAliveUnit{"mysql", "2"}, 4774 setAgentStatus{"mysql/0", status.Idle, "", nil}, 4775 setUnitStatus{ 4776 "mysql/0", 4777 status.Maintenance, 4778 "installing all the things", nil}, 4779 setUnitTools{"mysql/0", version.MustParseBinary("1.2.3-trusty-ppc")}, 4780 addApplication{name: "logging", charm: "logging"}, 4781 setApplicationExposed{"logging", true}, 4782 relateApplications{"wordpress", "mysql", "suspended"}, 4783 relateApplications{"wordpress", "logging", ""}, 4784 relateApplications{"mysql", "logging", ""}, 4785 addSubordinate{"wordpress/0", "logging"}, 4786 addSubordinate{"mysql/0", "logging"}, 4787 setUnitsAlive{"logging"}, 4788 setAgentStatus{"logging/0", status.Idle, "", nil}, 4789 setUnitStatus{"logging/0", status.Active, "", nil}, 4790 setAgentStatus{"logging/1", status.Error, "somehow lost in all those logs", nil}, 4791 setUnitWorkloadVersion{"logging/1", "a bit too long, really"}, 4792 setUnitWorkloadVersion{"wordpress/0", "4.5.3"}, 4793 setUnitWorkloadVersion{"mysql/0", "5.7.13"}, 4794 setUnitAsLeader{"mysql/0"}, 4795 setUnitAsLeader{"logging/1"}, 4796 setUnitAsLeader{"wordpress/0"}, 4797 addMachine{machineId: "3", job: state.JobHostUnits}, 4798 setAddresses{"3", network.NewAddresses("10.0.3.1")}, 4799 startAliveMachine{"3", ""}, 4800 setMachineStatus{"3", status.Started, ""}, 4801 setMachineInstanceStatus{"3", status.Started, "I am number three"}, 4802 4803 addApplicationOffer{name: "hosted-mysql", applicationName: "mysql", owner: "admin", endpoints: []string{"server"}}, 4804 addRemoteApplication{name: "remote-wordpress", charm: "wordpress", endpoints: []string{"db"}, isConsumerProxy: true}, 4805 relateApplications{"remote-wordpress", "mysql", ""}, 4806 addOfferConnection{sourceModelUUID: coretesting.ModelTag.Id(), name: "hosted-mysql", username: "fred", relationKey: "remote-wordpress:db mysql:server"}, 4807 } 4808 for _, s := range steps { 4809 s.step(c, ctx) 4810 } 4811 return ctx 4812 } 4813 4814 func (s *StatusSuite) TestStatusWithFormatTabular(c *gc.C) { 4815 ctx := s.prepareTabularData(c) 4816 defer s.resetContext(c, ctx) 4817 code, stdout, stderr := runStatus(c, "--format", "tabular", "--relations") 4818 c.Check(code, gc.Equals, 0) 4819 c.Check(string(stderr), gc.Equals, "") 4820 expected := ` 4821 Model Controller Cloud/Region Version SLA Timestamp Notes 4822 controller kontroll dummy/dummy-region 1.2.3 unsupported 15:04:05+07:00 upgrade available: 1.2.4 4823 4824 SAAS Status Store URL 4825 hosted-riak unknown local me/model.riak 4826 4827 App Version Status Scale Charm Store Rev OS Notes 4828 logging a bit too lo... error 2 logging jujucharms 1 ubuntu exposed 4829 mysql 5.7.13 maintenance 1 mysql jujucharms 1 ubuntu exposed 4830 wordpress 4.5.3 active 1 wordpress jujucharms 3 ubuntu exposed 4831 4832 Unit Workload Agent Machine Public address Ports Message 4833 mysql/0* maintenance idle 2 10.0.2.1 installing all the things 4834 logging/1* error idle 10.0.2.1 somehow lost in all those logs 4835 wordpress/0* active idle 1 10.0.1.1 4836 logging/0 active idle 10.0.1.1 4837 4838 Machine State DNS Inst id Series AZ Message 4839 0 started 10.0.0.1 controller-0 quantal us-east-1a 4840 1 started 10.0.1.1 snowflake quantal 4841 2 started 10.0.2.1 controller-2 quantal 4842 3 started 10.0.3.1 controller-3 quantal I am number three 4843 4844 Offer Application Charm Rev Connected Endpoint Interface Role 4845 hosted-mysql mysql mysql 1 1/1 server mysql provider 4846 4847 Relation provider Requirer Interface Type Message 4848 mysql:juju-info logging:info juju-info subordinate 4849 mysql:server wordpress:db mysql regular suspended 4850 wordpress:logging-dir logging:logging-directory logging subordinate 4851 4852 `[1:] 4853 4854 output := substituteFakeTimestamp(c, stdout, false) 4855 output = substituteSpacingBetweenTimestampAndNotes(c, output) 4856 c.Assert(string(output), gc.Equals, expected) 4857 } 4858 4859 func (s *StatusSuite) TestStatusWithFormatYaml(c *gc.C) { 4860 ctx := s.prepareTabularData(c) 4861 defer s.resetContext(c, ctx) 4862 code, stdout, stderr := runStatus(c, "--format", "yaml") 4863 c.Check(code, gc.Equals, 0) 4864 c.Check(string(stderr), gc.Equals, "") 4865 c.Assert(string(stdout), jc.Contains, "display-name: snowflake") 4866 } 4867 4868 func (s *StatusSuite) TestStatusWithFormatJson(c *gc.C) { 4869 ctx := s.prepareTabularData(c) 4870 defer s.resetContext(c, ctx) 4871 code, stdout, stderr := runStatus(c, "--format", "json") 4872 c.Check(code, gc.Equals, 0) 4873 c.Check(string(stderr), gc.Equals, "") 4874 c.Assert(string(stdout), jc.Contains, `"display-name":"snowflake"`) 4875 } 4876 4877 func (s *StatusSuite) TestFormatTabularHookActionName(c *gc.C) { 4878 status := formattedStatus{ 4879 Applications: map[string]applicationStatus{ 4880 "foo": { 4881 Units: map[string]unitStatus{ 4882 "foo/0": { 4883 JujuStatusInfo: statusInfoContents{ 4884 Current: status.Executing, 4885 Message: "running config-changed hook", 4886 }, 4887 WorkloadStatusInfo: statusInfoContents{ 4888 Current: status.Maintenance, 4889 Message: "doing some work", 4890 }, 4891 }, 4892 "foo/1": { 4893 JujuStatusInfo: statusInfoContents{ 4894 Current: status.Executing, 4895 Message: "running action backup database", 4896 }, 4897 WorkloadStatusInfo: statusInfoContents{ 4898 Current: status.Maintenance, 4899 Message: "doing some work", 4900 }, 4901 }, 4902 }, 4903 }, 4904 }, 4905 } 4906 out := &bytes.Buffer{} 4907 err := FormatTabular(out, false, status) 4908 c.Assert(err, jc.ErrorIsNil) 4909 c.Assert(out.String(), gc.Equals, ` 4910 Model Controller Cloud/Region Version 4911 4912 4913 App Version Status Scale Charm Store Rev OS Notes 4914 foo 2 0 4915 4916 Unit Workload Agent Machine Public address Ports Message 4917 foo/0 maintenance executing (config-changed) doing some work 4918 foo/1 maintenance executing (backup database) doing some work 4919 `[1:]) 4920 } 4921 4922 func (s *StatusSuite) TestFormatTabularCAASModel(c *gc.C) { 4923 status := formattedStatus{ 4924 Model: modelStatus{ 4925 Type: "caas", 4926 }, 4927 Applications: map[string]applicationStatus{ 4928 "foo": { 4929 Scale: 2, 4930 Address: "54.32.1.2", 4931 Units: map[string]unitStatus{ 4932 "foo/0": { 4933 JujuStatusInfo: statusInfoContents{ 4934 Current: status.Allocating, 4935 }, 4936 WorkloadStatusInfo: statusInfoContents{ 4937 Current: status.Active, 4938 }, 4939 }, 4940 "foo/1": { 4941 Address: "10.0.0.1", 4942 OpenedPorts: []string{"80/TCP"}, 4943 JujuStatusInfo: statusInfoContents{ 4944 Current: status.Running, 4945 }, 4946 WorkloadStatusInfo: statusInfoContents{ 4947 Current: status.Active, 4948 }, 4949 }, 4950 }, 4951 }, 4952 }, 4953 } 4954 out := &bytes.Buffer{} 4955 err := FormatTabular(out, false, status) 4956 c.Assert(err, jc.ErrorIsNil) 4957 c.Assert(out.String(), gc.Equals, ` 4958 Model Controller Cloud/Region Version 4959 4960 4961 App Version Status Scale Charm Store Rev OS Address Notes 4962 foo 1/2 0 54.32.1.2 4963 4964 Unit Workload Agent Address Ports Message 4965 foo/0 active allocating 4966 foo/1 active running 10.0.0.1 80/TCP 4967 `[1:]) 4968 } 4969 4970 func (s *StatusSuite) TestFormatTabularStatusNotes(c *gc.C) { 4971 fStatus := formattedStatus{ 4972 Model: modelStatus{ 4973 Type: "caas", 4974 }, 4975 Applications: map[string]applicationStatus{ 4976 "foo": { 4977 Scale: 1, 4978 Address: "54.32.1.2", 4979 StatusInfo: statusInfoContents{ 4980 Message: "Error: ImagePullBackOff", 4981 }, 4982 Units: map[string]unitStatus{ 4983 "foo/0": { 4984 Address: "10.0.0.1", 4985 OpenedPorts: []string{"80/TCP"}, 4986 JujuStatusInfo: statusInfoContents{ 4987 Current: status.Allocating, 4988 }, 4989 WorkloadStatusInfo: statusInfoContents{ 4990 Current: status.Waiting, 4991 }, 4992 }, 4993 }, 4994 }, 4995 }, 4996 } 4997 out := &bytes.Buffer{} 4998 err := FormatTabular(out, false, fStatus) 4999 c.Assert(err, jc.ErrorIsNil) 5000 c.Assert(out.String(), gc.Equals, ` 5001 Model Controller Cloud/Region Version 5002 5003 5004 App Version Status Scale Charm Store Rev OS Address Notes 5005 foo 0/1 0 54.32.1.2 Error: ImagePullBackOff 5006 5007 Unit Workload Agent Address Ports Message 5008 foo/0 waiting allocating 10.0.0.1 80/TCP 5009 `[1:]) 5010 } 5011 5012 func (s *StatusSuite) TestFormatTabularStatusNotesIAAS(c *gc.C) { 5013 status := formattedStatus{ 5014 Applications: map[string]applicationStatus{ 5015 "foo": { 5016 Address: "54.32.1.2", 5017 StatusInfo: statusInfoContents{ 5018 Message: "Error: ImagePullBackOff", 5019 }, 5020 Units: map[string]unitStatus{ 5021 "foo/0": { 5022 Address: "10.0.0.1", 5023 OpenedPorts: []string{"80/TCP"}, 5024 JujuStatusInfo: statusInfoContents{ 5025 Current: status.Idle, 5026 }, 5027 WorkloadStatusInfo: statusInfoContents{ 5028 Current: status.Waiting, 5029 }, 5030 }, 5031 }, 5032 }, 5033 }, 5034 } 5035 out := &bytes.Buffer{} 5036 err := FormatTabular(out, false, status) 5037 c.Assert(err, jc.ErrorIsNil) 5038 c.Assert(out.String(), gc.Equals, ` 5039 Model Controller Cloud/Region Version 5040 5041 5042 App Version Status Scale Charm Store Rev OS Notes 5043 foo 1 0 5044 5045 Unit Workload Agent Machine Public address Ports Message 5046 foo/0 waiting idle 80/TCP 5047 `[1:]) 5048 } 5049 5050 func (s *StatusSuite) TestStatusWithNilStatusAPI(c *gc.C) { 5051 ctx := s.newContext(c) 5052 defer s.resetContext(c, ctx) 5053 steps := []stepper{ 5054 addMachine{machineId: "0", job: state.JobManageModel}, 5055 setAddresses{"0", network.NewAddresses("10.0.0.1")}, 5056 startAliveMachine{"0", ""}, 5057 setMachineStatus{"0", status.Started, ""}, 5058 } 5059 5060 for _, s := range steps { 5061 s.step(c, ctx) 5062 } 5063 5064 client := fakeAPIClient{} 5065 var status = client.Status 5066 s.PatchValue(&status, func(_ []string) (*params.FullStatus, error) { 5067 return nil, nil 5068 }) 5069 s.PatchValue(&newAPIClientForStatus, func(_ *statusCommand) (statusAPI, error) { 5070 return &client, nil 5071 }) 5072 5073 code, _, stderr := runStatus(c, "--format", "tabular") 5074 c.Check(code, gc.Equals, 1) 5075 c.Check(string(stderr), gc.Equals, "ERROR unable to obtain the current status\n") 5076 } 5077 5078 func (s *StatusSuite) TestFormatTabularMetering(c *gc.C) { 5079 status := formattedStatus{ 5080 Applications: map[string]applicationStatus{ 5081 "foo": { 5082 Units: map[string]unitStatus{ 5083 "foo/0": { 5084 MeterStatus: &meterStatus{ 5085 Color: "strange", 5086 Message: "warning: stable strangelets", 5087 }, 5088 }, 5089 "foo/1": { 5090 MeterStatus: &meterStatus{ 5091 Color: "up", 5092 Message: "things are looking up", 5093 }, 5094 }, 5095 }, 5096 }, 5097 }, 5098 } 5099 out := &bytes.Buffer{} 5100 err := FormatTabular(out, false, status) 5101 c.Assert(err, jc.ErrorIsNil) 5102 c.Assert(out.String(), gc.Equals, ` 5103 Model Controller Cloud/Region Version 5104 5105 5106 App Version Status Scale Charm Store Rev OS Notes 5107 foo 0/2 0 5108 5109 Unit Workload Agent Machine Public address Ports Message 5110 foo/0 5111 foo/1 5112 5113 Entity Meter status Message 5114 foo/0 strange warning: stable strangelets 5115 foo/1 up things are looking up 5116 `[1:]) 5117 } 5118 5119 // 5120 // Filtering Feature 5121 // 5122 5123 func (s *StatusSuite) FilteringTestSetup(c *gc.C) *context { 5124 ctx := s.newContext(c) 5125 5126 steps := []stepper{ 5127 // Given a machine is started 5128 // And the machine's ID is "0" 5129 // And the machine's job is to manage the environment 5130 addMachine{machineId: "0", job: state.JobManageModel}, 5131 startAliveMachine{"0", ""}, 5132 setMachineStatus{"0", status.Started, ""}, 5133 // And the machine's address is "10.0.0.1" 5134 setAddresses{"0", network.NewAddresses("10.0.0.1")}, 5135 // And a container is started 5136 // And the container's ID is "0/lxd/0" 5137 addContainer{"0", "0/lxd/0", state.JobHostUnits}, 5138 5139 // And the "wordpress" charm is available 5140 addCharm{"wordpress"}, 5141 addApplication{name: "wordpress", charm: "wordpress"}, 5142 // And the "mysql" charm is available 5143 addCharm{"mysql"}, 5144 addApplication{name: "mysql", charm: "mysql"}, 5145 // And the "logging" charm is available 5146 addCharm{"logging"}, 5147 5148 // And a machine is started 5149 // And the machine's ID is "1" 5150 // And the machine's job is to host units 5151 addMachine{machineId: "1", job: state.JobHostUnits}, 5152 startAliveMachine{"1", ""}, 5153 setMachineStatus{"1", status.Started, ""}, 5154 // And the machine's address is "10.0.1.1" 5155 setAddresses{"1", network.NewAddresses("10.0.1.1")}, 5156 // And a unit of "wordpress" is deployed to machine "1" 5157 addAliveUnit{"wordpress", "1"}, 5158 // And the unit is started 5159 setAgentStatus{"wordpress/0", status.Idle, "", nil}, 5160 setUnitStatus{"wordpress/0", status.Active, "", nil}, 5161 // And a machine is started 5162 5163 // And the machine's ID is "2" 5164 // And the machine's job is to host units 5165 addMachine{machineId: "2", job: state.JobHostUnits}, 5166 startAliveMachine{"2", ""}, 5167 setMachineStatus{"2", status.Started, ""}, 5168 // And the machine's address is "10.0.2.1" 5169 setAddresses{"2", network.NewAddresses("10.0.2.1")}, 5170 // And a unit of "mysql" is deployed to machine "2" 5171 addAliveUnit{"mysql", "2"}, 5172 // And the unit is started 5173 setAgentStatus{"mysql/0", status.Idle, "", nil}, 5174 setUnitStatus{"mysql/0", status.Active, "", nil}, 5175 // And the "logging" application is added 5176 addApplication{name: "logging", charm: "logging"}, 5177 // And the application is exposed 5178 setApplicationExposed{"logging", true}, 5179 // And the "wordpress" application is related to the "mysql" application 5180 relateApplications{"wordpress", "mysql", ""}, 5181 // And the "wordpress" application is related to the "logging" application 5182 relateApplications{"wordpress", "logging", ""}, 5183 // And the "mysql" application is related to the "logging" application 5184 relateApplications{"mysql", "logging", ""}, 5185 // And the "logging" application is a subordinate to unit 0 of the "wordpress" application 5186 addSubordinate{"wordpress/0", "logging"}, 5187 setAgentStatus{"logging/0", status.Idle, "", nil}, 5188 setUnitStatus{"logging/0", status.Active, "", nil}, 5189 // And the "logging" application is a subordinate to unit 0 of the "mysql" application 5190 addSubordinate{"mysql/0", "logging"}, 5191 setAgentStatus{"logging/1", status.Idle, "", nil}, 5192 setUnitStatus{"logging/1", status.Active, "", nil}, 5193 setUnitsAlive{"logging"}, 5194 } 5195 5196 ctx.run(c, steps) 5197 return ctx 5198 } 5199 5200 // Scenario: One unit is in an errored state and user filters to active 5201 func (s *StatusSuite) TestFilterToActive(c *gc.C) { 5202 ctx := s.FilteringTestSetup(c) 5203 defer s.resetContext(c, ctx) 5204 5205 // Given unit 1 of the "logging" application has an error 5206 setAgentStatus{"logging/1", status.Error, "mock error", nil}.step(c, ctx) 5207 // And unit 0 of the "mysql" application has an error 5208 setAgentStatus{"mysql/0", status.Error, "mock error", nil}.step(c, ctx) 5209 // When I run juju status --format oneline started 5210 _, stdout, stderr := runStatus(c, "--format", "oneline", "active") 5211 c.Assert(string(stderr), gc.Equals, "") 5212 // Then I should receive output prefixed with: 5213 const expected = ` 5214 5215 - wordpress/0: 10.0.1.1 (agent:idle, workload:active) 5216 - logging/0: 10.0.1.1 (agent:idle, workload:active) 5217 ` 5218 c.Assert(string(stdout), gc.Equals, expected[1:]) 5219 } 5220 5221 // Scenario: user filters to a single machine 5222 func (s *StatusSuite) TestFilterToMachine(c *gc.C) { 5223 ctx := s.FilteringTestSetup(c) 5224 defer s.resetContext(c, ctx) 5225 5226 // When I run juju status --format oneline 1 5227 _, stdout, stderr := runStatus(c, "--format", "oneline", "1") 5228 c.Assert(string(stderr), gc.Equals, "") 5229 // Then I should receive output prefixed with: 5230 const expected = ` 5231 5232 - wordpress/0: 10.0.1.1 (agent:idle, workload:active) 5233 - logging/0: 10.0.1.1 (agent:idle, workload:active) 5234 ` 5235 c.Assert(string(stdout), gc.Equals, expected[1:]) 5236 } 5237 5238 // Scenario: user filters to a machine, shows containers 5239 func (s *StatusSuite) TestFilterToMachineShowsContainer(c *gc.C) { 5240 ctx := s.FilteringTestSetup(c) 5241 defer s.resetContext(c, ctx) 5242 5243 // When I run juju status --format yaml 0 5244 _, stdout, stderr := runStatus(c, "--format", "yaml", "0") 5245 c.Assert(string(stderr), gc.Equals, "") 5246 // Then I should receive output matching: 5247 const expected = "(.|\n)*machines:(.|\n)*\"0\"(.|\n)*0/lxd/0(.|\n)*" 5248 c.Assert(string(stdout), gc.Matches, expected) 5249 } 5250 5251 // Scenario: user filters to a container 5252 func (s *StatusSuite) TestFilterToContainer(c *gc.C) { 5253 ctx := s.FilteringTestSetup(c) 5254 defer s.resetContext(c, ctx) 5255 5256 // When I run juju status --format yaml 0/lxd/0 5257 _, stdout, stderr := runStatus(c, "--format", "yaml", "0/lxd/0") 5258 c.Assert(string(stderr), gc.Equals, "") 5259 5260 const expected = "" + 5261 "model:\n" + 5262 " name: controller\n" + 5263 " type: iaas\n" + 5264 " controller: kontroll\n" + 5265 " cloud: dummy\n" + 5266 " region: dummy-region\n" + 5267 " version: 1.2.3\n" + 5268 " model-status:\n" + 5269 " current: available\n" + 5270 " since: 01 Apr 15 01:23+10:00\n" + 5271 " sla: unsupported\n" + 5272 "machines:\n" + 5273 " \"0\":\n" + 5274 " juju-status:\n" + 5275 " current: started\n" + 5276 " since: 01 Apr 15 01:23+10:00\n" + 5277 " dns-name: 10.0.0.1\n" + 5278 " ip-addresses:\n" + 5279 " - 10.0.0.1\n" + 5280 " instance-id: controller-0\n" + 5281 " machine-status:\n" + 5282 " current: pending\n" + 5283 " since: 01 Apr 15 01:23+10:00\n" + 5284 " series: quantal\n" + 5285 " network-interfaces:\n" + 5286 " eth0:\n" + 5287 " ip-addresses:\n" + 5288 " - 10.0.0.1\n" + 5289 " mac-address: aa:bb:cc:dd:ee:ff\n" + 5290 " is-up: true\n" + 5291 " containers:\n" + 5292 " 0/lxd/0:\n" + 5293 " juju-status:\n" + 5294 " current: pending\n" + 5295 " since: 01 Apr 15 01:23+10:00\n" + 5296 " instance-id: pending\n" + 5297 " machine-status:\n" + 5298 " current: pending\n" + 5299 " since: 01 Apr 15 01:23+10:00\n" + 5300 " series: quantal\n" + 5301 " hardware: arch=amd64 cores=1 mem=1024M root-disk=8192M\n" + 5302 " controller-member-status: adding-vote\n" + 5303 "applications: {}\n" + 5304 "storage: {}\n" + 5305 "controller:\n" + 5306 " timestamp: 15:04:05+07:00\n" 5307 5308 out := substituteFakeTime(c, "since", stdout, ctx.expectIsoTime) 5309 out = substituteFakeTimestamp(c, out, ctx.expectIsoTime) 5310 c.Assert(string(out), gc.Equals, expected) 5311 } 5312 5313 // Scenario: One unit is in an errored state and user filters to errored 5314 func (s *StatusSuite) TestFilterToErrored(c *gc.C) { 5315 ctx := s.FilteringTestSetup(c) 5316 defer s.resetContext(c, ctx) 5317 5318 // Given unit 1 of the "logging" application has an error 5319 setAgentStatus{"logging/1", status.Error, "mock error", nil}.step(c, ctx) 5320 // When I run juju status --format oneline error 5321 _, stdout, stderr := runStatus(c, "--format", "oneline", "error") 5322 c.Assert(stderr, gc.IsNil) 5323 // Then I should receive output prefixed with: 5324 const expected = ` 5325 5326 - mysql/0: 10.0.2.1 (agent:idle, workload:active) 5327 - logging/1: 10.0.2.1 (agent:idle, workload:error) 5328 ` 5329 c.Assert(string(stdout), gc.Equals, expected[1:]) 5330 } 5331 5332 // Scenario: User filters to mysql application 5333 func (s *StatusSuite) TestFilterToApplication(c *gc.C) { 5334 ctx := s.FilteringTestSetup(c) 5335 defer s.resetContext(c, ctx) 5336 5337 // When I run juju status --format oneline error 5338 _, stdout, stderr := runStatus(c, "--format", "oneline", "mysql") 5339 c.Assert(stderr, gc.IsNil) 5340 // Then I should receive output prefixed with: 5341 const expected = ` 5342 5343 - mysql/0: 10.0.2.1 (agent:idle, workload:active) 5344 - logging/1: 10.0.2.1 (agent:idle, workload:active) 5345 ` 5346 5347 c.Assert(string(stdout), gc.Equals, expected[1:]) 5348 } 5349 5350 // Scenario: User filters to exposed applications 5351 func (s *StatusSuite) TestFilterToExposedApplication(c *gc.C) { 5352 ctx := s.FilteringTestSetup(c) 5353 defer s.resetContext(c, ctx) 5354 5355 // Given unit 1 of the "mysql" application is exposed 5356 setApplicationExposed{"mysql", true}.step(c, ctx) 5357 // And the logging application is not exposed 5358 setApplicationExposed{"logging", false}.step(c, ctx) 5359 // And the wordpress application is not exposed 5360 setApplicationExposed{"wordpress", false}.step(c, ctx) 5361 // When I run juju status --format oneline exposed 5362 _, stdout, stderr := runStatus(c, "--format", "oneline", "exposed") 5363 c.Assert(stderr, gc.IsNil) 5364 // Then I should receive output prefixed with: 5365 const expected = ` 5366 5367 - mysql/0: 10.0.2.1 (agent:idle, workload:active) 5368 - logging/1: 10.0.2.1 (agent:idle, workload:active) 5369 ` 5370 c.Assert(string(stdout), gc.Equals, expected[1:]) 5371 } 5372 5373 // Scenario: User filters to non-exposed applications 5374 func (s *StatusSuite) TestFilterToNotExposedApplication(c *gc.C) { 5375 ctx := s.FilteringTestSetup(c) 5376 defer s.resetContext(c, ctx) 5377 5378 setApplicationExposed{"mysql", true}.step(c, ctx) 5379 // When I run juju status --format oneline not exposed 5380 _, stdout, stderr := runStatus(c, "--format", "oneline", "not", "exposed") 5381 c.Assert(stderr, gc.IsNil) 5382 // Then I should receive output prefixed with: 5383 const expected = ` 5384 5385 - wordpress/0: 10.0.1.1 (agent:idle, workload:active) 5386 - logging/0: 10.0.1.1 (agent:idle, workload:active) 5387 ` 5388 c.Assert(string(stdout), gc.Equals, expected[1:]) 5389 } 5390 5391 // Scenario: Filtering on Subnets 5392 func (s *StatusSuite) TestFilterOnSubnet(c *gc.C) { 5393 ctx := s.FilteringTestSetup(c) 5394 defer s.resetContext(c, ctx) 5395 5396 // Given the address for machine "1" is "localhost" 5397 setAddresses{"1", network.NewAddresses("localhost", "127.0.0.1")}.step(c, ctx) 5398 // And the address for machine "2" is "10.0.2.1" 5399 setAddresses{"2", network.NewAddresses("10.0.2.1")}.step(c, ctx) 5400 // When I run juju status --format oneline 127.0.0.1 5401 _, stdout, stderr := runStatus(c, "--format", "oneline", "127.0.0.1") 5402 c.Assert(stderr, gc.IsNil) 5403 // Then I should receive output prefixed with: 5404 const expected = ` 5405 5406 - wordpress/0: localhost (agent:idle, workload:active) 5407 - logging/0: localhost (agent:idle, workload:active) 5408 ` 5409 c.Assert(string(stdout), gc.Equals, expected[1:]) 5410 } 5411 5412 // Scenario: Filtering on Ports 5413 func (s *StatusSuite) TestFilterOnPorts(c *gc.C) { 5414 ctx := s.FilteringTestSetup(c) 5415 defer s.resetContext(c, ctx) 5416 5417 // Given the address for machine "1" is "localhost" 5418 setAddresses{"1", network.NewAddresses("localhost")}.step(c, ctx) 5419 // And the address for machine "2" is "10.0.2.1" 5420 setAddresses{"2", network.NewAddresses("10.0.2.1")}.step(c, ctx) 5421 openUnitPort{"wordpress/0", "tcp", 80}.step(c, ctx) 5422 // When I run juju status --format oneline 80/tcp 5423 _, stdout, stderr := runStatus(c, "--format", "oneline", "80/tcp") 5424 c.Assert(stderr, gc.IsNil) 5425 // Then I should receive output prefixed with: 5426 const expected = ` 5427 5428 - wordpress/0: localhost (agent:idle, workload:active) 80/tcp 5429 - logging/0: localhost (agent:idle, workload:active) 5430 ` 5431 c.Assert(string(stdout), gc.Equals, expected[1:]) 5432 } 5433 5434 // Scenario: User filters out a parent, but not its subordinate 5435 func (s *StatusSuite) TestFilterParentButNotSubordinate(c *gc.C) { 5436 ctx := s.FilteringTestSetup(c) 5437 defer s.resetContext(c, ctx) 5438 5439 // When I run juju status --format oneline 80/tcp 5440 _, stdout, stderr := runStatus(c, "--format", "oneline", "logging") 5441 c.Assert(stderr, gc.IsNil) 5442 // Then I should receive output prefixed with: 5443 const expected = ` 5444 5445 - mysql/0: 10.0.2.1 (agent:idle, workload:active) 5446 - logging/1: 10.0.2.1 (agent:idle, workload:active) 5447 - wordpress/0: 10.0.1.1 (agent:idle, workload:active) 5448 - logging/0: 10.0.1.1 (agent:idle, workload:active) 5449 ` 5450 c.Assert(string(stdout), gc.Equals, expected[1:]) 5451 } 5452 5453 // Scenario: User filters out a subordinate, but not its parent 5454 func (s *StatusSuite) TestFilterSubordinateButNotParent(c *gc.C) { 5455 ctx := s.FilteringTestSetup(c) 5456 defer s.resetContext(c, ctx) 5457 5458 // Given the wordpress application is exposed 5459 setApplicationExposed{"wordpress", true}.step(c, ctx) 5460 // When I run juju status --format oneline not exposed 5461 _, stdout, stderr := runStatus(c, "--format", "oneline", "not", "exposed") 5462 c.Assert(stderr, gc.IsNil) 5463 // Then I should receive output prefixed with: 5464 const expected = ` 5465 5466 - mysql/0: 10.0.2.1 (agent:idle, workload:active) 5467 - logging/1: 10.0.2.1 (agent:idle, workload:active) 5468 ` 5469 c.Assert(string(stdout), gc.Equals, expected[1:]) 5470 } 5471 5472 func (s *StatusSuite) TestFilterMultipleHomogenousPatterns(c *gc.C) { 5473 ctx := s.FilteringTestSetup(c) 5474 defer s.resetContext(c, ctx) 5475 5476 _, stdout, stderr := runStatus(c, "--format", "oneline", "wordpress/0", "mysql/0") 5477 c.Assert(stderr, gc.IsNil) 5478 // Then I should receive output prefixed with: 5479 const expected = ` 5480 5481 - mysql/0: 10.0.2.1 (agent:idle, workload:active) 5482 - logging/1: 10.0.2.1 (agent:idle, workload:active) 5483 - wordpress/0: 10.0.1.1 (agent:idle, workload:active) 5484 - logging/0: 10.0.1.1 (agent:idle, workload:active) 5485 ` 5486 c.Assert(string(stdout), gc.Equals, expected[1:]) 5487 } 5488 5489 func (s *StatusSuite) TestFilterMultipleHeterogenousPatterns(c *gc.C) { 5490 ctx := s.FilteringTestSetup(c) 5491 defer s.resetContext(c, ctx) 5492 5493 _, stdout, stderr := runStatus(c, "--format", "oneline", "wordpress/0", "active") 5494 c.Assert(stderr, gc.IsNil) 5495 // Then I should receive output prefixed with: 5496 const expected = ` 5497 5498 - mysql/0: 10.0.2.1 (agent:idle, workload:active) 5499 - logging/1: 10.0.2.1 (agent:idle, workload:active) 5500 - wordpress/0: 10.0.1.1 (agent:idle, workload:active) 5501 - logging/0: 10.0.1.1 (agent:idle, workload:active) 5502 ` 5503 c.Assert(string(stdout), gc.Equals, expected[1:]) 5504 } 5505 5506 // TestSummaryStatusWithUnresolvableDns is result of bug# 1410320. 5507 func (s *StatusSuite) TestSummaryStatusWithUnresolvableDns(c *gc.C) { 5508 formatter := &summaryFormatter{} 5509 formatter.resolveAndTrackIp("invalidDns") 5510 // Test should not panic. 5511 } 5512 5513 func initStatusCommand(args ...string) (*statusCommand, error) { 5514 com := &statusCommand{} 5515 return com, cmdtesting.InitCommand(modelcmd.Wrap(com), args) 5516 } 5517 5518 var statusInitTests = []struct { 5519 args []string 5520 envVar string 5521 isoTime bool 5522 err string 5523 }{ 5524 { 5525 isoTime: false, 5526 }, { 5527 args: []string{"--utc"}, 5528 isoTime: true, 5529 }, { 5530 envVar: "true", 5531 isoTime: true, 5532 }, { 5533 envVar: "foo", 5534 err: "invalid JUJU_STATUS_ISO_TIME env var, expected true|false.*", 5535 }, 5536 } 5537 5538 func (*StatusSuite) TestStatusCommandInit(c *gc.C) { 5539 defer os.Setenv(osenv.JujuStatusIsoTimeEnvKey, os.Getenv(osenv.JujuStatusIsoTimeEnvKey)) 5540 5541 for i, t := range statusInitTests { 5542 c.Logf("test %d", i) 5543 os.Setenv(osenv.JujuStatusIsoTimeEnvKey, t.envVar) 5544 com, err := initStatusCommand(t.args...) 5545 if t.err != "" { 5546 c.Check(err, gc.ErrorMatches, t.err) 5547 } else { 5548 c.Check(err, jc.ErrorIsNil) 5549 } 5550 c.Check(com.isoTime, gc.DeepEquals, t.isoTime) 5551 } 5552 } 5553 5554 var statusTimeTest = test( 5555 "status generates timestamps as UTC in ISO format", 5556 addMachine{machineId: "0", job: state.JobManageModel}, 5557 setAddresses{"0", network.NewAddresses("10.0.0.1")}, 5558 startAliveMachine{"0", ""}, 5559 setMachineStatus{"0", status.Started, ""}, 5560 addCharm{"dummy"}, 5561 addApplication{name: "dummy-application", charm: "dummy"}, 5562 5563 addMachine{machineId: "1", job: state.JobHostUnits}, 5564 startAliveMachine{"1", ""}, 5565 setAddresses{"1", network.NewAddresses("10.0.1.1")}, 5566 setMachineStatus{"1", status.Started, ""}, 5567 5568 addAliveUnit{"dummy-application", "1"}, 5569 expect{ 5570 what: "add two units, one alive (in error state), one started", 5571 output: M{ 5572 "model": M{ 5573 "name": "controller", 5574 "type": "iaas", 5575 "controller": "kontroll", 5576 "cloud": "dummy", 5577 "region": "dummy-region", 5578 "version": "1.2.3", 5579 "model-status": M{ 5580 "current": "available", 5581 "since": "01 Apr 15 01:23+10:00", 5582 }, 5583 "sla": "unsupported", 5584 }, 5585 "machines": M{ 5586 "0": machine0, 5587 "1": machine1, 5588 }, 5589 "applications": M{ 5590 "dummy-application": dummyCharm(M{ 5591 "application-status": M{ 5592 "current": "waiting", 5593 "message": "waiting for machine", 5594 "since": "01 Apr 15 01:23+10:00", 5595 }, 5596 "units": M{ 5597 "dummy-application/0": M{ 5598 "machine": "1", 5599 "workload-status": M{ 5600 "current": "waiting", 5601 "message": "waiting for machine", 5602 "since": "01 Apr 15 01:23+10:00", 5603 }, 5604 "juju-status": M{ 5605 "current": "allocating", 5606 "since": "01 Apr 15 01:23+10:00", 5607 }, 5608 "public-address": "10.0.1.1", 5609 }, 5610 }, 5611 }), 5612 }, 5613 "storage": M{}, 5614 "controller": M{ 5615 "timestamp": "15:04:05", 5616 }, 5617 }, 5618 }, 5619 ) 5620 5621 func (s *StatusSuite) TestIsoTimeFormat(c *gc.C) { 5622 func(t testCase) { 5623 // Prepare context and run all steps to setup. 5624 ctx := s.newContext(c) 5625 ctx.expectIsoTime = true 5626 defer s.resetContext(c, ctx) 5627 ctx.run(c, t.steps) 5628 }(statusTimeTest) 5629 } 5630 5631 func (s *StatusSuite) TestFormatProvisioningError(c *gc.C) { 5632 now := time.Now() 5633 status := ¶ms.FullStatus{ 5634 Model: params.ModelStatusInfo{ 5635 CloudTag: "cloud-dummy", 5636 }, 5637 Machines: map[string]params.MachineStatus{ 5638 "1": { 5639 AgentStatus: params.DetailedStatus{ 5640 Status: "error", 5641 Info: "<error while provisioning>", 5642 }, 5643 InstanceId: "pending", 5644 InstanceStatus: params.DetailedStatus{}, 5645 Series: "trusty", 5646 Id: "1", 5647 Jobs: []multiwatcher.MachineJob{"JobHostUnits"}, 5648 }, 5649 }, 5650 ControllerTimestamp: &now, 5651 } 5652 isoTime := true 5653 formatter := NewStatusFormatter(status, isoTime) 5654 formatted, err := formatter.format() 5655 c.Assert(err, jc.ErrorIsNil) 5656 5657 c.Check(formatted, jc.DeepEquals, formattedStatus{ 5658 Model: modelStatus{ 5659 Cloud: "dummy", 5660 }, 5661 Machines: map[string]machineStatus{ 5662 "1": { 5663 JujuStatus: statusInfoContents{Current: "error", Message: "<error while provisioning>"}, 5664 InstanceId: "pending", 5665 Series: "trusty", 5666 Id: "1", 5667 Containers: map[string]machineStatus{}, 5668 NetworkInterfaces: map[string]networkInterface{}, 5669 LXDProfiles: map[string]lxdProfileContents{}, 5670 }, 5671 }, 5672 Applications: map[string]applicationStatus{}, 5673 RemoteApplications: map[string]remoteApplicationStatus{}, 5674 Offers: map[string]offerStatus{}, 5675 Controller: &controllerStatus{ 5676 Timestamp: common.FormatTimeAsTimestamp(&now, isoTime), 5677 }, 5678 }) 5679 } 5680 5681 func (s *StatusSuite) TestMissingControllerTimestampInFullStatus(c *gc.C) { 5682 status := ¶ms.FullStatus{ 5683 Model: params.ModelStatusInfo{ 5684 CloudTag: "cloud-dummy", 5685 }, 5686 Machines: map[string]params.MachineStatus{ 5687 "1": { 5688 AgentStatus: params.DetailedStatus{ 5689 Status: "error", 5690 Info: "<error while provisioning>", 5691 }, 5692 InstanceId: "pending", 5693 InstanceStatus: params.DetailedStatus{}, 5694 Series: "trusty", 5695 Id: "1", 5696 Jobs: []multiwatcher.MachineJob{"JobHostUnits"}, 5697 }, 5698 }, 5699 } 5700 isoTime := true 5701 formatter := NewStatusFormatter(status, isoTime) 5702 formatted, err := formatter.format() 5703 c.Assert(err, jc.ErrorIsNil) 5704 5705 c.Check(formatted, jc.DeepEquals, formattedStatus{ 5706 Model: modelStatus{ 5707 Cloud: "dummy", 5708 }, 5709 Machines: map[string]machineStatus{ 5710 "1": { 5711 JujuStatus: statusInfoContents{Current: "error", Message: "<error while provisioning>"}, 5712 InstanceId: "pending", 5713 Series: "trusty", 5714 Id: "1", 5715 Containers: map[string]machineStatus{}, 5716 NetworkInterfaces: map[string]networkInterface{}, 5717 LXDProfiles: map[string]lxdProfileContents{}, 5718 }, 5719 }, 5720 Applications: map[string]applicationStatus{}, 5721 RemoteApplications: map[string]remoteApplicationStatus{}, 5722 Offers: map[string]offerStatus{}, 5723 }) 5724 } 5725 5726 func (s *StatusSuite) TestControllerTimestampInFullStatus(c *gc.C) { 5727 now := time.Now() 5728 status := ¶ms.FullStatus{ 5729 Model: params.ModelStatusInfo{ 5730 CloudTag: "cloud-dummy", 5731 }, 5732 Machines: map[string]params.MachineStatus{ 5733 "1": { 5734 AgentStatus: params.DetailedStatus{ 5735 Status: "error", 5736 Info: "<error while provisioning>", 5737 }, 5738 InstanceId: "pending", 5739 InstanceStatus: params.DetailedStatus{}, 5740 Series: "trusty", 5741 Id: "1", 5742 Jobs: []multiwatcher.MachineJob{"JobHostUnits"}, 5743 }, 5744 }, 5745 ControllerTimestamp: &now, 5746 } 5747 isoTime := true 5748 formatter := NewStatusFormatter(status, isoTime) 5749 formatted, err := formatter.format() 5750 c.Assert(err, jc.ErrorIsNil) 5751 5752 c.Check(formatted, jc.DeepEquals, formattedStatus{ 5753 Model: modelStatus{ 5754 Cloud: "dummy", 5755 }, 5756 Machines: map[string]machineStatus{ 5757 "1": { 5758 JujuStatus: statusInfoContents{Current: "error", Message: "<error while provisioning>"}, 5759 InstanceId: "pending", 5760 Series: "trusty", 5761 Id: "1", 5762 Containers: map[string]machineStatus{}, 5763 NetworkInterfaces: map[string]networkInterface{}, 5764 LXDProfiles: map[string]lxdProfileContents{}, 5765 }, 5766 }, 5767 Applications: map[string]applicationStatus{}, 5768 RemoteApplications: map[string]remoteApplicationStatus{}, 5769 Offers: map[string]offerStatus{}, 5770 Controller: &controllerStatus{ 5771 Timestamp: common.FormatTimeAsTimestamp(&now, isoTime), 5772 }, 5773 }) 5774 } 5775 5776 func (s *StatusSuite) TestTabularNoRelations(c *gc.C) { 5777 ctx := s.FilteringTestSetup(c) 5778 defer s.resetContext(c, ctx) 5779 5780 _, stdout, stderr := runStatus(c) 5781 c.Assert(stderr, gc.IsNil) 5782 c.Assert(strings.Contains(string(stdout), "Relation provider"), jc.IsFalse) 5783 } 5784 5785 func (s *StatusSuite) TestTabularDisplayRelations(c *gc.C) { 5786 ctx := s.FilteringTestSetup(c) 5787 defer s.resetContext(c, ctx) 5788 5789 _, stdout, stderr := runStatus(c, "--relations") 5790 c.Assert(stderr, gc.IsNil) 5791 c.Assert(strings.Contains(string(stdout), "Relation provider"), jc.IsTrue) 5792 } 5793 5794 func (s *StatusSuite) TestNonTabularDisplayRelations(c *gc.C) { 5795 ctx := s.FilteringTestSetup(c) 5796 defer s.resetContext(c, ctx) 5797 5798 _, stdout, stderr := runStatus(c, "--format=yaml", "--relations") 5799 c.Assert(string(stderr), gc.Equals, "provided relations option is always enabled in non tabular formats\n") 5800 logger.Criticalf("stdout -> \n%q", stdout) 5801 c.Assert(strings.Contains(string(stdout), " relations:"), jc.IsTrue) 5802 c.Assert(strings.Contains(string(stdout), "storage:"), jc.IsTrue) 5803 } 5804 5805 func (s *StatusSuite) TestNonTabularDisplayStorage(c *gc.C) { 5806 ctx := s.FilteringTestSetup(c) 5807 defer s.resetContext(c, ctx) 5808 5809 _, stdout, stderr := runStatus(c, "--format=yaml", "--storage") 5810 c.Assert(string(stderr), gc.Equals, "provided storage option is always enabled in non tabular formats\n") 5811 c.Assert(strings.Contains(string(stdout), " relations:"), jc.IsTrue) 5812 c.Assert(strings.Contains(string(stdout), "storage:"), jc.IsTrue) 5813 } 5814 5815 func (s *StatusSuite) TestNonTabularDisplayRelationsAndStorage(c *gc.C) { 5816 ctx := s.FilteringTestSetup(c) 5817 defer s.resetContext(c, ctx) 5818 5819 _, stdout, stderr := runStatus(c, "--format=yaml", "--relations", "--storage") 5820 c.Assert(string(stderr), gc.Equals, "provided relations, storage options are always enabled in non tabular formats\n") 5821 c.Assert(strings.Contains(string(stdout), " relations:"), jc.IsTrue) 5822 c.Assert(strings.Contains(string(stdout), "storage:"), jc.IsTrue) 5823 } 5824 5825 func (s *StatusSuite) TestNonTabularRelations(c *gc.C) { 5826 ctx := s.FilteringTestSetup(c) 5827 defer s.resetContext(c, ctx) 5828 5829 _, stdout, stderr := runStatus(c, "--format=yaml") 5830 c.Assert(stderr, gc.IsNil) 5831 c.Assert(strings.Contains(string(stdout), " relations:"), jc.IsTrue) 5832 c.Assert(strings.Contains(string(stdout), "storage:"), jc.IsTrue) 5833 } 5834 5835 func (s *StatusSuite) TestStatusFormatTabularEmptyModel(c *gc.C) { 5836 code, stdout, stderr := runStatus(c) 5837 c.Check(code, gc.Equals, 0) 5838 c.Check(string(stderr), gc.Equals, "Model \"controller\" is empty.\n") 5839 expected := ` 5840 Model Controller Cloud/Region Version SLA Timestamp 5841 controller kontroll dummy/dummy-region 1.2.3 unsupported 15:04:05+07:00 5842 5843 `[1:] 5844 output := substituteFakeTimestamp(c, stdout, false) 5845 c.Assert(string(output), gc.Equals, expected) 5846 } 5847 5848 func (s *StatusSuite) TestStatusFormatTabularForUnmatchedFilter(c *gc.C) { 5849 code, stdout, stderr := runStatus(c, "unmatched") 5850 c.Check(code, gc.Equals, 0) 5851 c.Check(string(stderr), gc.Equals, "Nothing matched specified filter.\n") 5852 expected := ` 5853 Model Controller Cloud/Region Version SLA Timestamp 5854 controller kontroll dummy/dummy-region 1.2.3 unsupported 15:04:05+07:00 5855 5856 `[1:] 5857 output := substituteFakeTimestamp(c, stdout, false) 5858 c.Assert(string(output), gc.Equals, expected) 5859 5860 _, _, stderr = runStatus(c, "cannot", "match", "me") 5861 c.Check(string(stderr), gc.Equals, "Nothing matched specified filters.\n") 5862 }