github.com/axw/juju@v0.0.0-20161005053422-4bd6544d08d4/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  	} else {
   526  		if errors.IsNotProvisioned(err) {
   527  			status.InstanceId = "pending"
   528  		} else {
   529  			status.InstanceId = "error"
   530  		}
   531  	}
   532  	hc, err := machine.HardwareCharacteristics()
   533  	if err != nil {
   534  		if !errors.IsNotFound(err) {
   535  			status.Hardware = "error"
   536  		}
   537  	} else {
   538  		status.Hardware = hc.String()
   539  	}
   540  	status.Containers = make(map[string]params.MachineStatus)
   541  	return
   542  }
   543  
   544  func (context *statusContext) processRelations() []params.RelationStatus {
   545  	var out []params.RelationStatus
   546  	relations := context.getAllRelations()
   547  	for _, relation := range relations {
   548  		var eps []params.EndpointStatus
   549  		var scope charm.RelationScope
   550  		var relationInterface string
   551  		for _, ep := range relation.Endpoints() {
   552  			eps = append(eps, params.EndpointStatus{
   553  				ApplicationName: ep.ApplicationName,
   554  				Name:            ep.Name,
   555  				Role:            string(ep.Role),
   556  				Subordinate:     context.isSubordinate(&ep),
   557  			})
   558  			// these should match on both sides so use the last
   559  			relationInterface = ep.Interface
   560  			scope = ep.Scope
   561  		}
   562  		relStatus := params.RelationStatus{
   563  			Id:        relation.Id(),
   564  			Key:       relation.String(),
   565  			Interface: relationInterface,
   566  			Scope:     string(scope),
   567  			Endpoints: eps,
   568  		}
   569  		out = append(out, relStatus)
   570  	}
   571  	return out
   572  }
   573  
   574  // This method exists only to dedup the loaded relations as they will
   575  // appear multiple times in context.relations.
   576  func (context *statusContext) getAllRelations() []*state.Relation {
   577  	var out []*state.Relation
   578  	seenRelations := make(map[int]bool)
   579  	for _, relations := range context.relations {
   580  		for _, relation := range relations {
   581  			if _, found := seenRelations[relation.Id()]; !found {
   582  				out = append(out, relation)
   583  				seenRelations[relation.Id()] = true
   584  			}
   585  		}
   586  	}
   587  	return out
   588  }
   589  
   590  func (context *statusContext) isSubordinate(ep *state.Endpoint) bool {
   591  	service := context.services[ep.ApplicationName]
   592  	if service == nil {
   593  		return false
   594  	}
   595  	return isSubordinate(ep, service)
   596  }
   597  
   598  func isSubordinate(ep *state.Endpoint, service *state.Application) bool {
   599  	return ep.Scope == charm.ScopeContainer && !service.IsPrincipal()
   600  }
   601  
   602  // paramsJobsFromJobs converts state jobs to params jobs.
   603  func paramsJobsFromJobs(jobs []state.MachineJob) []multiwatcher.MachineJob {
   604  	paramsJobs := make([]multiwatcher.MachineJob, len(jobs))
   605  	for i, machineJob := range jobs {
   606  		paramsJobs[i] = machineJob.ToParams()
   607  	}
   608  	return paramsJobs
   609  }
   610  
   611  func (context *statusContext) processApplications() map[string]params.ApplicationStatus {
   612  	servicesMap := make(map[string]params.ApplicationStatus)
   613  	for _, s := range context.services {
   614  		servicesMap[s.Name()] = context.processApplication(s)
   615  	}
   616  	return servicesMap
   617  }
   618  
   619  func (context *statusContext) processApplication(service *state.Application) params.ApplicationStatus {
   620  	serviceCharm, _, err := service.Charm()
   621  	if err != nil {
   622  		return params.ApplicationStatus{Err: common.ServerError(err)}
   623  	}
   624  
   625  	var processedStatus = params.ApplicationStatus{
   626  		Charm:   serviceCharm.URL().String(),
   627  		Series:  service.Series(),
   628  		Exposed: service.IsExposed(),
   629  		Life:    processLife(service),
   630  	}
   631  
   632  	if latestCharm, ok := context.latestCharms[*serviceCharm.URL().WithRevision(-1)]; ok && latestCharm != nil {
   633  		if latestCharm.Revision() > serviceCharm.URL().Revision {
   634  			processedStatus.CanUpgradeTo = latestCharm.String()
   635  		}
   636  	}
   637  
   638  	processedStatus.Relations, processedStatus.SubordinateTo, err = context.processServiceRelations(service)
   639  	if err != nil {
   640  		processedStatus.Err = common.ServerError(err)
   641  		return processedStatus
   642  	}
   643  	units := context.units[service.Name()]
   644  	if service.IsPrincipal() {
   645  		processedStatus.Units = context.processUnits(units, serviceCharm.URL().String())
   646  	}
   647  	applicationStatus, err := service.Status()
   648  	if err != nil {
   649  		processedStatus.Err = common.ServerError(err)
   650  		return processedStatus
   651  	}
   652  	processedStatus.Status.Status = applicationStatus.Status.String()
   653  	processedStatus.Status.Info = applicationStatus.Message
   654  	processedStatus.Status.Data = applicationStatus.Data
   655  	processedStatus.Status.Since = applicationStatus.Since
   656  
   657  	metrics := serviceCharm.Metrics()
   658  	planRequired := metrics != nil && metrics.Plan != nil && metrics.Plan.Required
   659  	if planRequired || len(service.MetricCredentials()) > 0 {
   660  		processedStatus.MeterStatuses = context.processUnitMeterStatuses(units)
   661  	}
   662  
   663  	versions := make([]status.StatusInfo, 0, len(units))
   664  	for _, unit := range units {
   665  		statuses, err := unit.WorkloadVersionHistory().StatusHistory(
   666  			status.StatusHistoryFilter{Size: 1},
   667  		)
   668  		if err != nil {
   669  			processedStatus.Err = common.ServerError(err)
   670  			return processedStatus
   671  		}
   672  		// Even though we fully expect there to be historical values there,
   673  		// even the first should be the empty string, the status history
   674  		// collection is not added to in a transactional manner, so it may be
   675  		// not there even though we'd really like it to be. Such is mongo.
   676  		if len(statuses) > 0 {
   677  			versions = append(versions, statuses[0])
   678  		}
   679  	}
   680  	if len(versions) > 0 {
   681  		sort.Sort(bySinceDescending(versions))
   682  		processedStatus.WorkloadVersion = versions[0].Message
   683  	}
   684  
   685  	return processedStatus
   686  }
   687  
   688  func isColorStatus(code state.MeterStatusCode) bool {
   689  	return code == state.MeterGreen || code == state.MeterAmber || code == state.MeterRed
   690  }
   691  
   692  func (context *statusContext) processUnitMeterStatuses(units map[string]*state.Unit) map[string]params.MeterStatus {
   693  	unitsMap := make(map[string]params.MeterStatus)
   694  	for _, unit := range units {
   695  		meterStatus, err := unit.GetMeterStatus()
   696  		if err != nil {
   697  			continue
   698  		}
   699  		if isColorStatus(meterStatus.Code) {
   700  			unitsMap[unit.Name()] = params.MeterStatus{Color: strings.ToLower(meterStatus.Code.String()), Message: meterStatus.Info}
   701  		}
   702  	}
   703  	if len(unitsMap) > 0 {
   704  		return unitsMap
   705  	}
   706  	return nil
   707  }
   708  
   709  func (context *statusContext) processUnits(units map[string]*state.Unit, serviceCharm string) map[string]params.UnitStatus {
   710  	unitsMap := make(map[string]params.UnitStatus)
   711  	for _, unit := range units {
   712  		unitsMap[unit.Name()] = context.processUnit(unit, serviceCharm)
   713  	}
   714  	return unitsMap
   715  }
   716  
   717  func (context *statusContext) processUnit(unit *state.Unit, serviceCharm string) params.UnitStatus {
   718  	var result params.UnitStatus
   719  	addr, err := unit.PublicAddress()
   720  	if err != nil {
   721  		// Usually this indicates that no addresses have been set on the
   722  		// machine yet.
   723  		addr = network.Address{}
   724  		logger.Debugf("error fetching public address: %v", err)
   725  	}
   726  	result.PublicAddress = addr.Value
   727  	unitPorts, _ := unit.OpenedPorts()
   728  	for _, port := range unitPorts {
   729  		result.OpenedPorts = append(result.OpenedPorts, port.String())
   730  	}
   731  	if unit.IsPrincipal() {
   732  		result.Machine, _ = unit.AssignedMachineId()
   733  	}
   734  	curl, _ := unit.CharmURL()
   735  	if serviceCharm != "" && curl != nil && curl.String() != serviceCharm {
   736  		result.Charm = curl.String()
   737  	}
   738  	workloadVersion, err := unit.WorkloadVersion()
   739  	if err == nil {
   740  		result.WorkloadVersion = workloadVersion
   741  	} else {
   742  		logger.Debugf("error fetching workload version: %v", err)
   743  	}
   744  
   745  	processUnitAndAgentStatus(unit, &result)
   746  
   747  	if subUnits := unit.SubordinateNames(); len(subUnits) > 0 {
   748  		result.Subordinates = make(map[string]params.UnitStatus)
   749  		for _, name := range subUnits {
   750  			subUnit := context.unitByName(name)
   751  			// subUnit may be nil if subordinate was filtered out.
   752  			if subUnit != nil {
   753  				result.Subordinates[name] = context.processUnit(subUnit, serviceCharm)
   754  			}
   755  		}
   756  	}
   757  	if leader := context.leaders[unit.ApplicationName()]; leader == unit.Name() {
   758  		result.Leader = true
   759  	}
   760  	return result
   761  }
   762  
   763  func (context *statusContext) unitByName(name string) *state.Unit {
   764  	serviceName := strings.Split(name, "/")[0]
   765  	return context.units[serviceName][name]
   766  }
   767  
   768  func (context *statusContext) processServiceRelations(service *state.Application) (related map[string][]string, subord []string, err error) {
   769  	subordSet := make(set.Strings)
   770  	related = make(map[string][]string)
   771  	relations := context.relations[service.Name()]
   772  	for _, relation := range relations {
   773  		ep, err := relation.Endpoint(service.Name())
   774  		if err != nil {
   775  			return nil, nil, err
   776  		}
   777  		relationName := ep.Relation.Name
   778  		eps, err := relation.RelatedEndpoints(service.Name())
   779  		if err != nil {
   780  			return nil, nil, err
   781  		}
   782  		for _, ep := range eps {
   783  			if isSubordinate(&ep, service) {
   784  				subordSet.Add(ep.ApplicationName)
   785  			}
   786  			related[relationName] = append(related[relationName], ep.ApplicationName)
   787  		}
   788  	}
   789  	for relationName, serviceNames := range related {
   790  		sn := set.NewStrings(serviceNames...)
   791  		related[relationName] = sn.SortedValues()
   792  	}
   793  	return related, subordSet.SortedValues(), nil
   794  }
   795  
   796  type lifer interface {
   797  	Life() state.Life
   798  }
   799  
   800  // processUnitAndAgentStatus retrieves status information for both unit and unitAgents.
   801  func processUnitAndAgentStatus(unit *state.Unit, unitStatus *params.UnitStatus) {
   802  	unitStatus.AgentStatus, unitStatus.WorkloadStatus = processUnit(unit)
   803  }
   804  
   805  // populateStatusFromStatusInfoAndErr creates AgentStatus from the typical output
   806  // of a status getter.
   807  func populateStatusFromStatusInfoAndErr(agent *params.DetailedStatus, statusInfo status.StatusInfo, err error) {
   808  	agent.Err = err
   809  	agent.Status = statusInfo.Status.String()
   810  	agent.Info = statusInfo.Message
   811  	agent.Data = filterStatusData(statusInfo.Data)
   812  	agent.Since = statusInfo.Since
   813  }
   814  
   815  // processMachine retrieves version and status information for the given machine.
   816  // It also returns deprecated legacy status information.
   817  func processMachine(machine *state.Machine) (out params.DetailedStatus) {
   818  	statusInfo, err := common.MachineStatus(machine)
   819  	populateStatusFromStatusInfoAndErr(&out, statusInfo, err)
   820  
   821  	out.Life = processLife(machine)
   822  
   823  	if t, err := machine.AgentTools(); err == nil {
   824  		out.Version = t.Version.Number.String()
   825  	}
   826  	return
   827  }
   828  
   829  // processUnit retrieves version and status information for the given unit.
   830  func processUnit(unit *state.Unit) (agentStatus, workloadStatus params.DetailedStatus) {
   831  	agent, workload := common.UnitStatus(unit)
   832  	populateStatusFromStatusInfoAndErr(&agentStatus, agent.Status, agent.Err)
   833  	populateStatusFromStatusInfoAndErr(&workloadStatus, workload.Status, workload.Err)
   834  
   835  	agentStatus.Life = processLife(unit)
   836  
   837  	if t, err := unit.AgentTools(); err == nil {
   838  		agentStatus.Version = t.Version.Number.String()
   839  	}
   840  	return
   841  }
   842  
   843  // filterStatusData limits what agent StatusData data is passed over
   844  // the API. This prevents unintended leakage of internal-only data.
   845  func filterStatusData(status map[string]interface{}) map[string]interface{} {
   846  	out := make(map[string]interface{})
   847  	for name, value := range status {
   848  		// use a set here if we end up with a larger whitelist
   849  		if name == "relation-id" {
   850  			out[name] = value
   851  		}
   852  	}
   853  	return out
   854  }
   855  
   856  func processLife(entity lifer) string {
   857  	if life := entity.Life(); life != state.Alive {
   858  		// alive is the usual state so omit it by default.
   859  		return life.String()
   860  	}
   861  	return ""
   862  }
   863  
   864  type bySinceDescending []status.StatusInfo
   865  
   866  // Len implements sort.Interface.
   867  func (s bySinceDescending) Len() int { return len(s) }
   868  
   869  // Swap implements sort.Interface.
   870  func (s bySinceDescending) Swap(a, b int) { s[a], s[b] = s[b], s[a] }
   871  
   872  // Less implements sort.Interface.
   873  func (s bySinceDescending) Less(a, b int) bool { return s[a].Since.After(*s[b].Since) }