github.com/wallyworld/juju@v0.0.0-20161013125918-6cf1bc9d917a/apiserver/client/status.go (about)

     1  // Copyright 2013 Canonical Ltd.
     2  // Licensed under the AGPLv3, see LICENCE file for details.
     3  
     4  package client
     5  
     6  import (
     7  	"fmt"
     8  	"sort"
     9  	"strings"
    10  
    11  	"github.com/juju/errors"
    12  	"github.com/juju/utils/set"
    13  	"gopkg.in/juju/charm.v6-unstable"
    14  	"gopkg.in/juju/names.v2"
    15  
    16  	"github.com/juju/juju/apiserver/common"
    17  	"github.com/juju/juju/apiserver/params"
    18  	"github.com/juju/juju/network"
    19  	"github.com/juju/juju/state"
    20  	"github.com/juju/juju/state/multiwatcher"
    21  	"github.com/juju/juju/status"
    22  )
    23  
    24  func agentStatusFromStatusInfo(s []status.StatusInfo, kind status.HistoryKind) []params.DetailedStatus {
    25  	result := []params.DetailedStatus{}
    26  	for _, v := range s {
    27  		result = append(result, params.DetailedStatus{
    28  			Status: string(v.Status),
    29  			Info:   v.Message,
    30  			Data:   v.Data,
    31  			Since:  v.Since,
    32  			Kind:   string(kind),
    33  		})
    34  	}
    35  	return result
    36  
    37  }
    38  
    39  type byTime []params.DetailedStatus
    40  
    41  func (s byTime) Len() int {
    42  	return len(s)
    43  }
    44  func (s byTime) Swap(i, j int) {
    45  	s[i], s[j] = s[j], s[i]
    46  }
    47  func (s byTime) Less(i, j int) bool {
    48  	return s[i].Since.Before(*s[j].Since)
    49  }
    50  
    51  // unitStatusHistory returns a list of status history entries for unit agents or workloads.
    52  func (c *Client) unitStatusHistory(unitTag names.UnitTag, filter status.StatusHistoryFilter, kind status.HistoryKind) ([]params.DetailedStatus, error) {
    53  	unit, err := c.api.stateAccessor.Unit(unitTag.Id())
    54  	if err != nil {
    55  		return nil, errors.Trace(err)
    56  	}
    57  	statuses := []params.DetailedStatus{}
    58  	if kind == status.KindUnit || kind == status.KindWorkload {
    59  		unitStatuses, err := unit.StatusHistory(filter)
    60  		if err != nil {
    61  			return nil, errors.Trace(err)
    62  		}
    63  		statuses = agentStatusFromStatusInfo(unitStatuses, status.KindWorkload)
    64  
    65  	}
    66  	if kind == status.KindUnit || kind == status.KindUnitAgent {
    67  		agentStatuses, err := unit.AgentHistory().StatusHistory(filter)
    68  		if err != nil {
    69  			return nil, errors.Trace(err)
    70  		}
    71  		statuses = append(statuses, agentStatusFromStatusInfo(agentStatuses, status.KindUnitAgent)...)
    72  	}
    73  
    74  	sort.Sort(byTime(statuses))
    75  	if kind == status.KindUnit && filter.Size > 0 {
    76  		if len(statuses) > filter.Size {
    77  			statuses = statuses[len(statuses)-filter.Size:]
    78  		}
    79  	}
    80  
    81  	return statuses, nil
    82  }
    83  
    84  // machineStatusHistory returns status history for the given machine.
    85  func (c *Client) machineStatusHistory(machineTag names.MachineTag, filter status.StatusHistoryFilter, kind status.HistoryKind) ([]params.DetailedStatus, error) {
    86  	machine, err := c.api.stateAccessor.Machine(machineTag.Id())
    87  	if err != nil {
    88  		return nil, errors.Trace(err)
    89  	}
    90  	var sInfo []status.StatusInfo
    91  	if kind == status.KindMachineInstance || kind == status.KindContainerInstance {
    92  		sInfo, err = machine.InstanceStatusHistory(filter)
    93  	} else {
    94  		sInfo, err = machine.StatusHistory(filter)
    95  	}
    96  	if err != nil {
    97  		return nil, errors.Trace(err)
    98  	}
    99  	return agentStatusFromStatusInfo(sInfo, kind), nil
   100  }
   101  
   102  // StatusHistory returns a slice of past statuses for several entities.
   103  func (c *Client) StatusHistory(request params.StatusHistoryRequests) params.StatusHistoryResults {
   104  
   105  	results := params.StatusHistoryResults{}
   106  	// TODO(perrito666) the contents of the loop could be split into
   107  	// a oneHistory method for clarity.
   108  	for _, request := range request.Requests {
   109  		filter := status.StatusHistoryFilter{
   110  			Size:  request.Filter.Size,
   111  			Date:  request.Filter.Date,
   112  			Delta: request.Filter.Delta,
   113  		}
   114  		if err := c.checkCanRead(); err != nil {
   115  			history := params.StatusHistoryResult{
   116  				Error: common.ServerError(err),
   117  			}
   118  			results.Results = append(results.Results, history)
   119  			continue
   120  
   121  		}
   122  
   123  		if err := filter.Validate(); err != nil {
   124  			history := params.StatusHistoryResult{
   125  				Error: common.ServerError(errors.Annotate(err, "cannot validate status history filter")),
   126  			}
   127  			results.Results = append(results.Results, history)
   128  			continue
   129  		}
   130  
   131  		var (
   132  			err  error
   133  			hist []params.DetailedStatus
   134  		)
   135  		kind := status.HistoryKind(request.Kind)
   136  		err = errors.NotValidf("%q requires a unit, got %T", kind, request.Tag)
   137  		switch kind {
   138  		case status.KindUnit, status.KindWorkload, status.KindUnitAgent:
   139  			var u names.UnitTag
   140  			if u, err = names.ParseUnitTag(request.Tag); err == nil {
   141  				hist, err = c.unitStatusHistory(u, filter, kind)
   142  			}
   143  		default:
   144  			var m names.MachineTag
   145  			if m, err = names.ParseMachineTag(request.Tag); err == nil {
   146  				hist, err = c.machineStatusHistory(m, filter, kind)
   147  			}
   148  		}
   149  
   150  		if err == nil {
   151  			sort.Sort(byTime(hist))
   152  		}
   153  
   154  		results.Results = append(results.Results,
   155  			params.StatusHistoryResult{
   156  				History: params.History{Statuses: hist},
   157  				Error:   common.ServerError(errors.Annotatef(err, "fetching status history for %q", request.Tag)),
   158  			})
   159  	}
   160  	return results
   161  }
   162  
   163  // FullStatus gives the information needed for juju status over the api
   164  func (c *Client) FullStatus(args params.StatusParams) (params.FullStatus, error) {
   165  	if err := c.checkCanRead(); err != nil {
   166  		return params.FullStatus{}, err
   167  	}
   168  
   169  	var noStatus params.FullStatus
   170  	var context statusContext
   171  	var err error
   172  	if context.services, context.units, context.latestCharms, err =
   173  		fetchAllApplicationsAndUnits(c.api.stateAccessor, len(args.Patterns) <= 0); err != nil {
   174  		return noStatus, errors.Annotate(err, "could not fetch services and units")
   175  	}
   176  	if context.machines, err = fetchMachines(c.api.stateAccessor, nil); err != nil {
   177  		return noStatus, errors.Annotate(err, "could not fetch machines")
   178  	}
   179  	if context.relations, err = fetchRelations(c.api.stateAccessor); err != nil {
   180  		return noStatus, errors.Annotate(err, "could not fetch relations")
   181  	}
   182  	if len(context.services) > 0 {
   183  		if context.leaders, err = c.api.stateAccessor.ApplicationLeaders(); err != nil {
   184  			return noStatus, errors.Annotate(err, " could not fetch leaders")
   185  		}
   186  	}
   187  
   188  	logger.Debugf("Applications: %v", context.services)
   189  
   190  	if len(args.Patterns) > 0 {
   191  		predicate := BuildPredicateFor(args.Patterns)
   192  
   193  		// First, attempt to match machines. Any units on those
   194  		// machines are implicitly matched.
   195  		matchedMachines := make(set.Strings)
   196  		for _, machineList := range context.machines {
   197  			for _, m := range machineList {
   198  				matches, err := predicate(m)
   199  				if err != nil {
   200  					return noStatus, errors.Annotate(
   201  						err, "could not filter machines",
   202  					)
   203  				}
   204  				if matches {
   205  					matchedMachines.Add(m.Id())
   206  				}
   207  			}
   208  		}
   209  
   210  		// Filter units
   211  		matchedSvcs := make(set.Strings)
   212  		unitChainPredicate := UnitChainPredicateFn(predicate, context.unitByName)
   213  		for _, unitMap := range context.units {
   214  			for name, unit := range unitMap {
   215  				machineId, err := unit.AssignedMachineId()
   216  				if err != nil {
   217  					machineId = ""
   218  				} else if matchedMachines.Contains(machineId) {
   219  					// Unit is on a matching machine.
   220  					matchedSvcs.Add(unit.ApplicationName())
   221  					continue
   222  				}
   223  
   224  				// Always start examining at the top-level. This
   225  				// prevents a situation where we filter a subordinate
   226  				// before we discover its parent is a match.
   227  				if !unit.IsPrincipal() {
   228  					continue
   229  				} else if matches, err := unitChainPredicate(unit); err != nil {
   230  					return noStatus, errors.Annotate(err, "could not filter units")
   231  				} else if !matches {
   232  					delete(unitMap, name)
   233  					continue
   234  				}
   235  				matchedSvcs.Add(unit.ApplicationName())
   236  				if machineId != "" {
   237  					matchedMachines.Add(machineId)
   238  				}
   239  			}
   240  		}
   241  
   242  		// Filter services
   243  		for svcName, svc := range context.services {
   244  			if matchedSvcs.Contains(svcName) {
   245  				// There are matched units for this service.
   246  				continue
   247  			} else if matches, err := predicate(svc); err != nil {
   248  				return noStatus, errors.Annotate(err, "could not filter applications")
   249  			} else if !matches {
   250  				delete(context.services, svcName)
   251  			}
   252  		}
   253  
   254  		// Filter machines
   255  		for status, machineList := range context.machines {
   256  			matched := make([]*state.Machine, 0, len(machineList))
   257  			for _, m := range machineList {
   258  				machineContainers, err := m.Containers()
   259  				if err != nil {
   260  					return noStatus, err
   261  				}
   262  				machineContainersSet := set.NewStrings(machineContainers...)
   263  
   264  				if matchedMachines.Contains(m.Id()) || !matchedMachines.Intersection(machineContainersSet).IsEmpty() {
   265  					// The machine is matched directly, or contains a unit
   266  					// or container that matches.
   267  					logger.Tracef("machine %s is hosting something.", m.Id())
   268  					matched = append(matched, m)
   269  					continue
   270  				}
   271  			}
   272  			context.machines[status] = matched
   273  		}
   274  	}
   275  
   276  	modelStatus, err := c.modelStatus()
   277  	if err != nil {
   278  		return noStatus, errors.Annotate(err, "cannot determine model status")
   279  	}
   280  	return params.FullStatus{
   281  		Model:        modelStatus,
   282  		Machines:     processMachines(context.machines),
   283  		Applications: context.processApplications(),
   284  		Relations:    context.processRelations(),
   285  	}, nil
   286  }
   287  
   288  // newToolsVersionAvailable will return a string representing a tools
   289  // version only if the latest check is newer than current tools.
   290  func (c *Client) modelStatus() (params.ModelStatusInfo, error) {
   291  	var info params.ModelStatusInfo
   292  
   293  	m, err := c.api.stateAccessor.Model()
   294  	if err != nil {
   295  		return info, errors.Annotate(err, "cannot get model")
   296  	}
   297  	info.Name = m.Name()
   298  	info.CloudTag = names.NewCloudTag(m.Cloud()).String()
   299  	info.CloudRegion = m.CloudRegion()
   300  
   301  	cfg, err := m.Config()
   302  	if err != nil {
   303  		return info, errors.Annotate(err, "cannot obtain current model config")
   304  	}
   305  
   306  	latestVersion := m.LatestToolsVersion()
   307  	current, ok := cfg.AgentVersion()
   308  	if ok {
   309  		info.Version = current.String()
   310  		if current.Compare(latestVersion) < 0 {
   311  			info.AvailableVersion = latestVersion.String()
   312  		}
   313  	}
   314  
   315  	migStatus, err := c.getMigrationStatus()
   316  	if err != nil {
   317  		// It's not worth killing the entire status out if migration
   318  		// status can't be retrieved.
   319  		logger.Errorf("error retrieving migration status: %v", err)
   320  		info.Migration = "error retrieving migration status"
   321  	} else {
   322  		info.Migration = migStatus
   323  	}
   324  
   325  	return info, nil
   326  }
   327  
   328  func (c *Client) getMigrationStatus() (string, error) {
   329  	mig, err := c.api.stateAccessor.LatestMigration()
   330  	if err != nil {
   331  		if errors.IsNotFound(err) {
   332  			return "", nil
   333  		}
   334  		return "", errors.Trace(err)
   335  	}
   336  
   337  	phase, err := mig.Phase()
   338  	if err != nil {
   339  		return "", errors.Trace(err)
   340  	}
   341  	if phase.IsTerminal() {
   342  		// There has been a migration attempt but it's no longer
   343  		// active - don't include this in status.
   344  		return "", nil
   345  	}
   346  
   347  	return mig.StatusMessage(), nil
   348  }
   349  
   350  type statusContext struct {
   351  	// machines: top-level machine id -> list of machines nested in
   352  	// this machine.
   353  	machines map[string][]*state.Machine
   354  	// services: service name -> service
   355  	services     map[string]*state.Application
   356  	relations    map[string][]*state.Relation
   357  	units        map[string]map[string]*state.Unit
   358  	latestCharms map[charm.URL]*state.Charm
   359  	leaders      map[string]string
   360  }
   361  
   362  // fetchMachines returns a map from top level machine id to machines, where machines[0] is the host
   363  // machine and machines[1..n] are any containers (including nested ones).
   364  //
   365  // If machineIds is non-nil, only machines whose IDs are in the set are returned.
   366  func fetchMachines(st Backend, machineIds set.Strings) (map[string][]*state.Machine, error) {
   367  	v := make(map[string][]*state.Machine)
   368  	machines, err := st.AllMachines()
   369  	if err != nil {
   370  		return nil, err
   371  	}
   372  	// AllMachines gives us machines sorted by id.
   373  	for _, m := range machines {
   374  		if machineIds != nil && !machineIds.Contains(m.Id()) {
   375  			continue
   376  		}
   377  		parentId, ok := m.ParentId()
   378  		if !ok {
   379  			// Only top level host machines go directly into the machine map.
   380  			v[m.Id()] = []*state.Machine{m}
   381  		} else {
   382  			topParentId := state.TopParentId(m.Id())
   383  			machines, ok := v[topParentId]
   384  			if !ok {
   385  				panic(fmt.Errorf("unexpected machine id %q", parentId))
   386  			}
   387  			machines = append(machines, m)
   388  			v[topParentId] = machines
   389  		}
   390  	}
   391  	return v, nil
   392  }
   393  
   394  // fetchAllApplicationsAndUnits returns a map from service name to service,
   395  // a map from service name to unit name to unit, and a map from base charm URL to latest URL.
   396  func fetchAllApplicationsAndUnits(
   397  	st Backend,
   398  	matchAny bool,
   399  ) (map[string]*state.Application, map[string]map[string]*state.Unit, map[charm.URL]*state.Charm, error) {
   400  
   401  	svcMap := make(map[string]*state.Application)
   402  	unitMap := make(map[string]map[string]*state.Unit)
   403  	latestCharms := make(map[charm.URL]*state.Charm)
   404  	services, err := st.AllApplications()
   405  	if err != nil {
   406  		return nil, nil, nil, err
   407  	}
   408  	for _, s := range services {
   409  		units, err := s.AllUnits()
   410  		if err != nil {
   411  			return nil, nil, nil, err
   412  		}
   413  		svcUnitMap := make(map[string]*state.Unit)
   414  		for _, u := range units {
   415  			svcUnitMap[u.Name()] = u
   416  		}
   417  		if matchAny || len(svcUnitMap) > 0 {
   418  			unitMap[s.Name()] = svcUnitMap
   419  			svcMap[s.Name()] = s
   420  			// Record the base URL for the application's charm so that
   421  			// the latest store revision can be looked up.
   422  			charmURL, _ := s.CharmURL()
   423  			if charmURL.Schema == "cs" {
   424  				latestCharms[*charmURL.WithRevision(-1)] = nil
   425  			}
   426  		}
   427  	}
   428  	for baseURL := range latestCharms {
   429  		ch, err := st.LatestPlaceholderCharm(&baseURL)
   430  		if errors.IsNotFound(err) {
   431  			continue
   432  		}
   433  		if err != nil {
   434  			return nil, nil, nil, err
   435  		}
   436  		latestCharms[baseURL] = ch
   437  	}
   438  
   439  	return svcMap, unitMap, latestCharms, nil
   440  }
   441  
   442  // fetchRelations returns a map of all relations keyed by service name.
   443  //
   444  // This structure is useful for processServiceRelations() which needs
   445  // to have the relations for each service. Reading them once here
   446  // avoids the repeated DB hits to retrieve the relations for each
   447  // service that used to happen in processServiceRelations().
   448  func fetchRelations(st Backend) (map[string][]*state.Relation, error) {
   449  	relations, err := st.AllRelations()
   450  	if err != nil {
   451  		return nil, err
   452  	}
   453  	out := make(map[string][]*state.Relation)
   454  	for _, relation := range relations {
   455  		for _, ep := range relation.Endpoints() {
   456  			out[ep.ApplicationName] = append(out[ep.ApplicationName], relation)
   457  		}
   458  	}
   459  	return out, nil
   460  }
   461  
   462  type machineAndContainers map[string][]*state.Machine
   463  
   464  func (m machineAndContainers) HostForMachineId(id string) *state.Machine {
   465  	// Element 0 is assumed to be the top-level machine.
   466  	return m[id][0]
   467  }
   468  
   469  func (m machineAndContainers) Containers(id string) []*state.Machine {
   470  	return m[id][1:]
   471  }
   472  
   473  func processMachines(idToMachines map[string][]*state.Machine) map[string]params.MachineStatus {
   474  	machinesMap := make(map[string]params.MachineStatus)
   475  	cache := make(map[string]params.MachineStatus)
   476  	for id, machines := range idToMachines {
   477  
   478  		if len(machines) <= 0 {
   479  			continue
   480  		}
   481  
   482  		// Element 0 is assumed to be the top-level machine.
   483  		tlMachine := machines[0]
   484  		hostStatus := makeMachineStatus(tlMachine)
   485  		machinesMap[id] = hostStatus
   486  		cache[id] = hostStatus
   487  
   488  		for _, machine := range machines[1:] {
   489  			parent, ok := cache[state.ParentId(machine.Id())]
   490  			if !ok {
   491  				panic("We've broken an assumpution.")
   492  			}
   493  
   494  			status := makeMachineStatus(machine)
   495  			parent.Containers[machine.Id()] = status
   496  			cache[machine.Id()] = status
   497  		}
   498  	}
   499  	return machinesMap
   500  }
   501  
   502  func makeMachineStatus(machine *state.Machine) (status params.MachineStatus) {
   503  	var err error
   504  	status.Id = machine.Id()
   505  	agentStatus := processMachine(machine)
   506  	status.AgentStatus = agentStatus
   507  
   508  	status.Series = machine.Series()
   509  	status.Jobs = paramsJobsFromJobs(machine.Jobs())
   510  	status.WantsVote = machine.WantsVote()
   511  	status.HasVote = machine.HasVote()
   512  	sInfo, err := machine.InstanceStatus()
   513  	populateStatusFromStatusInfoAndErr(&status.InstanceStatus, sInfo, err)
   514  	instid, err := machine.InstanceId()
   515  	if err == nil {
   516  		status.InstanceId = instid
   517  		addr, err := machine.PublicAddress()
   518  		if err != nil {
   519  			// Usually this indicates that no addresses have been set on the
   520  			// machine yet.
   521  			addr = network.Address{}
   522  			logger.Debugf("error fetching public address: %q", err)
   523  		}
   524  		status.DNSName = addr.Value
   525  
   526  		mAddrs := machine.Addresses()
   527  		if len(mAddrs) == 0 {
   528  			logger.Debugf("no IP addresses fetched for machine %q", instid)
   529  			// At least give it the newly created DNSName address, if it exists.
   530  			if addr.Value != "" {
   531  				mAddrs = append(mAddrs, addr)
   532  			}
   533  		}
   534  		for _, mAddr := range mAddrs {
   535  			switch mAddr.Scope {
   536  			case network.ScopeMachineLocal, network.ScopeLinkLocal:
   537  				continue
   538  			}
   539  			status.IPAddresses = append(status.IPAddresses, mAddr.Value)
   540  		}
   541  	} else {
   542  		if errors.IsNotProvisioned(err) {
   543  			status.InstanceId = "pending"
   544  		} else {
   545  			status.InstanceId = "error"
   546  		}
   547  	}
   548  	hc, err := machine.HardwareCharacteristics()
   549  	if err != nil {
   550  		if !errors.IsNotFound(err) {
   551  			status.Hardware = "error"
   552  		}
   553  	} else {
   554  		status.Hardware = hc.String()
   555  	}
   556  	status.Containers = make(map[string]params.MachineStatus)
   557  	return
   558  }
   559  
   560  func (context *statusContext) processRelations() []params.RelationStatus {
   561  	var out []params.RelationStatus
   562  	relations := context.getAllRelations()
   563  	for _, relation := range relations {
   564  		var eps []params.EndpointStatus
   565  		var scope charm.RelationScope
   566  		var relationInterface string
   567  		for _, ep := range relation.Endpoints() {
   568  			eps = append(eps, params.EndpointStatus{
   569  				ApplicationName: ep.ApplicationName,
   570  				Name:            ep.Name,
   571  				Role:            string(ep.Role),
   572  				Subordinate:     context.isSubordinate(&ep),
   573  			})
   574  			// these should match on both sides so use the last
   575  			relationInterface = ep.Interface
   576  			scope = ep.Scope
   577  		}
   578  		relStatus := params.RelationStatus{
   579  			Id:        relation.Id(),
   580  			Key:       relation.String(),
   581  			Interface: relationInterface,
   582  			Scope:     string(scope),
   583  			Endpoints: eps,
   584  		}
   585  		out = append(out, relStatus)
   586  	}
   587  	return out
   588  }
   589  
   590  // This method exists only to dedup the loaded relations as they will
   591  // appear multiple times in context.relations.
   592  func (context *statusContext) getAllRelations() []*state.Relation {
   593  	var out []*state.Relation
   594  	seenRelations := make(map[int]bool)
   595  	for _, relations := range context.relations {
   596  		for _, relation := range relations {
   597  			if _, found := seenRelations[relation.Id()]; !found {
   598  				out = append(out, relation)
   599  				seenRelations[relation.Id()] = true
   600  			}
   601  		}
   602  	}
   603  	return out
   604  }
   605  
   606  func (context *statusContext) isSubordinate(ep *state.Endpoint) bool {
   607  	service := context.services[ep.ApplicationName]
   608  	if service == nil {
   609  		return false
   610  	}
   611  	return isSubordinate(ep, service)
   612  }
   613  
   614  func isSubordinate(ep *state.Endpoint, service *state.Application) bool {
   615  	return ep.Scope == charm.ScopeContainer && !service.IsPrincipal()
   616  }
   617  
   618  // paramsJobsFromJobs converts state jobs to params jobs.
   619  func paramsJobsFromJobs(jobs []state.MachineJob) []multiwatcher.MachineJob {
   620  	paramsJobs := make([]multiwatcher.MachineJob, len(jobs))
   621  	for i, machineJob := range jobs {
   622  		paramsJobs[i] = machineJob.ToParams()
   623  	}
   624  	return paramsJobs
   625  }
   626  
   627  func (context *statusContext) processApplications() map[string]params.ApplicationStatus {
   628  	servicesMap := make(map[string]params.ApplicationStatus)
   629  	for _, s := range context.services {
   630  		servicesMap[s.Name()] = context.processApplication(s)
   631  	}
   632  	return servicesMap
   633  }
   634  
   635  func (context *statusContext) processApplication(service *state.Application) params.ApplicationStatus {
   636  	serviceCharm, _, err := service.Charm()
   637  	if err != nil {
   638  		return params.ApplicationStatus{Err: common.ServerError(err)}
   639  	}
   640  
   641  	var processedStatus = params.ApplicationStatus{
   642  		Charm:   serviceCharm.URL().String(),
   643  		Series:  service.Series(),
   644  		Exposed: service.IsExposed(),
   645  		Life:    processLife(service),
   646  	}
   647  
   648  	if latestCharm, ok := context.latestCharms[*serviceCharm.URL().WithRevision(-1)]; ok && latestCharm != nil {
   649  		if latestCharm.Revision() > serviceCharm.URL().Revision {
   650  			processedStatus.CanUpgradeTo = latestCharm.String()
   651  		}
   652  	}
   653  
   654  	processedStatus.Relations, processedStatus.SubordinateTo, err = context.processServiceRelations(service)
   655  	if err != nil {
   656  		processedStatus.Err = common.ServerError(err)
   657  		return processedStatus
   658  	}
   659  	units := context.units[service.Name()]
   660  	if service.IsPrincipal() {
   661  		processedStatus.Units = context.processUnits(units, serviceCharm.URL().String())
   662  	}
   663  	applicationStatus, err := service.Status()
   664  	if err != nil {
   665  		processedStatus.Err = common.ServerError(err)
   666  		return processedStatus
   667  	}
   668  	processedStatus.Status.Status = applicationStatus.Status.String()
   669  	processedStatus.Status.Info = applicationStatus.Message
   670  	processedStatus.Status.Data = applicationStatus.Data
   671  	processedStatus.Status.Since = applicationStatus.Since
   672  
   673  	metrics := serviceCharm.Metrics()
   674  	planRequired := metrics != nil && metrics.Plan != nil && metrics.Plan.Required
   675  	if planRequired || len(service.MetricCredentials()) > 0 {
   676  		processedStatus.MeterStatuses = context.processUnitMeterStatuses(units)
   677  	}
   678  
   679  	versions := make([]status.StatusInfo, 0, len(units))
   680  	for _, unit := range units {
   681  		statuses, err := unit.WorkloadVersionHistory().StatusHistory(
   682  			status.StatusHistoryFilter{Size: 1},
   683  		)
   684  		if err != nil {
   685  			processedStatus.Err = common.ServerError(err)
   686  			return processedStatus
   687  		}
   688  		// Even though we fully expect there to be historical values there,
   689  		// even the first should be the empty string, the status history
   690  		// collection is not added to in a transactional manner, so it may be
   691  		// not there even though we'd really like it to be. Such is mongo.
   692  		if len(statuses) > 0 {
   693  			versions = append(versions, statuses[0])
   694  		}
   695  	}
   696  	if len(versions) > 0 {
   697  		sort.Sort(bySinceDescending(versions))
   698  		processedStatus.WorkloadVersion = versions[0].Message
   699  	}
   700  
   701  	return processedStatus
   702  }
   703  
   704  func isColorStatus(code state.MeterStatusCode) bool {
   705  	return code == state.MeterGreen || code == state.MeterAmber || code == state.MeterRed
   706  }
   707  
   708  func (context *statusContext) processUnitMeterStatuses(units map[string]*state.Unit) map[string]params.MeterStatus {
   709  	unitsMap := make(map[string]params.MeterStatus)
   710  	for _, unit := range units {
   711  		meterStatus, err := unit.GetMeterStatus()
   712  		if err != nil {
   713  			continue
   714  		}
   715  		if isColorStatus(meterStatus.Code) {
   716  			unitsMap[unit.Name()] = params.MeterStatus{Color: strings.ToLower(meterStatus.Code.String()), Message: meterStatus.Info}
   717  		}
   718  	}
   719  	if len(unitsMap) > 0 {
   720  		return unitsMap
   721  	}
   722  	return nil
   723  }
   724  
   725  func (context *statusContext) processUnits(units map[string]*state.Unit, serviceCharm string) map[string]params.UnitStatus {
   726  	unitsMap := make(map[string]params.UnitStatus)
   727  	for _, unit := range units {
   728  		unitsMap[unit.Name()] = context.processUnit(unit, serviceCharm)
   729  	}
   730  	return unitsMap
   731  }
   732  
   733  func (context *statusContext) processUnit(unit *state.Unit, serviceCharm string) params.UnitStatus {
   734  	var result params.UnitStatus
   735  	addr, err := unit.PublicAddress()
   736  	if err != nil {
   737  		// Usually this indicates that no addresses have been set on the
   738  		// machine yet.
   739  		addr = network.Address{}
   740  		logger.Debugf("error fetching public address: %v", err)
   741  	}
   742  	result.PublicAddress = addr.Value
   743  	unitPorts, _ := unit.OpenedPorts()
   744  	for _, port := range unitPorts {
   745  		result.OpenedPorts = append(result.OpenedPorts, port.String())
   746  	}
   747  	if unit.IsPrincipal() {
   748  		result.Machine, _ = unit.AssignedMachineId()
   749  	}
   750  	curl, _ := unit.CharmURL()
   751  	if serviceCharm != "" && curl != nil && curl.String() != serviceCharm {
   752  		result.Charm = curl.String()
   753  	}
   754  	workloadVersion, err := unit.WorkloadVersion()
   755  	if err == nil {
   756  		result.WorkloadVersion = workloadVersion
   757  	} else {
   758  		logger.Debugf("error fetching workload version: %v", err)
   759  	}
   760  
   761  	processUnitAndAgentStatus(unit, &result)
   762  
   763  	if subUnits := unit.SubordinateNames(); len(subUnits) > 0 {
   764  		result.Subordinates = make(map[string]params.UnitStatus)
   765  		for _, name := range subUnits {
   766  			subUnit := context.unitByName(name)
   767  			// subUnit may be nil if subordinate was filtered out.
   768  			if subUnit != nil {
   769  				result.Subordinates[name] = context.processUnit(subUnit, serviceCharm)
   770  			}
   771  		}
   772  	}
   773  	if leader := context.leaders[unit.ApplicationName()]; leader == unit.Name() {
   774  		result.Leader = true
   775  	}
   776  	return result
   777  }
   778  
   779  func (context *statusContext) unitByName(name string) *state.Unit {
   780  	serviceName := strings.Split(name, "/")[0]
   781  	return context.units[serviceName][name]
   782  }
   783  
   784  func (context *statusContext) processServiceRelations(service *state.Application) (related map[string][]string, subord []string, err error) {
   785  	subordSet := make(set.Strings)
   786  	related = make(map[string][]string)
   787  	relations := context.relations[service.Name()]
   788  	for _, relation := range relations {
   789  		ep, err := relation.Endpoint(service.Name())
   790  		if err != nil {
   791  			return nil, nil, err
   792  		}
   793  		relationName := ep.Relation.Name
   794  		eps, err := relation.RelatedEndpoints(service.Name())
   795  		if err != nil {
   796  			return nil, nil, err
   797  		}
   798  		for _, ep := range eps {
   799  			if isSubordinate(&ep, service) {
   800  				subordSet.Add(ep.ApplicationName)
   801  			}
   802  			related[relationName] = append(related[relationName], ep.ApplicationName)
   803  		}
   804  	}
   805  	for relationName, serviceNames := range related {
   806  		sn := set.NewStrings(serviceNames...)
   807  		related[relationName] = sn.SortedValues()
   808  	}
   809  	return related, subordSet.SortedValues(), nil
   810  }
   811  
   812  type lifer interface {
   813  	Life() state.Life
   814  }
   815  
   816  // processUnitAndAgentStatus retrieves status information for both unit and unitAgents.
   817  func processUnitAndAgentStatus(unit *state.Unit, unitStatus *params.UnitStatus) {
   818  	unitStatus.AgentStatus, unitStatus.WorkloadStatus = processUnit(unit)
   819  }
   820  
   821  // populateStatusFromStatusInfoAndErr creates AgentStatus from the typical output
   822  // of a status getter.
   823  func populateStatusFromStatusInfoAndErr(agent *params.DetailedStatus, statusInfo status.StatusInfo, err error) {
   824  	agent.Err = err
   825  	agent.Status = statusInfo.Status.String()
   826  	agent.Info = statusInfo.Message
   827  	agent.Data = filterStatusData(statusInfo.Data)
   828  	agent.Since = statusInfo.Since
   829  }
   830  
   831  // processMachine retrieves version and status information for the given machine.
   832  // It also returns deprecated legacy status information.
   833  func processMachine(machine *state.Machine) (out params.DetailedStatus) {
   834  	statusInfo, err := common.MachineStatus(machine)
   835  	populateStatusFromStatusInfoAndErr(&out, statusInfo, err)
   836  
   837  	out.Life = processLife(machine)
   838  
   839  	if t, err := machine.AgentTools(); err == nil {
   840  		out.Version = t.Version.Number.String()
   841  	}
   842  	return
   843  }
   844  
   845  // processUnit retrieves version and status information for the given unit.
   846  func processUnit(unit *state.Unit) (agentStatus, workloadStatus params.DetailedStatus) {
   847  	agent, workload := common.UnitStatus(unit)
   848  	populateStatusFromStatusInfoAndErr(&agentStatus, agent.Status, agent.Err)
   849  	populateStatusFromStatusInfoAndErr(&workloadStatus, workload.Status, workload.Err)
   850  
   851  	agentStatus.Life = processLife(unit)
   852  
   853  	if t, err := unit.AgentTools(); err == nil {
   854  		agentStatus.Version = t.Version.Number.String()
   855  	}
   856  	return
   857  }
   858  
   859  // filterStatusData limits what agent StatusData data is passed over
   860  // the API. This prevents unintended leakage of internal-only data.
   861  func filterStatusData(status map[string]interface{}) map[string]interface{} {
   862  	out := make(map[string]interface{})
   863  	for name, value := range status {
   864  		// use a set here if we end up with a larger whitelist
   865  		if name == "relation-id" {
   866  			out[name] = value
   867  		}
   868  	}
   869  	return out
   870  }
   871  
   872  func processLife(entity lifer) string {
   873  	if life := entity.Life(); life != state.Alive {
   874  		// alive is the usual state so omit it by default.
   875  		return life.String()
   876  	}
   877  	return ""
   878  }
   879  
   880  type bySinceDescending []status.StatusInfo
   881  
   882  // Len implements sort.Interface.
   883  func (s bySinceDescending) Len() int { return len(s) }
   884  
   885  // Swap implements sort.Interface.
   886  func (s bySinceDescending) Swap(a, b int) { s[a], s[b] = s[b], s[a] }
   887  
   888  // Less implements sort.Interface.
   889  func (s bySinceDescending) Less(a, b int) bool { return s[a].Since.After(*s[b].Since) }