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