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  }