github.com/mhilton/juju-juju@v0.0.0-20150901100907-a94dd2c73455/cmd/juju/status/formatter.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  	"fmt"
     8  
     9  	"github.com/juju/juju/apiserver/params"
    10  	"github.com/juju/juju/cmd/juju/common"
    11  	"github.com/juju/juju/state/multiwatcher"
    12  )
    13  
    14  type statusFormatter struct {
    15  	status        *params.FullStatus
    16  	relations     map[int]params.RelationStatus
    17  	isoTime       bool
    18  	compatVersion int
    19  }
    20  
    21  func newStatusFormatter(status *params.FullStatus, compatVersion int, isoTime bool) *statusFormatter {
    22  	sf := statusFormatter{
    23  		status:        status,
    24  		relations:     make(map[int]params.RelationStatus),
    25  		compatVersion: compatVersion,
    26  		isoTime:       isoTime,
    27  	}
    28  	for _, relation := range status.Relations {
    29  		sf.relations[relation.Id] = relation
    30  	}
    31  	return &sf
    32  }
    33  
    34  func (sf *statusFormatter) format() formattedStatus {
    35  	if sf.status == nil {
    36  		return formattedStatus{}
    37  	}
    38  	out := formattedStatus{
    39  		Environment: sf.status.EnvironmentName,
    40  		Machines:    make(map[string]machineStatus),
    41  		Services:    make(map[string]serviceStatus),
    42  	}
    43  	for k, m := range sf.status.Machines {
    44  		out.Machines[k] = sf.formatMachine(m)
    45  	}
    46  	for sn, s := range sf.status.Services {
    47  		out.Services[sn] = sf.formatService(sn, s)
    48  	}
    49  	for k, n := range sf.status.Networks {
    50  		if out.Networks == nil {
    51  			out.Networks = make(map[string]networkStatus)
    52  		}
    53  		out.Networks[k] = sf.formatNetwork(n)
    54  	}
    55  	return out
    56  }
    57  
    58  func (sf *statusFormatter) formatMachine(machine params.MachineStatus) machineStatus {
    59  	var out machineStatus
    60  
    61  	if machine.Agent.Status == "" {
    62  		// Older server
    63  		// TODO: this will go away at some point (v1.21?).
    64  		out = machineStatus{
    65  			AgentState:     machine.AgentState,
    66  			AgentStateInfo: machine.AgentStateInfo,
    67  			AgentVersion:   machine.AgentVersion,
    68  			Life:           machine.Life,
    69  			Err:            machine.Err,
    70  			DNSName:        machine.DNSName,
    71  			InstanceId:     machine.InstanceId,
    72  			InstanceState:  machine.InstanceState,
    73  			Series:         machine.Series,
    74  			Id:             machine.Id,
    75  			Containers:     make(map[string]machineStatus),
    76  			Hardware:       machine.Hardware,
    77  		}
    78  	} else {
    79  		// New server
    80  		agent := machine.Agent
    81  		out = machineStatus{
    82  			AgentState:     machine.AgentState,
    83  			AgentStateInfo: adjustInfoIfMachineAgentDown(machine.AgentState, agent.Status, agent.Info),
    84  			AgentVersion:   agent.Version,
    85  			Life:           agent.Life,
    86  			Err:            agent.Err,
    87  			DNSName:        machine.DNSName,
    88  			InstanceId:     machine.InstanceId,
    89  			InstanceState:  machine.InstanceState,
    90  			Series:         machine.Series,
    91  			Id:             machine.Id,
    92  			Containers:     make(map[string]machineStatus),
    93  			Hardware:       machine.Hardware,
    94  		}
    95  	}
    96  
    97  	for k, m := range machine.Containers {
    98  		out.Containers[k] = sf.formatMachine(m)
    99  	}
   100  
   101  	for _, job := range machine.Jobs {
   102  		if job == multiwatcher.JobManageEnviron {
   103  			out.HAStatus = makeHAStatus(machine.HasVote, machine.WantsVote)
   104  			break
   105  		}
   106  	}
   107  	return out
   108  }
   109  
   110  func (sf *statusFormatter) formatService(name string, service params.ServiceStatus) serviceStatus {
   111  	out := serviceStatus{
   112  		Err:           service.Err,
   113  		Charm:         service.Charm,
   114  		Exposed:       service.Exposed,
   115  		Life:          service.Life,
   116  		Relations:     service.Relations,
   117  		Networks:      make(map[string][]string),
   118  		CanUpgradeTo:  service.CanUpgradeTo,
   119  		SubordinateTo: service.SubordinateTo,
   120  		Units:         make(map[string]unitStatus),
   121  		StatusInfo:    sf.getServiceStatusInfo(service),
   122  	}
   123  	if len(service.Networks.Enabled) > 0 {
   124  		out.Networks["enabled"] = service.Networks.Enabled
   125  	}
   126  	if len(service.Networks.Disabled) > 0 {
   127  		out.Networks["disabled"] = service.Networks.Disabled
   128  	}
   129  	for k, m := range service.Units {
   130  		out.Units[k] = sf.formatUnit(unitFormatInfo{
   131  			unit:          m,
   132  			unitName:      k,
   133  			serviceName:   name,
   134  			meterStatuses: service.MeterStatuses,
   135  		})
   136  	}
   137  	return out
   138  }
   139  
   140  func (sf *statusFormatter) getServiceStatusInfo(service params.ServiceStatus) statusInfoContents {
   141  	info := statusInfoContents{
   142  		Err:     service.Status.Err,
   143  		Current: service.Status.Status,
   144  		Message: service.Status.Info,
   145  		Version: service.Status.Version,
   146  	}
   147  	if service.Status.Since != nil {
   148  		info.Since = common.FormatTime(service.Status.Since, sf.isoTime)
   149  	}
   150  	return info
   151  }
   152  
   153  type unitFormatInfo struct {
   154  	unit          params.UnitStatus
   155  	unitName      string
   156  	serviceName   string
   157  	meterStatuses map[string]params.MeterStatus
   158  }
   159  
   160  func (sf *statusFormatter) formatUnit(info unitFormatInfo) unitStatus {
   161  	// TODO(Wallyworld) - this should be server side but we still need to support older servers.
   162  	sf.updateUnitStatusInfo(&info.unit, info.serviceName)
   163  
   164  	out := unitStatus{
   165  		WorkloadStatusInfo: sf.getWorkloadStatusInfo(info.unit),
   166  		AgentStatusInfo:    sf.getAgentStatusInfo(info.unit),
   167  		Machine:            info.unit.Machine,
   168  		OpenedPorts:        info.unit.OpenedPorts,
   169  		PublicAddress:      info.unit.PublicAddress,
   170  		Charm:              info.unit.Charm,
   171  		Subordinates:       make(map[string]unitStatus),
   172  	}
   173  
   174  	if ms, ok := info.meterStatuses[info.unitName]; ok {
   175  		out.MeterStatus = &meterStatus{
   176  			Color:   ms.Color,
   177  			Message: ms.Message,
   178  		}
   179  	}
   180  
   181  	// These legacy fields will be dropped for Juju 2.0.
   182  	if sf.compatVersion < 2 || out.AgentStatusInfo.Current == "" {
   183  		out.Err = info.unit.Err
   184  		out.AgentState = info.unit.AgentState
   185  		out.AgentStateInfo = info.unit.AgentStateInfo
   186  		out.Life = info.unit.Life
   187  		out.AgentVersion = info.unit.AgentVersion
   188  	}
   189  
   190  	for k, m := range info.unit.Subordinates {
   191  		out.Subordinates[k] = sf.formatUnit(unitFormatInfo{
   192  			unit:          m,
   193  			unitName:      k,
   194  			serviceName:   info.serviceName,
   195  			meterStatuses: info.meterStatuses,
   196  		})
   197  	}
   198  	return out
   199  }
   200  
   201  func (sf *statusFormatter) getWorkloadStatusInfo(unit params.UnitStatus) statusInfoContents {
   202  	info := statusInfoContents{
   203  		Err:     unit.Workload.Err,
   204  		Current: unit.Workload.Status,
   205  		Message: unit.Workload.Info,
   206  		Version: unit.Workload.Version,
   207  	}
   208  	if unit.Workload.Since != nil {
   209  		info.Since = common.FormatTime(unit.Workload.Since, sf.isoTime)
   210  	}
   211  	return info
   212  }
   213  
   214  func (sf *statusFormatter) getAgentStatusInfo(unit params.UnitStatus) statusInfoContents {
   215  	info := statusInfoContents{
   216  		Err:     unit.UnitAgent.Err,
   217  		Current: unit.UnitAgent.Status,
   218  		Message: unit.UnitAgent.Info,
   219  		Version: unit.UnitAgent.Version,
   220  	}
   221  	if unit.UnitAgent.Since != nil {
   222  		info.Since = common.FormatTime(unit.UnitAgent.Since, sf.isoTime)
   223  	}
   224  	return info
   225  }
   226  
   227  func (sf *statusFormatter) updateUnitStatusInfo(unit *params.UnitStatus, serviceName string) {
   228  	// This logic has no business here but can't be moved until Juju 2.0.
   229  	statusInfo := unit.Workload.Info
   230  	if unit.Workload.Status == "" {
   231  		// Old server that doesn't support this field and others.
   232  		// Just use the info string as-is.
   233  		statusInfo = unit.AgentStateInfo
   234  	}
   235  	if unit.Workload.Status == params.StatusError {
   236  		if relation, ok := sf.relations[getRelationIdFromData(unit)]; ok {
   237  			// Append the details of the other endpoint on to the status info string.
   238  			if ep, ok := findOtherEndpoint(relation.Endpoints, serviceName); ok {
   239  				unit.Workload.Info = statusInfo + " for " + ep.String()
   240  				unit.AgentStateInfo = unit.Workload.Info
   241  			}
   242  		}
   243  	}
   244  }
   245  
   246  func (sf *statusFormatter) formatNetwork(network params.NetworkStatus) networkStatus {
   247  	return networkStatus{
   248  		Err:        network.Err,
   249  		ProviderId: network.ProviderId,
   250  		CIDR:       network.CIDR,
   251  		VLANTag:    network.VLANTag,
   252  	}
   253  }
   254  
   255  func makeHAStatus(hasVote, wantsVote bool) string {
   256  	var s string
   257  	switch {
   258  	case hasVote && wantsVote:
   259  		s = "has-vote"
   260  	case hasVote && !wantsVote:
   261  		s = "removing-vote"
   262  	case !hasVote && wantsVote:
   263  		s = "adding-vote"
   264  	case !hasVote && !wantsVote:
   265  		s = "no-vote"
   266  	}
   267  	return s
   268  }
   269  
   270  func getRelationIdFromData(unit *params.UnitStatus) int {
   271  	if relationId_, ok := unit.Workload.Data["relation-id"]; ok {
   272  		if relationId, ok := relationId_.(float64); ok {
   273  			return int(relationId)
   274  		} else {
   275  			logger.Infof("relation-id found status data but was unexpected "+
   276  				"type: %q. Status output may be lacking some detail.", relationId_)
   277  		}
   278  	}
   279  	return -1
   280  }
   281  
   282  // findOtherEndpoint searches the provided endpoints for an endpoint
   283  // that *doesn't* match serviceName. The returned bool indicates if
   284  // such an endpoint was found.
   285  func findOtherEndpoint(endpoints []params.EndpointStatus, serviceName string) (params.EndpointStatus, bool) {
   286  	for _, endpoint := range endpoints {
   287  		if endpoint.ServiceName != serviceName {
   288  			return endpoint, true
   289  		}
   290  	}
   291  	return params.EndpointStatus{}, false
   292  }
   293  
   294  // adjustInfoIfMachineAgentDown modifies the agent status info string if the
   295  // agent is down. The original status and info is included in
   296  // parentheses.
   297  func adjustInfoIfMachineAgentDown(status, origStatus params.Status, info string) string {
   298  	if status == params.StatusDown {
   299  		if info == "" {
   300  			return fmt.Sprintf("(%s)", origStatus)
   301  		}
   302  		return fmt.Sprintf("(%s: %s)", origStatus, info)
   303  	}
   304  	return info
   305  }