github.com/altoros/juju-vmware@v0.0.0-20150312064031-f19ae857ccca/cmd/juju/status.go (about)

     1  // Copyright 2012, 2013 Canonical Ltd.
     2  // Licensed under the AGPLv3, see LICENCE file for details.
     3  
     4  package main
     5  
     6  import (
     7  	"encoding/json"
     8  	"fmt"
     9  
    10  	"github.com/juju/cmd"
    11  	"launchpad.net/gnuflag"
    12  
    13  	"github.com/juju/errors"
    14  
    15  	"github.com/juju/juju/api"
    16  	"github.com/juju/juju/apiserver/params"
    17  	"github.com/juju/juju/cmd/envcmd"
    18  	"github.com/juju/juju/instance"
    19  	"github.com/juju/juju/network"
    20  	"github.com/juju/juju/state/multiwatcher"
    21  )
    22  
    23  type StatusCommand struct {
    24  	envcmd.EnvCommandBase
    25  	out      cmd.Output
    26  	patterns []string
    27  }
    28  
    29  var statusDoc = `
    30  This command will report on the runtime state of various system entities.
    31  
    32  There are a number of ways to format the status output:
    33  
    34  - {short|line|oneline}: List units and their subordinates. For each
    35             unit, the IP address and agent status are listed.
    36  - summary: Displays the subnet(s) and port(s) the environment utilizes.
    37             Also displays aggregate information about:
    38             - MACHINES: total #, and # in each state.
    39             - UNITS: total #, and # in each state.
    40             - SERVICES: total #, and # exposed of each service.
    41  - tabular: Displays information in a tabular format in these sections:
    42             - Machines: ID, STATE, VERSION, DNS, INS-ID, SERIES, HARDWARE
    43             - Services: NAME, EXPOSED, CHARM
    44             - Units: ID, STATE, VERSION, MACHINE, PORTS, PUBLIC-ADDRESS
    45               - Also displays subordinate units.
    46  - yaml (DEFAULT): Displays information on machines, services, and units
    47                    in the yaml format.
    48  
    49  Service or unit names may be specified to filter the status to only those
    50  services and units that match, along with the related machines, services
    51  and units. If a subordinate unit is matched, then its principal unit will
    52  be displayed. If a principal unit is matched, then all of its subordinates
    53  will be displayed.
    54  
    55  Wildcards ('*') may be specified in service/unit names to match any sequence
    56  of characters. For example, 'nova-*' will match any service whose name begins
    57  with 'nova-': 'nova-compute', 'nova-volume', etc.
    58  `
    59  
    60  func (c *StatusCommand) Info() *cmd.Info {
    61  	return &cmd.Info{
    62  		Name:    "status",
    63  		Args:    "[pattern ...]",
    64  		Purpose: "output status information about an environment",
    65  		Doc:     statusDoc,
    66  		Aliases: []string{"stat"},
    67  	}
    68  }
    69  
    70  func (c *StatusCommand) SetFlags(f *gnuflag.FlagSet) {
    71  	c.out.AddFlags(f, "yaml", map[string]cmd.Formatter{
    72  		"yaml":    cmd.FormatYaml,
    73  		"json":    cmd.FormatJson,
    74  		"short":   FormatOneline,
    75  		"oneline": FormatOneline,
    76  		"line":    FormatOneline,
    77  		"tabular": FormatTabular,
    78  		"summary": FormatSummary,
    79  	})
    80  }
    81  
    82  func (c *StatusCommand) Init(args []string) error {
    83  	c.patterns = args
    84  	return nil
    85  }
    86  
    87  var connectionError = `Unable to connect to environment %q.
    88  Please check your credentials or use 'juju bootstrap' to create a new environment.
    89  
    90  Error details:
    91  %v
    92  `
    93  
    94  type statusAPI interface {
    95  	Status(patterns []string) (*api.Status, error)
    96  	Close() error
    97  }
    98  
    99  var newApiClientForStatus = func(c *StatusCommand) (statusAPI, error) {
   100  	return c.NewAPIClient()
   101  }
   102  
   103  func (c *StatusCommand) Run(ctx *cmd.Context) error {
   104  
   105  	apiclient, err := newApiClientForStatus(c)
   106  	if err != nil {
   107  		return fmt.Errorf(connectionError, c.ConnectionName(), err)
   108  	}
   109  	defer apiclient.Close()
   110  
   111  	status, err := apiclient.Status(c.patterns)
   112  	if err != nil {
   113  		if status == nil {
   114  			// Status call completely failed, there is nothing to report
   115  			return err
   116  		}
   117  		// Display any error, but continue to print status if some was returned
   118  		fmt.Fprintf(ctx.Stderr, "%v\n", err)
   119  	} else if status == nil {
   120  		return errors.Errorf("unable to obtain the current status")
   121  	}
   122  
   123  	result := newStatusFormatter(status).format()
   124  	return c.out.Write(ctx, result)
   125  }
   126  
   127  type formattedStatus struct {
   128  	Environment string                   `json:"environment"`
   129  	Machines    map[string]machineStatus `json:"machines"`
   130  	Services    map[string]serviceStatus `json:"services"`
   131  	Networks    map[string]networkStatus `json:"networks,omitempty" yaml:",omitempty"`
   132  }
   133  
   134  type errorStatus struct {
   135  	StatusError string `json:"status-error" yaml:"status-error"`
   136  }
   137  
   138  type machineStatus struct {
   139  	Err            error                    `json:"-" yaml:",omitempty"`
   140  	AgentState     params.Status            `json:"agent-state,omitempty" yaml:"agent-state,omitempty"`
   141  	AgentStateInfo string                   `json:"agent-state-info,omitempty" yaml:"agent-state-info,omitempty"`
   142  	AgentVersion   string                   `json:"agent-version,omitempty" yaml:"agent-version,omitempty"`
   143  	DNSName        string                   `json:"dns-name,omitempty" yaml:"dns-name,omitempty"`
   144  	InstanceId     instance.Id              `json:"instance-id,omitempty" yaml:"instance-id,omitempty"`
   145  	InstanceState  string                   `json:"instance-state,omitempty" yaml:"instance-state,omitempty"`
   146  	Life           string                   `json:"life,omitempty" yaml:"life,omitempty"`
   147  	Series         string                   `json:"series,omitempty" yaml:"series,omitempty"`
   148  	Id             string                   `json:"-" yaml:"-"`
   149  	Containers     map[string]machineStatus `json:"containers,omitempty" yaml:"containers,omitempty"`
   150  	Hardware       string                   `json:"hardware,omitempty" yaml:"hardware,omitempty"`
   151  	HAStatus       string                   `json:"state-server-member-status,omitempty" yaml:"state-server-member-status,omitempty"`
   152  }
   153  
   154  // A goyaml bug means we can't declare these types
   155  // locally to the GetYAML methods.
   156  type machineStatusNoMarshal machineStatus
   157  
   158  func (s machineStatus) MarshalJSON() ([]byte, error) {
   159  	if s.Err != nil {
   160  		return json.Marshal(errorStatus{s.Err.Error()})
   161  	}
   162  	return json.Marshal(machineStatusNoMarshal(s))
   163  }
   164  
   165  func (s machineStatus) GetYAML() (tag string, value interface{}) {
   166  	if s.Err != nil {
   167  		return "", errorStatus{s.Err.Error()}
   168  	}
   169  	// TODO(rog) rename mNoMethods to noMethods (and also in
   170  	// the other GetYAML methods) when people are using the non-buggy
   171  	// goyaml version.
   172  	type mNoMethods machineStatus
   173  	return "", mNoMethods(s)
   174  }
   175  
   176  type serviceStatus struct {
   177  	Err           error                 `json:"-" yaml:",omitempty"`
   178  	Charm         string                `json:"charm" yaml:"charm"`
   179  	CanUpgradeTo  string                `json:"can-upgrade-to,omitempty" yaml:"can-upgrade-to,omitempty"`
   180  	Exposed       bool                  `json:"exposed" yaml:"exposed"`
   181  	Life          string                `json:"life,omitempty" yaml:"life,omitempty"`
   182  	Relations     map[string][]string   `json:"relations,omitempty" yaml:"relations,omitempty"`
   183  	Networks      map[string][]string   `json:"networks,omitempty" yaml:"networks,omitempty"`
   184  	SubordinateTo []string              `json:"subordinate-to,omitempty" yaml:"subordinate-to,omitempty"`
   185  	Units         map[string]unitStatus `json:"units,omitempty" yaml:"units,omitempty"`
   186  }
   187  
   188  type serviceStatusNoMarshal serviceStatus
   189  
   190  func (s serviceStatus) MarshalJSON() ([]byte, error) {
   191  	if s.Err != nil {
   192  		return json.Marshal(errorStatus{s.Err.Error()})
   193  	}
   194  	type sNoMethods serviceStatus
   195  	return json.Marshal(sNoMethods(s))
   196  }
   197  
   198  func (s serviceStatus) GetYAML() (tag string, value interface{}) {
   199  	if s.Err != nil {
   200  		return "", errorStatus{s.Err.Error()}
   201  	}
   202  	type sNoMethods serviceStatus
   203  	return "", sNoMethods(s)
   204  }
   205  
   206  type unitStatus struct {
   207  	Err            error                 `json:"-" yaml:",omitempty"`
   208  	Charm          string                `json:"upgrading-from,omitempty" yaml:"upgrading-from,omitempty"`
   209  	AgentState     params.Status         `json:"agent-state,omitempty" yaml:"agent-state,omitempty"`
   210  	AgentStateInfo string                `json:"agent-state-info,omitempty" yaml:"agent-state-info,omitempty"`
   211  	AgentVersion   string                `json:"agent-version,omitempty" yaml:"agent-version,omitempty"`
   212  	Life           string                `json:"life,omitempty" yaml:"life,omitempty"`
   213  	Machine        string                `json:"machine,omitempty" yaml:"machine,omitempty"`
   214  	OpenedPorts    []string              `json:"open-ports,omitempty" yaml:"open-ports,omitempty"`
   215  	PublicAddress  string                `json:"public-address,omitempty" yaml:"public-address,omitempty"`
   216  	Subordinates   map[string]unitStatus `json:"subordinates,omitempty" yaml:"subordinates,omitempty"`
   217  }
   218  
   219  type unitStatusNoMarshal unitStatus
   220  
   221  func (s unitStatus) MarshalJSON() ([]byte, error) {
   222  	if s.Err != nil {
   223  		return json.Marshal(errorStatus{s.Err.Error()})
   224  	}
   225  	return json.Marshal(unitStatusNoMarshal(s))
   226  }
   227  
   228  func (s unitStatus) GetYAML() (tag string, value interface{}) {
   229  	if s.Err != nil {
   230  		return "", errorStatus{s.Err.Error()}
   231  	}
   232  	type uNoMethods unitStatus
   233  	return "", unitStatusNoMarshal(s)
   234  }
   235  
   236  type networkStatus struct {
   237  	Err        error      `json:"-" yaml:",omitempty"`
   238  	ProviderId network.Id `json:"provider-id" yaml:"provider-id"`
   239  	CIDR       string     `json:"cidr,omitempty" yaml:"cidr,omitempty"`
   240  	VLANTag    int        `json:"vlan-tag,omitempty" yaml:"vlan-tag,omitempty"`
   241  }
   242  
   243  type networkStatusNoMarshal networkStatus
   244  
   245  func (n networkStatus) MarshalJSON() ([]byte, error) {
   246  	if n.Err != nil {
   247  		return json.Marshal(errorStatus{n.Err.Error()})
   248  	}
   249  	type nNoMethods networkStatus
   250  	return json.Marshal(nNoMethods(n))
   251  }
   252  
   253  func (n networkStatus) GetYAML() (tag string, value interface{}) {
   254  	if n.Err != nil {
   255  		return "", errorStatus{n.Err.Error()}
   256  	}
   257  	type nNoMethods networkStatus
   258  	return "", nNoMethods(n)
   259  }
   260  
   261  type statusFormatter struct {
   262  	status    *api.Status
   263  	relations map[int]api.RelationStatus
   264  }
   265  
   266  func newStatusFormatter(status *api.Status) *statusFormatter {
   267  	sf := statusFormatter{
   268  		status:    status,
   269  		relations: make(map[int]api.RelationStatus),
   270  	}
   271  	for _, relation := range status.Relations {
   272  		sf.relations[relation.Id] = relation
   273  	}
   274  	return &sf
   275  }
   276  
   277  func (sf *statusFormatter) format() formattedStatus {
   278  	if sf.status == nil {
   279  		return formattedStatus{}
   280  	}
   281  	out := formattedStatus{
   282  		Environment: sf.status.EnvironmentName,
   283  		Machines:    make(map[string]machineStatus),
   284  		Services:    make(map[string]serviceStatus),
   285  	}
   286  	for k, m := range sf.status.Machines {
   287  		out.Machines[k] = sf.formatMachine(m)
   288  	}
   289  	for sn, s := range sf.status.Services {
   290  		out.Services[sn] = sf.formatService(sn, s)
   291  	}
   292  	for k, n := range sf.status.Networks {
   293  		if out.Networks == nil {
   294  			out.Networks = make(map[string]networkStatus)
   295  		}
   296  		out.Networks[k] = sf.formatNetwork(n)
   297  	}
   298  	return out
   299  }
   300  
   301  func (sf *statusFormatter) formatMachine(machine api.MachineStatus) machineStatus {
   302  	var out machineStatus
   303  
   304  	if machine.Agent.Status == "" {
   305  		// Older server
   306  		// TODO: this will go away at some point (v1.21?).
   307  		out = machineStatus{
   308  			AgentState:     machine.AgentState,
   309  			AgentStateInfo: machine.AgentStateInfo,
   310  			AgentVersion:   machine.AgentVersion,
   311  			Life:           machine.Life,
   312  			Err:            machine.Err,
   313  			DNSName:        machine.DNSName,
   314  			InstanceId:     machine.InstanceId,
   315  			InstanceState:  machine.InstanceState,
   316  			Series:         machine.Series,
   317  			Id:             machine.Id,
   318  			Containers:     make(map[string]machineStatus),
   319  			Hardware:       machine.Hardware,
   320  		}
   321  	} else {
   322  		// New server
   323  		agent := machine.Agent
   324  		out = machineStatus{
   325  			AgentState:     machine.AgentState,
   326  			AgentStateInfo: adjustInfoIfAgentDown(machine.AgentState, agent.Status, agent.Info),
   327  			AgentVersion:   agent.Version,
   328  			Life:           agent.Life,
   329  			Err:            agent.Err,
   330  			DNSName:        machine.DNSName,
   331  			InstanceId:     machine.InstanceId,
   332  			InstanceState:  machine.InstanceState,
   333  			Series:         machine.Series,
   334  			Id:             machine.Id,
   335  			Containers:     make(map[string]machineStatus),
   336  			Hardware:       machine.Hardware,
   337  		}
   338  	}
   339  
   340  	for k, m := range machine.Containers {
   341  		out.Containers[k] = sf.formatMachine(m)
   342  	}
   343  
   344  	for _, job := range machine.Jobs {
   345  		if job == multiwatcher.JobManageEnviron {
   346  			out.HAStatus = makeHAStatus(machine.HasVote, machine.WantsVote)
   347  			break
   348  		}
   349  	}
   350  	return out
   351  }
   352  
   353  func (sf *statusFormatter) formatService(name string, service api.ServiceStatus) serviceStatus {
   354  	out := serviceStatus{
   355  		Err:           service.Err,
   356  		Charm:         service.Charm,
   357  		Exposed:       service.Exposed,
   358  		Life:          service.Life,
   359  		Relations:     service.Relations,
   360  		Networks:      make(map[string][]string),
   361  		CanUpgradeTo:  service.CanUpgradeTo,
   362  		SubordinateTo: service.SubordinateTo,
   363  		Units:         make(map[string]unitStatus),
   364  	}
   365  	if len(service.Networks.Enabled) > 0 {
   366  		out.Networks["enabled"] = service.Networks.Enabled
   367  	}
   368  	if len(service.Networks.Disabled) > 0 {
   369  		out.Networks["disabled"] = service.Networks.Disabled
   370  	}
   371  	for k, m := range service.Units {
   372  		out.Units[k] = sf.formatUnit(m, name)
   373  	}
   374  	return out
   375  }
   376  
   377  func (sf *statusFormatter) formatUnit(unit api.UnitStatus, serviceName string) unitStatus {
   378  	out := unitStatus{
   379  		Err:            unit.Err,
   380  		AgentState:     unit.AgentState,
   381  		AgentStateInfo: sf.getUnitStatusInfo(unit, serviceName),
   382  		AgentVersion:   unit.AgentVersion,
   383  		Life:           unit.Life,
   384  		Machine:        unit.Machine,
   385  		OpenedPorts:    unit.OpenedPorts,
   386  		PublicAddress:  unit.PublicAddress,
   387  		Charm:          unit.Charm,
   388  		Subordinates:   make(map[string]unitStatus),
   389  	}
   390  	for k, m := range unit.Subordinates {
   391  		out.Subordinates[k] = sf.formatUnit(m, serviceName)
   392  	}
   393  	return out
   394  }
   395  
   396  func (sf *statusFormatter) getUnitStatusInfo(unit api.UnitStatus, serviceName string) string {
   397  	if unit.Agent.Status == "" {
   398  		// Old server that doesn't support this field and others.
   399  		// Just return the info string as-is.
   400  		return unit.AgentStateInfo
   401  	}
   402  	statusInfo := unit.Agent.Info
   403  	if unit.Agent.Status == params.StatusError {
   404  		if relation, ok := sf.relations[getRelationIdFromData(unit)]; ok {
   405  			// Append the details of the other endpoint on to the status info string.
   406  			if ep, ok := findOtherEndpoint(relation.Endpoints, serviceName); ok {
   407  				statusInfo = statusInfo + " for " + ep.String()
   408  			}
   409  		}
   410  	}
   411  	return adjustInfoIfAgentDown(unit.AgentState, unit.Agent.Status, statusInfo)
   412  }
   413  
   414  func (sf *statusFormatter) formatNetwork(network api.NetworkStatus) networkStatus {
   415  	return networkStatus{
   416  		Err:        network.Err,
   417  		ProviderId: network.ProviderId,
   418  		CIDR:       network.CIDR,
   419  		VLANTag:    network.VLANTag,
   420  	}
   421  }
   422  
   423  func makeHAStatus(hasVote, wantsVote bool) string {
   424  	var s string
   425  	switch {
   426  	case hasVote && wantsVote:
   427  		s = "has-vote"
   428  	case hasVote && !wantsVote:
   429  		s = "removing-vote"
   430  	case !hasVote && wantsVote:
   431  		s = "adding-vote"
   432  	case !hasVote && !wantsVote:
   433  		s = "no-vote"
   434  	}
   435  	return s
   436  }
   437  
   438  func getRelationIdFromData(unit api.UnitStatus) int {
   439  	if relationId_, ok := unit.Agent.Data["relation-id"]; ok {
   440  		if relationId, ok := relationId_.(float64); ok {
   441  			return int(relationId)
   442  		} else {
   443  			logger.Infof("relation-id found status data but was unexpected "+
   444  				"type: %q. Status output may be lacking some detail.", relationId_)
   445  		}
   446  	}
   447  	return -1
   448  }
   449  
   450  // findOtherEndpoint searches the provided endpoints for an endpoint
   451  // that *doesn't* match serviceName. The returned bool indicates if
   452  // such an endpoint was found.
   453  func findOtherEndpoint(endpoints []api.EndpointStatus, serviceName string) (api.EndpointStatus, bool) {
   454  	for _, endpoint := range endpoints {
   455  		if endpoint.ServiceName != serviceName {
   456  			return endpoint, true
   457  		}
   458  	}
   459  	return api.EndpointStatus{}, false
   460  }
   461  
   462  // adjustInfoIfAgentDown modifies the agent status info string if the
   463  // agent is down. The original status and info is included in
   464  // parentheses.
   465  func adjustInfoIfAgentDown(status, origStatus params.Status, info string) string {
   466  	if status == params.StatusDown || status == params.StatusFailed {
   467  		if info == "" {
   468  			return fmt.Sprintf("(%s)", origStatus)
   469  		}
   470  		return fmt.Sprintf("(%s: %s)", origStatus, info)
   471  	}
   472  	return info
   473  }