~nskaggs/+junk/xenial-test

« back to all changes in this revision

Viewing changes to src/github.com/juju/juju/cmd/juju/status/output_summary.go

  • Committer: Nicholas Skaggs
  • Date: 2016-10-24 20:56:05 UTC
  • Revision ID: nicholas.skaggs@canonical.com-20161024205605-z8lta0uvuhtxwzwl
Initi with beta15

Show diffs side-by-side

added added

removed removed

Lines of Context:
 
1
// Copyright 2015 Canonical Ltd.
 
2
// Licensed under the AGPLv3, see LICENCE file for details.
 
3
 
 
4
package status
 
5
 
 
6
import (
 
7
        "bytes"
 
8
        "fmt"
 
9
        "net"
 
10
        "strings"
 
11
        "text/tabwriter"
 
12
 
 
13
        "github.com/juju/errors"
 
14
        "github.com/juju/utils"
 
15
        "github.com/juju/utils/set"
 
16
 
 
17
        "github.com/juju/juju/status"
 
18
)
 
19
 
 
20
// FormatSummary returns a summary of the current environment
 
21
// including the following information:
 
22
// - Headers:
 
23
//   - All subnets the environment occupies.
 
24
//   - All ports the environment utilizes.
 
25
// - Sections:
 
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
 
29
//     are exposed.
 
30
func FormatSummary(value interface{}) ([]byte, error) {
 
31
        fs, valueConverted := value.(formattedStatus)
 
32
        if !valueConverted {
 
33
                return nil, errors.Errorf("expected value of type %T, got %T", fs, value)
 
34
        }
 
35
 
 
36
        f := newSummaryFormatter()
 
37
        stateToMachine := f.aggregateMachineStates(fs.Machines)
 
38
        svcExposure := f.aggregateServiceAndUnitStates(fs.Applications)
 
39
        p := f.delimitValuesWithTabs
 
40
 
 
41
        // Print everything out
 
42
        p("Running on subnets:", strings.Join(f.netStrings, ", "))
 
43
        p("Utilizing ports:", f.portsInColumnsOf(3))
 
44
        f.tw.Flush()
 
45
 
 
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)
 
50
        p(" ")
 
51
 
 
52
        p("# UNITS:", fmt.Sprintf("(%d)", f.numUnits))
 
53
        f.printStateToCount(f.stateToUnit)
 
54
        p(" ")
 
55
 
 
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]))
 
60
        }
 
61
        f.tw.Flush()
 
62
 
 
63
        return f.out.Bytes(), nil
 
64
}
 
65
 
 
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),
 
72
        }
 
73
        f.tw = tabwriter.NewWriter(&f.out, 0, 1, 1, ' ', 0)
 
74
        return f
 
75
}
 
76
 
 
77
type summaryFormatter struct {
 
78
        ipAddrs    []net.IPNet
 
79
        netStrings []string
 
80
        numUnits   int
 
81
        openPorts  set.Strings
 
82
        // status -> count
 
83
        stateToUnit map[status.Status]int
 
84
        tw          *tabwriter.Writer
 
85
        out         bytes.Buffer
 
86
}
 
87
 
 
88
func (f *summaryFormatter) delimitValuesWithTabs(values ...string) {
 
89
        for _, v := range values {
 
90
                fmt.Fprintf(f.tw, "%s\t", v)
 
91
        }
 
92
        fmt.Fprintln(f.tw)
 
93
}
 
94
 
 
95
func (f *summaryFormatter) portsInColumnsOf(col int) string {
 
96
 
 
97
        var b bytes.Buffer
 
98
        for i, p := range f.openPorts.SortedValues() {
 
99
                if i != 0 && i%col == 0 {
 
100
                        fmt.Fprintf(&b, "\n\t")
 
101
                }
 
102
                fmt.Fprintf(&b, "%s, ", p)
 
103
        }
 
104
        // Elide the last delimiter
 
105
        portList := b.String()
 
106
        if len(portList) >= 2 {
 
107
                return portList[:b.Len()-2]
 
108
        }
 
109
        return portList
 
110
}
 
111
 
 
112
func (f *summaryFormatter) trackUnit(name string, status unitStatus, indentLevel int) {
 
113
        f.resolveAndTrackIp(status.PublicAddress)
 
114
 
 
115
        for _, p := range status.OpenedPorts {
 
116
                if p != "" {
 
117
                        f.openPorts.Add(p)
 
118
                }
 
119
        }
 
120
        f.numUnits++
 
121
        f.stateToUnit[status.WorkloadStatusInfo.Current]++
 
122
}
 
123
 
 
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))
 
128
        }
 
129
}
 
130
 
 
131
func (f *summaryFormatter) trackIp(ip net.IP) {
 
132
        for _, net := range f.ipAddrs {
 
133
                if net.Contains(ip) {
 
134
                        return
 
135
                }
 
136
        }
 
137
 
 
138
        ipNet := net.IPNet{IP: ip, Mask: ip.DefaultMask()}
 
139
        f.ipAddrs = append(f.ipAddrs, ipNet)
 
140
        f.netStrings = append(f.netStrings, ipNet.String())
 
141
}
 
142
 
 
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)
 
146
        if err != nil {
 
147
                logger.Warningf(
 
148
                        "unable to resolve %s to an IP address. Status may be incorrect: %v",
 
149
                        publicDns,
 
150
                        err,
 
151
                )
 
152
                return
 
153
        }
 
154
        f.trackIp(ip.IP)
 
155
}
 
156
 
 
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)) {
 
160
                m := machines[name]
 
161
                f.resolveAndTrackIp(m.DNSName)
 
162
 
 
163
                if agentState := m.JujuStatus.Current; agentState == "" {
 
164
                        agentState = status.StatusPending
 
165
                } else {
 
166
                        stateToMachine[agentState]++
 
167
                }
 
168
        }
 
169
        return stateToMachine
 
170
}
 
171
 
 
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)) {
 
175
                s := services[name]
 
176
                // Grab unit states
 
177
                for _, un := range utils.SortStringsNaturally(stringKeysFromMap(s.Units)) {
 
178
                        u := s.Units[un]
 
179
                        f.trackUnit(un, u, 0)
 
180
                        recurseUnits(u, 1, f.trackUnit)
 
181
                }
 
182
 
 
183
                if _, ok := svcExposure[name]; !ok {
 
184
                        svcExposure[name] = make(map[bool]int)
 
185
                }
 
186
 
 
187
                svcExposure[name][s.Exposed]++
 
188
        }
 
189
        return svcExposure
 
190
}