github.com/mhilton/juju-juju@v0.0.0-20150901100907-a94dd2c73455/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/apiserver/params"
    17  	"github.com/juju/juju/cmd/juju/common"
    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[params.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[params.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.AgentState]++
   122  }
   123  
   124  func (f *summaryFormatter) printStateToCount(m map[params.Status]int) {
   125  	for _, status := range common.SortStringsNaturally(stringKeysFromMap(m)) {
   126  		numInStatus := m[params.Status(status)]
   127  		f.delimitValuesWithTabs(status+":", 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[params.Status]int {
   158  	stateToMachine := make(map[params.Status]int)
   159  	for _, name := range common.SortStringsNaturally(stringKeysFromMap(machines)) {
   160  		m := machines[name]
   161  		f.resolveAndTrackIp(m.DNSName)
   162  
   163  		if agentState := m.AgentState; agentState == "" {
   164  			agentState = params.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  }