github.com/axw/juju@v0.0.0-20161005053422-4bd6544d08d4/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  		InstanceId:    machine.InstanceId,
   105  		MachineStatus: sf.getStatusInfoContents(machine.InstanceStatus),
   106  		Series:        machine.Series,
   107  		Id:            machine.Id,
   108  		Containers:    make(map[string]machineStatus),
   109  		Hardware:      machine.Hardware,
   110  	}
   111  
   112  	for k, m := range machine.Containers {
   113  		out.Containers[k] = sf.formatMachine(m)
   114  	}
   115  
   116  	for _, job := range machine.Jobs {
   117  		if job == multiwatcher.JobManageModel {
   118  			out.HAStatus = makeHAStatus(machine.HasVote, machine.WantsVote)
   119  			break
   120  		}
   121  	}
   122  	return out
   123  }
   124  
   125  func (sf *statusFormatter) formatApplication(name string, application params.ApplicationStatus) applicationStatus {
   126  	appOS, _ := series.GetOSFromSeries(application.Series)
   127  	var (
   128  		charmOrigin = ""
   129  		charmName   = ""
   130  		charmRev    = 0
   131  	)
   132  	if curl, err := charm.ParseURL(application.Charm); err != nil {
   133  		// We should never fail to parse a charm url sent back
   134  		// but if we do, don't crash.
   135  		logger.Errorf("failed to parse charm: %v", err)
   136  	} else {
   137  		switch curl.Schema {
   138  		case "cs":
   139  			charmOrigin = "jujucharms"
   140  		case "local":
   141  			charmOrigin = "local"
   142  		default:
   143  			charmOrigin = "unknown"
   144  		}
   145  		charmName = curl.Name
   146  		charmRev = curl.Revision
   147  	}
   148  
   149  	out := applicationStatus{
   150  		Err:           application.Err,
   151  		Charm:         application.Charm,
   152  		Series:        application.Series,
   153  		OS:            strings.ToLower(appOS.String()),
   154  		CharmOrigin:   charmOrigin,
   155  		CharmName:     charmName,
   156  		CharmRev:      charmRev,
   157  		Exposed:       application.Exposed,
   158  		Life:          application.Life,
   159  		Relations:     application.Relations,
   160  		CanUpgradeTo:  application.CanUpgradeTo,
   161  		SubordinateTo: application.SubordinateTo,
   162  		Units:         make(map[string]unitStatus),
   163  		StatusInfo:    sf.getServiceStatusInfo(application),
   164  		Version:       application.WorkloadVersion,
   165  	}
   166  	for k, m := range application.Units {
   167  		out.Units[k] = sf.formatUnit(unitFormatInfo{
   168  			unit:            m,
   169  			unitName:        k,
   170  			applicationName: name,
   171  			meterStatuses:   application.MeterStatuses,
   172  		})
   173  	}
   174  	return out
   175  }
   176  
   177  func (sf *statusFormatter) getServiceStatusInfo(service params.ApplicationStatus) statusInfoContents {
   178  	// TODO(perrito66) add status validation.
   179  	info := statusInfoContents{
   180  		Err:     service.Status.Err,
   181  		Current: status.Status(service.Status.Status),
   182  		Message: service.Status.Info,
   183  		Version: service.Status.Version,
   184  	}
   185  	if service.Status.Since != nil {
   186  		info.Since = common.FormatTime(service.Status.Since, sf.isoTime)
   187  	}
   188  	return info
   189  }
   190  
   191  type unitFormatInfo struct {
   192  	unit            params.UnitStatus
   193  	unitName        string
   194  	applicationName string
   195  	meterStatuses   map[string]params.MeterStatus
   196  }
   197  
   198  func (sf *statusFormatter) formatUnit(info unitFormatInfo) unitStatus {
   199  	// TODO(Wallyworld) - this should be server side but we still need to support older servers.
   200  	sf.updateUnitStatusInfo(&info.unit, info.applicationName)
   201  
   202  	out := unitStatus{
   203  		WorkloadStatusInfo: sf.getWorkloadStatusInfo(info.unit),
   204  		JujuStatusInfo:     sf.getAgentStatusInfo(info.unit),
   205  		Machine:            info.unit.Machine,
   206  		OpenedPorts:        info.unit.OpenedPorts,
   207  		PublicAddress:      info.unit.PublicAddress,
   208  		Charm:              info.unit.Charm,
   209  		Subordinates:       make(map[string]unitStatus),
   210  		Leader:             info.unit.Leader,
   211  	}
   212  
   213  	if ms, ok := info.meterStatuses[info.unitName]; ok {
   214  		out.MeterStatus = &meterStatus{
   215  			Color:   ms.Color,
   216  			Message: ms.Message,
   217  		}
   218  	}
   219  
   220  	for k, m := range info.unit.Subordinates {
   221  		out.Subordinates[k] = sf.formatUnit(unitFormatInfo{
   222  			unit:            m,
   223  			unitName:        k,
   224  			applicationName: info.applicationName,
   225  			meterStatuses:   info.meterStatuses,
   226  		})
   227  	}
   228  	return out
   229  }
   230  
   231  func (sf *statusFormatter) getStatusInfoContents(inst params.DetailedStatus) statusInfoContents {
   232  	// TODO(perrito66) add status validation.
   233  	info := statusInfoContents{
   234  		Err:     inst.Err,
   235  		Current: status.Status(inst.Status),
   236  		Message: inst.Info,
   237  		Version: inst.Version,
   238  		Life:    inst.Life,
   239  	}
   240  	if inst.Since != nil {
   241  		info.Since = common.FormatTime(inst.Since, sf.isoTime)
   242  	}
   243  	return info
   244  }
   245  
   246  func (sf *statusFormatter) getWorkloadStatusInfo(unit params.UnitStatus) statusInfoContents {
   247  	// TODO(perrito66) add status validation.
   248  	info := statusInfoContents{
   249  		Err:     unit.WorkloadStatus.Err,
   250  		Current: status.Status(unit.WorkloadStatus.Status),
   251  		Message: unit.WorkloadStatus.Info,
   252  		Version: unit.WorkloadStatus.Version,
   253  	}
   254  	if unit.WorkloadStatus.Since != nil {
   255  		info.Since = common.FormatTime(unit.WorkloadStatus.Since, sf.isoTime)
   256  	}
   257  	return info
   258  }
   259  
   260  func (sf *statusFormatter) getAgentStatusInfo(unit params.UnitStatus) statusInfoContents {
   261  	// TODO(perrito66) add status validation.
   262  	info := statusInfoContents{
   263  		Err:     unit.AgentStatus.Err,
   264  		Current: status.Status(unit.AgentStatus.Status),
   265  		Message: unit.AgentStatus.Info,
   266  		Version: unit.AgentStatus.Version,
   267  	}
   268  	if unit.AgentStatus.Since != nil {
   269  		info.Since = common.FormatTime(unit.AgentStatus.Since, sf.isoTime)
   270  	}
   271  	return info
   272  }
   273  
   274  func (sf *statusFormatter) updateUnitStatusInfo(unit *params.UnitStatus, applicationName string) {
   275  	// TODO(perrito66) add status validation.
   276  	if status.Status(unit.WorkloadStatus.Status) == status.Error {
   277  		if relation, ok := sf.relations[getRelationIdFromData(unit)]; ok {
   278  			// Append the details of the other endpoint on to the status info string.
   279  			if ep, ok := findOtherEndpoint(relation.Endpoints, applicationName); ok {
   280  				unit.WorkloadStatus.Info = unit.WorkloadStatus.Info + " for " + ep.String()
   281  			}
   282  		}
   283  	}
   284  }
   285  
   286  func makeHAStatus(hasVote, wantsVote bool) string {
   287  	var s string
   288  	switch {
   289  	case hasVote && wantsVote:
   290  		s = "has-vote"
   291  	case hasVote && !wantsVote:
   292  		s = "removing-vote"
   293  	case !hasVote && wantsVote:
   294  		s = "adding-vote"
   295  	case !hasVote && !wantsVote:
   296  		s = "no-vote"
   297  	}
   298  	return s
   299  }
   300  
   301  func getRelationIdFromData(unit *params.UnitStatus) int {
   302  	if relationId_, ok := unit.WorkloadStatus.Data["relation-id"]; ok {
   303  		if relationId, ok := relationId_.(float64); ok {
   304  			return int(relationId)
   305  		} else {
   306  			logger.Infof("relation-id found status data but was unexpected "+
   307  				"type: %q. Status output may be lacking some detail.", relationId_)
   308  		}
   309  	}
   310  	return -1
   311  }
   312  
   313  // findOtherEndpoint searches the provided endpoints for an endpoint
   314  // that *doesn't* match applicationName. The returned bool indicates if
   315  // such an endpoint was found.
   316  func findOtherEndpoint(endpoints []params.EndpointStatus, applicationName string) (params.EndpointStatus, bool) {
   317  	for _, endpoint := range endpoints {
   318  		if endpoint.ApplicationName != applicationName {
   319  			return endpoint, true
   320  		}
   321  	}
   322  	return params.EndpointStatus{}, false
   323  }