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

Side by Side Diff: state/statecmd/status.go

Issue 40350043: Add status cmd to api with state access fallback
Patch Set: Add status cmd to api with state access fallback Created 11 years, 3 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
« cmd/juju/status.go ('K') | « state/apiserver/client/status.go ('k') | no next file » | no next file with comments »
Toggle Intra-line Diffs ('i') | Expand Comments ('e') | Collapse Comments ('c') | Show Comments Hide Comments ('s')
OLDNEW
(Empty)
1 // Copyright 2012, 2013 Canonical Ltd.
2 // Licensed under the AGPLv3, see LICENCE file for details.
3
4 package statecmd
5
6 import (
7 "fmt"
8 "path"
9 "regexp"
10 "strings"
11
12 "launchpad.net/juju-core/charm"
13 "launchpad.net/juju-core/environs"
14 "launchpad.net/juju-core/errors"
15 "launchpad.net/juju-core/instance"
16 "launchpad.net/juju-core/juju"
17 "launchpad.net/juju-core/state"
18 "launchpad.net/juju-core/state/api"
19 "launchpad.net/juju-core/state/api/params"
20 "launchpad.net/juju-core/tools"
21 "launchpad.net/juju-core/utils/set"
22 )
23
24 func Status(conn *juju.Conn, patterns []string) (*api.Status, error) {
25 var context statusContext
26 unitMatcher, err := NewUnitMatcher(patterns)
27 if err != nil {
28 return nil, err
29 }
30 if context.services, context.units, err = fetchAllServicesAndUnits(conn. State, unitMatcher); err != nil {
31 return nil, err
32 }
33
34 // Filter machines by units in scope.
35 var machineIds *set.Strings
36 if !unitMatcher.matchesAny() {
37 machineIds, err = fetchUnitMachineIds(context.units)
38 if err != nil {
39 return nil, err
40 }
41 }
42 if context.machines, err = fetchMachines(conn.State, machineIds); err != nil {
43 return nil, err
44 }
45
46 context.instances, err = fetchAllInstances(conn.Environ)
47 if err != nil {
48 // XXX: return both err and result as both may be useful?
49 err = nil
50 }
51 return &api.Status{
52 EnvironmentName: conn.Environ.Name(),
53 Machines: context.processMachines(),
54 Services: context.processServices(),
55 }, nil
56 }
57
58 type statusContext struct {
59 instances map[instance.Id]instance.Instance
60 machines map[string][]*state.Machine
61 services map[string]*state.Service
62 units map[string]map[string]*state.Unit
63 }
64
65 type unitMatcher struct {
66 patterns []string
67 }
68
69 // matchesAny returns true if the unitMatcher will
70 // match any unit, regardless of its attributes.
71 func (m unitMatcher) matchesAny() bool {
72 return len(m.patterns) == 0
73 }
74
75 // matchUnit attempts to match a state.Unit to one of
76 // a set of patterns, taking into account subordinate
77 // relationships.
78 func (m unitMatcher) matchUnit(u *state.Unit) bool {
79 if m.matchesAny() {
80 return true
81 }
82
83 // Keep the unit if:
84 // (a) its name matches a pattern, or
85 // (b) it's a principal and one of its subordinates matches, or
86 // (c) it's a subordinate and its principal matches.
87 //
88 // Note: do *not* include a second subordinate if the principal is
89 // only matched on account of a first subordinate matching.
90 if m.matchString(u.Name()) {
91 return true
92 }
93 if u.IsPrincipal() {
94 for _, s := range u.SubordinateNames() {
95 if m.matchString(s) {
96 return true
97 }
98 }
99 return false
100 }
101 principal, valid := u.PrincipalName()
102 if !valid {
103 panic("PrincipalName failed for subordinate unit")
104 }
105 return m.matchString(principal)
106 }
107
108 // matchString matches a string to one of the patterns in
109 // the unit matcher, returning an error if a pattern with
110 // invalid syntax is encountered.
111 func (m unitMatcher) matchString(s string) bool {
112 for _, pattern := range m.patterns {
113 ok, err := path.Match(pattern, s)
114 if err != nil {
115 // We validate patterns, so should never get here.
116 panic(fmt.Errorf("pattern syntax error in %q", pattern))
117 } else if ok {
118 return true
119 }
120 }
121 return false
122 }
123
124 // validPattern must match the parts of a unit or service name
125 // pattern either side of the '/' for it to be valid.
126 var validPattern = regexp.MustCompile("^[a-z0-9-*]+$")
127
128 // NewUnitMatcher returns a unitMatcher that matches units
129 // with one of the specified patterns, or all units if no
130 // patterns are specified.
131 //
132 // An error will be returned if any of the specified patterns
133 // is invalid. Patterns are valid if they contain only
134 // alpha-numeric characters, hyphens, or asterisks (and one
135 // optional '/' to separate service/unit).
136 func NewUnitMatcher(patterns []string) (unitMatcher, error) {
137 for i, pattern := range patterns {
138 fields := strings.Split(pattern, "/")
139 if len(fields) > 2 {
140 return unitMatcher{}, fmt.Errorf("pattern %q contains to o many '/' characters", pattern)
141 }
142 for _, f := range fields {
143 if !validPattern.MatchString(f) {
144 return unitMatcher{}, fmt.Errorf("pattern %q con tains invalid characters", pattern)
145 }
146 }
147 if len(fields) == 1 {
148 patterns[i] += "/*"
149 }
150 }
151 return unitMatcher{patterns}, nil
152 }
153
154 // fetchAllInstances returns a map from instance id to instance.
155 func fetchAllInstances(env environs.Environ) (map[instance.Id]instance.Instance, error) {
156 m := make(map[instance.Id]instance.Instance)
157 insts, err := env.AllInstances()
158 if err != nil {
159 return nil, err
160 }
161 for _, i := range insts {
162 m[i.Id()] = i
163 }
164 return m, nil
165 }
166
167 // fetchMachines returns a map from top level machine id to machines, where mach ines[0] is the host
168 // machine and machines[1..n] are any containers (including nested ones).
169 //
170 // If machineIds is non-nil, only machines whose IDs are in the set are returned .
171 func fetchMachines(st *state.State, machineIds *set.Strings) (map[string][]*stat e.Machine, error) {
172 v := make(map[string][]*state.Machine)
173 machines, err := st.AllMachines()
174 if err != nil {
175 return nil, err
176 }
177 // AllMachines gives us machines sorted by id.
178 for _, m := range machines {
179 if machineIds != nil && !machineIds.Contains(m.Id()) {
180 continue
181 }
182 parentId, ok := m.ParentId()
183 if !ok {
184 // Only top level host machines go directly into the mac hine map.
185 v[m.Id()] = []*state.Machine{m}
186 } else {
187 topParentId := state.TopParentId(m.Id())
188 machines, ok := v[topParentId]
189 if !ok {
190 panic(fmt.Errorf("unexpected machine id %q", par entId))
191 }
192 machines = append(machines, m)
193 v[topParentId] = machines
194 }
195 }
196 return v, nil
197 }
198
199 // fetchAllServicesAndUnits returns a map from service name to service
200 // and a map from service name to unit name to unit.
201 func fetchAllServicesAndUnits(st *state.State, unitMatcher unitMatcher) (map[str ing]*state.Service, map[string]map[string]*state.Unit, error) {
202 svcMap := make(map[string]*state.Service)
203 unitMap := make(map[string]map[string]*state.Unit)
204 services, err := st.AllServices()
205 if err != nil {
206 return nil, nil, err
207 }
208 for _, s := range services {
209 units, err := s.AllUnits()
210 if err != nil {
211 return nil, nil, err
212 }
213 svcUnitMap := make(map[string]*state.Unit)
214 for _, u := range units {
215 if !unitMatcher.matchUnit(u) {
216 continue
217 }
218 svcUnitMap[u.Name()] = u
219 }
220 if unitMatcher.matchesAny() || len(svcUnitMap) > 0 {
221 unitMap[s.Name()] = svcUnitMap
222 svcMap[s.Name()] = s
223 }
224 }
225 return svcMap, unitMap, nil
226 }
227
228 // fetchUnitMachineIds returns a set of IDs for machines that
229 // the specified units reside on, and those machines' ancestors.
230 func fetchUnitMachineIds(units map[string]map[string]*state.Unit) (*set.Strings, error) {
231 machineIds := new(set.Strings)
232 for _, svcUnitMap := range units {
233 for _, unit := range svcUnitMap {
234 if !unit.IsPrincipal() {
235 continue
236 }
237 mid, err := unit.AssignedMachineId()
238 if err != nil {
239 return nil, err
240 }
241 for mid != "" {
242 machineIds.Add(mid)
243 mid = state.ParentId(mid)
244 }
245 }
246 }
247 return machineIds, nil
248 }
249
250 func (context *statusContext) processMachines() map[string]api.MachineStatus {
251 machinesMap := make(map[string]api.MachineStatus)
252 for id, machines := range context.machines {
253 hostStatus := context.makeMachineStatus(machines[0])
254 context.processMachine(machines, &hostStatus, 0)
255 machinesMap[id] = hostStatus
256 }
257 return machinesMap
258 }
259
260 func (context *statusContext) processMachine(machines []*state.Machine, host *ap i.MachineStatus, startIndex int) (nextIndex int) {
261 nextIndex = startIndex + 1
262 currentHost := host
263 var previousContainer *api.MachineStatus
264 for nextIndex < len(machines) {
265 machine := machines[nextIndex]
266 container := context.makeMachineStatus(machine)
267 if currentHost.Id == state.ParentId(machine.Id()) {
268 currentHost.Containers[machine.Id()] = container
269 previousContainer = &container
270 nextIndex++
271 } else {
272 if state.NestingLevel(machine.Id()) > state.NestingLevel (previousContainer.Id) {
273 nextIndex = context.processMachine(machines, pre viousContainer, nextIndex-1)
274 } else {
275 break
276 }
277 }
278 }
279 return
280 }
281
282 func (context *statusContext) makeMachineStatus(machine *state.Machine) (status api.MachineStatus) {
283 status.Id = machine.Id()
284 status.Life,
285 status.AgentVersion,
286 status.AgentState,
287 status.AgentStateInfo,
288 status.Err = processAgent(machine)
289 status.Series = machine.Series()
290 instid, err := machine.InstanceId()
291 if err == nil {
292 status.InstanceId = instid
293 inst, ok := context.instances[instid]
294 if ok {
295 status.DNSName, _ = inst.DNSName()
296 status.InstanceState = inst.Status()
297 } else {
298 // Double plus ungood. There is an instance id recorded
299 // for this machine in the state, yet the environ cannot
300 // find that id.
301 status.InstanceState = "missing"
302 }
303 } else {
304 if state.IsNotProvisionedError(err) {
305 status.InstanceId = "pending"
306 } else {
307 status.InstanceId = "error"
308 }
309 // There's no point in reporting a pending agent state
310 // if the machine hasn't been provisioned. This
311 // also makes unprovisioned machines visually distinct
312 // in the output.
313 status.AgentState = ""
314 }
315 hc, err := machine.HardwareCharacteristics()
316 if err != nil {
317 if !errors.IsNotFoundError(err) {
318 status.Hardware = "error"
319 }
320 } else {
321 status.Hardware = hc.String()
322 }
323 status.Containers = make(map[string]api.MachineStatus)
324 return
325 }
326
327 func (context *statusContext) processServices() map[string]api.ServiceStatus {
328 servicesMap := make(map[string]api.ServiceStatus)
329 for _, s := range context.services {
330 servicesMap[s.Name()] = context.processService(s)
331 }
332 return servicesMap
333 }
334
335 func (context *statusContext) processService(service *state.Service) (status api .ServiceStatus) {
336 url, _ := service.CharmURL()
337 status.Charm = url.String()
338 status.Exposed = service.IsExposed()
339 status.Life = processLife(service)
340 var err error
341 status.Relations, status.SubordinateTo, err = context.processRelations(s ervice)
342 if err != nil {
343 status.Err = err
344 return
345 }
346 if service.IsPrincipal() {
347 status.Units = context.processUnits(context.units[service.Name() ])
348 }
349 return status
350 }
351
352 func (context *statusContext) processUnits(units map[string]*state.Unit) map[str ing]api.UnitStatus {
353 unitsMap := make(map[string]api.UnitStatus)
354 for _, unit := range units {
355 unitsMap[unit.Name()] = context.processUnit(unit)
356 }
357 return unitsMap
358 }
359
360 func (context *statusContext) processUnit(unit *state.Unit) (status api.UnitStat us) {
361 status.PublicAddress, _ = unit.PublicAddress()
362 for _, port := range unit.OpenedPorts() {
363 status.OpenedPorts = append(status.OpenedPorts, port.String())
364 }
365 if unit.IsPrincipal() {
366 status.Machine, _ = unit.AssignedMachineId()
367 }
368 status.Life,
369 status.AgentVersion,
370 status.AgentState,
371 status.AgentStateInfo,
372 status.Err = processAgent(unit)
373 if subUnits := unit.SubordinateNames(); len(subUnits) > 0 {
374 status.Subordinates = make(map[string]api.UnitStatus)
375 for _, name := range subUnits {
376 subUnit := context.unitByName(name)
377 // subUnit may be nil if subordinate was filtered out.
378 if subUnit != nil {
379 status.Subordinates[name] = context.processUnit( subUnit)
380 }
381 }
382 }
383 return
384 }
385
386 func (context *statusContext) unitByName(name string) *state.Unit {
387 serviceName := strings.Split(name, "/")[0]
388 return context.units[serviceName][name]
389 }
390
391 func (*statusContext) processRelations(service *state.Service) (related map[stri ng][]string, subord []string, err error) {
392 // TODO(mue) This way the same relation is read twice (for each service) .
393 // Maybe add Relations() to state, read them only once and pass them to each
394 // call of this function.
395 relations, err := service.Relations()
396 if err != nil {
397 return nil, nil, err
398 }
399 var subordSet set.Strings
400 related = make(map[string][]string)
401 for _, relation := range relations {
402 ep, err := relation.Endpoint(service.Name())
403 if err != nil {
404 return nil, nil, err
405 }
406 relationName := ep.Relation.Name
407 eps, err := relation.RelatedEndpoints(service.Name())
408 if err != nil {
409 return nil, nil, err
410 }
411 for _, ep := range eps {
412 if ep.Scope == charm.ScopeContainer && !service.IsPrinci pal() {
413 subordSet.Add(ep.ServiceName)
414 }
415 related[relationName] = append(related[relationName], ep .ServiceName)
416 }
417 }
418 for relationName, serviceNames := range related {
419 sn := set.NewStrings(serviceNames...)
420 related[relationName] = sn.SortedValues()
421 }
422 return related, subordSet.SortedValues(), nil
423 }
424
425 type lifer interface {
426 Life() state.Life
427 }
428
429 type stateAgent interface {
430 lifer
431 AgentAlive() (bool, error)
432 AgentTools() (*tools.Tools, error)
433 Status() (params.Status, string, params.StatusData, error)
434 }
435
436 // processAgent retrieves version and status information from the given entity
437 // and sets the destination version, status and info values accordingly.
438 func processAgent(entity stateAgent) (life string, version string, status params .Status, info string, err error) {
439 life = processLife(entity)
440 if t, err := entity.AgentTools(); err == nil {
441 version = t.Version.Number.String()
442 }
443 // TODO(mue) StatusData may be useful here too.
444 status, info, _, err = entity.Status()
445 if err != nil {
446 return
447 }
448 if status == params.StatusPending {
449 // The status is pending - there's no point
450 // in enquiring about the agent liveness.
451 return
452 }
453 agentAlive, err := entity.AgentAlive()
454 if err != nil {
455 return
456 }
457 if entity.Life() != state.Dead && !agentAlive {
458 // The agent *should* be alive but is not.
459 // Add the original status to the info, so it's not lost.
460 if info != "" {
461 info = fmt.Sprintf("(%s: %s)", status, info)
462 } else {
463 info = fmt.Sprintf("(%s)", status)
464 }
465 status = params.StatusDown
466 }
467 return
468 }
469
470 func processLife(entity lifer) string {
471 if life := entity.Life(); life != state.Alive {
472 // alive is the usual state so omit it by default.
473 return life.String()
474 }
475 return ""
476 }
OLDNEW
« cmd/juju/status.go ('K') | « state/apiserver/client/status.go ('k') | no next file » | no next file with comments »

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