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  }