github.com/wallyworld/juju@v0.0.0-20161013125918-6cf1bc9d917a/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  	"strings"
     8  
     9  	"github.com/juju/utils/series"
    10  	"gopkg.in/juju/charm.v6-unstable"
    11  	"gopkg.in/juju/names.v2"
    12  
    13  	"github.com/juju/juju/apiserver/params"
    14  	"github.com/juju/juju/cmd/juju/common"
    15  	"github.com/juju/juju/state/multiwatcher"
    16  	"github.com/juju/juju/status"
    17  )
    18  
    19  type statusFormatter struct {
    20  	status         *params.FullStatus
    21  	controllerName string
    22  	relations      map[int]params.RelationStatus
    23  	isoTime        bool
    24  }
    25  
    26  // NewStatusFormatter takes stored model information (params.FullStatus) and populates
    27  // the statusFormatter struct used in various status formatting methods
    28  func NewStatusFormatter(status *params.FullStatus, isoTime bool) *statusFormatter {
    29  	return newStatusFormatter(status, "", isoTime)
    30  }
    31  
    32  func newStatusFormatter(status *params.FullStatus, controllerName string, isoTime bool) *statusFormatter {
    33  	sf := statusFormatter{
    34  		status:         status,
    35  		controllerName: controllerName,
    36  		relations:      make(map[int]params.RelationStatus),
    37  		isoTime:        isoTime,
    38  	}
    39  	for _, relation := range status.Relations {
    40  		sf.relations[relation.Id] = relation
    41  	}
    42  	return &sf
    43  }
    44  
    45  func (sf *statusFormatter) format() (formattedStatus, error) {
    46  	if sf.status == nil {
    47  		return formattedStatus{}, nil
    48  	}
    49  	cloudTag, err := names.ParseCloudTag(sf.status.Model.CloudTag)
    50  	if err != nil {
    51  		return formattedStatus{}, err
    52  	}
    53  	out := formattedStatus{
    54  		Model: modelStatus{
    55  			Name:             sf.status.Model.Name,
    56  			Controller:       sf.controllerName,
    57  			Cloud:            cloudTag.Id(),
    58  			CloudRegion:      sf.status.Model.CloudRegion,
    59  			Version:          sf.status.Model.Version,
    60  			AvailableVersion: sf.status.Model.AvailableVersion,
    61  			Migration:        sf.status.Model.Migration,
    62  		},
    63  		Machines:     make(map[string]machineStatus),
    64  		Applications: make(map[string]applicationStatus),
    65  	}
    66  	for k, m := range sf.status.Machines {
    67  		out.Machines[k] = sf.formatMachine(m)
    68  	}
    69  	for sn, s := range sf.status.Applications {
    70  		out.Applications[sn] = sf.formatApplication(sn, s)
    71  	}
    72  	return out, nil
    73  }
    74  
    75  // MachineFormat takes stored model information (params.FullStatus) and formats machine status info.
    76  func (sf *statusFormatter) MachineFormat(machineId []string) formattedMachineStatus {
    77  	if sf.status == nil {
    78  		return formattedMachineStatus{}
    79  	}
    80  	out := formattedMachineStatus{
    81  		Model:    sf.status.Model.Name,
    82  		Machines: make(map[string]machineStatus),
    83  	}
    84  	for k, m := range sf.status.Machines {
    85  		if len(machineId) != 0 {
    86  			for i := 0; i < len(machineId); i++ {
    87  				if m.Id == machineId[i] {
    88  					out.Machines[k] = sf.formatMachine(m)
    89  				}
    90  			}
    91  		} else {
    92  			out.Machines[k] = sf.formatMachine(m)
    93  		}
    94  	}
    95  	return out
    96  }
    97  
    98  func (sf *statusFormatter) formatMachine(machine params.MachineStatus) machineStatus {
    99  	var out machineStatus
   100  
   101  	out = machineStatus{
   102  		JujuStatus:    sf.getStatusInfoContents(machine.AgentStatus),
   103  		DNSName:       machine.DNSName,
   104  		IPAddresses:   machine.IPAddresses,
   105  		InstanceId:    machine.InstanceId,
   106  		MachineStatus: sf.getStatusInfoContents(machine.InstanceStatus),
   107  		Series:        machine.Series,
   108  		Id:            machine.Id,
   109  		Containers:    make(map[string]machineStatus),
   110  		Hardware:      machine.Hardware,
   111  	}
   112  
   113  	for k, m := range machine.Containers {
   114  		out.Containers[k] = sf.formatMachine(m)
   115  	}
   116  
   117  	for _, job := range machine.Jobs {
   118  		if job == multiwatcher.JobManageModel {
   119  			out.HAStatus = makeHAStatus(machine.HasVote, machine.WantsVote)
   120  			break
   121  		}
   122  	}
   123  	return out
   124  }
   125  
   126  func (sf *statusFormatter) formatApplication(name string, application params.ApplicationStatus) applicationStatus {
   127  	appOS, _ := series.GetOSFromSeries(application.Series)
   128  	var (
   129  		charmOrigin = ""
   130  		charmName   = ""
   131  		charmRev    = 0
   132  	)
   133  	if curl, err := charm.ParseURL(application.Charm); err != nil {
   134  		// We should never fail to parse a charm url sent back
   135  		// but if we do, don't crash.
   136  		logger.Errorf("failed to parse charm: %v", err)
   137  	} else {
   138  		switch curl.Schema {
   139  		case "cs":
   140  			charmOrigin = "jujucharms"
   141  		case "local":
   142  			charmOrigin = "local"
   143  		default:
   144  			charmOrigin = "unknown"
   145  		}
   146  		charmName = curl.Name
   147  		charmRev = curl.Revision
   148  	}
   149  
   150  	out := applicationStatus{
   151  		Err:           application.Err,
   152  		Charm:         application.Charm,
   153  		Series:        application.Series,
   154  		OS:            strings.ToLower(appOS.String()),
   155  		CharmOrigin:   charmOrigin,
   156  		CharmName:     charmName,
   157  		CharmRev:      charmRev,
   158  		Exposed:       application.Exposed,
   159  		Life:          application.Life,
   160  		Relations:     application.Relations,
   161  		CanUpgradeTo:  application.CanUpgradeTo,
   162  		SubordinateTo: application.SubordinateTo,
   163  		Units:         make(map[string]unitStatus),
   164  		StatusInfo:    sf.getServiceStatusInfo(application),
   165  		Version:       application.WorkloadVersion,
   166  	}
   167  	for k, m := range application.Units {
   168  		out.Units[k] = sf.formatUnit(unitFormatInfo{
   169  			unit:            m,
   170  			unitName:        k,
   171  			applicationName: name,
   172  			meterStatuses:   application.MeterStatuses,
   173  		})
   174  	}
   175  	return out
   176  }
   177  
   178  func (sf *statusFormatter) getServiceStatusInfo(service params.ApplicationStatus) statusInfoContents {
   179  	// TODO(perrito66) add status validation.
   180  	info := statusInfoContents{
   181  		Err:     service.Status.Err,
   182  		Current: status.Status(service.Status.Status),
   183  		Message: service.Status.Info,
   184  		Version: service.Status.Version,
   185  	}
   186  	if service.Status.Since != nil {
   187  		info.Since = common.FormatTime(service.Status.Since, sf.isoTime)
   188  	}
   189  	return info
   190  }
   191  
   192  type unitFormatInfo struct {
   193  	unit            params.UnitStatus
   194  	unitName        string
   195  	applicationName string
   196  	meterStatuses   map[string]params.MeterStatus
   197  }
   198  
   199  func (sf *statusFormatter) formatUnit(info unitFormatInfo) unitStatus {
   200  	// TODO(Wallyworld) - this should be server side but we still need to support older servers.
   201  	sf.updateUnitStatusInfo(&info.unit, info.applicationName)
   202  
   203  	out := unitStatus{
   204  		WorkloadStatusInfo: sf.getWorkloadStatusInfo(info.unit),
   205  		JujuStatusInfo:     sf.getAgentStatusInfo(info.unit),
   206  		Machine:            info.unit.Machine,
   207  		OpenedPorts:        info.unit.OpenedPorts,
   208  		PublicAddress:      info.unit.PublicAddress,
   209  		Charm:              info.unit.Charm,
   210  		Subordinates:       make(map[string]unitStatus),
   211  		Leader:             info.unit.Leader,
   212  	}
   213  
   214  	if ms, ok := info.meterStatuses[info.unitName]; ok {
   215  		out.MeterStatus = &meterStatus{
   216  			Color:   ms.Color,
   217  			Message: ms.Message,
   218  		}
   219  	}
   220  
   221  	for k, m := range info.unit.Subordinates {
   222  		out.Subordinates[k] = sf.formatUnit(unitFormatInfo{
   223  			unit:            m,
   224  			unitName:        k,
   225  			applicationName: info.applicationName,
   226  			meterStatuses:   info.meterStatuses,
   227  		})
   228  	}
   229  	return out
   230  }
   231  
   232  func (sf *statusFormatter) getStatusInfoContents(inst params.DetailedStatus) statusInfoContents {
   233  	// TODO(perrito66) add status validation.
   234  	info := statusInfoContents{
   235  		Err:     inst.Err,
   236  		Current: status.Status(inst.Status),
   237  		Message: inst.Info,
   238  		Version: inst.Version,
   239  		Life:    inst.Life,
   240  	}
   241  	if inst.Since != nil {
   242  		info.Since = common.FormatTime(inst.Since, sf.isoTime)
   243  	}
   244  	return info
   245  }
   246  
   247  func (sf *statusFormatter) getWorkloadStatusInfo(unit params.UnitStatus) statusInfoContents {
   248  	// TODO(perrito66) add status validation.
   249  	info := statusInfoContents{
   250  		Err:     unit.WorkloadStatus.Err,
   251  		Current: status.Status(unit.WorkloadStatus.Status),
   252  		Message: unit.WorkloadStatus.Info,
   253  		Version: unit.WorkloadStatus.Version,
   254  	}
   255  	if unit.WorkloadStatus.Since != nil {
   256  		info.Since = common.FormatTime(unit.WorkloadStatus.Since, sf.isoTime)
   257  	}
   258  	return info
   259  }
   260  
   261  func (sf *statusFormatter) getAgentStatusInfo(unit params.UnitStatus) statusInfoContents {
   262  	// TODO(perrito66) add status validation.
   263  	info := statusInfoContents{
   264  		Err:     unit.AgentStatus.Err,
   265  		Current: status.Status(unit.AgentStatus.Status),
   266  		Message: unit.AgentStatus.Info,
   267  		Version: unit.AgentStatus.Version,
   268  	}
   269  	if unit.AgentStatus.Since != nil {
   270  		info.Since = common.FormatTime(unit.AgentStatus.Since, sf.isoTime)
   271  	}
   272  	return info
   273  }
   274  
   275  func (sf *statusFormatter) updateUnitStatusInfo(unit *params.UnitStatus, applicationName string) {
   276  	// TODO(perrito66) add status validation.
   277  	if status.Status(unit.WorkloadStatus.Status) == status.Error {
   278  		if relation, ok := sf.relations[getRelationIdFromData(unit)]; ok {
   279  			// Append the details of the other endpoint on to the status info string.
   280  			if ep, ok := findOtherEndpoint(relation.Endpoints, applicationName); ok {
   281  				unit.WorkloadStatus.Info = unit.WorkloadStatus.Info + " for " + ep.String()
   282  			}
   283  		}
   284  	}
   285  }
   286  
   287  func makeHAStatus(hasVote, wantsVote bool) string {
   288  	var s string
   289  	switch {
   290  	case hasVote && wantsVote:
   291  		s = "has-vote"
   292  	case hasVote && !wantsVote:
   293  		s = "removing-vote"
   294  	case !hasVote && wantsVote:
   295  		s = "adding-vote"
   296  	case !hasVote && !wantsVote:
   297  		s = "no-vote"
   298  	}
   299  	return s
   300  }
   301  
   302  func getRelationIdFromData(unit *params.UnitStatus) int {
   303  	if relationId_, ok := unit.WorkloadStatus.Data["relation-id"]; ok {
   304  		if relationId, ok := relationId_.(float64); ok {
   305  			return int(relationId)
   306  		} else {
   307  			logger.Infof("relation-id found status data but was unexpected "+
   308  				"type: %q. Status output may be lacking some detail.", relationId_)
   309  		}
   310  	}
   311  	return -1
   312  }
   313  
   314  // findOtherEndpoint searches the provided endpoints for an endpoint
   315  // that *doesn't* match applicationName. The returned bool indicates if
   316  // such an endpoint was found.
   317  func findOtherEndpoint(endpoints []params.EndpointStatus, applicationName string) (params.EndpointStatus, bool) {
   318  	for _, endpoint := range endpoints {
   319  		if endpoint.ApplicationName != applicationName {
   320  			return endpoint, true
   321  		}
   322  	}
   323  	return params.EndpointStatus{}, false
   324  }