Left: | ||
Right: |
OLD | NEW |
---|---|
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 Loading... | |
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 Loading... | |
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 } |
OLD | NEW |