github.com/Pankov404/juju@v0.0.0-20150703034450-be266991dceb/cmd/juju/commands/status.go (about)

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