1
// Copyright 2015 Canonical Ltd.
2
// Licensed under the AGPLv3, see LICENCE file for details.
13
"github.com/juju/errors"
14
"github.com/juju/utils"
15
"github.com/juju/utils/set"
17
"github.com/juju/juju/status"
20
// FormatSummary returns a summary of the current environment
21
// including the following information:
23
// - All subnets the environment occupies.
24
// - All ports the environment utilizes.
26
// - Machines: Displays total #, and then the # in each state.
27
// - Units: Displays total #, and then # in each state.
28
// - Applications: Displays total #, their names, and how many of each
30
func FormatSummary(value interface{}) ([]byte, error) {
31
fs, valueConverted := value.(formattedStatus)
33
return nil, errors.Errorf("expected value of type %T, got %T", fs, value)
36
f := newSummaryFormatter()
37
stateToMachine := f.aggregateMachineStates(fs.Machines)
38
svcExposure := f.aggregateServiceAndUnitStates(fs.Applications)
39
p := f.delimitValuesWithTabs
41
// Print everything out
42
p("Running on subnets:", strings.Join(f.netStrings, ", "))
43
p("Utilizing ports:", f.portsInColumnsOf(3))
46
// Right align summary information
47
f.tw.Init(&f.out, 0, 2, 1, ' ', tabwriter.AlignRight)
48
p("# MACHINES:", fmt.Sprintf("(%d)", len(fs.Machines)))
49
f.printStateToCount(stateToMachine)
52
p("# UNITS:", fmt.Sprintf("(%d)", f.numUnits))
53
f.printStateToCount(f.stateToUnit)
56
p("# APPLICATIONS:", fmt.Sprintf(" (%d)", len(fs.Applications)))
57
for _, svcName := range utils.SortStringsNaturally(stringKeysFromMap(svcExposure)) {
58
s := svcExposure[svcName]
59
p(svcName, fmt.Sprintf("%d/%d\texposed", s[true], s[true]+s[false]))
63
return f.out.Bytes(), nil
66
func newSummaryFormatter() *summaryFormatter {
67
f := &summaryFormatter{
68
ipAddrs: make([]net.IPNet, 0),
69
netStrings: make([]string, 0),
70
openPorts: set.NewStrings(),
71
stateToUnit: make(map[status.Status]int),
73
f.tw = tabwriter.NewWriter(&f.out, 0, 1, 1, ' ', 0)
77
type summaryFormatter struct {
83
stateToUnit map[status.Status]int
88
func (f *summaryFormatter) delimitValuesWithTabs(values ...string) {
89
for _, v := range values {
90
fmt.Fprintf(f.tw, "%s\t", v)
95
func (f *summaryFormatter) portsInColumnsOf(col int) string {
98
for i, p := range f.openPorts.SortedValues() {
99
if i != 0 && i%col == 0 {
100
fmt.Fprintf(&b, "\n\t")
102
fmt.Fprintf(&b, "%s, ", p)
104
// Elide the last delimiter
105
portList := b.String()
106
if len(portList) >= 2 {
107
return portList[:b.Len()-2]
112
func (f *summaryFormatter) trackUnit(name string, status unitStatus, indentLevel int) {
113
f.resolveAndTrackIp(status.PublicAddress)
115
for _, p := range status.OpenedPorts {
121
f.stateToUnit[status.WorkloadStatusInfo.Current]++
124
func (f *summaryFormatter) printStateToCount(m map[status.Status]int) {
125
for _, stateToCount := range utils.SortStringsNaturally(stringKeysFromMap(m)) {
126
numInStatus := m[status.Status(stateToCount)]
127
f.delimitValuesWithTabs(stateToCount+":", fmt.Sprintf(" %d ", numInStatus))
131
func (f *summaryFormatter) trackIp(ip net.IP) {
132
for _, net := range f.ipAddrs {
133
if net.Contains(ip) {
138
ipNet := net.IPNet{IP: ip, Mask: ip.DefaultMask()}
139
f.ipAddrs = append(f.ipAddrs, ipNet)
140
f.netStrings = append(f.netStrings, ipNet.String())
143
func (f *summaryFormatter) resolveAndTrackIp(publicDns string) {
144
// TODO(katco-): We may be able to utilize upcoming work which will expose these addresses outright.
145
ip, err := net.ResolveIPAddr("ip4", publicDns)
148
"unable to resolve %s to an IP address. Status may be incorrect: %v",
157
func (f *summaryFormatter) aggregateMachineStates(machines map[string]machineStatus) map[status.Status]int {
158
stateToMachine := make(map[status.Status]int)
159
for _, name := range utils.SortStringsNaturally(stringKeysFromMap(machines)) {
161
f.resolveAndTrackIp(m.DNSName)
163
if agentState := m.JujuStatus.Current; agentState == "" {
164
agentState = status.StatusPending
166
stateToMachine[agentState]++
169
return stateToMachine
172
func (f *summaryFormatter) aggregateServiceAndUnitStates(services map[string]applicationStatus) map[string]map[bool]int {
173
svcExposure := make(map[string]map[bool]int)
174
for _, name := range utils.SortStringsNaturally(stringKeysFromMap(services)) {
177
for _, un := range utils.SortStringsNaturally(stringKeysFromMap(s.Units)) {
179
f.trackUnit(un, u, 0)
180
recurseUnits(u, 1, f.trackUnit)
183
if _, ok := svcExposure[name]; !ok {
184
svcExposure[name] = make(map[bool]int)
187
svcExposure[name][s.Exposed]++