PageRenderTime 45ms CodeModel.GetById 0ms RepoModel.GetById 0ms app.codeStats 0ms

/cmd/juju/status_test.go

https://github.com/frankban/juju
Go | 2762 lines | 2452 code | 199 blank | 111 comment | 28 complexity | 7a5ec530f4cde1a45d992b2cfd6fae94 MD5 | raw file
Possible License(s): AGPL-3.0
  1. // Copyright 2012, 2013 Canonical Ltd.
  2. // Licensed under the AGPLv3, see LICENCE file for details.
  3. package main
  4. import (
  5. "bytes"
  6. "encoding/json"
  7. "fmt"
  8. "strings"
  9. "github.com/juju/cmd"
  10. jc "github.com/juju/testing/checkers"
  11. gc "gopkg.in/check.v1"
  12. "gopkg.in/juju/charm.v4"
  13. goyaml "gopkg.in/yaml.v1"
  14. "github.com/juju/juju/api"
  15. "github.com/juju/juju/cmd/envcmd"
  16. "github.com/juju/juju/constraints"
  17. "github.com/juju/juju/environs"
  18. "github.com/juju/juju/instance"
  19. "github.com/juju/juju/juju/testing"
  20. "github.com/juju/juju/network"
  21. "github.com/juju/juju/state"
  22. "github.com/juju/juju/state/multiwatcher"
  23. "github.com/juju/juju/state/presence"
  24. "github.com/juju/juju/testcharms"
  25. coretesting "github.com/juju/juju/testing"
  26. "github.com/juju/juju/version"
  27. )
  28. func runStatus(c *gc.C, args ...string) (code int, stdout, stderr []byte) {
  29. ctx := coretesting.Context(c)
  30. code = cmd.Main(envcmd.Wrap(&StatusCommand{}), ctx, args)
  31. stdout = ctx.Stdout.(*bytes.Buffer).Bytes()
  32. stderr = ctx.Stderr.(*bytes.Buffer).Bytes()
  33. return
  34. }
  35. type StatusSuite struct {
  36. testing.JujuConnSuite
  37. }
  38. var _ = gc.Suite(&StatusSuite{})
  39. type M map[string]interface{}
  40. type L []interface{}
  41. type testCase struct {
  42. summary string
  43. steps []stepper
  44. }
  45. func test(summary string, steps ...stepper) testCase {
  46. return testCase{summary, steps}
  47. }
  48. type stepper interface {
  49. step(c *gc.C, ctx *context)
  50. }
  51. //
  52. // context
  53. //
  54. func newContext(c *gc.C, st *state.State, env environs.Environ, adminUserTag string) *context {
  55. // We make changes in the API server's state so that
  56. // our changes to presence are immediately noticed
  57. // in the status.
  58. return &context{
  59. st: st,
  60. env: env,
  61. charms: make(map[string]*state.Charm),
  62. pingers: make(map[string]*presence.Pinger),
  63. adminUserTag: adminUserTag,
  64. }
  65. }
  66. type context struct {
  67. st *state.State
  68. env environs.Environ
  69. charms map[string]*state.Charm
  70. pingers map[string]*presence.Pinger
  71. adminUserTag string // A string repr of the tag.
  72. }
  73. func (ctx *context) reset(c *gc.C) {
  74. for _, up := range ctx.pingers {
  75. err := up.Kill()
  76. c.Check(err, jc.ErrorIsNil)
  77. }
  78. }
  79. func (ctx *context) run(c *gc.C, steps []stepper) {
  80. for i, s := range steps {
  81. c.Logf("step %d", i)
  82. c.Logf("%#v", s)
  83. s.step(c, ctx)
  84. }
  85. }
  86. func (ctx *context) setAgentPresence(c *gc.C, p presence.Presencer) *presence.Pinger {
  87. pinger, err := p.SetAgentPresence()
  88. c.Assert(err, jc.ErrorIsNil)
  89. ctx.st.StartSync()
  90. err = p.WaitAgentPresence(coretesting.LongWait)
  91. c.Assert(err, jc.ErrorIsNil)
  92. agentPresence, err := p.AgentPresence()
  93. c.Assert(err, jc.ErrorIsNil)
  94. c.Assert(agentPresence, jc.IsTrue)
  95. return pinger
  96. }
  97. func (s *StatusSuite) newContext(c *gc.C) *context {
  98. st := s.Environ.(testing.GetStater).GetStateInAPIServer()
  99. // We make changes in the API server's state so that
  100. // our changes to presence are immediately noticed
  101. // in the status.
  102. return newContext(c, st, s.Environ, s.AdminUserTag(c).String())
  103. }
  104. func (s *StatusSuite) resetContext(c *gc.C, ctx *context) {
  105. ctx.reset(c)
  106. s.JujuConnSuite.Reset(c)
  107. }
  108. // shortcuts for expected output.
  109. var (
  110. machine0 = M{
  111. "agent-state": "started",
  112. "dns-name": "dummyenv-0.dns",
  113. "instance-id": "dummyenv-0",
  114. "series": "quantal",
  115. "hardware": "arch=amd64 cpu-cores=1 mem=1024M root-disk=8192M",
  116. "state-server-member-status": "adding-vote",
  117. }
  118. machine1 = M{
  119. "agent-state": "started",
  120. "dns-name": "dummyenv-1.dns",
  121. "instance-id": "dummyenv-1",
  122. "series": "quantal",
  123. "hardware": "arch=amd64 cpu-cores=1 mem=1024M root-disk=8192M",
  124. }
  125. machine2 = M{
  126. "agent-state": "started",
  127. "dns-name": "dummyenv-2.dns",
  128. "instance-id": "dummyenv-2",
  129. "series": "quantal",
  130. "hardware": "arch=amd64 cpu-cores=1 mem=1024M root-disk=8192M",
  131. }
  132. machine3 = M{
  133. "agent-state": "started",
  134. "dns-name": "dummyenv-3.dns",
  135. "instance-id": "dummyenv-3",
  136. "series": "quantal",
  137. "hardware": "arch=amd64 cpu-cores=1 mem=1024M root-disk=8192M",
  138. }
  139. machine4 = M{
  140. "agent-state": "started",
  141. "dns-name": "dummyenv-4.dns",
  142. "instance-id": "dummyenv-4",
  143. "series": "quantal",
  144. "hardware": "arch=amd64 cpu-cores=1 mem=1024M root-disk=8192M",
  145. }
  146. machine1WithContainers = M{
  147. "agent-state": "started",
  148. "containers": M{
  149. "1/lxc/0": M{
  150. "agent-state": "started",
  151. "containers": M{
  152. "1/lxc/0/lxc/0": M{
  153. "agent-state": "started",
  154. "dns-name": "dummyenv-3.dns",
  155. "instance-id": "dummyenv-3",
  156. "series": "quantal",
  157. },
  158. },
  159. "dns-name": "dummyenv-2.dns",
  160. "instance-id": "dummyenv-2",
  161. "series": "quantal",
  162. },
  163. "1/lxc/1": M{
  164. "instance-id": "pending",
  165. "series": "quantal",
  166. },
  167. },
  168. "dns-name": "dummyenv-1.dns",
  169. "instance-id": "dummyenv-1",
  170. "series": "quantal",
  171. "hardware": "arch=amd64 cpu-cores=1 mem=1024M root-disk=8192M",
  172. }
  173. machine1WithContainersScoped = M{
  174. "agent-state": "started",
  175. "containers": M{
  176. "1/lxc/0": M{
  177. "agent-state": "started",
  178. "dns-name": "dummyenv-2.dns",
  179. "instance-id": "dummyenv-2",
  180. "series": "quantal",
  181. },
  182. },
  183. "dns-name": "dummyenv-1.dns",
  184. "instance-id": "dummyenv-1",
  185. "series": "quantal",
  186. "hardware": "arch=amd64 cpu-cores=1 mem=1024M root-disk=8192M",
  187. }
  188. unexposedService = M{
  189. "charm": "cs:quantal/dummy-1",
  190. "exposed": false,
  191. }
  192. exposedService = M{
  193. "charm": "cs:quantal/dummy-1",
  194. "exposed": true,
  195. }
  196. )
  197. type outputFormat struct {
  198. name string
  199. marshal func(v interface{}) ([]byte, error)
  200. unmarshal func(data []byte, v interface{}) error
  201. }
  202. // statusFormats list all output formats supported by status command.
  203. var statusFormats = []outputFormat{
  204. {"yaml", goyaml.Marshal, goyaml.Unmarshal},
  205. {"json", json.Marshal, json.Unmarshal},
  206. }
  207. var machineCons = constraints.MustParse("cpu-cores=2 mem=8G root-disk=8G")
  208. var statusTests = []testCase{
  209. // Status tests
  210. test(
  211. "bootstrap and starting a single instance",
  212. addMachine{machineId: "0", job: state.JobManageEnviron},
  213. expect{
  214. "simulate juju bootstrap by adding machine/0 to the state",
  215. M{
  216. "environment": "dummyenv",
  217. "machines": M{
  218. "0": M{
  219. "instance-id": "pending",
  220. "series": "quantal",
  221. "state-server-member-status": "adding-vote",
  222. },
  223. },
  224. "services": M{},
  225. },
  226. },
  227. startAliveMachine{"0"},
  228. setAddresses{"0", []network.Address{
  229. network.NewAddress("10.0.0.1", network.ScopeUnknown),
  230. network.NewAddress("dummyenv-0.dns", network.ScopePublic),
  231. }},
  232. expect{
  233. "simulate the PA starting an instance in response to the state change",
  234. M{
  235. "environment": "dummyenv",
  236. "machines": M{
  237. "0": M{
  238. "agent-state": "pending",
  239. "dns-name": "dummyenv-0.dns",
  240. "instance-id": "dummyenv-0",
  241. "series": "quantal",
  242. "hardware": "arch=amd64 cpu-cores=1 mem=1024M root-disk=8192M",
  243. "state-server-member-status": "adding-vote",
  244. },
  245. },
  246. "services": M{},
  247. },
  248. },
  249. setMachineStatus{"0", state.StatusStarted, ""},
  250. expect{
  251. "simulate the MA started and set the machine status",
  252. M{
  253. "environment": "dummyenv",
  254. "machines": M{
  255. "0": machine0,
  256. },
  257. "services": M{},
  258. },
  259. },
  260. setTools{"0", version.MustParseBinary("1.2.3-trusty-ppc")},
  261. expect{
  262. "simulate the MA setting the version",
  263. M{
  264. "environment": "dummyenv",
  265. "machines": M{
  266. "0": M{
  267. "dns-name": "dummyenv-0.dns",
  268. "instance-id": "dummyenv-0",
  269. "agent-version": "1.2.3",
  270. "agent-state": "started",
  271. "series": "quantal",
  272. "hardware": "arch=amd64 cpu-cores=1 mem=1024M root-disk=8192M",
  273. "state-server-member-status": "adding-vote",
  274. },
  275. },
  276. "services": M{},
  277. },
  278. },
  279. ), test(
  280. "deploy two services and two networks",
  281. addMachine{machineId: "0", job: state.JobManageEnviron},
  282. startAliveMachine{"0"},
  283. setMachineStatus{"0", state.StatusStarted, ""},
  284. setAddresses{"0", []network.Address{
  285. network.NewAddress("10.0.0.1", network.ScopeUnknown),
  286. network.NewAddress("dummyenv-0.dns", network.ScopePublic),
  287. }},
  288. addCharm{"dummy"},
  289. addService{
  290. name: "networks-service",
  291. charm: "dummy",
  292. networks: []string{"net1", "net2"},
  293. cons: constraints.MustParse("networks=foo,bar,^no,^good"),
  294. },
  295. addService{
  296. name: "no-networks-service",
  297. charm: "dummy",
  298. cons: constraints.MustParse("networks=^mynet"),
  299. },
  300. addNetwork{
  301. name: "net1",
  302. providerId: network.Id("provider-net1"),
  303. cidr: "0.1.2.0/24",
  304. vlanTag: 0,
  305. },
  306. addNetwork{
  307. name: "net2",
  308. providerId: network.Id("provider-vlan42"),
  309. cidr: "0.42.1.0/24",
  310. vlanTag: 42,
  311. },
  312. expect{
  313. "simulate just the two services and a bootstrap node",
  314. M{
  315. "environment": "dummyenv",
  316. "machines": M{
  317. "0": machine0,
  318. },
  319. "services": M{
  320. "networks-service": M{
  321. "charm": "cs:quantal/dummy-1",
  322. "exposed": false,
  323. "networks": M{
  324. "enabled": L{"net1", "net2"},
  325. "disabled": L{"foo", "bar", "no", "good"},
  326. },
  327. },
  328. "no-networks-service": M{
  329. "charm": "cs:quantal/dummy-1",
  330. "exposed": false,
  331. "networks": M{
  332. "disabled": L{"mynet"},
  333. },
  334. },
  335. },
  336. "networks": M{
  337. "net1": M{
  338. "provider-id": "provider-net1",
  339. "cidr": "0.1.2.0/24",
  340. },
  341. "net2": M{
  342. "provider-id": "provider-vlan42",
  343. "cidr": "0.42.1.0/24",
  344. "vlan-tag": 42,
  345. },
  346. },
  347. },
  348. },
  349. ), test(
  350. "instance with different hardware characteristics",
  351. addMachine{machineId: "0", cons: machineCons, job: state.JobManageEnviron},
  352. setAddresses{"0", []network.Address{
  353. network.NewAddress("10.0.0.1", network.ScopeUnknown),
  354. network.NewAddress("dummyenv-0.dns", network.ScopePublic),
  355. }},
  356. startAliveMachine{"0"},
  357. setMachineStatus{"0", state.StatusStarted, ""},
  358. expect{
  359. "machine 0 has specific hardware characteristics",
  360. M{
  361. "environment": "dummyenv",
  362. "machines": M{
  363. "0": M{
  364. "agent-state": "started",
  365. "dns-name": "dummyenv-0.dns",
  366. "instance-id": "dummyenv-0",
  367. "series": "quantal",
  368. "hardware": "arch=amd64 cpu-cores=2 mem=8192M root-disk=8192M",
  369. "state-server-member-status": "adding-vote",
  370. },
  371. },
  372. "services": M{},
  373. },
  374. },
  375. ), test(
  376. "instance without addresses",
  377. addMachine{machineId: "0", cons: machineCons, job: state.JobManageEnviron},
  378. startAliveMachine{"0"},
  379. setMachineStatus{"0", state.StatusStarted, ""},
  380. expect{
  381. "machine 0 has no dns-name",
  382. M{
  383. "environment": "dummyenv",
  384. "machines": M{
  385. "0": M{
  386. "agent-state": "started",
  387. "instance-id": "dummyenv-0",
  388. "series": "quantal",
  389. "hardware": "arch=amd64 cpu-cores=2 mem=8192M root-disk=8192M",
  390. "state-server-member-status": "adding-vote",
  391. },
  392. },
  393. "services": M{},
  394. },
  395. },
  396. ), test(
  397. "test pending and missing machines",
  398. addMachine{machineId: "0", job: state.JobManageEnviron},
  399. expect{
  400. "machine 0 reports pending",
  401. M{
  402. "environment": "dummyenv",
  403. "machines": M{
  404. "0": M{
  405. "instance-id": "pending",
  406. "series": "quantal",
  407. "state-server-member-status": "adding-vote",
  408. },
  409. },
  410. "services": M{},
  411. },
  412. },
  413. startMissingMachine{"0"},
  414. expect{
  415. "machine 0 reports missing",
  416. M{
  417. "environment": "dummyenv",
  418. "machines": M{
  419. "0": M{
  420. "instance-state": "missing",
  421. "instance-id": "i-missing",
  422. "agent-state": "pending",
  423. "series": "quantal",
  424. "hardware": "arch=amd64 cpu-cores=1 mem=1024M root-disk=8192M",
  425. "state-server-member-status": "adding-vote",
  426. },
  427. },
  428. "services": M{},
  429. },
  430. },
  431. ), test(
  432. "add two services and expose one, then add 2 more machines and some units",
  433. addMachine{machineId: "0", job: state.JobManageEnviron},
  434. setAddresses{"0", []network.Address{network.NewAddress("dummyenv-0.dns", network.ScopeUnknown)}},
  435. startAliveMachine{"0"},
  436. setMachineStatus{"0", state.StatusStarted, ""},
  437. addCharm{"dummy"},
  438. addService{name: "dummy-service", charm: "dummy"},
  439. addService{name: "exposed-service", charm: "dummy"},
  440. expect{
  441. "no services exposed yet",
  442. M{
  443. "environment": "dummyenv",
  444. "machines": M{
  445. "0": machine0,
  446. },
  447. "services": M{
  448. "dummy-service": unexposedService,
  449. "exposed-service": unexposedService,
  450. },
  451. },
  452. },
  453. setServiceExposed{"exposed-service", true},
  454. expect{
  455. "one exposed service",
  456. M{
  457. "environment": "dummyenv",
  458. "machines": M{
  459. "0": machine0,
  460. },
  461. "services": M{
  462. "dummy-service": unexposedService,
  463. "exposed-service": exposedService,
  464. },
  465. },
  466. },
  467. addMachine{machineId: "1", job: state.JobHostUnits},
  468. setAddresses{"1", []network.Address{network.NewAddress("dummyenv-1.dns", network.ScopeUnknown)}},
  469. startAliveMachine{"1"},
  470. setMachineStatus{"1", state.StatusStarted, ""},
  471. addMachine{machineId: "2", job: state.JobHostUnits},
  472. setAddresses{"2", []network.Address{network.NewAddress("dummyenv-2.dns", network.ScopeUnknown)}},
  473. startAliveMachine{"2"},
  474. setMachineStatus{"2", state.StatusStarted, ""},
  475. expect{
  476. "two more machines added",
  477. M{
  478. "environment": "dummyenv",
  479. "machines": M{
  480. "0": machine0,
  481. "1": machine1,
  482. "2": machine2,
  483. },
  484. "services": M{
  485. "dummy-service": unexposedService,
  486. "exposed-service": exposedService,
  487. },
  488. },
  489. },
  490. addUnit{"dummy-service", "1"},
  491. addAliveUnit{"exposed-service", "2"},
  492. setUnitStatus{"exposed-service/0", state.StatusError, "You Require More Vespene Gas", nil},
  493. // Open multiple ports with different protocols,
  494. // ensure they're sorted on protocol, then number.
  495. openUnitPort{"exposed-service/0", "udp", 10},
  496. openUnitPort{"exposed-service/0", "udp", 2},
  497. openUnitPort{"exposed-service/0", "tcp", 3},
  498. openUnitPort{"exposed-service/0", "tcp", 2},
  499. // Simulate some status with no info, while the agent is down.
  500. setUnitStatus{"dummy-service/0", state.StatusActive, "", nil},
  501. expect{
  502. "add two units, one alive (in error state), one down",
  503. M{
  504. "environment": "dummyenv",
  505. "machines": M{
  506. "0": machine0,
  507. "1": machine1,
  508. "2": machine2,
  509. },
  510. "services": M{
  511. "exposed-service": M{
  512. "charm": "cs:quantal/dummy-1",
  513. "exposed": true,
  514. "units": M{
  515. "exposed-service/0": M{
  516. "machine": "2",
  517. "agent-state": "error",
  518. "agent-state-info": "You Require More Vespene Gas",
  519. "open-ports": L{
  520. "2/tcp", "3/tcp", "2/udp", "10/udp",
  521. },
  522. "public-address": "dummyenv-2.dns",
  523. },
  524. },
  525. },
  526. "dummy-service": M{
  527. "charm": "cs:quantal/dummy-1",
  528. "exposed": false,
  529. "units": M{
  530. "dummy-service/0": M{
  531. "machine": "1",
  532. "agent-state": "down",
  533. "agent-state-info": "(started)",
  534. "public-address": "dummyenv-1.dns",
  535. },
  536. },
  537. },
  538. },
  539. },
  540. },
  541. addMachine{machineId: "3", job: state.JobHostUnits},
  542. startMachine{"3"},
  543. // Simulate some status with info, while the agent is down.
  544. setAddresses{"3", []network.Address{network.NewAddress("dummyenv-3.dns", network.ScopeUnknown)}},
  545. setMachineStatus{"3", state.StatusStopped, "Really?"},
  546. addMachine{machineId: "4", job: state.JobHostUnits},
  547. setAddresses{"4", []network.Address{network.NewAddress("dummyenv-4.dns", network.ScopeUnknown)}},
  548. startAliveMachine{"4"},
  549. setMachineStatus{"4", state.StatusError, "Beware the red toys"},
  550. ensureDyingUnit{"dummy-service/0"},
  551. addMachine{machineId: "5", job: state.JobHostUnits},
  552. ensureDeadMachine{"5"},
  553. expect{
  554. "add three more machine, one with a dead agent, one in error state and one dead itself; also one dying unit",
  555. M{
  556. "environment": "dummyenv",
  557. "machines": M{
  558. "0": machine0,
  559. "1": machine1,
  560. "2": machine2,
  561. "3": M{
  562. "dns-name": "dummyenv-3.dns",
  563. "instance-id": "dummyenv-3",
  564. "agent-state": "down",
  565. "agent-state-info": "(stopped: Really?)",
  566. "series": "quantal",
  567. "hardware": "arch=amd64 cpu-cores=1 mem=1024M root-disk=8192M",
  568. },
  569. "4": M{
  570. "dns-name": "dummyenv-4.dns",
  571. "instance-id": "dummyenv-4",
  572. "agent-state": "error",
  573. "agent-state-info": "Beware the red toys",
  574. "series": "quantal",
  575. "hardware": "arch=amd64 cpu-cores=1 mem=1024M root-disk=8192M",
  576. },
  577. "5": M{
  578. "life": "dead",
  579. "instance-id": "pending",
  580. "series": "quantal",
  581. },
  582. },
  583. "services": M{
  584. "exposed-service": M{
  585. "charm": "cs:quantal/dummy-1",
  586. "exposed": true,
  587. "units": M{
  588. "exposed-service/0": M{
  589. "machine": "2",
  590. "agent-state": "error",
  591. "agent-state-info": "You Require More Vespene Gas",
  592. "open-ports": L{
  593. "2/tcp", "3/tcp", "2/udp", "10/udp",
  594. },
  595. "public-address": "dummyenv-2.dns",
  596. },
  597. },
  598. },
  599. "dummy-service": M{
  600. "charm": "cs:quantal/dummy-1",
  601. "exposed": false,
  602. "units": M{
  603. "dummy-service/0": M{
  604. "machine": "1",
  605. "life": "dying",
  606. "agent-state": "down",
  607. "agent-state-info": "(started)",
  608. "public-address": "dummyenv-1.dns",
  609. },
  610. },
  611. },
  612. },
  613. },
  614. },
  615. scopedExpect{
  616. "scope status on dummy-service/0 unit",
  617. []string{"dummy-service/0"},
  618. M{
  619. "environment": "dummyenv",
  620. "machines": M{
  621. "1": machine1,
  622. },
  623. "services": M{
  624. "dummy-service": M{
  625. "charm": "cs:quantal/dummy-1",
  626. "exposed": false,
  627. "units": M{
  628. "dummy-service/0": M{
  629. "machine": "1",
  630. "life": "dying",
  631. "agent-state": "down",
  632. "agent-state-info": "(started)",
  633. "public-address": "dummyenv-1.dns",
  634. },
  635. },
  636. },
  637. },
  638. },
  639. },
  640. scopedExpect{
  641. "scope status on exposed-service service",
  642. []string{"exposed-service"},
  643. M{
  644. "environment": "dummyenv",
  645. "machines": M{
  646. "2": machine2,
  647. },
  648. "services": M{
  649. "exposed-service": M{
  650. "charm": "cs:quantal/dummy-1",
  651. "exposed": true,
  652. "units": M{
  653. "exposed-service/0": M{
  654. "machine": "2",
  655. "agent-state": "error",
  656. "agent-state-info": "You Require More Vespene Gas",
  657. "open-ports": L{
  658. "2/tcp", "3/tcp", "2/udp", "10/udp",
  659. },
  660. "public-address": "dummyenv-2.dns",
  661. },
  662. },
  663. },
  664. },
  665. },
  666. },
  667. scopedExpect{
  668. "scope status on service pattern",
  669. []string{"d*-service"},
  670. M{
  671. "environment": "dummyenv",
  672. "machines": M{
  673. "1": machine1,
  674. },
  675. "services": M{
  676. "dummy-service": M{
  677. "charm": "cs:quantal/dummy-1",
  678. "exposed": false,
  679. "units": M{
  680. "dummy-service/0": M{
  681. "machine": "1",
  682. "life": "dying",
  683. "agent-state": "down",
  684. "agent-state-info": "(started)",
  685. "public-address": "dummyenv-1.dns",
  686. },
  687. },
  688. },
  689. },
  690. },
  691. },
  692. scopedExpect{
  693. "scope status on unit pattern",
  694. []string{"e*posed-service/*"},
  695. M{
  696. "environment": "dummyenv",
  697. "machines": M{
  698. "2": machine2,
  699. },
  700. "services": M{
  701. "exposed-service": M{
  702. "charm": "cs:quantal/dummy-1",
  703. "exposed": true,
  704. "units": M{
  705. "exposed-service/0": M{
  706. "machine": "2",
  707. "agent-state": "error",
  708. "agent-state-info": "You Require More Vespene Gas",
  709. "open-ports": L{
  710. "2/tcp", "3/tcp", "2/udp", "10/udp",
  711. },
  712. "public-address": "dummyenv-2.dns",
  713. },
  714. },
  715. },
  716. },
  717. },
  718. },
  719. scopedExpect{
  720. "scope status on combination of service and unit patterns",
  721. []string{"exposed-service", "dummy-service", "e*posed-service/*", "dummy-service/*"},
  722. M{
  723. "environment": "dummyenv",
  724. "machines": M{
  725. "1": machine1,
  726. "2": machine2,
  727. },
  728. "services": M{
  729. "dummy-service": M{
  730. "charm": "cs:quantal/dummy-1",
  731. "exposed": false,
  732. "units": M{
  733. "dummy-service/0": M{
  734. "machine": "1",
  735. "life": "dying",
  736. "agent-state": "down",
  737. "agent-state-info": "(started)",
  738. "public-address": "dummyenv-1.dns",
  739. },
  740. },
  741. },
  742. "exposed-service": M{
  743. "charm": "cs:quantal/dummy-1",
  744. "exposed": true,
  745. "units": M{
  746. "exposed-service/0": M{
  747. "machine": "2",
  748. "agent-state": "error",
  749. "agent-state-info": "You Require More Vespene Gas",
  750. "open-ports": L{
  751. "2/tcp", "3/tcp", "2/udp", "10/udp",
  752. },
  753. "public-address": "dummyenv-2.dns",
  754. },
  755. },
  756. },
  757. },
  758. },
  759. },
  760. ), test(
  761. "a unit with a hook relation error",
  762. addMachine{machineId: "0", job: state.JobManageEnviron},
  763. setAddresses{"0", []network.Address{network.NewAddress("dummyenv-0.dns", network.ScopeUnknown)}},
  764. startAliveMachine{"0"},
  765. setMachineStatus{"0", state.StatusStarted, ""},
  766. addMachine{machineId: "1", job: state.JobHostUnits},
  767. setAddresses{"1", []network.Address{network.NewAddress("dummyenv-1.dns", network.ScopeUnknown)}},
  768. startAliveMachine{"1"},
  769. setMachineStatus{"1", state.StatusStarted, ""},
  770. addCharm{"wordpress"},
  771. addService{name: "wordpress", charm: "wordpress"},
  772. addAliveUnit{"wordpress", "1"},
  773. addCharm{"mysql"},
  774. addService{name: "mysql", charm: "mysql"},
  775. addAliveUnit{"mysql", "1"},
  776. relateServices{"wordpress", "mysql"},
  777. setUnitStatus{"wordpress/0", state.StatusError,
  778. "hook failed: some-relation-changed",
  779. map[string]interface{}{"relation-id": 0}},
  780. expect{
  781. "a unit with a hook relation error",
  782. M{
  783. "environment": "dummyenv",
  784. "machines": M{
  785. "0": machine0,
  786. "1": machine1,
  787. },
  788. "services": M{
  789. "wordpress": M{
  790. "charm": "cs:quantal/wordpress-3",
  791. "exposed": false,
  792. "relations": M{
  793. "db": L{"mysql"},
  794. },
  795. "units": M{
  796. "wordpress/0": M{
  797. "machine": "1",
  798. "agent-state": "error",
  799. "agent-state-info": "hook failed: some-relation-changed for mysql:server",
  800. "public-address": "dummyenv-1.dns",
  801. },
  802. },
  803. },
  804. "mysql": M{
  805. "charm": "cs:quantal/mysql-1",
  806. "exposed": false,
  807. "relations": M{
  808. "server": L{"wordpress"},
  809. },
  810. "units": M{
  811. "mysql/0": M{
  812. "machine": "1",
  813. "agent-state": "allocating",
  814. "public-address": "dummyenv-1.dns",
  815. },
  816. },
  817. },
  818. },
  819. },
  820. },
  821. ), test(
  822. "a unit with a hook relation error when the agent is down",
  823. addMachine{machineId: "0", job: state.JobManageEnviron},
  824. setAddresses{"0", []network.Address{network.NewAddress("dummyenv-0.dns", network.ScopeUnknown)}},
  825. startAliveMachine{"0"},
  826. setMachineStatus{"0", state.StatusStarted, ""},
  827. addMachine{machineId: "1", job: state.JobHostUnits},
  828. setAddresses{"1", []network.Address{network.NewAddress("dummyenv-1.dns", network.ScopeUnknown)}},
  829. startAliveMachine{"1"},
  830. setMachineStatus{"1", state.StatusStarted, ""},
  831. addCharm{"wordpress"},
  832. addService{name: "wordpress", charm: "wordpress"},
  833. addUnit{"wordpress", "1"},
  834. addCharm{"mysql"},
  835. addService{name: "mysql", charm: "mysql"},
  836. addAliveUnit{"mysql", "1"},
  837. relateServices{"wordpress", "mysql"},
  838. setUnitStatus{"wordpress/0", state.StatusError,
  839. "hook failed: some-relation-changed",
  840. map[string]interface{}{"relation-id": 0}},
  841. expect{
  842. "a unit with a hook relation error when the agent is down",
  843. M{
  844. "environment": "dummyenv",
  845. "machines": M{
  846. "0": machine0,
  847. "1": machine1,
  848. },
  849. "services": M{
  850. "wordpress": M{
  851. "charm": "cs:quantal/wordpress-3",
  852. "exposed": false,
  853. "relations": M{
  854. "db": L{"mysql"},
  855. },
  856. "units": M{
  857. "wordpress/0": M{
  858. "machine": "1",
  859. "agent-state": "down",
  860. "agent-state-info": "(error: hook failed: some-relation-changed for mysql:server)",
  861. "public-address": "dummyenv-1.dns",
  862. },
  863. },
  864. },
  865. "mysql": M{
  866. "charm": "cs:quantal/mysql-1",
  867. "exposed": false,
  868. "relations": M{
  869. "server": L{"wordpress"},
  870. },
  871. "units": M{
  872. "mysql/0": M{
  873. "machine": "1",
  874. "agent-state": "allocating",
  875. "public-address": "dummyenv-1.dns",
  876. },
  877. },
  878. },
  879. },
  880. },
  881. },
  882. ), test(
  883. "add a dying service",
  884. addCharm{"dummy"},
  885. addService{name: "dummy-service", charm: "dummy"},
  886. addMachine{machineId: "0", job: state.JobHostUnits},
  887. addUnit{"dummy-service", "0"},
  888. ensureDyingService{"dummy-service"},
  889. expect{
  890. "service shows life==dying",
  891. M{
  892. "environment": "dummyenv",
  893. "machines": M{
  894. "0": M{
  895. "instance-id": "pending",
  896. "series": "quantal",
  897. },
  898. },
  899. "services": M{
  900. "dummy-service": M{
  901. "charm": "cs:quantal/dummy-1",
  902. "exposed": false,
  903. "life": "dying",
  904. "units": M{
  905. "dummy-service/0": M{
  906. "machine": "0",
  907. "agent-state": "allocating",
  908. },
  909. },
  910. },
  911. },
  912. },
  913. },
  914. ),
  915. // Relation tests
  916. test(
  917. "complex scenario with multiple related services",
  918. addMachine{machineId: "0", job: state.JobManageEnviron},
  919. setAddresses{"0", []network.Address{network.NewAddress("dummyenv-0.dns", network.ScopeUnknown)}},
  920. startAliveMachine{"0"},
  921. setMachineStatus{"0", state.StatusStarted, ""},
  922. addCharm{"wordpress"},
  923. addCharm{"mysql"},
  924. addCharm{"varnish"},
  925. addService{name: "project", charm: "wordpress"},
  926. setServiceExposed{"project", true},
  927. addMachine{machineId: "1", job: state.JobHostUnits},
  928. setAddresses{"1", []network.Address{network.NewAddress("dummyenv-1.dns", network.ScopeUnknown)}},
  929. startAliveMachine{"1"},
  930. setMachineStatus{"1", state.StatusStarted, ""},
  931. addAliveUnit{"project", "1"},
  932. setUnitStatus{"project/0", state.StatusActive, "", nil},
  933. addService{name: "mysql", charm: "mysql"},
  934. setServiceExposed{"mysql", true},
  935. addMachine{machineId: "2", job: state.JobHostUnits},
  936. setAddresses{"2", []network.Address{network.NewAddress("dummyenv-2.dns", network.ScopeUnknown)}},
  937. startAliveMachine{"2"},
  938. setMachineStatus{"2", state.StatusStarted, ""},
  939. addAliveUnit{"mysql", "2"},
  940. setUnitStatus{"mysql/0", state.StatusActive, "", nil},
  941. addService{name: "varnish", charm: "varnish"},
  942. setServiceExposed{"varnish", true},
  943. addMachine{machineId: "3", job: state.JobHostUnits},
  944. setAddresses{"3", []network.Address{network.NewAddress("dummyenv-3.dns", network.ScopeUnknown)}},
  945. startAliveMachine{"3"},
  946. setMachineStatus{"3", state.StatusStarted, ""},
  947. addUnit{"varnish", "3"},
  948. addService{name: "private", charm: "wordpress"},
  949. setServiceExposed{"private", true},
  950. addMachine{machineId: "4", job: state.JobHostUnits},
  951. setAddresses{"4", []network.Address{network.NewAddress("dummyenv-4.dns", network.ScopeUnknown)}},
  952. startAliveMachine{"4"},
  953. setMachineStatus{"4", state.StatusStarted, ""},
  954. addUnit{"private", "4"},
  955. relateServices{"project", "mysql"},
  956. relateServices{"project", "varnish"},
  957. relateServices{"private", "mysql"},
  958. expect{
  959. "multiples services with relations between some of them",
  960. M{
  961. "environment": "dummyenv",
  962. "machines": M{
  963. "0": machine0,
  964. "1": machine1,
  965. "2": machine2,
  966. "3": machine3,
  967. "4": machine4,
  968. },
  969. "services": M{
  970. "project": M{
  971. "charm": "cs:quantal/wordpress-3",
  972. "exposed": true,
  973. "units": M{
  974. "project/0": M{
  975. "machine": "1",
  976. "agent-state": "started",
  977. "public-address": "dummyenv-1.dns",
  978. },
  979. },
  980. "relations": M{
  981. "db": L{"mysql"},
  982. "cache": L{"varnish"},
  983. },
  984. },
  985. "mysql": M{
  986. "charm": "cs:quantal/mysql-1",
  987. "exposed": true,
  988. "units": M{
  989. "mysql/0": M{
  990. "machine": "2",
  991. "agent-state": "started",
  992. "public-address": "dummyenv-2.dns",
  993. },
  994. },
  995. "relations": M{
  996. "server": L{"private", "project"},
  997. },
  998. },
  999. "varnish": M{
  1000. "charm": "cs:quantal/varnish-1",
  1001. "exposed": true,
  1002. "units": M{
  1003. "varnish/0": M{
  1004. "machine": "3",
  1005. "agent-state": "allocating",
  1006. "public-address": "dummyenv-3.dns",
  1007. },
  1008. },
  1009. "relations": M{
  1010. "webcache": L{"project"},
  1011. },
  1012. },
  1013. "private": M{
  1014. "charm": "cs:quantal/wordpress-3",
  1015. "exposed": true,
  1016. "units": M{
  1017. "private/0": M{
  1018. "machine": "4",
  1019. "agent-state": "allocating",
  1020. "public-address": "dummyenv-4.dns",
  1021. },
  1022. },
  1023. "relations": M{
  1024. "db": L{"mysql"},
  1025. },
  1026. },
  1027. },
  1028. },
  1029. },
  1030. ), test(
  1031. "simple peer scenario",
  1032. addMachine{machineId: "0", job: state.JobManageEnviron},
  1033. setAddresses{"0", []network.Address{network.NewAddress("dummyenv-0.dns", network.ScopeUnknown)}},
  1034. startAliveMachine{"0"},
  1035. setMachineStatus{"0", state.StatusStarted, ""},
  1036. addCharm{"riak"},
  1037. addCharm{"wordpress"},
  1038. addService{name: "riak", charm: "riak"},
  1039. setServiceExposed{"riak", true},
  1040. addMachine{machineId: "1", job: state.JobHostUnits},
  1041. setAddresses{"1", []network.Address{network.NewAddress("dummyenv-1.dns", network.ScopeUnknown)}},
  1042. startAliveMachine{"1"},
  1043. setMachineStatus{"1", state.StatusStarted, ""},
  1044. addAliveUnit{"riak", "1"},
  1045. setUnitStatus{"riak/0", state.StatusActive, "", nil},
  1046. addMachine{machineId: "2", job: state.JobHostUnits},
  1047. setAddresses{"2", []network.Address{network.NewAddress("dummyenv-2.dns", network.ScopeUnknown)}},
  1048. startAliveMachine{"2"},
  1049. setMachineStatus{"2", state.StatusStarted, ""},
  1050. addAliveUnit{"riak", "2"},
  1051. setUnitStatus{"riak/1", state.StatusActive, "", nil},
  1052. addMachine{machineId: "3", job: state.JobHostUnits},
  1053. setAddresses{"3", []network.Address{network.NewAddress("dummyenv-3.dns", network.ScopeUnknown)}},
  1054. startAliveMachine{"3"},
  1055. setMachineStatus{"3", state.StatusStarted, ""},
  1056. addAliveUnit{"riak", "3"},
  1057. setUnitStatus{"riak/2", state.StatusActive, "", nil},
  1058. expect{
  1059. "multiples related peer units",
  1060. M{
  1061. "environment": "dummyenv",
  1062. "machines": M{
  1063. "0": machine0,
  1064. "1": machine1,
  1065. "2": machine2,
  1066. "3": machine3,
  1067. },
  1068. "services": M{
  1069. "riak": M{
  1070. "charm": "cs:quantal/riak-7",
  1071. "exposed": true,
  1072. "units": M{
  1073. "riak/0": M{
  1074. "machine": "1",
  1075. "agent-state": "started",
  1076. "public-address": "dummyenv-1.dns",
  1077. },
  1078. "riak/1": M{
  1079. "machine": "2",
  1080. "agent-state": "started",
  1081. "public-address": "dummyenv-2.dns",
  1082. },
  1083. "riak/2": M{
  1084. "machine": "3",
  1085. "agent-state": "started",
  1086. "public-address": "dummyenv-3.dns",
  1087. },
  1088. },
  1089. "relations": M{
  1090. "ring": L{"riak"},
  1091. },
  1092. },
  1093. },
  1094. },
  1095. },
  1096. ),
  1097. // Subordinate tests
  1098. test(
  1099. "one service with one subordinate service",
  1100. addMachine{machineId: "0", job: state.JobManageEnviron},
  1101. setAddresses{"0", []network.Address{network.NewAddress("dummyenv-0.dns", network.ScopeUnknown)}},
  1102. startAliveMachine{"0"},
  1103. setMachineStatus{"0", state.StatusStarted, ""},
  1104. addCharm{"wordpress"},
  1105. addCharm{"mysql"},
  1106. addCharm{"logging"},
  1107. addService{name: "wordpress", charm: "wordpress"},
  1108. setServiceExposed{"wordpress", true},
  1109. addMachine{machineId: "1", job: state.JobHostUnits},
  1110. setAddresses{"1", []network.Address{network.NewAddress("dummyenv-1.dns", network.ScopeUnknown)}},
  1111. startAliveMachine{"1"},
  1112. setMachineStatus{"1", state.StatusStarted, ""},
  1113. addAliveUnit{"wordpress", "1"},
  1114. setUnitStatus{"wordpress/0", state.StatusActive, "", nil},
  1115. addService{name: "mysql", charm: "mysql"},
  1116. setServiceExposed{"mysql", true},
  1117. addMachine{machineId: "2", job: state.JobHostUnits},
  1118. setAddresses{"2", []network.Address{network.NewAddress("dummyenv-2.dns", network.ScopeUnknown)}},
  1119. startAliveMachine{"2"},
  1120. setMachineStatus{"2", state.StatusStarted, ""},
  1121. addAliveUnit{"mysql", "2"},
  1122. setUnitStatus{"mysql/0", state.StatusActive, "", nil},
  1123. addService{name: "logging", charm: "logging"},
  1124. setServiceExposed{"logging", true},
  1125. relateServices{"wordpress", "mysql"},
  1126. relateServices{"wordpress", "logging"},
  1127. relateServices{"mysql", "logging"},
  1128. addSubordinate{"wordpress/0", "logging"},
  1129. addSubordinate{"mysql/0", "logging"},
  1130. setUnitsAlive{"logging"},
  1131. setUnitStatus{"logging/0", state.StatusActive, "", nil},
  1132. setUnitStatus{"logging/1", state.StatusError, "somehow lost in all those logs", nil},
  1133. expect{
  1134. "multiples related peer units",
  1135. M{
  1136. "environment": "dummyenv",
  1137. "machines": M{
  1138. "0": machine0,
  1139. "1": machine1,
  1140. "2": machine2,
  1141. },
  1142. "services": M{
  1143. "wordpress": M{
  1144. "charm": "cs:quantal/wordpress-3",
  1145. "exposed": true,
  1146. "units": M{
  1147. "wordpress/0": M{
  1148. "machine": "1",
  1149. "agent-state": "started",
  1150. "subordinates": M{
  1151. "logging/0": M{
  1152. "agent-state": "started",
  1153. "public-address": "dummyenv-1.dns",
  1154. },
  1155. },
  1156. "public-address": "dummyenv-1.dns",
  1157. },
  1158. },
  1159. "relations": M{
  1160. "db": L{"mysql"},
  1161. "logging-dir": L{"logging"},
  1162. },
  1163. },
  1164. "mysql": M{
  1165. "charm": "cs:quantal/mysql-1",
  1166. "exposed": true,
  1167. "units": M{
  1168. "mysql/0": M{
  1169. "machine": "2",
  1170. "agent-state": "started",
  1171. "subordinates": M{
  1172. "logging/1": M{
  1173. "agent-state": "error",
  1174. "agent-state-info": "somehow lost in all those logs",
  1175. "public-address": "dummyenv-2.dns",
  1176. },
  1177. },
  1178. "public-address": "dummyenv-2.dns",
  1179. },
  1180. },
  1181. "relations": M{
  1182. "server": L{"wordpress"},
  1183. "juju-info": L{"logging"},
  1184. },
  1185. },
  1186. "logging": M{
  1187. "charm": "cs:quantal/logging-1",
  1188. "exposed": true,
  1189. "relations": M{
  1190. "logging-directory": L{"wordpress"},
  1191. "info": L{"mysql"},
  1192. },
  1193. "subordinate-to": L{"mysql", "wordpress"},
  1194. },
  1195. },
  1196. },
  1197. },
  1198. // scoped on 'logging'
  1199. scopedExpect{
  1200. "subordinates scoped on logging",
  1201. []string{"logging"},
  1202. M{
  1203. "environment": "dummyenv",
  1204. "machines": M{
  1205. "1": machine1,
  1206. "2": machine2,
  1207. },
  1208. "services": M{
  1209. "wordpress": M{
  1210. "charm": "cs:quantal/wordpress-3",
  1211. "exposed": true,
  1212. "units": M{
  1213. "wordpress/0": M{
  1214. "machine": "1",
  1215. "agent-state": "started",
  1216. "subordinates": M{
  1217. "logging/0": M{
  1218. "agent-state": "started",
  1219. "public-address": "dummyenv-1.dns",
  1220. },
  1221. },
  1222. "public-address": "dummyenv-1.dns",
  1223. },
  1224. },
  1225. "relations": M{
  1226. "db": L{"mysql"},
  1227. "logging-dir": L{"logging"},
  1228. },
  1229. },
  1230. "mysql": M{
  1231. "charm": "cs:quantal/mysql-1",
  1232. "exposed": true,
  1233. "units": M{
  1234. "mysql/0": M{
  1235. "machine": "2",
  1236. "agent-state": "started",
  1237. "subordinates": M{
  1238. "logging/1": M{
  1239. "agent-state": "error",
  1240. "agent-state-info": "somehow lost in all those logs",
  1241. "public-address": "dummyenv-2.dns",
  1242. },
  1243. },
  1244. "public-address": "dummyenv-2.dns",
  1245. },
  1246. },
  1247. "relations": M{
  1248. "server": L{"wordpress"},
  1249. "juju-info": L{"logging"},
  1250. },
  1251. },
  1252. "logging": M{
  1253. "charm": "cs:quantal/logging-1",
  1254. "exposed": true,
  1255. "relations": M{
  1256. "logging-directory": L{"wordpress"},
  1257. "info": L{"mysql"},
  1258. },
  1259. "subordinate-to": L{"mysql", "wordpress"},
  1260. },
  1261. },
  1262. },
  1263. },
  1264. // scoped on wordpress/0
  1265. scopedExpect{
  1266. "subordinates scoped on logging",
  1267. []string{"wordpress/0"},
  1268. M{
  1269. "environment": "dummyenv",
  1270. "machines": M{
  1271. "1": machine1,
  1272. },
  1273. "services": M{
  1274. "wordpress": M{
  1275. "charm": "cs:quantal/wordpress-3",
  1276. "exposed": true,
  1277. "units": M{
  1278. "wordpress/0": M{
  1279. "machine": "1",
  1280. "agent-state": "started",
  1281. "subordinates": M{
  1282. "logging/0": M{
  1283. "agent-state": "started",
  1284. "public-address": "dummyenv-1.dns",
  1285. },
  1286. },
  1287. "public-address": "dummyenv-1.dns",
  1288. },
  1289. },
  1290. "relations": M{
  1291. "db": L{"mysql"},
  1292. "logging-dir": L{"logging"},
  1293. },
  1294. },
  1295. "logging": M{
  1296. "charm": "cs:quantal/logging-1",
  1297. "exposed": true,
  1298. "relations": M{
  1299. "logging-directory": L{"wordpress"},
  1300. "info": L{"mysql"},
  1301. },
  1302. "subordinate-to": L{"mysql", "wordpress"},
  1303. },
  1304. },
  1305. },
  1306. },
  1307. ),
  1308. test(
  1309. "machines with containers",
  1310. addMachine{machineId: "0", job: state.JobManageEnviron},
  1311. setAddresses{"0", []network.Address{network.NewAddress("dummyenv-0.dns", network.ScopeUnknown)}},
  1312. startAliveMachine{"0"},
  1313. setMachineStatus{"0", state.StatusStarted, ""},
  1314. addCharm{"mysql"},
  1315. addService{name: "mysql", charm: "mysql"},
  1316. setServiceExposed{"mysql", true},
  1317. addMachine{machineId: "1", job: state.JobHostUnits},
  1318. setAddresses{"1", []network.Address{network.NewAddress("dummyenv-1.dns", network.ScopeUnknown)}},
  1319. startAliveMachine{"1"},
  1320. setMachineStatus{"1", state.StatusStarted, ""},
  1321. addAliveUnit{"mysql", "1"},
  1322. setUnitStatus{"mysql/0", state.StatusActive, "", nil},
  1323. // A container on machine 1.
  1324. addContainer{"1", "1/lxc/0", state.JobHostUnits},
  1325. setAddresses{"1/lxc/0", []network.Address{network.NewAddress("dummyenv-2.dns", network.ScopeUnknown)}},
  1326. startAliveMachine{"1/lxc/0"},
  1327. setMachineStatus{"1/lxc/0", state.StatusStarted, ""},
  1328. addAliveUnit{"mysql", "1/lxc/0"},
  1329. setUnitStatus{"mysql/1", state.StatusActive, "", nil},
  1330. addContainer{"1", "1/lxc/1", state.JobHostUnits},
  1331. // A nested container.
  1332. addContainer{"1/lxc/0", "1/lxc/0/lxc/0", state.JobHostUnits},
  1333. setAddresses{"1/lxc/0/lxc/0", []network.Address{network.NewAddress("dummyenv-3.dns", network.ScopeUnknown)}},
  1334. startAliveMachine{"1/lxc/0/lxc/0"},
  1335. setMachineStatus{"1/lxc/0/lxc/0", state.StatusStarted, ""},
  1336. expect{
  1337. "machines with nested containers",
  1338. M{
  1339. "environment": "dummyenv",
  1340. "machines": M{
  1341. "0": machine0,
  1342. "1": machine1WithContainers,
  1343. },
  1344. "services": M{
  1345. "mysql": M{
  1346. "charm": "cs:quantal/mysql-1",
  1347. "exposed": true,
  1348. "units": M{
  1349. "mysql/0": M{
  1350. "machine": "1",
  1351. "agent-state": "started",
  1352. "public-address": "dummyenv-1.dns",
  1353. },
  1354. "mysql/1": M{
  1355. "machine": "1/lxc/0",
  1356. "agent-state": "started",
  1357. "public-address": "dummyenv-2.dns",
  1358. },
  1359. },
  1360. },
  1361. },
  1362. },
  1363. },
  1364. // once again, with a scope on mysql/1
  1365. scopedExpect{
  1366. "machines with nested containers",
  1367. []string{"mysql/1"},
  1368. M{
  1369. "environment": "dummyenv",
  1370. "machines": M{
  1371. "1": M{
  1372. "agent-state": "started",
  1373. "containers": M{
  1374. "1/lxc/0": M{
  1375. "agent-state": "started",
  1376. "dns-name": "dummyenv-2.dns",
  1377. "instance-id": "dummyenv-2",
  1378. "series": "quantal",
  1379. },
  1380. },
  1381. "dns-name": "dummyenv-1.dns",
  1382. "instance-id": "dummyenv-1",
  1383. "series": "quantal",
  1384. "hardware": "arch=amd64 cpu-cores=1 mem=1024M root-disk=8192M",
  1385. },
  1386. },
  1387. "services": M{
  1388. "mysql": M{
  1389. "charm": "cs:quantal/mysql-1",
  1390. "exposed": true,
  1391. "units": M{
  1392. "mysql/1": M{
  1393. "machine": "1/lxc/0",
  1394. "agent-state": "started",
  1395. "public-address": "dummyenv-2.dns",
  1396. },
  1397. },
  1398. },
  1399. },
  1400. },
  1401. },
  1402. ), test(
  1403. "service with out of date charm",
  1404. addMachine{machineId: "0", job: state.JobManageEnviron},
  1405. setAddresses{"0", []network.Address{network.NewAddress("dummyenv-0.dns", network.ScopeUnknown)}},
  1406. startAliveMachine{"0"},
  1407. setMachineStatus{"0", state.StatusStarted, ""},
  1408. addMachine{machineId: "1", job: state.JobHostUnits},
  1409. setAddresses{"1", []network.Address{network.NewAddress("dummyenv-1.dns", network.ScopeUnknown)}},
  1410. startAliveMachine{"1"},
  1411. setMachineStatus{"1", state.StatusStarted, ""},
  1412. addCharm{"mysql"},
  1413. addService{name: "mysql", charm: "mysql"},
  1414. setServiceExposed{"mysql", true},
  1415. addCharmPlaceholder{"mysql", 23},
  1416. addAliveUnit{"mysql", "1"},
  1417. expect{
  1418. "services and units with correct charm status",
  1419. M{
  1420. "environment": "dummyenv",
  1421. "machines": M{
  1422. "0": machine0,
  1423. "1": machine1,
  1424. },
  1425. "services": M{
  1426. "mysql": M{
  1427. "charm": "cs:quantal/mysql-1",
  1428. "can-upgrade-to": "cs:quantal/mysql-23",
  1429. "exposed": true,
  1430. "units": M{
  1431. "mysql/0": M{
  1432. "machine": "1",
  1433. "agent-state": "allocating",
  1434. "public-address": "dummyenv-1.dns",
  1435. },
  1436. },
  1437. },
  1438. },
  1439. },
  1440. },
  1441. ), test(
  1442. "unit with out of date charm",
  1443. addMachine{machineId: "0", job: state.JobManageEnviron},
  1444. setAddresses{"0", []network.Address{network.NewAddress("dummyenv-0.dns", network.ScopeUnknown)}},
  1445. startAliveMachine{"0"},
  1446. setMachineStatus{"0", state.StatusStarted, ""},
  1447. addMachine{machineId: "1", job: state.JobHostUnits},
  1448. setAddresses{"1", []network.Address{network.NewAddress("dummyenv-1.dns", network.ScopeUnknown)}},
  1449. startAliveMachine{"1"},
  1450. setMachineStatus{"1", state.StatusStarted, ""},
  1451. addCharm{"mysql"},
  1452. addService{name: "mysql", charm: "mysql"},
  1453. setServiceExposed{"mysql", true},
  1454. addAliveUnit{"mysql", "1"},
  1455. setUnitCharmURL{"mysql/0", "cs:quantal/mysql-1"},
  1456. addCharmWithRevision{addCharm{"mysql"}, "local", 1},
  1457. setServiceCharm{"mysql", "local:quantal/mysql-1"},
  1458. expect{
  1459. "services and units with correct charm status",
  1460. M{
  1461. "environment": "dummyenv",
  1462. "machines": M{
  1463. "0": machine0,
  1464. "1": machine1,
  1465. },
  1466. "services": M{
  1467. "mysql": M{
  1468. "charm": "local:quantal/mysql-1",
  1469. "exposed": true,
  1470. "units": M{
  1471. "mysql/0": M{
  1472. "machine": "1",
  1473. "agent-state": "started",
  1474. "upgrading-from": "cs:quantal/mysql-1",
  1475. "public-address": "dummyenv-1.dns",
  1476. },
  1477. },
  1478. },
  1479. },
  1480. },
  1481. },
  1482. ), test(
  1483. "service and unit with out of date charms",
  1484. addMachine{machineId: "0", job: state.JobManageEnviron},
  1485. setAddresses{"0", []network.Address{network.NewAddress("dummyenv-0.dns", network.ScopeUnknown)}},
  1486. startAliveMachine{"0"},
  1487. setMachineStatus{"0", state.StatusStarted, ""},
  1488. addMachine{machineId: "1", job: state.JobHostUnits},
  1489. setAddresses{"1", []network.Address{network.NewAddress("dummyenv-1.dns", network.ScopeUnknown)}},
  1490. startAliveMachine{"1"},
  1491. setMachineStatus{"1", state.StatusStarted, ""},
  1492. addCharm{"mysql"},
  1493. addService{name: "mysql", charm: "mysql"},
  1494. setServiceExposed{"mysql", true},
  1495. addAliveUnit{"mysql", "1"},
  1496. setUnitCharmURL{"mysql/0", "cs:quantal/mysql-1"},
  1497. addCharmWithRevision{addCharm{"mysql"}, "cs", 2},
  1498. setServiceCharm{"mysql", "cs:quantal/mysql-2"},
  1499. addCharmPlaceholder{"mysql", 23},
  1500. expect{
  1501. "services and units with correct charm status",
  1502. M{
  1503. "environment": "dummyenv",
  1504. "machines": M{
  1505. "0": machine0,
  1506. "1": machine1,
  1507. },
  1508. "services": M{
  1509. "mysql": M{
  1510. "charm": "cs:quantal/mysql-2",
  1511. "can-upgrade-to": "cs:quantal/mysql-23",
  1512. "exposed": true,
  1513. "units": M{
  1514. "mysql/0": M{
  1515. "machine": "1",
  1516. "agent-state": "started",
  1517. "upgrading-from": "cs:quantal/mysql-1",
  1518. "public-address": "dummyenv-1.dns",
  1519. },
  1520. },
  1521. },
  1522. },
  1523. },
  1524. },
  1525. ), test(
  1526. "service with local charm not shown as out of date",
  1527. addMachine{machineId: "0", job: state.JobManageEnviron},
  1528. setAddresses{"0", []network.Address{network.NewAddress("dummyenv-0.dns", network.ScopeUnknown)}},
  1529. startAliveMachine{"0"},
  1530. setMachineStatus{"0", state.StatusStarted, ""},
  1531. addMachine{machineId: "1", job: state.JobHostUnits},
  1532. setAddresses{"1", []network.Address{network.NewAddress("dummyenv-1.dns", network.ScopeUnknown)}},
  1533. startAliveMachine{"1"},
  1534. setMachineStatus{"1", state.StatusStarted, ""},
  1535. addCharm{"mysql"},
  1536. addService{name: "mysql", charm: "mysql"},
  1537. setServiceExposed{"mysql", true},
  1538. addAliveUnit{"mysql", "1"},
  1539. setUnitCharmURL{"mysql/0", "cs:quantal/mysql-1"},
  1540. addCharmWithRevision{addCharm{"mysql"}, "local", 1},
  1541. setServiceCharm{"mysql", "local:quantal/mysql-1"},
  1542. addCharmPlaceholder{"mysql", 23},
  1543. expect{
  1544. "services and units with correct charm status",
  1545. M{
  1546. "environment": "dummyenv",
  1547. "machines": M{
  1548. "0": machine0,
  1549. "1": machine1,
  1550. },
  1551. "services": M{
  1552. "mysql": M{
  1553. "charm": "local:quantal/mysql-1",
  1554. "exposed": true,
  1555. "units": M{
  1556. "mysql/0": M{
  1557. "machine": "1",
  1558. "agent-state": "started",
  1559. "upgrading-from": "cs:quantal/mysql-1",
  1560. "public-address": "dummyenv-1.dns",
  1561. },
  1562. },
  1563. },
  1564. },
  1565. },
  1566. },
  1567. ),
  1568. }
  1569. // TODO(dfc) test failing components by destructively mutating the state under the hood
  1570. type addMachine struct {
  1571. machineId string
  1572. cons constraints.Value
  1573. job state.MachineJob
  1574. }
  1575. func (am addMachine) step(c *gc.C, ctx *context) {
  1576. m, err := ctx.st.AddOneMachine(state.MachineTemplate{
  1577. Series: "quantal",
  1578. Constraints: am.cons,
  1579. Jobs: []state.MachineJob{am.job},
  1580. })
  1581. c.Assert(err, jc.ErrorIsNil)
  1582. c.Assert(m.Id(), gc.Equals, am.machineId)
  1583. }
  1584. type addNetwork struct {
  1585. name string
  1586. providerId network.Id
  1587. cidr string
  1588. vlanTag int
  1589. }
  1590. func (an addNetwork) step(c *gc.C, ctx *context) {
  1591. n, err := ctx.st.AddNetwork(state.NetworkInfo{
  1592. Name: an.name,
  1593. ProviderId: an.providerId,
  1594. CIDR: an.cidr,
  1595. VLANTag: an.vlanTag,
  1596. })
  1597. c.Assert(err, jc.ErrorIsNil)
  1598. c.Assert(n.Name(), gc.Equals, an.name)
  1599. }
  1600. type addContainer struct {
  1601. parentId string
  1602. machineId string
  1603. job state.MachineJob
  1604. }
  1605. func (ac addContainer) step(c *gc.C, ctx *context) {
  1606. template := state.MachineTemplate{
  1607. Series: "quantal",
  1608. Jobs: []state.MachineJob{ac.job},
  1609. }
  1610. m, err := ctx.st.AddMachineInsideMachine(template, ac.parentId, instance.LXC)
  1611. c.Assert(err, jc.ErrorIsNil)
  1612. c.Assert(m.Id(), gc.Equals, ac.machineId)
  1613. }
  1614. type startMachine struct {
  1615. machineId string
  1616. }
  1617. func (sm startMachine) step(c *gc.C, ctx *context) {
  1618. m, err := ctx.st.Machine(sm.machineId)
  1619. c.Assert(err, jc.ErrorIsNil)
  1620. cons, err := m.Constraints()
  1621. c.Assert(err, jc.ErrorIsNil)
  1622. inst, hc := testing.AssertStartInstanceWithConstraints(c, ctx.env, m.Id(), cons)
  1623. err = m.SetProvisioned(inst.Id(), "fake_nonce", hc)
  1624. c.Assert(err, jc.ErrorIsNil)
  1625. }
  1626. type startMissingMachine struct {
  1627. machineId string
  1628. }
  1629. func (sm startMissingMachine) step(c *gc.C, ctx *context) {
  1630. m, err := ctx.st.Machine(sm.machineId)
  1631. c.Assert(err, jc.ErrorIsNil)
  1632. cons, err := m.Constraints()
  1633. c.Assert(err, jc.ErrorIsNil)
  1634. _, hc := testing.AssertStartInstanceWithConstraints(c, ctx.env, m.Id(), cons)
  1635. err = m.SetProvisioned("i-missing", "fake_nonce", hc)
  1636. c.Assert(err, jc.ErrorIsNil)
  1637. err = m.SetInstanceStatus("missing")
  1638. c.Assert(err, jc.ErrorIsNil)
  1639. }
  1640. type startAliveMachine struct {
  1641. machineId string
  1642. }
  1643. func (sam startAliveMachine) step(c *gc.C, ctx *context) {
  1644. m, err := ctx.st.Machine(sam.machineId)
  1645. c.Assert(err, jc.ErrorIsNil)
  1646. pinger := ctx.setAgentPresence(c, m)
  1647. cons, err := m.Constraints()
  1648. c.Assert(err, jc.ErrorIsNil)
  1649. inst, hc := testing.AssertStartInstanceWithConstraints(c, ctx.env, m.Id(), cons)
  1650. err = m.SetProvisioned(inst.Id(), "fake_nonce", hc)
  1651. c.Assert(err, jc.ErrorIsNil)
  1652. ctx.pingers[m.Id()] = pinger
  1653. }
  1654. type setAddresses struct {
  1655. machineId string
  1656. addresses []network.Address
  1657. }
  1658. func (sa setAddresses) step(c *gc.C, ctx *context) {
  1659. m, err := ctx.st.Machine(sa.machineId)
  1660. c.Assert(err, jc.ErrorIsNil)
  1661. err = m.SetAddresses(sa.addresses...)
  1662. c.Assert(err, jc.ErrorIsNil)
  1663. }
  1664. type setTools struct {
  1665. machineId string
  1666. version version.Binary
  1667. }
  1668. func (st setTools) step(c *gc.C, ctx *context) {
  1669. m, err := ctx.st.Machine(st.machineId)
  1670. c.Assert(err, jc.ErrorIsNil)
  1671. err = m.SetAgentVersion(st.version)
  1672. c.Assert(err, jc.ErrorIsNil)
  1673. }
  1674. type addCharm struct {
  1675. name string
  1676. }
  1677. func (ac addCharm) addCharmStep(c *gc.C, ctx *context, scheme string, rev int) {
  1678. ch := testcharms.Repo.CharmDir(ac.name)
  1679. name := ch.Meta().Name
  1680. curl := charm.MustParseURL(fmt.Sprintf("%s:quantal/%s-%d", scheme, name, rev))
  1681. dummy, err := ctx.st.AddCharm(ch, curl, "dummy-path", fmt.Sprintf("%s-%d-sha256", name, rev))
  1682. c.Assert(err, jc.ErrorIsNil)
  1683. ctx.charms[ac.name] = dummy
  1684. }
  1685. func (ac addCharm) step(c *gc.C, ctx *context) {
  1686. ch := testcharms.Repo.CharmDir(ac.name)
  1687. ac.addCharmStep(c, ctx, "cs", ch.Revision())
  1688. }
  1689. type addCharmWithRevision struct {
  1690. addCharm
  1691. scheme string
  1692. rev int
  1693. }
  1694. func (ac addCharmWithRevision) step(c *gc.C, ctx *context) {
  1695. ac.addCharmStep(c, ctx, ac.scheme, ac.rev)
  1696. }
  1697. type addService struct {
  1698. name string
  1699. charm string
  1700. networks []string
  1701. cons constraints.Value
  1702. }
  1703. func (as addService) step(c *gc.C, ctx *context) {
  1704. ch, ok := ctx.charms[as.charm]
  1705. c.Assert(ok, jc.IsTrue)
  1706. svc, err := ctx.st.AddService(as.name, ctx.adminUserTag, ch, as.networks, nil)
  1707. c.Assert(err, jc.ErrorIsNil)
  1708. if svc.IsPrincipal() {
  1709. err = svc.SetConstraints(as.cons)
  1710. c.Assert(err, jc.ErrorIsNil)
  1711. }
  1712. }
  1713. type setServiceExposed struct {
  1714. name string
  1715. exposed bool
  1716. }
  1717. func (sse setServiceExposed) step(c *gc.C, ctx *context) {
  1718. s, err := ctx.st.Service(sse.name)
  1719. c.Assert(err, jc.ErrorIsNil)
  1720. err = s.ClearExposed()
  1721. c.Assert(err, jc.ErrorIsNil)
  1722. if sse.exposed {
  1723. err = s.SetExposed()
  1724. c.Assert(err, jc.ErrorIsNil)
  1725. }
  1726. }
  1727. type setServiceCharm struct {
  1728. name string
  1729. charm string
  1730. }
  1731. func (ssc setServiceCharm) step(c *gc.C, ctx *context) {
  1732. ch, err := ctx.st.Charm(charm.MustParseURL(ssc.charm))
  1733. c.Assert(err, jc.ErrorIsNil)
  1734. s, err := ctx.st.Service(ssc.name)
  1735. c.Assert(err, jc.ErrorIsNil)
  1736. err = s.SetCharm(ch, false)
  1737. c.Assert(err, jc.ErrorIsNil)
  1738. }
  1739. type addCharmPlaceholder struct {
  1740. name string
  1741. rev int
  1742. }
  1743. func (ac addCharmPlaceholder) step(c *gc.C, ctx *context) {
  1744. ch := testcharms.Repo.CharmDir(ac.name)
  1745. name := ch.Meta().Name
  1746. curl := charm.MustParseURL(fmt.Sprintf("cs:quantal/%s-%d", name, ac.rev))
  1747. err := ctx.st.AddStoreCharmPlaceholder(curl)
  1748. c.Assert(err, jc.ErrorIsNil)
  1749. }
  1750. type addUnit struct {
  1751. serviceName string
  1752. machineId string
  1753. }
  1754. func (au addUnit) step(c *gc.C, ctx *context) {
  1755. s, err := ctx.st.Service(au.serviceName)
  1756. c.Assert(err, jc.ErrorIsNil)
  1757. u, err := s.AddUnit()
  1758. c.Assert(err, jc.ErrorIsNil)
  1759. m, err := ctx.st.Machine(au.machineId)
  1760. c.Assert(err, jc.ErrorIsNil)
  1761. err = u.AssignToMachine(m)
  1762. c.Assert(err, jc.ErrorIsNil)
  1763. }
  1764. type addAliveUnit struct {
  1765. serviceName string
  1766. machineId string
  1767. }
  1768. func (aau addAliveUnit) step(c *gc.C, ctx *context) {
  1769. s, err := ctx.st.Service(aau.serviceName)
  1770. c.Assert(err, jc.ErrorIsNil)
  1771. u, err := s.AddUnit()
  1772. c.Assert(err, jc.ErrorIsNil)
  1773. pinger := ctx.setAgentPresence(c, u)
  1774. m, err := ctx.st.Machine(aau.machineId)
  1775. c.Assert(err, jc.ErrorIsNil)
  1776. err = u.AssignToMachine(m)
  1777. c.Assert(err, jc.ErrorIsNil)
  1778. ctx.pingers[u.Name()] = pinger
  1779. }
  1780. type setUnitsAlive struct {
  1781. serviceName string
  1782. }
  1783. func (sua setUnitsAlive) step(c *gc.C, ctx *context) {
  1784. s, err := ctx.st.Service(sua.serviceName)
  1785. c.Assert(err, jc.ErrorIsNil)
  1786. us, err := s.AllUnits()
  1787. c.Assert(err, jc.ErrorIsNil)
  1788. for _, u := range us {
  1789. ctx.pingers[u.Name()] = ctx.setAgentPresence(c, u)
  1790. }
  1791. }
  1792. type setUnitStatus struct {
  1793. unitName string
  1794. status state.Status
  1795. statusInfo string
  1796. statusData map[string]interface{}
  1797. }
  1798. func (sus setUnitStatus) step(c *gc.C, ctx *context) {
  1799. u, err := ctx.st.Unit(sus.unitName)
  1800. c.Assert(err, jc.ErrorIsNil)
  1801. err = u.SetAgentStatus(sus.status, sus.statusInfo, sus.statusData)
  1802. c.Assert(err, jc.ErrorIsNil)
  1803. }
  1804. type setUnitCharmURL struct {
  1805. unitName string
  1806. charm string
  1807. }
  1808. func (uc setUnitCharmURL) step(c *gc.C, ctx *context) {
  1809. u, err := ctx.st.Unit(uc.unitName)
  1810. c.Assert(err, jc.ErrorIsNil)
  1811. curl := charm.MustParseURL(uc.charm)
  1812. err = u.SetCharmURL(curl)
  1813. c.Assert(err, jc.ErrorIsNil)
  1814. err = u.SetAgentStatus(state.StatusActive, "", nil)
  1815. c.Assert(err, jc.ErrorIsNil)
  1816. }
  1817. type openUnitPort struct {
  1818. unitName string
  1819. protocol string
  1820. number int
  1821. }
  1822. func (oup openUnitPort) step(c *gc.C, ctx *context) {
  1823. u, err := ctx.st.Unit(oup.unitName)
  1824. c.Assert(err, jc.ErrorIsNil)
  1825. err = u.OpenPort(oup.protocol, oup.number)
  1826. c.Assert(err, jc.ErrorIsNil)
  1827. }
  1828. type ensureDyingUnit struct {
  1829. unitName string
  1830. }
  1831. func (e ensureDyingUnit) step(c *gc.C, ctx *context) {
  1832. u, err := ctx.st.Unit(e.unitName)
  1833. c.Assert(err, jc.ErrorIsNil)
  1834. err = u.Destroy()
  1835. c.Assert(err, jc.ErrorIsNil)
  1836. c.Assert(u.Life(), gc.Equals, state.Dying)
  1837. }
  1838. type ensureDyingService struct {
  1839. serviceName string
  1840. }
  1841. func (e ensureDyingService) step(c *gc.C, ctx *context) {
  1842. svc, err := ctx.st.Service(e.serviceName)
  1843. c.Assert(err, jc.ErrorIsNil)
  1844. err = svc.Destroy()
  1845. c.Assert(err, jc.ErrorIsNil)
  1846. err = svc.Refresh()
  1847. c.Assert(err, jc.ErrorIsNil)
  1848. c.Assert(svc.Life(), gc.Equals, state.Dying)
  1849. }
  1850. type ensureDeadMachine struct {
  1851. machineId string
  1852. }
  1853. func (e ensureDeadMachine) step(c *gc.C, ctx *context) {
  1854. m, err := ctx.st.Machine(e.machineId)
  1855. c.Assert(err, jc.ErrorIsNil)
  1856. err = m.EnsureDead()
  1857. c.Assert(err, jc.ErrorIsNil)
  1858. c.Assert(m.Life(), gc.Equals, state.Dead)
  1859. }
  1860. type setMachineStatus struct {
  1861. machineId string
  1862. status state.Status
  1863. statusInfo string
  1864. }
  1865. func (sms setMachineStatus) step(c *gc.C, ctx *context) {
  1866. m, err := ctx.st.Machine(sms.machineId)
  1867. c.Assert(err, jc.ErrorIsNil)
  1868. err = m.SetStatus(sms.status, sms.statusInfo, nil)
  1869. c.Assert(err, jc.ErrorIsNil)
  1870. }
  1871. type relateServices struct {
  1872. ep1, ep2 string
  1873. }
  1874. func (rs relateServices) step(c *gc.C, ctx *context) {
  1875. eps, err := ctx.st.InferEndpoints(rs.ep1, rs.ep2)
  1876. c.Assert(err, jc.ErrorIsNil)
  1877. _, err = ctx.st.AddRelation(eps...)
  1878. c.Assert(err, jc.ErrorIsNil)
  1879. }
  1880. type addSubordinate struct {
  1881. prinUnit string
  1882. subService string
  1883. }
  1884. func (as addSubordinate) step(c *gc.C, ctx *context) {
  1885. u, err := ctx.st.Unit(as.prinUnit)
  1886. c.Assert(err, jc.ErrorIsNil)
  1887. eps, err := ctx.st.InferEndpoints(u.ServiceName(), as.subService)
  1888. c.Assert(err, jc.ErrorIsNil)
  1889. rel, err := ctx.st.EndpointsRelation(eps...)
  1890. c.Assert(err, jc.ErrorIsNil)
  1891. ru, err := rel.Unit(u)
  1892. c.Assert(err, jc.ErrorIsNil)
  1893. err = ru.EnterScope(nil)
  1894. c.Assert(err, jc.ErrorIsNil)
  1895. }
  1896. type scopedExpect struct {
  1897. what string
  1898. scope []string
  1899. output M
  1900. }
  1901. type expect struct {
  1902. what string
  1903. output M
  1904. }
  1905. func (e scopedExpect) step(c *gc.C, ctx *context) {
  1906. c.Logf("\nexpect: %s %s\n", e.what, strings.Join(e.scope, " "))
  1907. // Now execute the command for each format.
  1908. for _, format := range statusFormats {
  1909. c.Logf("format %q", format.name)
  1910. // Run command with the required format.
  1911. args := append([]string{"--format", format.name}, e.scope...)
  1912. c.Logf("running status %s", strings.Join(args, " "))
  1913. code, stdout, stderr := runStatus(c, args...)
  1914. c.Assert(code, gc.Equals, 0)
  1915. if !c.Check(stderr, gc.HasLen, 0) {
  1916. c.Fatalf("status failed: %s", string(stderr))
  1917. }
  1918. // Prepare the output in the same format.
  1919. buf, err := format.marshal(e.output)
  1920. c.Assert(err, jc.ErrorIsNil)
  1921. expected := make(M)
  1922. err = format.unmarshal(buf, &expected)
  1923. c.Assert(err, jc.ErrorIsNil)
  1924. // Check the output is as expected.
  1925. actual := make(M)
  1926. err = format.unmarshal(stdout, &actual)
  1927. c.Assert(err, jc.ErrorIsNil)
  1928. c.Assert(actual, jc.DeepEquals, expected)
  1929. }
  1930. }
  1931. func (e expect) step(c *gc.C, ctx *context) {
  1932. scopedExpect{e.what, nil, e.output}.step(c, ctx)
  1933. }
  1934. func (s *StatusSuite) TestStatusAllFormats(c *gc.C) {
  1935. for i, t := range statusTests {
  1936. c.Logf("test %d: %s", i, t.summary)
  1937. func(t testCase) {
  1938. // Prepare context and run all steps to setup.
  1939. ctx := s.newContext(c)
  1940. defer s.resetContext(c, ctx)
  1941. ctx.run(c, t.steps)
  1942. }(t)
  1943. }
  1944. }
  1945. type fakeApiClient struct {
  1946. statusReturn *api.Status
  1947. patternsUsed []string
  1948. closeCalled bool
  1949. }
  1950. func newFakeApiClient(statusReturn *api.Status) fakeApiClient {
  1951. return fakeApiClient{
  1952. statusReturn: statusReturn,
  1953. }
  1954. }
  1955. func (a *fakeApiClient) Status(patterns []string) (*api.Status, error) {
  1956. a.patternsUsed = patterns
  1957. return a.statusReturn, nil
  1958. }
  1959. func (a *fakeApiClient) Close() error {
  1960. a.closeCalled = true
  1961. return nil
  1962. }
  1963. // Check that the client works with an older server which doesn't
  1964. // return the top level Relations field nor the unit and machine level
  1965. // Agent field (they were introduced at the same time).
  1966. func (s *StatusSuite) TestStatusWithPreRelationsServer(c *gc.C) {
  1967. // Construct an older style status response
  1968. client := newFakeApiClient(&api.Status{
  1969. EnvironmentName: "dummyenv",
  1970. Machines: map[string]api.MachineStatus{
  1971. "0": {
  1972. // Agent field intentionally not set
  1973. Id: "0",
  1974. InstanceId: instance.Id("dummyenv-0"),
  1975. AgentState: "down",
  1976. AgentStateInfo: "(started)",
  1977. Series: "quantal",
  1978. Containers: map[string]api.MachineStatus{},
  1979. Jobs: []multiwatcher.MachineJob{multiwatcher.JobManageEnviron},
  1980. HasVote: false,
  1981. WantsVote: true,
  1982. },
  1983. "1": {
  1984. // Agent field intentionally not set
  1985. Id: "1",
  1986. InstanceId: instance.Id("dummyenv-1"),
  1987. AgentState: "started",
  1988. AgentStateInfo: "hello",
  1989. Series: "quantal",
  1990. Containers: map[string]api.MachineStatus{},
  1991. Jobs: []multiwatcher.MachineJob{multiwatcher.JobHostUnits},
  1992. HasVote: false,
  1993. WantsVote: false,
  1994. },
  1995. },
  1996. Services: map[string]api.ServiceStatus{
  1997. "mysql": {
  1998. Charm: "local:quantal/mysql-1",
  1999. Relations: map[string][]string{
  2000. "server": {"wordpress"},
  2001. },
  2002. Units: map[string]api.UnitStatus{
  2003. "mysql/0": {
  2004. // Agent field intentionally not set
  2005. Machine: "1",
  2006. AgentState: "allocating",
  2007. },
  2008. },
  2009. },
  2010. "wordpress": {
  2011. Charm: "local:quantal/wordpress-3",
  2012. Relations: map[string][]string{
  2013. "db": {"mysql"},
  2014. },
  2015. Units: map[string]api.UnitStatus{
  2016. "wordpress/0": {
  2017. // Agent field intentionally not set
  2018. AgentState: "error",
  2019. AgentStateInfo: "blam",
  2020. Machine: "1",
  2021. },
  2022. },
  2023. },
  2024. },
  2025. Networks: map[string]api.NetworkStatus{},
  2026. // Relations field intentionally not set
  2027. })
  2028. s.PatchValue(&newApiClientForStatus, func(_ *StatusCommand) (statusAPI, error) {
  2029. return &client, nil
  2030. })
  2031. expected := expect{
  2032. "sane output with an older client that doesn't return Agent or Relations fields",
  2033. M{
  2034. "environment": "dummyenv",
  2035. "machines": M{
  2036. "0": M{
  2037. "agent-state": "down",
  2038. "agent-state-info": "(started)",
  2039. "instance-id": "dummyenv-0",
  2040. "series": "quantal",
  2041. "state-server-member-status": "adding-vote",
  2042. },
  2043. "1": M{
  2044. "agent-state": "started",
  2045. "agent-state-info": "hello",
  2046. "instance-id": "dummyenv-1",
  2047. "series": "quantal",
  2048. },
  2049. },
  2050. "services": M{
  2051. "mysql": M{
  2052. "charm": "local:quantal/mysql-1",
  2053. "exposed": false,
  2054. "relations": M{
  2055. "server": L{"wordpress"},
  2056. },
  2057. "units": M{
  2058. "mysql/0": M{
  2059. "machine": "1",
  2060. "agent-state": "allocating",
  2061. },
  2062. },
  2063. },
  2064. "wordpress": M{
  2065. "charm": "local:quantal/wordpress-3",
  2066. "exposed": false,
  2067. "relations": M{
  2068. "db": L{"mysql"},
  2069. },
  2070. "units": M{
  2071. "wordpress/0": M{
  2072. "machine": "1",
  2073. "agent-state": "error",
  2074. "agent-state-info": "blam",
  2075. },
  2076. },
  2077. },
  2078. },
  2079. },
  2080. }
  2081. ctx := s.newContext(c)
  2082. defer s.resetContext(c, ctx)
  2083. ctx.run(c, []stepper{expected})
  2084. }
  2085. func (s *StatusSuite) TestStatusWithFormatSummary(c *gc.C) {
  2086. ctx := s.newContext(c)
  2087. defer s.resetContext(c, ctx)
  2088. steps := []stepper{
  2089. addMachine{machineId: "0", job: state.JobManageEnviron},
  2090. setAddresses{"0", []network.Address{network.NewAddress("localhost", network.ScopeUnknown)}},
  2091. startAliveMachine{"0"},
  2092. setMachineStatus{"0", state.StatusStarted, ""},
  2093. addCharm{"wordpress"},
  2094. addCharm{"mysql"},
  2095. addCharm{"logging"},
  2096. addService{name: "wordpress", charm: "wordpress"},
  2097. setServiceExposed{"wordpress", true},
  2098. addMachine{machineId: "1", job: state.JobHostUnits},
  2099. setAddresses{"1", []network.Address{network.NewAddress("localhost", network.ScopeUnknown)}},
  2100. startAliveMachine{"1"},
  2101. setMachineStatus{"1", state.StatusStarted, ""},
  2102. addAliveUnit{"wordpress", "1"},
  2103. setUnitStatus{"wordpress/0", state.StatusActive, "", nil},
  2104. addService{name: "mysql", charm: "mysql"},
  2105. setServiceExposed{"mysql", true},
  2106. addMachine{machineId: "2", job: state.JobHostUnits},
  2107. setAddresses{"2", []network.Address{network.NewAddress("10.0.0.1", network.ScopeUnknown)}},
  2108. startAliveMachine{"2"},
  2109. setMachineStatus{"2", state.StatusStarted, ""},
  2110. addAliveUnit{"mysql", "2"},
  2111. setUnitStatus{"mysql/0", state.StatusActive, "", nil},
  2112. addService{name: "logging", charm: "logging"},
  2113. setServiceExposed{"logging", true},
  2114. relateServices{"wordpress", "mysql"},
  2115. relateServices{"wordpress", "logging"},
  2116. relateServices{"mysql", "logging"},
  2117. addSubordinate{"wordpress/0", "logging"},
  2118. addSubordinate{"mysql/0", "logging"},
  2119. setUnitsAlive{"logging"},
  2120. setUnitStatus{"logging/0", state.StatusActive, "", nil},
  2121. setUnitStatus{"logging/1", state.StatusError, "somehow lost in all those logs", nil},
  2122. }
  2123. for _, s := range steps {
  2124. s.step(c, ctx)
  2125. }
  2126. code, stdout, stderr := runStatus(c, "--format", "summary")
  2127. c.Check(code, gc.Equals, 0)
  2128. c.Check(string(stderr), gc.Equals, "")
  2129. c.Assert(
  2130. string(stdout),
  2131. gc.Equals,
  2132. "Running on subnets: 127.0.0.1/8, 10.0.0.1/8 \n"+
  2133. "Utilizing ports: \n"+
  2134. " # MACHINES: (3)\n"+
  2135. " started: 3 \n"+
  2136. " \n"+
  2137. " # UNITS: (4)\n"+
  2138. " error: 1 \n"+
  2139. " started: 3 \n"+
  2140. " \n"+
  2141. " # SERVICES: (3)\n"+
  2142. " logging 1/1 exposed\n"+
  2143. " mysql 1/1 exposed\n"+
  2144. " wordpress 1/1 exposed\n"+
  2145. "\n",
  2146. )
  2147. }
  2148. func (s *StatusSuite) TestStatusWithFormatOneline(c *gc.C) {
  2149. ctx := s.newContext(c)
  2150. defer s.resetContext(c, ctx)
  2151. steps := []stepper{
  2152. addMachine{machineId: "0", job: state.JobManageEnviron},
  2153. setAddresses{"0", []network.Address{network.NewAddress("dummyenv-0.dns", network.ScopeUnknown)}},
  2154. startAliveMachine{"0"},
  2155. setMachineStatus{"0", state.StatusStarted, ""},
  2156. addCharm{"wordpress"},
  2157. addCharm{"mysql"},
  2158. addCharm{"logging"},
  2159. addService{name: "wordpress", charm: "wordpress"},
  2160. setServiceExposed{"wordpress", true},
  2161. addMachine{machineId: "1", job: state.JobHostUnits},
  2162. setAddresses{"1", []network.Address{network.NewAddress("dummyenv-1.dns", network.ScopeUnknown)}},
  2163. startAliveMachine{"1"},
  2164. setMachineStatus{"1", state.StatusStarted, ""},
  2165. addAliveUnit{"wordpress", "1"},
  2166. setUnitStatus{"wordpress/0", state.StatusActive, "", nil},
  2167. addService{name: "mysql", charm: "mysql"},
  2168. setServiceExposed{"mysql", true},
  2169. addMachine{machineId: "2", job: state.JobHostUnits},
  2170. setAddresses{"2", []network.Address{network.NewAddress("dummyenv-2.dns", network.ScopeUnknown)}},
  2171. startAliveMachine{"2"},
  2172. setMachineStatus{"2", state.StatusStarted, ""},
  2173. addAliveUnit{"mysql", "2"},
  2174. setUnitStatus{"mysql/0", state.StatusActive, "", nil},
  2175. addService{name: "logging", charm: "logging"},
  2176. setServiceExposed{"logging", true},
  2177. relateServices{"wordpress", "mysql"},
  2178. relateServices{"wordpress", "logging"},
  2179. relateServices{"mysql", "logging"},
  2180. addSubordinate{"wordpress/0", "logging"},
  2181. addSubordinate{"mysql/0", "logging"},
  2182. setUnitsAlive{"logging"},
  2183. setUnitStatus{"logging/0", state.StatusActive, "", nil},
  2184. setUnitStatus{"logging/1", state.StatusError, "somehow lost in all those logs", nil},
  2185. }
  2186. ctx.run(c, steps)
  2187. const expected = `
  2188. - mysql/0: dummyenv-2.dns (started)
  2189. - logging/1: dummyenv-2.dns (error)
  2190. - wordpress/0: dummyenv-1.dns (started)
  2191. - logging/0: dummyenv-1.dns (started)
  2192. `
  2193. code, stdout, stderr := runStatus(c, "--format", "oneline")
  2194. c.Check(code, gc.Equals, 0)
  2195. c.Check(string(stderr), gc.Equals, "")
  2196. c.Assert(string(stdout), gc.Equals, expected)
  2197. c.Log(`Check that "short" is an alias for oneline.`)
  2198. code, stdout, stderr = runStatus(c, "--format", "short")
  2199. c.Check(code, gc.Equals, 0)
  2200. c.Check(string(stderr), gc.Equals, "")
  2201. c.Assert(string(stdout), gc.Equals, expected)
  2202. c.Log(`Check that "line" is an alias for oneline.`)
  2203. code, stdout, stderr = runStatus(c, "--format", "line")
  2204. c.Check(code, gc.Equals, 0)
  2205. c.Check(string(stderr), gc.Equals, "")
  2206. c.Assert(string(stdout), gc.Equals, expected)
  2207. }
  2208. func (s *StatusSuite) TestStatusWithFormatTabular(c *gc.C) {
  2209. ctx := s.newContext(c)
  2210. defer s.resetContext(c, ctx)
  2211. steps := []stepper{
  2212. addMachine{machineId: "0", job: state.JobManageEnviron},
  2213. setAddresses{"0", []network.Address{network.NewAddress("dummyenv-0.dns", network.ScopeUnknown)}},
  2214. startAliveMachine{"0"},
  2215. setMachineStatus{"0", state.StatusStarted, ""},
  2216. addCharm{"wordpress"},
  2217. addCharm{"mysql"},
  2218. addCharm{"logging"},
  2219. addService{name: "wordpress", charm: "wordpress"},
  2220. setServiceExposed{"wordpress", true},
  2221. addMachine{machineId: "1", job: state.JobHostUnits},
  2222. setAddresses{"1", []network.Address{network.NewAddress("dummyenv-1.dns", network.ScopeUnknown)}},
  2223. startAliveMachine{"1"},
  2224. setMachineStatus{"1", state.StatusStarted, ""},
  2225. addAliveUnit{"wordpress", "1"},
  2226. setUnitStatus{"wordpress/0", state.StatusActive, "", nil},
  2227. addService{name: "mysql", charm: "mysql"},
  2228. setServiceExposed{"mysql", true},
  2229. addMachine{machineId: "2", job: state.JobHostUnits},
  2230. setAddresses{"2", []network.Address{network.NewAddress("dummyenv-2.dns", network.ScopeUnknown)}},
  2231. startAliveMachine{"2"},
  2232. setMachineStatus{"2", state.StatusStarted, ""},
  2233. addAliveUnit{"mysql", "2"},
  2234. setUnitStatus{"mysql/0", state.StatusActive, "", nil},
  2235. addService{name: "logging", charm: "logging"},
  2236. setServiceExposed{"logging", true},
  2237. relateServices{"wordpress", "mysql"},
  2238. relateServices{"wordpress", "logging"},
  2239. relateServices{"mysql", "logging"},
  2240. addSubordinate{"wordpress/0", "logging"},
  2241. addSubordinate{"mysql/0", "logging"},
  2242. setUnitsAlive{"logging"},
  2243. setUnitStatus{"logging/0", state.StatusActive, "", nil},
  2244. setUnitStatus{"logging/1", state.StatusError, "somehow lost in all those logs", nil},
  2245. }
  2246. for _, s := range steps {
  2247. s.step(c, ctx)
  2248. }
  2249. code, stdout, stderr := runStatus(c, "--format", "tabular")
  2250. c.Check(code, gc.Equals, 0)
  2251. c.Check(string(stderr), gc.Equals, "")
  2252. c.Assert(
  2253. string(stdout),
  2254. gc.Equals,
  2255. "[Machines] \n"+
  2256. "ID STATE VERSION DNS INS-ID SERIES HARDWARE \n"+
  2257. "0 started dummyenv-0.dns dummyenv-0 quantal arch=amd64 cpu-cores=1 mem=1024M root-disk=8192M \n"+
  2258. "1 started dummyenv-1.dns dummyenv-1 quantal arch=amd64 cpu-cores=1 mem=1024M root-disk=8192M \n"+
  2259. "2 started dummyenv-2.dns dummyenv-2 quantal arch=amd64 cpu-cores=1 mem=1024M root-disk=8192M \n"+
  2260. "\n"+
  2261. "[Services] \n"+
  2262. "NAME EXPOSED CHARM \n"+
  2263. "logging true cs:quantal/logging-1 \n"+
  2264. "mysql true cs:quantal/mysql-1 \n"+
  2265. "wordpress true cs:quantal/wordpress-3 \n"+
  2266. "\n"+
  2267. "[Units] \n"+
  2268. "ID STATE VERSION MACHINE PORTS PUBLIC-ADDRESS \n"+
  2269. "mysql/0 started 2 dummyenv-2.dns \n"+
  2270. " logging/1 error dummyenv-2.dns \n"+
  2271. "wordpress/0 started 1 dummyenv-1.dns \n"+
  2272. " logging/0 started dummyenv-1.dns \n"+
  2273. "\n",
  2274. )
  2275. }
  2276. func (s *StatusSuite) TestStatusWithNilStatusApi(c *gc.C) {
  2277. ctx := s.newContext(c)
  2278. defer s.resetContext(c, ctx)
  2279. steps := []stepper{
  2280. addMachine{machineId: "0", job: state.JobManageEnviron},
  2281. setAddresses{"0", []network.Address{network.NewAddress("dummyenv-0.dns", network.ScopeUnknown)}},
  2282. startAliveMachine{"0"},
  2283. setMachineStatus{"0", state.StatusStarted, ""},
  2284. }
  2285. for _, s := range steps {
  2286. s.step(c, ctx)
  2287. }
  2288. client := fakeApiClient{}
  2289. var status = client.Status
  2290. s.PatchValue(&status, func(_ []string) (*api.Status, error) {
  2291. return nil, nil
  2292. })
  2293. s.PatchValue(&newApiClientForStatus, func(_ *StatusCommand) (statusAPI, error) {
  2294. return &client, nil
  2295. })
  2296. code, _, stderr := runStatus(c, "--format", "tabular")
  2297. c.Check(code, gc.Equals, 1)
  2298. c.Check(string(stderr), gc.Equals, "error: unable to obtain the current status\n")
  2299. }
  2300. //
  2301. // Filtering Feature
  2302. //
  2303. func (s *StatusSuite) FilteringTestSetup(c *gc.C) *context {
  2304. ctx := s.newContext(c)
  2305. steps := []stepper{
  2306. // Given a machine is started
  2307. // And the machine's ID is "0"
  2308. // And the machine's job is to manage the environment
  2309. addMachine{machineId: "0", job: state.JobManageEnviron},
  2310. startAliveMachine{"0"},
  2311. setMachineStatus{"0", state.StatusStarted, ""},
  2312. // And the machine's address is "dummyenv-0.dns"
  2313. setAddresses{"0", []network.Address{network.NewAddress("dummyenv-0.dns", network.ScopeUnknown)}},
  2314. // And the "wordpress" charm is available
  2315. addCharm{"wordpress"},
  2316. addService{name: "wordpress", charm: "wordpress"},
  2317. // And the "mysql" charm is available
  2318. addCharm{"mysql"},
  2319. addService{name: "mysql", charm: "mysql"},
  2320. // And the "logging" charm is available
  2321. addCharm{"logging"},
  2322. // And a machine is started
  2323. // And the machine's ID is "1"
  2324. // And the machine's job is to host units
  2325. addMachine{machineId: "1", job: state.JobHostUnits},
  2326. startAliveMachine{"1"},
  2327. setMachineStatus{"1", state.StatusStarted, ""},
  2328. // And the machine's address is "dummyenv-1.dns"
  2329. setAddresses{"1", []network.Address{network.NewAddress("dummyenv-1.dns", network.ScopeUnknown)}},
  2330. // And a unit of "wordpress" is deployed to machine "1"
  2331. addAliveUnit{"wordpress", "1"},
  2332. // And the unit is started
  2333. setUnitStatus{"wordpress/0", state.StatusActive, "", nil},
  2334. // And a machine is started
  2335. // And the machine's ID is "2"
  2336. // And the machine's job is to host units
  2337. addMachine{machineId: "2", job: state.JobHostUnits},
  2338. startAliveMachine{"2"},
  2339. setMachineStatus{"2", state.StatusStarted, ""},
  2340. // And the machine's address is "dummyenv-2.dns"
  2341. setAddresses{"2", []network.Address{network.NewAddress("dummyenv-2.dns", network.ScopeUnknown)}},
  2342. // And a unit of "mysql" is deployed to machine "2"
  2343. addAliveUnit{"mysql", "2"},
  2344. // And the unit is started
  2345. setUnitStatus{"mysql/0", state.StatusActive, "", nil},
  2346. // And the "logging" service is added
  2347. addService{name: "logging", charm: "logging"},
  2348. // And the service is exposed
  2349. setServiceExposed{"logging", true},
  2350. // And the "wordpress" service is related to the "mysql" service
  2351. relateServices{"wordpress", "mysql"},
  2352. // And the "wordpress" service is related to the "logging" service
  2353. relateServices{"wordpress", "logging"},
  2354. // And the "mysql" service is related to the "logging" service
  2355. relateServices{"mysql", "logging"},
  2356. // And the "logging" service is a subordinate to unit 0 of the "wordpress" service
  2357. addSubordinate{"wordpress/0", "logging"},
  2358. setUnitStatus{"logging/0", state.StatusActive, "", nil},
  2359. // And the "logging" service is a subordinate to unit 0 of the "mysql" service
  2360. addSubordinate{"mysql/0", "logging"},
  2361. setUnitStatus{"logging/1", state.StatusActive, "", nil},
  2362. setUnitsAlive{"logging"},
  2363. }
  2364. ctx.run(c, steps)
  2365. return ctx
  2366. }
  2367. // Scenario: One unit is in an errored state and user filters to started
  2368. func (s *StatusSuite) TestFilterToStarted(c *gc.C) {
  2369. ctx := s.FilteringTestSetup(c)
  2370. defer s.resetContext(c, ctx)
  2371. // Given unit 1 of the "logging" service has an error
  2372. setUnitStatus{"logging/1", state.StatusError, "mock error", nil}.step(c, ctx)
  2373. // And unit 0 of the "mysql" service has an error
  2374. setUnitStatus{"mysql/0", state.StatusError, "mock error", nil}.step(c, ctx)
  2375. // When I run juju status --format oneline started
  2376. _, stdout, stderr := runStatus(c, "--format", "oneline", "started")
  2377. c.Assert(string(stderr), gc.Equals, "")
  2378. // Then I should receive output prefixed with:
  2379. const expected = `
  2380. - wordpress/0: dummyenv-1.dns (started)
  2381. - logging/0: dummyenv-1.dns (started)
  2382. `
  2383. c.Assert(string(stdout), gc.Equals, expected[1:])
  2384. }
  2385. // Scenario: One unit is in an errored state and user filters to errored
  2386. func (s *StatusSuite) TestFilterToErrored(c *gc.C) {
  2387. ctx := s.FilteringTestSetup(c)
  2388. defer s.resetContext(c, ctx)
  2389. // Given unit 1 of the "logging" service has an error
  2390. setUnitStatus{"logging/1", state.StatusError, "mock error", nil}.step(c, ctx)
  2391. // When I run juju status --format oneline error
  2392. _, stdout, stderr := runStatus(c, "--format", "oneline", "error")
  2393. c.Assert(stderr, gc.IsNil)
  2394. // Then I should receive output prefixed with:
  2395. const expected = `
  2396. - mysql/0: dummyenv-2.dns (started)
  2397. - logging/1: dummyenv-2.dns (error)
  2398. `
  2399. c.Assert(string(stdout), gc.Equals, expected[1:])
  2400. }
  2401. // Scenario: User filters to mysql service
  2402. func (s *StatusSuite) TestFilterToService(c *gc.C) {
  2403. ctx := s.FilteringTestSetup(c)
  2404. defer s.resetContext(c, ctx)
  2405. // When I run juju status --format oneline error
  2406. _, stdout, stderr := runStatus(c, "--format", "oneline", "mysql")
  2407. c.Assert(stderr, gc.IsNil)
  2408. // Then I should receive output prefixed with:
  2409. const expected = `
  2410. - mysql/0: dummyenv-2.dns (started)
  2411. - logging/1: dummyenv-2.dns (started)
  2412. `
  2413. c.Assert(string(stdout), gc.Equals, expected[1:])
  2414. }
  2415. // Scenario: User filters to exposed services
  2416. func (s *StatusSuite) TestFilterToExposedService(c *gc.C) {
  2417. ctx := s.FilteringTestSetup(c)
  2418. defer s.resetContext(c, ctx)
  2419. // Given unit 1 of the "mysql" service is exposed
  2420. setServiceExposed{"mysql", true}.step(c, ctx)
  2421. // And the logging service is not exposed
  2422. setServiceExposed{"logging", false}.step(c, ctx)
  2423. // And the wordpress service is not exposed
  2424. setServiceExposed{"wordpress", false}.step(c, ctx)
  2425. // When I run juju status --format oneline exposed
  2426. _, stdout, stderr := runStatus(c, "--format", "oneline", "exposed")
  2427. c.Assert(stderr, gc.IsNil)
  2428. // Then I should receive output prefixed with:
  2429. const expected = `
  2430. - mysql/0: dummyenv-2.dns (started)
  2431. - logging/1: dummyenv-2.dns (started)
  2432. `
  2433. c.Assert(string(stdout), gc.Equals, expected[1:])
  2434. }
  2435. // Scenario: User filters to non-exposed services
  2436. func (s *StatusSuite) TestFilterToNotExposedService(c *gc.C) {
  2437. ctx := s.FilteringTestSetup(c)
  2438. defer s.resetContext(c, ctx)
  2439. setServiceExposed{"mysql", true}.step(c, ctx)
  2440. // When I run juju status --format oneline not exposed
  2441. _, stdout, stderr := runStatus(c, "--format", "oneline", "not", "exposed")
  2442. c.Assert(stderr, gc.IsNil)
  2443. // Then I should receive output prefixed with:
  2444. const expected = `
  2445. - wordpress/0: dummyenv-1.dns (started)
  2446. - logging/0: dummyenv-1.dns (started)
  2447. `
  2448. c.Assert(string(stdout), gc.Equals, expected[1:])
  2449. }
  2450. // Scenario: Filtering on Subnets
  2451. func (s *StatusSuite) TestFilterOnSubnet(c *gc.C) {
  2452. ctx := s.FilteringTestSetup(c)
  2453. defer s.resetContext(c, ctx)
  2454. // Given the address for machine "1" is "localhost"
  2455. setAddresses{"1", []network.Address{network.NewAddress("localhost", network.ScopeUnknown)}}.step(c, ctx)
  2456. // And the address for machine "2" is "10.0.0.1"
  2457. setAddresses{"2", []network.Address{network.NewAddress("10.0.0.1", network.ScopeUnknown)}}.step(c, ctx)
  2458. // When I run juju status --format oneline 127.0.0.1
  2459. _, stdout, stderr := runStatus(c, "--format", "oneline", "127.0.0.1")
  2460. c.Assert(stderr, gc.IsNil)
  2461. // Then I should receive output prefixed with:
  2462. const expected = `
  2463. - wordpress/0: localhost (started)
  2464. - logging/0: localhost (started)
  2465. `
  2466. c.Assert(string(stdout), gc.Equals, expected[1:])
  2467. }
  2468. // Scenario: Filtering on Ports
  2469. func (s *StatusSuite) TestFilterOnPorts(c *gc.C) {
  2470. ctx := s.FilteringTestSetup(c)
  2471. defer s.resetContext(c, ctx)
  2472. // Given the address for machine "1" is "localhost"
  2473. setAddresses{"1", []network.Address{network.NewAddress("localhost", network.ScopeUnknown)}}.step(c, ctx)
  2474. // And the address for machine "2" is "10.0.0.1"
  2475. setAddresses{"2", []network.Address{network.NewAddress("10.0.0.1", network.ScopeUnknown)}}.step(c, ctx)
  2476. openUnitPort{"wordpress/0", "tcp", 80}.step(c, ctx)
  2477. // When I run juju status --format oneline 80/tcp
  2478. _, stdout, stderr := runStatus(c, "--format", "oneline", "80/tcp")
  2479. c.Assert(stderr, gc.IsNil)
  2480. // Then I should receive output prefixed with:
  2481. const expected = `
  2482. - wordpress/0: localhost (started) 80/tcp
  2483. - logging/0: localhost (started)
  2484. `
  2485. c.Assert(string(stdout), gc.Equals, expected[1:])
  2486. }
  2487. // Scenario: User filters out a parent, but not its subordinate
  2488. func (s *StatusSuite) TestFilterParentButNotSubordinate(c *gc.C) {
  2489. ctx := s.FilteringTestSetup(c)
  2490. defer s.resetContext(c, ctx)
  2491. // When I run juju status --format oneline 80/tcp
  2492. _, stdout, stderr := runStatus(c, "--format", "oneline", "logging")
  2493. c.Assert(stderr, gc.IsNil)
  2494. // Then I should receive output prefixed with:
  2495. const expected = `
  2496. - mysql/0: dummyenv-2.dns (started)
  2497. - logging/1: dummyenv-2.dns (started)
  2498. - wordpress/0: dummyenv-1.dns (started)
  2499. - logging/0: dummyenv-1.dns (started)
  2500. `
  2501. c.Assert(string(stdout), gc.Equals, expected[1:])
  2502. }
  2503. // Scenario: User filters out a subordinate, but not its parent
  2504. func (s *StatusSuite) TestFilterSubordinateButNotParent(c *gc.C) {
  2505. ctx := s.FilteringTestSetup(c)
  2506. defer s.resetContext(c, ctx)
  2507. // Given the wordpress service is exposed
  2508. setServiceExposed{"wordpress", true}.step(c, ctx)
  2509. // When I run juju status --format oneline not exposed
  2510. _, stdout, stderr := runStatus(c, "--format", "oneline", "not", "exposed")
  2511. c.Assert(stderr, gc.IsNil)
  2512. // Then I should receive output prefixed with:
  2513. const expected = `
  2514. - mysql/0: dummyenv-2.dns (started)
  2515. - logging/1: dummyenv-2.dns (started)
  2516. `
  2517. c.Assert(string(stdout), gc.Equals, expected[1:])
  2518. }
  2519. func (s *StatusSuite) TestFilterMultipleHomogenousPatterns(c *gc.C) {
  2520. ctx := s.FilteringTestSetup(c)
  2521. defer s.resetContext(c, ctx)
  2522. _, stdout, stderr := runStatus(c, "--format", "oneline", "wordpress/0", "mysql/0")
  2523. c.Assert(stderr, gc.IsNil)
  2524. // Then I should receive output prefixed with:
  2525. const expected = `
  2526. - mysql/0: dummyenv-2.dns (started)
  2527. - logging/1: dummyenv-2.dns (started)
  2528. - wordpress/0: dummyenv-1.dns (started)
  2529. - logging/0: dummyenv-1.dns (started)
  2530. `
  2531. c.Assert(string(stdout), gc.Equals, expected[1:])
  2532. }
  2533. func (s *StatusSuite) TestFilterMultipleHeterogenousPatterns(c *gc.C) {
  2534. ctx := s.FilteringTestSetup(c)
  2535. defer s.resetContext(c, ctx)
  2536. _, stdout, stderr := runStatus(c, "--format", "oneline", "wordpress/0", "started")
  2537. c.Assert(stderr, gc.IsNil)
  2538. // Then I should receive output prefixed with:
  2539. const expected = `
  2540. - mysql/0: dummyenv-2.dns (started)
  2541. - logging/1: dummyenv-2.dns (started)
  2542. - wordpress/0: dummyenv-1.dns (started)
  2543. - logging/0: dummyenv-1.dns (started)
  2544. `
  2545. c.Assert(string(stdout), gc.Equals, expected[1:])
  2546. }
  2547. // TestSummaryStatusWithUnresolvableDns is result of bug# 1410320.
  2548. func (s *StatusSuite) TestSummaryStatusWithUnresolvableDns(c *gc.C) {
  2549. formatter := &summaryFormatter{}
  2550. formatter.resolveAndTrackIp("invalidDns")
  2551. // Test should not panic.
  2552. }