github.com/makyo/juju@v0.0.0-20160425123129-2608902037e9/cmd/juju/status/output_summary.go (about) 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/set" 15 16 "github.com/juju/juju/cmd/juju/common" 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 // - Services: 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.Services) 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("# SERVICES:", fmt.Sprintf(" (%d)", len(fs.Services))) 57 for _, svcName := range common.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 common.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.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 common.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]serviceStatus) map[string]map[bool]int { 173 svcExposure := make(map[string]map[bool]int) 174 for _, name := range common.SortStringsNaturally(stringKeysFromMap(services)) { 175 s := services[name] 176 // Grab unit states 177 for _, un := range common.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 }