github.com/altoros/juju-vmware@v0.0.0-20150312064031-f19ae857ccca/cmd/juju/status_formatters.go (about) 1 // Copyright 2014 Canonical Ltd. 2 // Licensed under the AGPLv3, see LICENCE file for details. 3 4 package main 5 6 import ( 7 "bytes" 8 "fmt" 9 "net" 10 "reflect" 11 "sort" 12 "strings" 13 "text/tabwriter" 14 15 "github.com/juju/errors" 16 "github.com/juju/utils/set" 17 18 "github.com/juju/juju/apiserver/params" 19 ) 20 21 // FormatOneline returns a brief list of units and their subordinates. 22 // Subordinates will be indented 2 spaces and listed under their 23 // superiors. 24 func FormatOneline(value interface{}) ([]byte, error) { 25 fs, valueConverted := value.(formattedStatus) 26 if !valueConverted { 27 return nil, errors.Errorf("expected value of type %T, got %T", fs, value) 28 } 29 var out bytes.Buffer 30 31 pprint := func(uName string, u unitStatus, level int) { 32 var fmtPorts string 33 if len(u.OpenedPorts) > 0 { 34 fmtPorts = fmt.Sprintf(" %s", strings.Join(u.OpenedPorts, ", ")) 35 } 36 fmt.Fprintf(&out, indent("\n", level*2, "- %s: %s (%v)%v"), 37 uName, 38 u.PublicAddress, 39 u.AgentState, 40 fmtPorts, 41 ) 42 } 43 44 for _, svcName := range sortStrings(stringKeysFromMap(fs.Services)) { 45 svc := fs.Services[svcName] 46 for _, uName := range sortStrings(stringKeysFromMap(svc.Units)) { 47 unit := svc.Units[uName] 48 pprint(uName, unit, 0) 49 recurseUnits(unit, 1, pprint) 50 } 51 } 52 53 return out.Bytes(), nil 54 } 55 56 // FormatTabular returns a tabular summary of machines, services, and 57 // units. Any subordinate items are indented by two spaces beneath 58 // their superior. 59 func FormatTabular(value interface{}) ([]byte, error) { 60 fs, valueConverted := value.(formattedStatus) 61 if !valueConverted { 62 return nil, errors.Errorf("expected value of type %T, got %T", fs, value) 63 } 64 var out bytes.Buffer 65 // To format things into columns. 66 tw := tabwriter.NewWriter(&out, 0, 1, 1, ' ', 0) 67 p := func(values ...interface{}) { 68 for _, v := range values { 69 fmt.Fprintf(tw, "%s\t", v) 70 } 71 fmt.Fprintln(tw) 72 } 73 74 p("[Machines]") 75 p("ID\tSTATE\tVERSION\tDNS\tINS-ID\tSERIES\tHARDWARE") 76 for _, name := range sortStrings(stringKeysFromMap(fs.Machines)) { 77 m := fs.Machines[name] 78 p(m.Id, m.AgentState, m.AgentVersion, m.DNSName, m.InstanceId, m.Series, m.Hardware) 79 } 80 tw.Flush() 81 82 units := make(map[string]unitStatus) 83 84 p("\n[Services]") 85 p("NAME\tEXPOSED\tCHARM") 86 for _, svcName := range sortStrings(stringKeysFromMap(fs.Services)) { 87 svc := fs.Services[svcName] 88 for un, u := range svc.Units { 89 units[un] = u 90 } 91 p(svcName, fmt.Sprintf("%t", svc.Exposed), svc.Charm) 92 } 93 tw.Flush() 94 95 pUnit := func(name string, u unitStatus, level int) { 96 p( 97 indent("", level*2, name), 98 u.AgentState, 99 u.AgentVersion, 100 u.Machine, 101 strings.Join(u.OpenedPorts, ","), 102 u.PublicAddress, 103 ) 104 } 105 106 p("\n[Units]") 107 p("ID\tSTATE\tVERSION\tMACHINE\tPORTS\tPUBLIC-ADDRESS") 108 for _, name := range sortStrings(stringKeysFromMap(units)) { 109 u := units[name] 110 pUnit(name, u, 0) 111 const indentationLevel = 1 112 recurseUnits(u, indentationLevel, pUnit) 113 } 114 tw.Flush() 115 116 return out.Bytes(), nil 117 } 118 119 // FormatSummary returns a summary of the current environment 120 // including the following information: 121 // - Headers: 122 // - All subnets the environment occupies. 123 // - All ports the environment utilizes. 124 // - Sections: 125 // - Machines: Displays total #, and then the # in each state. 126 // - Units: Displays total #, and then # in each state. 127 // - Services: Displays total #, their names, and how many of each 128 // are exposed. 129 func FormatSummary(value interface{}) ([]byte, error) { 130 fs, valueConverted := value.(formattedStatus) 131 if !valueConverted { 132 return nil, errors.Errorf("expected value of type %T, got %T", fs, value) 133 } 134 135 f := newSummaryFormatter() 136 stateToMachine := f.aggregateMachineStates(fs.Machines) 137 svcExposure := f.aggregateServiceAndUnitStates(fs.Services) 138 p := f.delimitValuesWithTabs 139 140 // Print everything out 141 p("Running on subnets:", strings.Join(f.netStrings, ", ")) 142 p("Utilizing ports:", f.portsInColumnsOf(3)) 143 f.tw.Flush() 144 145 // Right align summary information 146 f.tw.Init(&f.out, 0, 2, 1, ' ', tabwriter.AlignRight) 147 p("# MACHINES:", fmt.Sprintf("(%d)", len(fs.Machines))) 148 f.printStateToCount(stateToMachine) 149 p(" ") 150 151 p("# UNITS:", fmt.Sprintf("(%d)", f.numUnits)) 152 f.printStateToCount(f.stateToUnit) 153 p(" ") 154 155 p("# SERVICES:", fmt.Sprintf(" (%d)", len(fs.Services))) 156 for _, svcName := range sortStrings(stringKeysFromMap(svcExposure)) { 157 s := svcExposure[svcName] 158 p(svcName, fmt.Sprintf("%d/%d\texposed", s[true], s[true]+s[false])) 159 } 160 f.tw.Flush() 161 162 return f.out.Bytes(), nil 163 } 164 165 func newSummaryFormatter() *summaryFormatter { 166 f := &summaryFormatter{ 167 ipAddrs: make([]net.IPNet, 0), 168 netStrings: make([]string, 0), 169 openPorts: set.NewStrings(), 170 stateToUnit: make(map[params.Status]int), 171 } 172 f.tw = tabwriter.NewWriter(&f.out, 0, 1, 1, ' ', 0) 173 return f 174 } 175 176 type summaryFormatter struct { 177 ipAddrs []net.IPNet 178 netStrings []string 179 numUnits int 180 openPorts set.Strings 181 // status -> count 182 stateToUnit map[params.Status]int 183 tw *tabwriter.Writer 184 out bytes.Buffer 185 } 186 187 func (f *summaryFormatter) delimitValuesWithTabs(values ...string) { 188 for _, v := range values { 189 fmt.Fprintf(f.tw, "%s\t", v) 190 } 191 fmt.Fprintln(f.tw) 192 } 193 194 func (f *summaryFormatter) portsInColumnsOf(col int) string { 195 196 var b bytes.Buffer 197 for i, p := range f.openPorts.SortedValues() { 198 if i != 0 && i%col == 0 { 199 fmt.Fprintf(&b, "\n\t") 200 } 201 fmt.Fprintf(&b, "%s, ", p) 202 } 203 // Elide the last delimiter 204 portList := b.String() 205 if len(portList) >= 2 { 206 return portList[:b.Len()-2] 207 } 208 return portList 209 } 210 211 func (f *summaryFormatter) trackUnit(name string, status unitStatus, indentLevel int) { 212 f.resolveAndTrackIp(status.PublicAddress) 213 214 for _, p := range status.OpenedPorts { 215 if p != "" { 216 f.openPorts.Add(p) 217 } 218 } 219 f.numUnits++ 220 f.stateToUnit[status.AgentState]++ 221 } 222 223 func (f *summaryFormatter) printStateToCount(m map[params.Status]int) { 224 for _, status := range sortStrings(stringKeysFromMap(m)) { 225 numInStatus := m[params.Status(status)] 226 f.delimitValuesWithTabs(status+":", fmt.Sprintf(" %d ", numInStatus)) 227 } 228 } 229 230 func (f *summaryFormatter) trackIp(ip net.IP) { 231 for _, net := range f.ipAddrs { 232 if net.Contains(ip) { 233 return 234 } 235 } 236 237 ipNet := net.IPNet{ip, ip.DefaultMask()} 238 f.ipAddrs = append(f.ipAddrs, ipNet) 239 f.netStrings = append(f.netStrings, ipNet.String()) 240 } 241 242 func (f *summaryFormatter) resolveAndTrackIp(publicDns string) { 243 // TODO(katco-): We may be able to utilize upcoming work which will expose these addresses outright. 244 ip, err := net.ResolveIPAddr("ip4", publicDns) 245 if err != nil { 246 logger.Warningf( 247 "unable to resolve %s to an IP address. Status may be incorrect: %v", 248 publicDns, 249 err, 250 ) 251 return 252 } 253 f.trackIp(ip.IP) 254 } 255 256 func (f *summaryFormatter) aggregateMachineStates(machines map[string]machineStatus) map[params.Status]int { 257 stateToMachine := make(map[params.Status]int) 258 for _, name := range sortStrings(stringKeysFromMap(machines)) { 259 m := machines[name] 260 f.resolveAndTrackIp(m.DNSName) 261 262 if agentState := m.AgentState; agentState == "" { 263 agentState = params.StatusPending 264 } else { 265 stateToMachine[agentState]++ 266 } 267 } 268 return stateToMachine 269 } 270 271 func (f *summaryFormatter) aggregateServiceAndUnitStates(services map[string]serviceStatus) map[string]map[bool]int { 272 svcExposure := make(map[string]map[bool]int) 273 for _, name := range sortStrings(stringKeysFromMap(services)) { 274 s := services[name] 275 // Grab unit states 276 for _, un := range sortStrings(stringKeysFromMap(s.Units)) { 277 u := s.Units[un] 278 f.trackUnit(un, u, 0) 279 recurseUnits(u, 1, f.trackUnit) 280 } 281 282 if _, ok := svcExposure[name]; !ok { 283 svcExposure[name] = make(map[bool]int) 284 } 285 286 svcExposure[name][s.Exposed]++ 287 } 288 return svcExposure 289 } 290 291 // sortStrings is syntactic sugar so we can do sorts in one line. 292 func sortStrings(s []string) []string { 293 sort.Strings(s) 294 return s 295 } 296 297 // stringKeysFromMap takes a map with keys which are strings and returns 298 // only the keys. 299 func stringKeysFromMap(m interface{}) (keys []string) { 300 for _, k := range reflect.ValueOf(m).MapKeys() { 301 keys = append(keys, k.String()) 302 } 303 return 304 } 305 306 // recurseUnits calls the given recurseMap function on the given unit 307 // and its subordinates (recursively defined on the given unit). 308 func recurseUnits(u unitStatus, il int, recurseMap func(string, unitStatus, int)) { 309 if len(u.Subordinates) == 0 { 310 return 311 } 312 for _, uName := range sortStrings(stringKeysFromMap(u.Subordinates)) { 313 unit := u.Subordinates[uName] 314 recurseMap(uName, unit, il) 315 recurseUnits(unit, il+1, recurseMap) 316 } 317 } 318 319 // indent prepends a format string with the given number of spaces. 320 func indent(prepend string, level int, append string) string { 321 return fmt.Sprintf("%s%*s%s", prepend, level, "", append) 322 }