Rietveld Code Review Tool
Help | Bug tracker | Discussion group | Source code | Sign in
(3992)

Side by Side Diff: cmd/juju/status.go

Issue 12235043: juju status: add service/unit filters (Closed)
Patch Set: juju status: add service/unit filters Created 10 years, 7 months ago
Left:
Right:
Use n/p to move between diff chunks; N/P to move between comments. Please Sign in to add in-line comments.
Jump to:
View unified diff | Download patch
OLDNEW
1 // Copyright 2012, 2013 Canonical Ltd. 1 // Copyright 2012, 2013 Canonical Ltd.
2 // Licensed under the AGPLv3, see LICENCE file for details. 2 // Licensed under the AGPLv3, see LICENCE file for details.
3 3
4 package main 4 package main
5 5
6 import ( 6 import (
7 "encoding/json" 7 "encoding/json"
8 "fmt" 8 "fmt"
9 "path"
9 "strings" 10 "strings"
10 11
11 "launchpad.net/gnuflag" 12 "launchpad.net/gnuflag"
12 13
13 "launchpad.net/juju-core/agent/tools" 14 "launchpad.net/juju-core/agent/tools"
14 "launchpad.net/juju-core/charm" 15 "launchpad.net/juju-core/charm"
15 "launchpad.net/juju-core/cmd" 16 "launchpad.net/juju-core/cmd"
16 "launchpad.net/juju-core/environs" 17 "launchpad.net/juju-core/environs"
17 "launchpad.net/juju-core/errors" 18 "launchpad.net/juju-core/errors"
18 "launchpad.net/juju-core/instance" 19 "launchpad.net/juju-core/instance"
19 "launchpad.net/juju-core/juju" 20 "launchpad.net/juju-core/juju"
20 "launchpad.net/juju-core/state" 21 "launchpad.net/juju-core/state"
21 "launchpad.net/juju-core/state/api/params" 22 "launchpad.net/juju-core/state/api/params"
22 "launchpad.net/juju-core/utils/set" 23 "launchpad.net/juju-core/utils/set"
23 ) 24 )
24 25
25 type StatusCommand struct { 26 type StatusCommand struct {
26 cmd.EnvCommandBase 27 cmd.EnvCommandBase
27 » out cmd.Output 28 » out cmd.Output
29 » patterns []string
28 } 30 }
29 31
30 var statusDoc = "This command will report on the runtime state of various system entities." 32 var statusDoc = `
33 This command will report on the runtime state of various system entities.
34
35 Patterns may be specified to filter the status to only those services and
36 units that match one the patterns. The pattern syntax follows Unix file
37 matching:
38 * matches any sequence of characters, excluding '/'
39 ? matches any single character, excluding '/'
40 [range] matches any single character in the range
41 [^range] matches any single character not in the range
42 (range can be a set of characters, or a range like a-z)
43
44 If a pattern includes a '/', then it will be used as-is to match units;
45 otherwise, the pattern will used to match services. Patterns will be
46 tried in the sequence specified; invalid patterns will only be detected
47 if and when they are tried.`[1:]
fwereade 2013/08/07 17:06:54 Maybe add a little bit more explaining which bits
31 48
32 func (c *StatusCommand) Info() *cmd.Info { 49 func (c *StatusCommand) Info() *cmd.Info {
33 return &cmd.Info{ 50 return &cmd.Info{
34 Name: "status", 51 Name: "status",
52 Args: "[pattern...]",
fwereade 2013/08/07 17:06:54 s/n./n ./ ?
35 Purpose: "output status information about an environment", 53 Purpose: "output status information about an environment",
36 Doc: statusDoc, 54 Doc: statusDoc,
37 Aliases: []string{"stat"}, 55 Aliases: []string{"stat"},
38 } 56 }
39 } 57 }
40 58
41 func (c *StatusCommand) SetFlags(f *gnuflag.FlagSet) { 59 func (c *StatusCommand) SetFlags(f *gnuflag.FlagSet) {
42 c.EnvCommandBase.SetFlags(f) 60 c.EnvCommandBase.SetFlags(f)
43 c.out.AddFlags(f, "yaml", map[string]cmd.Formatter{ 61 c.out.AddFlags(f, "yaml", map[string]cmd.Formatter{
44 "yaml": cmd.FormatYaml, 62 "yaml": cmd.FormatYaml,
45 "json": cmd.FormatJson, 63 "json": cmd.FormatJson,
46 }) 64 })
47 } 65 }
48 66
67 func (c *StatusCommand) Init(args []string) error {
68 c.patterns = args
69 return nil
70 }
71
49 type statusContext struct { 72 type statusContext struct {
50 instances map[instance.Id]instance.Instance 73 instances map[instance.Id]instance.Instance
51 machines map[string][]*state.Machine 74 machines map[string][]*state.Machine
52 services map[string]*state.Service 75 services map[string]*state.Service
53 units map[string]map[string]*state.Unit 76 units map[string]map[string]*state.Unit
54 } 77 }
55 78
79 type unitMatcher struct {
80 patterns []string
81 }
82
83 // matchesAny returns true if the unitMatcher will
84 // match any unit, regardless of its attributes.
85 func (m unitMatcher) matchesAny() bool {
86 return len(m.patterns) == 0
87 }
88
89 // matchUnit attempts to match a state.Unit to one of
90 // a set of patterns, taking into account subordinate
91 // relationships.
92 func (m unitMatcher) matchUnit(u *state.Unit) (bool, error) {
93 if m.matchesAny() {
94 return true, nil
95 }
96
97 // Keep the unit if:
98 // (a) its name matches a pattern, or
99 // (b) it's a principal and one of its subordinates matches, or
100 // (c) it's a subordinate and its principal matches.
101 //
102 // Note: do *not* include a second subordinate if the principal is
103 // only matched on account of a first subordinate matching.
104 match, err := m.matchString(u.Name())
105 if match || err != nil {
106 return match, err
107 }
108 if u.IsPrincipal() {
109 for _, s := range u.SubordinateNames() {
110 match, err = m.matchString(s)
111 if match || err != nil {
112 return match, err
113 }
114 }
115 return false, nil
116 } else {
fwereade 2013/08/07 17:06:54 lose the else?
117 principal, valid := u.PrincipalName()
118 if !valid {
119 panic("PrincipalName failed for subordinate unit")
120 }
121 return m.matchString(principal)
122 }
123 }
124
125 // matchString matches a string to one of the patterns in
126 // the unit matcher, returning an error if a pattern with
127 // invalid syntax is encountered.
128 func (m unitMatcher) matchString(s string) (bool, error) {
129 for _, pattern := range m.patterns {
130 ok, err := path.Match(pattern, s)
rog 2013/08/07 17:02:31 It's a pity that because path.Match is lazy about
131 if err != nil {
132 err = fmt.Errorf("pattern syntax error in %q", pattern)
133 return false, err
134 } else if ok {
135 return true, nil
136 }
137 }
138 return false, nil
139 }
140
141 // newUnitMatcher returns a unitMatcher that matches units
142 // with one of the specified patterns, or all units if no
143 // patterns are specified.
144 func newUnitMatcher(patterns []string) unitMatcher {
145 for i, pattern := range patterns {
146 if !strings.Contains(pattern, "/") {
147 patterns[i] += "/*"
148 }
149 }
150 return unitMatcher{patterns}
151 }
152
56 func (c *StatusCommand) Run(ctx *cmd.Context) error { 153 func (c *StatusCommand) Run(ctx *cmd.Context) error {
57 conn, err := juju.NewConnFromName(c.EnvName) 154 conn, err := juju.NewConnFromName(c.EnvName)
58 if err != nil { 155 if err != nil {
59 return err 156 return err
60 } 157 }
61 defer conn.Close() 158 defer conn.Close()
62 159
63 var context statusContext 160 var context statusContext
64 » if context.machines, err = fetchAllMachines(conn.State); err != nil { 161 » unitMatcher := newUnitMatcher(c.patterns)
162 » if context.services, context.units, err = fetchAllServicesAndUnits(conn. State, unitMatcher); err != nil {
65 return err 163 return err
66 } 164 }
67 » if context.services, context.units, err = fetchAllServicesAndUnits(conn. State); err != nil { 165
166 » // Filter machines by units in scope.
167 » var machineIds *set.Strings
168 » if !unitMatcher.matchesAny() {
169 » » machineIds, err = fetchUnitMachineIds(context.units)
170 » » if err != nil {
171 » » » return err
172 » » }
173 » }
174 » if context.machines, err = fetchMachines(conn.State, machineIds); err != nil {
68 return err 175 return err
69 } 176 }
177
70 context.instances, err = fetchAllInstances(conn.Environ) 178 context.instances, err = fetchAllInstances(conn.Environ)
71 if err != nil { 179 if err != nil {
72 // We cannot see instances from the environment, but 180 // We cannot see instances from the environment, but
73 // there's still lots of potentially useful info to print. 181 // there's still lots of potentially useful info to print.
74 fmt.Fprintf(ctx.Stderr, "cannot retrieve instances from the envi ronment: %v\n", err) 182 fmt.Fprintf(ctx.Stderr, "cannot retrieve instances from the envi ronment: %v\n", err)
75 } 183 }
76 result := struct { 184 result := struct {
77 Machines map[string]machineStatus `json:"machines"` 185 Machines map[string]machineStatus `json:"machines"`
78 Services map[string]serviceStatus `json:"services"` 186 Services map[string]serviceStatus `json:"services"`
79 }{ 187 }{
80 Machines: context.processMachines(), 188 Machines: context.processMachines(),
81 Services: context.processServices(), 189 Services: context.processServices(),
82 } 190 }
83 return c.out.Write(ctx, result) 191 return c.out.Write(ctx, result)
84 } 192 }
85 193
86 // fetchAllInstances returns a map from instance id to instance. 194 // fetchAllInstances returns a map from instance id to instance.
87 func fetchAllInstances(env environs.Environ) (map[instance.Id]instance.Instance, error) { 195 func fetchAllInstances(env environs.Environ) (map[instance.Id]instance.Instance, error) {
88 m := make(map[instance.Id]instance.Instance) 196 m := make(map[instance.Id]instance.Instance)
89 insts, err := env.AllInstances() 197 insts, err := env.AllInstances()
90 if err != nil { 198 if err != nil {
91 return nil, err 199 return nil, err
92 } 200 }
93 for _, i := range insts { 201 for _, i := range insts {
94 m[i.Id()] = i 202 m[i.Id()] = i
95 } 203 }
96 return m, nil 204 return m, nil
97 } 205 }
98 206
99 // fetchAllMachines returns a map from top level machine id to machines, where m achines[0] is the host 207 // fetchMachines returns a map from top level machine id to machines, where mach ines[0] is the host
100 // machine and machines[1..n] are any containers (including nested ones). 208 // machine and machines[1..n] are any containers (including nested ones).
101 func fetchAllMachines(st *state.State) (map[string][]*state.Machine, error) { 209 //
210 // If machineIds is non-nil, only machines whose IDs are in the set are returned .
211 func fetchMachines(st *state.State, machineIds *set.Strings) (map[string][]*stat e.Machine, error) {
102 v := make(map[string][]*state.Machine) 212 v := make(map[string][]*state.Machine)
103 machines, err := st.AllMachines() 213 machines, err := st.AllMachines()
104 if err != nil { 214 if err != nil {
105 return nil, err 215 return nil, err
106 } 216 }
107 // AllMachines gives us machines sorted by id. 217 // AllMachines gives us machines sorted by id.
108 for _, m := range machines { 218 for _, m := range machines {
219 if machineIds != nil && !machineIds.Contains(m.Id()) {
220 continue
221 }
109 parentId, ok := m.ParentId() 222 parentId, ok := m.ParentId()
110 if !ok { 223 if !ok {
111 // Only top level host machines go directly into the mac hine map. 224 // Only top level host machines go directly into the mac hine map.
112 v[m.Id()] = []*state.Machine{m} 225 v[m.Id()] = []*state.Machine{m}
113 } else { 226 } else {
114 topParentId := state.TopParentId(m.Id()) 227 topParentId := state.TopParentId(m.Id())
115 machines, ok := v[topParentId] 228 machines, ok := v[topParentId]
116 if !ok { 229 if !ok {
117 panic(fmt.Errorf("unexpected machine id %q", par entId)) 230 panic(fmt.Errorf("unexpected machine id %q", par entId))
118 } 231 }
119 machines = append(machines, m) 232 machines = append(machines, m)
120 v[topParentId] = machines 233 v[topParentId] = machines
121 } 234 }
122 } 235 }
123 return v, nil 236 return v, nil
124 } 237 }
125 238
126 // fetchAllServicesAndUnits returns a map from service name to service 239 // fetchAllServicesAndUnits returns a map from service name to service
127 // and a map from service name to unit name to unit. 240 // and a map from service name to unit name to unit.
128 func fetchAllServicesAndUnits(st *state.State) (map[string]*state.Service, map[s tring]map[string]*state.Unit, error) { 241 func fetchAllServicesAndUnits(st *state.State, unitMatcher unitMatcher) (map[str ing]*state.Service, map[string]map[string]*state.Unit, error) {
129 svcMap := make(map[string]*state.Service) 242 svcMap := make(map[string]*state.Service)
130 unitMap := make(map[string]map[string]*state.Unit) 243 unitMap := make(map[string]map[string]*state.Unit)
131 services, err := st.AllServices() 244 services, err := st.AllServices()
132 if err != nil { 245 if err != nil {
133 return nil, nil, err 246 return nil, nil, err
134 } 247 }
135 for _, s := range services { 248 for _, s := range services {
136 svcMap[s.Name()] = s
137 units, err := s.AllUnits() 249 units, err := s.AllUnits()
138 if err != nil { 250 if err != nil {
139 return nil, nil, err 251 return nil, nil, err
140 } 252 }
141 svcUnitMap := make(map[string]*state.Unit) 253 svcUnitMap := make(map[string]*state.Unit)
142 for _, u := range units { 254 for _, u := range units {
255 ok, err := unitMatcher.matchUnit(u)
256 if err != nil {
257 return nil, nil, err
258 } else if !ok {
259 continue
260 }
143 svcUnitMap[u.Name()] = u 261 svcUnitMap[u.Name()] = u
144 } 262 }
145 » » unitMap[s.Name()] = svcUnitMap 263 » » if unitMatcher.matchesAny() || len(svcUnitMap) > 0 {
264 » » » unitMap[s.Name()] = svcUnitMap
265 » » » svcMap[s.Name()] = s
266 » » }
146 } 267 }
147 return svcMap, unitMap, nil 268 return svcMap, unitMap, nil
148 } 269 }
149 270
271 // fetchUnitMachineIds returns a set of IDs for machines that
272 // the specified units reside on, and those machines' ancestors.
273 func fetchUnitMachineIds(units map[string]map[string]*state.Unit) (*set.Strings, error) {
274 machineIds := new(set.Strings)
275 for _, svcUnitMap := range units {
276 for _, unit := range svcUnitMap {
277 mid, err := unit.AssignedMachineId()
rog 2013/08/07 17:02:31 This still isn't quite right - it should only call
278 if err != nil {
279 return nil, err
280 }
281 for mid != "" {
282 machineIds.Add(mid)
283 mid = state.ParentId(mid)
284 }
285 }
286 }
287 return machineIds, nil
288 }
289
150 func (context *statusContext) processMachines() map[string]machineStatus { 290 func (context *statusContext) processMachines() map[string]machineStatus {
151 machinesMap := make(map[string]machineStatus) 291 machinesMap := make(map[string]machineStatus)
152 for id, machines := range context.machines { 292 for id, machines := range context.machines {
153 hostStatus := context.makeMachineStatus(machines[0]) 293 hostStatus := context.makeMachineStatus(machines[0])
154 context.processMachine(machines, &hostStatus, 0) 294 context.processMachine(machines, &hostStatus, 0)
155 machinesMap[id] = hostStatus 295 machinesMap[id] = hostStatus
156 } 296 }
157 return machinesMap 297 return machinesMap
158 } 298 }
159 299
(...skipping 106 matching lines...) Expand 10 before | Expand all | Expand 10 after
266 } 406 }
267 status.Life, 407 status.Life,
268 status.AgentVersion, 408 status.AgentVersion,
269 status.AgentState, 409 status.AgentState,
270 status.AgentStateInfo, 410 status.AgentStateInfo,
271 status.Err = processAgent(unit) 411 status.Err = processAgent(unit)
272 if subUnits := unit.SubordinateNames(); len(subUnits) > 0 { 412 if subUnits := unit.SubordinateNames(); len(subUnits) > 0 {
273 status.Subordinates = make(map[string]unitStatus) 413 status.Subordinates = make(map[string]unitStatus)
274 for _, name := range subUnits { 414 for _, name := range subUnits {
275 subUnit := context.unitByName(name) 415 subUnit := context.unitByName(name)
276 » » » status.Subordinates[name] = context.processUnit(subUnit) 416 » » » // subUnit may be nil if subordinate was filtered out.
417 » » » if subUnit != nil {
418 » » » » status.Subordinates[name] = context.processUnit( subUnit)
419 » » » }
277 } 420 }
278 } 421 }
279 return 422 return
280 } 423 }
281 424
282 func (context *statusContext) unitByName(name string) *state.Unit { 425 func (context *statusContext) unitByName(name string) *state.Unit {
283 serviceName := strings.Split(name, "/")[0] 426 serviceName := strings.Split(name, "/")[0]
284 return context.units[serviceName][name] 427 return context.units[serviceName][name]
285 } 428 }
286 429
(...skipping 172 matching lines...) Expand 10 before | Expand all | Expand 10 after
459 return json.Marshal(unitStatusNoMarshal(s)) 602 return json.Marshal(unitStatusNoMarshal(s))
460 } 603 }
461 604
462 func (s unitStatus) GetYAML() (tag string, value interface{}) { 605 func (s unitStatus) GetYAML() (tag string, value interface{}) {
463 if s.Err != nil { 606 if s.Err != nil {
464 return "", errorStatus{s.Err.Error()} 607 return "", errorStatus{s.Err.Error()}
465 } 608 }
466 type uNoMethods unitStatus 609 type uNoMethods unitStatus
467 return "", unitStatusNoMarshal(s) 610 return "", unitStatusNoMarshal(s)
468 } 611 }
OLDNEW
« no previous file with comments | « cmd/juju/cmd_test.go ('k') | cmd/juju/status_test.go » ('j') | cmd/juju/status_test.go » ('J')

Powered by Google App Engine
RSS Feeds Recent Issues | This issue
This is Rietveld f62528b