github.com/axw/juju@v0.0.0-20161005053422-4bd6544d08d4/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 "io" 10 "net" 11 "strings" 12 13 "github.com/juju/ansiterm" 14 "github.com/juju/ansiterm/tabwriter" 15 "github.com/juju/errors" 16 "github.com/juju/utils" 17 "github.com/juju/utils/set" 18 19 "github.com/juju/juju/cmd/output" 20 "github.com/juju/juju/status" 21 ) 22 23 // FormatSummary writes a summary of the current environment 24 // including the following information: 25 // - Headers: 26 // - All subnets the environment occupies. 27 // - All ports the environment utilizes. 28 // - Sections: 29 // - Machines: Displays total #, and then the # in each state. 30 // - Units: Displays total #, and then # in each state. 31 // - Applications: Displays total #, their names, and how many of each 32 // are exposed. 33 func FormatSummary(writer io.Writer, value interface{}) error { 34 fs, valueConverted := value.(formattedStatus) 35 if !valueConverted { 36 return errors.Errorf("expected value of type %T, got %T", fs, value) 37 } 38 39 f := newSummaryFormatter(writer) 40 stateToMachine := f.aggregateMachineStates(fs.Machines) 41 svcExposure := f.aggregateServiceAndUnitStates(fs.Applications) 42 p := f.delimitValuesWithTabs 43 44 // Print everything out 45 p("Running on subnets:", strings.Join(f.netStrings, ", ")) 46 p(" Utilizing ports:", f.portsInColumnsOf(3)) 47 f.tw.Flush() 48 49 // Right align summary information 50 f.tw.Init(writer, 0, 1, 2, ' ', tabwriter.AlignRight) 51 p("# MACHINES:", fmt.Sprintf("(%d)", len(fs.Machines))) 52 f.printStateToCount(stateToMachine) 53 p(" ") 54 55 p("# UNITS:", fmt.Sprintf("(%d)", f.numUnits)) 56 f.printStateToCount(f.stateToUnit) 57 p(" ") 58 59 p("# APPLICATIONS:", fmt.Sprintf("(%d)", len(fs.Applications))) 60 for _, svcName := range utils.SortStringsNaturally(stringKeysFromMap(svcExposure)) { 61 s := svcExposure[svcName] 62 p(svcName, fmt.Sprintf("%d/%d\texposed", s[true], s[true]+s[false])) 63 } 64 f.tw.Flush() 65 66 return nil 67 } 68 69 func newSummaryFormatter(writer io.Writer) *summaryFormatter { 70 f := &summaryFormatter{ 71 ipAddrs: make([]net.IPNet, 0), 72 netStrings: make([]string, 0), 73 openPorts: set.NewStrings(), 74 stateToUnit: make(map[status.Status]int), 75 } 76 f.tw = output.TabWriter(writer) 77 return f 78 } 79 80 type summaryFormatter struct { 81 ipAddrs []net.IPNet 82 netStrings []string 83 numUnits int 84 openPorts set.Strings 85 // status -> count 86 stateToUnit map[status.Status]int 87 tw *ansiterm.TabWriter 88 } 89 90 func (f *summaryFormatter) delimitValuesWithTabs(values ...string) { 91 for _, v := range values { 92 fmt.Fprintf(f.tw, "%s\t", v) 93 } 94 fmt.Fprintln(f.tw) 95 } 96 97 func (f *summaryFormatter) portsInColumnsOf(col int) string { 98 99 var b bytes.Buffer 100 for i, p := range f.openPorts.SortedValues() { 101 if i != 0 && i%col == 0 { 102 fmt.Fprintf(&b, "\n\t") 103 } 104 fmt.Fprintf(&b, "%s, ", p) 105 } 106 // Elide the last delimiter 107 portList := b.String() 108 if len(portList) >= 2 { 109 return portList[:b.Len()-2] 110 } 111 return portList 112 } 113 114 func (f *summaryFormatter) trackUnit(name string, status unitStatus, indentLevel int) { 115 f.resolveAndTrackIp(status.PublicAddress) 116 117 for _, p := range status.OpenedPorts { 118 if p != "" { 119 f.openPorts.Add(p) 120 } 121 } 122 f.numUnits++ 123 f.stateToUnit[status.WorkloadStatusInfo.Current]++ 124 } 125 126 func (f *summaryFormatter) printStateToCount(m map[status.Status]int) { 127 for _, stateToCount := range utils.SortStringsNaturally(stringKeysFromMap(m)) { 128 numInStatus := m[status.Status(stateToCount)] 129 f.delimitValuesWithTabs(stateToCount+":", fmt.Sprintf(" %d ", numInStatus)) 130 } 131 } 132 133 func (f *summaryFormatter) trackIp(ip net.IP) { 134 for _, net := range f.ipAddrs { 135 if net.Contains(ip) { 136 return 137 } 138 } 139 140 ipNet := net.IPNet{IP: ip, Mask: ip.DefaultMask()} 141 f.ipAddrs = append(f.ipAddrs, ipNet) 142 f.netStrings = append(f.netStrings, ipNet.String()) 143 } 144 145 func (f *summaryFormatter) resolveAndTrackIp(publicDns string) { 146 // TODO(katco-): We may be able to utilize upcoming work which will expose these addresses outright. 147 ip, err := net.ResolveIPAddr("ip4", publicDns) 148 if err != nil { 149 logger.Warningf( 150 "unable to resolve %s to an IP address. Status may be incorrect: %v", 151 publicDns, 152 err, 153 ) 154 return 155 } 156 f.trackIp(ip.IP) 157 } 158 159 func (f *summaryFormatter) aggregateMachineStates(machines map[string]machineStatus) map[status.Status]int { 160 stateToMachine := make(map[status.Status]int) 161 for _, name := range utils.SortStringsNaturally(stringKeysFromMap(machines)) { 162 m := machines[name] 163 f.resolveAndTrackIp(m.DNSName) 164 165 if agentState := m.JujuStatus.Current; agentState == "" { 166 agentState = status.Pending 167 } else { 168 stateToMachine[agentState]++ 169 } 170 } 171 return stateToMachine 172 } 173 174 func (f *summaryFormatter) aggregateServiceAndUnitStates(services map[string]applicationStatus) map[string]map[bool]int { 175 svcExposure := make(map[string]map[bool]int) 176 for _, name := range utils.SortStringsNaturally(stringKeysFromMap(services)) { 177 s := services[name] 178 // Grab unit states 179 for _, un := range utils.SortStringsNaturally(stringKeysFromMap(s.Units)) { 180 u := s.Units[un] 181 f.trackUnit(un, u, 0) 182 recurseUnits(u, 1, f.trackUnit) 183 } 184 185 if _, ok := svcExposure[name]; !ok { 186 svcExposure[name] = make(map[bool]int) 187 } 188 189 svcExposure[name][s.Exposed]++ 190 } 191 return svcExposure 192 }