/
status.go
846 lines (767 loc) · 25.8 KB
/
status.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
// Copyright 2013 Canonical Ltd.
// Licensed under the AGPLv3, see LICENCE file for details.
package client
import (
"fmt"
"sort"
"strings"
"github.com/juju/errors"
"github.com/juju/utils/set"
"gopkg.in/juju/charm.v5"
"gopkg.in/juju/charm.v5/hooks"
"github.com/juju/juju/api"
"github.com/juju/juju/apiserver/params"
"github.com/juju/juju/constraints"
"github.com/juju/juju/network"
"github.com/juju/juju/state"
"github.com/juju/juju/state/multiwatcher"
"github.com/juju/juju/worker/uniter/operation"
)
func agentStatusFromStatusInfo(s []state.StatusInfo, kind params.HistoryKind) []api.AgentStatus {
result := []api.AgentStatus{}
for _, v := range s {
result = append(result, api.AgentStatus{
Status: params.Status(v.Status),
Info: v.Message,
Data: v.Data,
Since: v.Since,
Kind: kind,
})
}
return result
}
type sortableStatuses []api.AgentStatus
func (s sortableStatuses) Len() int {
return len(s)
}
func (s sortableStatuses) Swap(i, j int) {
s[i], s[j] = s[j], s[i]
}
func (s sortableStatuses) Less(i, j int) bool {
return s[i].Since.Before(*s[j].Since)
}
// TODO(perrito666) this client method requires more testing, only its parts are unittested.
// UnitStatusHistory returns a slice of past statuses for a given unit.
func (c *Client) UnitStatusHistory(args params.StatusHistory) (api.UnitStatusHistory, error) {
size := args.Size - 1
if size < 1 {
return api.UnitStatusHistory{}, errors.Errorf("invalid history size: %d", args.Size)
}
unit, err := c.api.state.Unit(args.Name)
if err != nil {
return api.UnitStatusHistory{}, errors.Trace(err)
}
statuses := api.UnitStatusHistory{}
if args.Kind == params.KindCombined || args.Kind == params.KindWorkload {
unitStatuses, err := unit.StatusHistory(size)
if err != nil {
return api.UnitStatusHistory{}, errors.Trace(err)
}
current, err := unit.Status()
if err != nil {
return api.UnitStatusHistory{}, errors.Trace(err)
}
unitStatuses = append(unitStatuses, current)
statuses.Statuses = append(statuses.Statuses, agentStatusFromStatusInfo(unitStatuses, params.KindWorkload)...)
}
if args.Kind == params.KindCombined || args.Kind == params.KindAgent {
agentEntity := unit.Agent()
agent, ok := agentEntity.(*state.UnitAgent)
if !ok {
return api.UnitStatusHistory{}, errors.Errorf("cannot obtain agent for %q", args.Name)
}
agentStatuses, err := agent.StatusHistory(size)
if err != nil {
return api.UnitStatusHistory{}, errors.Trace(err)
}
current, err := agent.Status()
if err != nil {
return api.UnitStatusHistory{}, errors.Trace(err)
}
agentStatuses = append(agentStatuses, current)
statuses.Statuses = append(statuses.Statuses, agentStatusFromStatusInfo(agentStatuses, params.KindAgent)...)
}
sort.Sort(sortableStatuses(statuses.Statuses))
if args.Kind == params.KindCombined {
if len(statuses.Statuses) > args.Size {
statuses.Statuses = statuses.Statuses[len(statuses.Statuses)-args.Size:]
}
}
return statuses, nil
}
// FullStatus gives the information needed for juju status over the api
func (c *Client) FullStatus(args params.StatusParams) (api.Status, error) {
cfg, err := c.api.state.EnvironConfig()
if err != nil {
return api.Status{}, errors.Annotate(err, "could not get environ config")
}
var noStatus api.Status
var context statusContext
if context.services, context.units, context.latestCharms, err =
fetchAllServicesAndUnits(c.api.state, len(args.Patterns) <= 0); err != nil {
return noStatus, errors.Annotate(err, "could not fetch services and units")
} else if context.machines, err = fetchMachines(c.api.state, nil); err != nil {
return noStatus, errors.Annotate(err, "could not fetch machines")
} else if context.relations, err = fetchRelations(c.api.state); err != nil {
return noStatus, errors.Annotate(err, "could not fetch relations")
} else if context.networks, err = fetchNetworks(c.api.state); err != nil {
return noStatus, errors.Annotate(err, "could not fetch networks")
}
logger.Debugf("Services: %v", context.services)
if len(args.Patterns) > 0 {
predicate := BuildPredicateFor(args.Patterns)
// Filter units
unfilteredSvcs := make(set.Strings)
unfilteredMachines := make(set.Strings)
unitChainPredicate := UnitChainPredicateFn(predicate, context.unitByName)
for _, unitMap := range context.units {
for name, unit := range unitMap {
// Always start examining at the top-level. This
// prevents a situation where we filter a subordinate
// before we discover its parent is a match.
if !unit.IsPrincipal() {
continue
} else if matches, err := unitChainPredicate(unit); err != nil {
return noStatus, errors.Annotate(err, "could not filter units")
} else if !matches {
delete(unitMap, name)
continue
}
// Track which services are utilized by the units so
// that we can be sure to not filter that service out.
unfilteredSvcs.Add(unit.ServiceName())
machineId, err := unit.AssignedMachineId()
if err != nil {
return noStatus, err
}
unfilteredMachines.Add(machineId)
}
}
// Filter services
for svcName, svc := range context.services {
if unfilteredSvcs.Contains(svcName) {
// Don't filter services which have units that were
// not filtered.
continue
} else if matches, err := predicate(svc); err != nil {
return noStatus, errors.Annotate(err, "could not filter services")
} else if !matches {
delete(context.services, svcName)
}
}
// Filter machines
for status, machineList := range context.machines {
filteredList := make([]*state.Machine, 0, len(machineList))
for _, m := range machineList {
machineContainers, err := m.Containers()
if err != nil {
return noStatus, err
}
machineContainersSet := set.NewStrings(machineContainers...)
if unfilteredMachines.Contains(m.Id()) || !unfilteredMachines.Intersection(machineContainersSet).IsEmpty() {
// Don't filter machines which have an unfiltered
// unit running on them.
logger.Debugf("mid %s is hosting something.", m.Id())
filteredList = append(filteredList, m)
continue
} else if matches, err := predicate(m); err != nil {
return noStatus, errors.Annotate(err, "could not filter machines")
} else if matches {
filteredList = append(filteredList, m)
}
}
context.machines[status] = filteredList
}
}
return api.Status{
EnvironmentName: cfg.Name(),
Machines: processMachines(context.machines),
Services: context.processServices(),
Networks: context.processNetworks(),
Relations: context.processRelations(),
}, nil
}
// Status is a stub version of FullStatus that was introduced in 1.16
func (c *Client) Status() (api.LegacyStatus, error) {
var legacyStatus api.LegacyStatus
status, err := c.FullStatus(params.StatusParams{})
if err != nil {
return legacyStatus, err
}
legacyStatus.Machines = make(map[string]api.LegacyMachineStatus)
for machineName, machineStatus := range status.Machines {
legacyStatus.Machines[machineName] = api.LegacyMachineStatus{
InstanceId: string(machineStatus.InstanceId),
}
}
return legacyStatus, nil
}
type statusContext struct {
// machines: top-level machine id -> list of machines nested in
// this machine.
machines map[string][]*state.Machine
// services: service name -> service
services map[string]*state.Service
relations map[string][]*state.Relation
units map[string]map[string]*state.Unit
networks map[string]*state.Network
latestCharms map[charm.URL]string
}
// fetchMachines returns a map from top level machine id to machines, where machines[0] is the host
// machine and machines[1..n] are any containers (including nested ones).
//
// If machineIds is non-nil, only machines whose IDs are in the set are returned.
func fetchMachines(st *state.State, machineIds set.Strings) (map[string][]*state.Machine, error) {
v := make(map[string][]*state.Machine)
machines, err := st.AllMachines()
if err != nil {
return nil, err
}
// AllMachines gives us machines sorted by id.
for _, m := range machines {
if machineIds != nil && !machineIds.Contains(m.Id()) {
continue
}
parentId, ok := m.ParentId()
if !ok {
// Only top level host machines go directly into the machine map.
v[m.Id()] = []*state.Machine{m}
} else {
topParentId := state.TopParentId(m.Id())
machines, ok := v[topParentId]
if !ok {
panic(fmt.Errorf("unexpected machine id %q", parentId))
}
machines = append(machines, m)
v[topParentId] = machines
}
}
return v, nil
}
// fetchAllServicesAndUnits returns a map from service name to service,
// a map from service name to unit name to unit, and a map from base charm URL to latest URL.
func fetchAllServicesAndUnits(
st *state.State,
matchAny bool,
) (map[string]*state.Service, map[string]map[string]*state.Unit, map[charm.URL]string, error) {
svcMap := make(map[string]*state.Service)
unitMap := make(map[string]map[string]*state.Unit)
latestCharms := make(map[charm.URL]string)
services, err := st.AllServices()
if err != nil {
return nil, nil, nil, err
}
for _, s := range services {
units, err := s.AllUnits()
if err != nil {
return nil, nil, nil, err
}
svcUnitMap := make(map[string]*state.Unit)
for _, u := range units {
svcUnitMap[u.Name()] = u
}
if matchAny || len(svcUnitMap) > 0 {
unitMap[s.Name()] = svcUnitMap
svcMap[s.Name()] = s
// Record the base URL for the service's charm so that
// the latest store revision can be looked up.
charmURL, _ := s.CharmURL()
if charmURL.Schema == "cs" {
latestCharms[*charmURL.WithRevision(-1)] = ""
}
}
}
for baseURL := range latestCharms {
ch, err := st.LatestPlaceholderCharm(&baseURL)
if errors.IsNotFound(err) {
continue
}
if err != nil {
return nil, nil, nil, err
}
latestCharms[baseURL] = ch.String()
}
return svcMap, unitMap, latestCharms, nil
}
// fetchUnitMachineIds returns a set of IDs for machines that
// the specified units reside on, and those machines' ancestors.
func fetchUnitMachineIds(units map[string]map[string]*state.Unit) (set.Strings, error) {
machineIds := make(set.Strings)
for _, svcUnitMap := range units {
for _, unit := range svcUnitMap {
if !unit.IsPrincipal() {
continue
}
mid, err := unit.AssignedMachineId()
if err != nil {
return nil, err
}
for mid != "" {
machineIds.Add(mid)
mid = state.ParentId(mid)
}
}
}
return machineIds, nil
}
// fetchRelations returns a map of all relations keyed by service name.
//
// This structure is useful for processServiceRelations() which needs
// to have the relations for each service. Reading them once here
// avoids the repeated DB hits to retrieve the relations for each
// service that used to happen in processServiceRelations().
func fetchRelations(st *state.State) (map[string][]*state.Relation, error) {
relations, err := st.AllRelations()
if err != nil {
return nil, err
}
out := make(map[string][]*state.Relation)
for _, relation := range relations {
for _, ep := range relation.Endpoints() {
out[ep.ServiceName] = append(out[ep.ServiceName], relation)
}
}
return out, nil
}
// fetchNetworks returns a map from network name to network.
func fetchNetworks(st *state.State) (map[string]*state.Network, error) {
networks, err := st.AllNetworks()
if err != nil {
return nil, err
}
out := make(map[string]*state.Network)
for _, n := range networks {
out[n.Name()] = n
}
return out, nil
}
type machineAndContainers map[string][]*state.Machine
func (m machineAndContainers) HostForMachineId(id string) *state.Machine {
// Element 0 is assumed to be the top-level machine.
return m[id][0]
}
func (m machineAndContainers) Containers(id string) []*state.Machine {
return m[id][1:]
}
func processMachines(idToMachines map[string][]*state.Machine) map[string]api.MachineStatus {
machinesMap := make(map[string]api.MachineStatus)
cache := make(map[string]api.MachineStatus)
for id, machines := range idToMachines {
if len(machines) <= 0 {
continue
}
// Element 0 is assumed to be the top-level machine.
hostStatus := makeMachineStatus(machines[0])
machinesMap[id] = hostStatus
cache[id] = hostStatus
for _, machine := range machines[1:] {
parent, ok := cache[state.ParentId(machine.Id())]
if !ok {
panic("We've broken an assumpution.")
}
status := makeMachineStatus(machine)
parent.Containers[machine.Id()] = status
cache[machine.Id()] = status
}
}
return machinesMap
}
func makeMachineStatus(machine *state.Machine) (status api.MachineStatus) {
status.Id = machine.Id()
agentStatus, compatStatus := processMachine(machine)
status.Agent = agentStatus
// These legacy status values will be deprecated for Juju 2.0.
status.AgentState = compatStatus.Status
status.AgentStateInfo = compatStatus.Info
status.AgentVersion = compatStatus.Version
status.Life = compatStatus.Life
status.Err = compatStatus.Err
status.Series = machine.Series()
status.Jobs = paramsJobsFromJobs(machine.Jobs())
status.WantsVote = machine.WantsVote()
status.HasVote = machine.HasVote()
instid, err := machine.InstanceId()
if err == nil {
status.InstanceId = instid
status.InstanceState, err = machine.InstanceStatus()
if err != nil {
status.InstanceState = "error"
}
status.DNSName = network.SelectPublicAddress(machine.Addresses())
} else {
if errors.IsNotProvisioned(err) {
status.InstanceId = "pending"
} else {
status.InstanceId = "error"
}
// There's no point in reporting a pending agent state
// if the machine hasn't been provisioned. This
// also makes unprovisioned machines visually distinct
// in the output.
status.AgentState = ""
}
hc, err := machine.HardwareCharacteristics()
if err != nil {
if !errors.IsNotFound(err) {
status.Hardware = "error"
}
} else {
status.Hardware = hc.String()
}
status.Containers = make(map[string]api.MachineStatus)
return
}
func (context *statusContext) processRelations() []api.RelationStatus {
var out []api.RelationStatus
relations := context.getAllRelations()
for _, relation := range relations {
var eps []api.EndpointStatus
var scope charm.RelationScope
var relationInterface string
for _, ep := range relation.Endpoints() {
eps = append(eps, api.EndpointStatus{
ServiceName: ep.ServiceName,
Name: ep.Name,
Role: ep.Role,
Subordinate: context.isSubordinate(&ep),
})
// these should match on both sides so use the last
relationInterface = ep.Interface
scope = ep.Scope
}
relStatus := api.RelationStatus{
Id: relation.Id(),
Key: relation.String(),
Interface: relationInterface,
Scope: scope,
Endpoints: eps,
}
out = append(out, relStatus)
}
return out
}
// This method exists only to dedup the loaded relations as they will
// appear multiple times in context.relations.
func (context *statusContext) getAllRelations() []*state.Relation {
var out []*state.Relation
seenRelations := make(map[int]bool)
for _, relations := range context.relations {
for _, relation := range relations {
if _, found := seenRelations[relation.Id()]; !found {
out = append(out, relation)
seenRelations[relation.Id()] = true
}
}
}
return out
}
func (context *statusContext) processNetworks() map[string]api.NetworkStatus {
networksMap := make(map[string]api.NetworkStatus)
for name, network := range context.networks {
networksMap[name] = context.makeNetworkStatus(network)
}
return networksMap
}
func (context *statusContext) makeNetworkStatus(network *state.Network) api.NetworkStatus {
return api.NetworkStatus{
ProviderId: network.ProviderId(),
CIDR: network.CIDR(),
VLANTag: network.VLANTag(),
}
}
func (context *statusContext) isSubordinate(ep *state.Endpoint) bool {
service := context.services[ep.ServiceName]
if service == nil {
return false
}
return isSubordinate(ep, service)
}
func isSubordinate(ep *state.Endpoint, service *state.Service) bool {
return ep.Scope == charm.ScopeContainer && !service.IsPrincipal()
}
// paramsJobsFromJobs converts state jobs to params jobs.
func paramsJobsFromJobs(jobs []state.MachineJob) []multiwatcher.MachineJob {
paramsJobs := make([]multiwatcher.MachineJob, len(jobs))
for i, machineJob := range jobs {
paramsJobs[i] = machineJob.ToParams()
}
return paramsJobs
}
func (context *statusContext) processServices() map[string]api.ServiceStatus {
servicesMap := make(map[string]api.ServiceStatus)
for _, s := range context.services {
servicesMap[s.Name()] = context.processService(s)
}
return servicesMap
}
func (context *statusContext) processService(service *state.Service) (status api.ServiceStatus) {
serviceCharmURL, _ := service.CharmURL()
status.Charm = serviceCharmURL.String()
status.Exposed = service.IsExposed()
status.Life = processLife(service)
latestCharm, ok := context.latestCharms[*serviceCharmURL.WithRevision(-1)]
if ok && latestCharm != serviceCharmURL.String() {
status.CanUpgradeTo = latestCharm
}
var err error
status.Relations, status.SubordinateTo, err = context.processServiceRelations(service)
if err != nil {
status.Err = err
return
}
networks, err := service.Networks()
if err != nil {
status.Err = err
return
}
var cons constraints.Value
if service.IsPrincipal() {
// Only principals can have constraints.
cons, err = service.Constraints()
if err != nil {
status.Err = err
return
}
}
if len(networks) > 0 || cons.HaveNetworks() {
// Only the explicitly requested networks (using "juju deploy
// <svc> --networks=...") will be enabled, and altough when
// specified, networks constraints will be used for instance
// selection, they won't be actually enabled.
status.Networks = api.NetworksSpecification{
Enabled: networks,
Disabled: append(cons.IncludeNetworks(), cons.ExcludeNetworks()...),
}
}
if service.IsPrincipal() {
status.Units = context.processUnits(context.units[service.Name()], serviceCharmURL.String())
serviceStatus, err := service.Status()
if err != nil {
status.Err = err
return
}
status.Status.Status = params.Status(serviceStatus.Status)
status.Status.Info = serviceStatus.Message
status.Status.Data = serviceStatus.Data
status.Status.Since = serviceStatus.Since
}
return status
}
func (context *statusContext) processUnits(units map[string]*state.Unit, serviceCharm string) map[string]api.UnitStatus {
unitsMap := make(map[string]api.UnitStatus)
for _, unit := range units {
unitsMap[unit.Name()] = context.processUnit(unit, serviceCharm)
}
return unitsMap
}
func (context *statusContext) processUnit(unit *state.Unit, serviceCharm string) api.UnitStatus {
var result api.UnitStatus
result.PublicAddress, _ = unit.PublicAddress()
unitPorts, _ := unit.OpenedPorts()
for _, port := range unitPorts {
result.OpenedPorts = append(result.OpenedPorts, port.String())
}
if unit.IsPrincipal() {
result.Machine, _ = unit.AssignedMachineId()
}
curl, _ := unit.CharmURL()
if serviceCharm != "" && curl != nil && curl.String() != serviceCharm {
result.Charm = curl.String()
}
processUnitAndAgentStatus(unit, &result)
if subUnits := unit.SubordinateNames(); len(subUnits) > 0 {
result.Subordinates = make(map[string]api.UnitStatus)
for _, name := range subUnits {
subUnit := context.unitByName(name)
// subUnit may be nil if subordinate was filtered out.
if subUnit != nil {
result.Subordinates[name] = context.processUnit(subUnit, serviceCharm)
}
}
}
return result
}
func (context *statusContext) unitByName(name string) *state.Unit {
serviceName := strings.Split(name, "/")[0]
return context.units[serviceName][name]
}
func (context *statusContext) processServiceRelations(service *state.Service) (related map[string][]string, subord []string, err error) {
subordSet := make(set.Strings)
related = make(map[string][]string)
relations := context.relations[service.Name()]
for _, relation := range relations {
ep, err := relation.Endpoint(service.Name())
if err != nil {
return nil, nil, err
}
relationName := ep.Relation.Name
eps, err := relation.RelatedEndpoints(service.Name())
if err != nil {
return nil, nil, err
}
for _, ep := range eps {
if isSubordinate(&ep, service) {
subordSet.Add(ep.ServiceName)
}
related[relationName] = append(related[relationName], ep.ServiceName)
}
}
for relationName, serviceNames := range related {
sn := set.NewStrings(serviceNames...)
related[relationName] = sn.SortedValues()
}
return related, subordSet.SortedValues(), nil
}
type lifer interface {
Life() state.Life
}
// processUnitAndAgentStatus retrieves status information for both unit and unitAgents.
func processUnitAndAgentStatus(unit *state.Unit, status *api.UnitStatus) {
status.UnitAgent, status.Workload = processUnitStatus(unit)
// Legacy fields required until Juju 2.0.
// We only display pending, started, error, stopped.
var ok bool
legacyState, ok := state.TranslateToLegacyAgentState(
state.Status(status.UnitAgent.Status),
state.Status(status.Workload.Status),
status.Workload.Info,
)
if !ok {
logger.Warningf(
"translate to legacy status encounted unexpected workload status %q and agent status %q",
status.Workload.Status, status.UnitAgent.Status)
}
status.AgentState = params.Status(legacyState)
if status.AgentState == params.StatusError {
status.AgentStateInfo = status.Workload.Info
}
status.AgentVersion = status.UnitAgent.Version
status.Life = status.UnitAgent.Life
status.Err = status.UnitAgent.Err
processUnitLost(unit, status)
return
}
// makeStatusForEntity creates status information for machines, units.
func makeStatusForEntity(agent *api.AgentStatus, getter state.StatusGetter) {
statusInfo, err := getter.Status()
agent.Err = err
agent.Status = params.Status(statusInfo.Status)
agent.Info = statusInfo.Message
agent.Data = filterStatusData(statusInfo.Data)
agent.Since = statusInfo.Since
}
// processMachine retrieves version and status information for the given machine.
// It also returns deprecated legacy status information.
func processMachine(machine *state.Machine) (out api.AgentStatus, compat api.AgentStatus) {
out.Life = processLife(machine)
if t, err := machine.AgentTools(); err == nil {
out.Version = t.Version.Number.String()
}
makeStatusForEntity(&out, machine)
compat = out
if out.Err != nil {
return
}
if out.Status == params.StatusPending {
// The status is pending - there's no point
// in enquiring about the agent liveness.
return
}
agentAlive, err := machine.AgentPresence()
if err != nil {
return
}
if machine.Life() != state.Dead && !agentAlive {
// The agent *should* be alive but is not. Set status to
// StatusDown and munge Info to indicate the previous status and
// info. This is unfortunately making presentation decisions
// on behalf of the client (crappy).
//
// This is munging is only being left in place for
// compatibility with older clients. TODO: At some point we
// should change this so that Info left alone. API version may
// help here.
//
// Better yet, Status shouldn't be changed here in the API at
// all! Status changes should only happen in State. One
// problem caused by this is that this status change won't be
// seen by clients using a watcher because it didn't happen in
// State.
if out.Info != "" {
compat.Info = fmt.Sprintf("(%s: %s)", out.Status, out.Info)
} else {
compat.Info = fmt.Sprintf("(%s)", out.Status)
}
compat.Status = params.StatusDown
}
return
}
// processUnit retrieves version and status information for the given unit.
func processUnitStatus(unit *state.Unit) (agentStatus, workloadStatus api.AgentStatus) {
// First determine the agent status information.
unitAgent := unit.Agent().(*state.UnitAgent)
makeStatusForEntity(&agentStatus, unitAgent)
agentStatus.Life = processLife(unit)
if t, err := unit.AgentTools(); err == nil {
agentStatus.Version = t.Version.Number.String()
}
// Second, determine the workload (unit) status.
makeStatusForEntity(&workloadStatus, unit)
return
}
func canBeLost(status *api.UnitStatus) bool {
// Pending and Installing are deprecated.
// Need to still check pending for existing deployments.
switch status.UnitAgent.Status {
case params.StatusPending, params.StatusInstalling, params.StatusAllocating:
return false
case params.StatusExecuting:
return status.UnitAgent.Info != operation.RunningHookMessage(string(hooks.Install))
}
// TODO(wallyworld) - use status history to see if start hook has run.
isInstalled := status.Workload.Status != params.StatusMaintenance || status.Workload.Info != state.MessageInstalling
return isInstalled
}
// processUnitLost determines whether the given unit should be marked as lost.
// TODO(wallyworld) - move this to state and the canBeLost() code can be simplified.
func processUnitLost(unit *state.Unit, status *api.UnitStatus) {
if !canBeLost(status) {
// The status is allocating or installing - there's no point
// in enquiring about the agent liveness.
return
}
agentAlive, err := unit.AgentPresence()
if err != nil {
return
}
if unit.Life() != state.Dead && !agentAlive {
// If the unit is in error, it would be bad to throw away
// the error information as when the agent reconnects, that
// error information would then be lost.
if status.Workload.Status != params.StatusError {
status.Workload.Status = params.StatusUnknown
status.Workload.Info = fmt.Sprintf("agent is lost, sorry! See 'juju status-history %s'", unit.Name())
}
status.UnitAgent.Status = params.StatusLost
status.UnitAgent.Info = "agent is not communicating with the server"
}
}
// filterStatusData limits what agent StatusData data is passed over
// the API. This prevents unintended leakage of internal-only data.
func filterStatusData(status map[string]interface{}) map[string]interface{} {
out := make(map[string]interface{})
for name, value := range status {
// use a set here if we end up with a larger whitelist
if name == "relation-id" {
out[name] = value
}
}
return out
}
func processLife(entity lifer) string {
if life := entity.Life(); life != state.Alive {
// alive is the usual state so omit it by default.
return life.String()
}
return ""
}