github.com/Pankov404/juju@v0.0.0-20150703034450-be266991dceb/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.v5"
    14  	"gopkg.in/juju/charm.v5/hooks"
    15  
    16  	"github.com/juju/juju/api"
    17  	"github.com/juju/juju/apiserver/params"
    18  	"github.com/juju/juju/constraints"
    19  	"github.com/juju/juju/network"
    20  	"github.com/juju/juju/state"
    21  	"github.com/juju/juju/state/multiwatcher"
    22  	"github.com/juju/juju/worker/uniter/operation"
    23  )
    24  
    25  func agentStatusFromStatusInfo(s []state.StatusInfo, kind params.HistoryKind) []api.AgentStatus {
    26  	result := []api.AgentStatus{}
    27  	for _, v := range s {
    28  		result = append(result, api.AgentStatus{
    29  			Status: params.Status(v.Status),
    30  			Info:   v.Message,
    31  			Data:   v.Data,
    32  			Since:  v.Since,
    33  			Kind:   kind,
    34  		})
    35  	}
    36  	return result
    37  
    38  }
    39  
    40  type sortableStatuses []api.AgentStatus
    41  
    42  func (s sortableStatuses) Len() int {
    43  	return len(s)
    44  }
    45  func (s sortableStatuses) Swap(i, j int) {
    46  	s[i], s[j] = s[j], s[i]
    47  }
    48  func (s sortableStatuses) Less(i, j int) bool {
    49  	return s[i].Since.Before(*s[j].Since)
    50  }
    51  
    52  // TODO(perrito666) this client method requires more testing, only its parts are unittested.
    53  // UnitStatusHistory returns a slice of past statuses for a given unit.
    54  func (c *Client) UnitStatusHistory(args params.StatusHistory) (api.UnitStatusHistory, error) {
    55  	size := args.Size - 1
    56  	if size < 1 {
    57  		return api.UnitStatusHistory{}, errors.Errorf("invalid history size: %d", args.Size)
    58  	}
    59  	unit, err := c.api.state.Unit(args.Name)
    60  	if err != nil {
    61  		return api.UnitStatusHistory{}, errors.Trace(err)
    62  	}
    63  	statuses := api.UnitStatusHistory{}
    64  	if args.Kind == params.KindCombined || args.Kind == params.KindWorkload {
    65  		unitStatuses, err := unit.StatusHistory(size)
    66  		if err != nil {
    67  			return api.UnitStatusHistory{}, errors.Trace(err)
    68  		}
    69  
    70  		current, err := unit.Status()
    71  		if err != nil {
    72  			return api.UnitStatusHistory{}, errors.Trace(err)
    73  		}
    74  		unitStatuses = append(unitStatuses, current)
    75  
    76  		statuses.Statuses = append(statuses.Statuses, agentStatusFromStatusInfo(unitStatuses, params.KindWorkload)...)
    77  	}
    78  	if args.Kind == params.KindCombined || args.Kind == params.KindAgent {
    79  		agentEntity := unit.Agent()
    80  		agent, ok := agentEntity.(*state.UnitAgent)
    81  		if !ok {
    82  			return api.UnitStatusHistory{}, errors.Errorf("cannot obtain agent for %q", args.Name)
    83  		}
    84  		agentStatuses, err := agent.StatusHistory(size)
    85  		if err != nil {
    86  			return api.UnitStatusHistory{}, errors.Trace(err)
    87  		}
    88  
    89  		current, err := agent.Status()
    90  		if err != nil {
    91  			return api.UnitStatusHistory{}, errors.Trace(err)
    92  		}
    93  		agentStatuses = append(agentStatuses, current)
    94  
    95  		statuses.Statuses = append(statuses.Statuses, agentStatusFromStatusInfo(agentStatuses, params.KindAgent)...)
    96  	}
    97  
    98  	sort.Sort(sortableStatuses(statuses.Statuses))
    99  	if args.Kind == params.KindCombined {
   100  
   101  		if len(statuses.Statuses) > args.Size {
   102  			statuses.Statuses = statuses.Statuses[len(statuses.Statuses)-args.Size:]
   103  		}
   104  
   105  	}
   106  	return statuses, nil
   107  }
   108  
   109  // FullStatus gives the information needed for juju status over the api
   110  func (c *Client) FullStatus(args params.StatusParams) (api.Status, error) {
   111  	cfg, err := c.api.state.EnvironConfig()
   112  	if err != nil {
   113  		return api.Status{}, errors.Annotate(err, "could not get environ config")
   114  	}
   115  	var noStatus api.Status
   116  	var context statusContext
   117  	if context.services, context.units, context.latestCharms, err =
   118  		fetchAllServicesAndUnits(c.api.state, len(args.Patterns) <= 0); err != nil {
   119  		return noStatus, errors.Annotate(err, "could not fetch services and units")
   120  	} else if context.machines, err = fetchMachines(c.api.state, nil); err != nil {
   121  		return noStatus, errors.Annotate(err, "could not fetch machines")
   122  	} else if context.relations, err = fetchRelations(c.api.state); err != nil {
   123  		return noStatus, errors.Annotate(err, "could not fetch relations")
   124  	} else if context.networks, err = fetchNetworks(c.api.state); err != nil {
   125  		return noStatus, errors.Annotate(err, "could not fetch networks")
   126  	}
   127  
   128  	logger.Debugf("Services: %v", context.services)
   129  
   130  	if len(args.Patterns) > 0 {
   131  		predicate := BuildPredicateFor(args.Patterns)
   132  
   133  		// Filter units
   134  		unfilteredSvcs := make(set.Strings)
   135  		unfilteredMachines := make(set.Strings)
   136  		unitChainPredicate := UnitChainPredicateFn(predicate, context.unitByName)
   137  		for _, unitMap := range context.units {
   138  			for name, unit := range unitMap {
   139  				// Always start examining at the top-level. This
   140  				// prevents a situation where we filter a subordinate
   141  				// before we discover its parent is a match.
   142  				if !unit.IsPrincipal() {
   143  					continue
   144  				} else if matches, err := unitChainPredicate(unit); err != nil {
   145  					return noStatus, errors.Annotate(err, "could not filter units")
   146  				} else if !matches {
   147  					delete(unitMap, name)
   148  					continue
   149  				}
   150  
   151  				// Track which services are utilized by the units so
   152  				// that we can be sure to not filter that service out.
   153  				unfilteredSvcs.Add(unit.ServiceName())
   154  				machineId, err := unit.AssignedMachineId()
   155  				if err != nil {
   156  					return noStatus, err
   157  				}
   158  				unfilteredMachines.Add(machineId)
   159  			}
   160  		}
   161  
   162  		// Filter services
   163  		for svcName, svc := range context.services {
   164  			if unfilteredSvcs.Contains(svcName) {
   165  				// Don't filter services which have units that were
   166  				// not filtered.
   167  				continue
   168  			} else if matches, err := predicate(svc); err != nil {
   169  				return noStatus, errors.Annotate(err, "could not filter services")
   170  			} else if !matches {
   171  				delete(context.services, svcName)
   172  			}
   173  		}
   174  
   175  		// Filter machines
   176  		for status, machineList := range context.machines {
   177  			filteredList := make([]*state.Machine, 0, len(machineList))
   178  			for _, m := range machineList {
   179  				machineContainers, err := m.Containers()
   180  				if err != nil {
   181  					return noStatus, err
   182  				}
   183  				machineContainersSet := set.NewStrings(machineContainers...)
   184  
   185  				if unfilteredMachines.Contains(m.Id()) || !unfilteredMachines.Intersection(machineContainersSet).IsEmpty() {
   186  					// Don't filter machines which have an unfiltered
   187  					// unit running on them.
   188  					logger.Debugf("mid %s is hosting something.", m.Id())
   189  					filteredList = append(filteredList, m)
   190  					continue
   191  				} else if matches, err := predicate(m); err != nil {
   192  					return noStatus, errors.Annotate(err, "could not filter machines")
   193  				} else if matches {
   194  					filteredList = append(filteredList, m)
   195  				}
   196  			}
   197  			context.machines[status] = filteredList
   198  		}
   199  	}
   200  
   201  	return api.Status{
   202  		EnvironmentName: cfg.Name(),
   203  		Machines:        processMachines(context.machines),
   204  		Services:        context.processServices(),
   205  		Networks:        context.processNetworks(),
   206  		Relations:       context.processRelations(),
   207  	}, nil
   208  }
   209  
   210  // Status is a stub version of FullStatus that was introduced in 1.16
   211  func (c *Client) Status() (api.LegacyStatus, error) {
   212  	var legacyStatus api.LegacyStatus
   213  	status, err := c.FullStatus(params.StatusParams{})
   214  	if err != nil {
   215  		return legacyStatus, err
   216  	}
   217  
   218  	legacyStatus.Machines = make(map[string]api.LegacyMachineStatus)
   219  	for machineName, machineStatus := range status.Machines {
   220  		legacyStatus.Machines[machineName] = api.LegacyMachineStatus{
   221  			InstanceId: string(machineStatus.InstanceId),
   222  		}
   223  	}
   224  	return legacyStatus, nil
   225  }
   226  
   227  type statusContext struct {
   228  	// machines: top-level machine id -> list of machines nested in
   229  	// this machine.
   230  	machines map[string][]*state.Machine
   231  	// services: service name -> service
   232  	services     map[string]*state.Service
   233  	relations    map[string][]*state.Relation
   234  	units        map[string]map[string]*state.Unit
   235  	networks     map[string]*state.Network
   236  	latestCharms map[charm.URL]string
   237  }
   238  
   239  // fetchMachines returns a map from top level machine id to machines, where machines[0] is the host
   240  // machine and machines[1..n] are any containers (including nested ones).
   241  //
   242  // If machineIds is non-nil, only machines whose IDs are in the set are returned.
   243  func fetchMachines(st *state.State, machineIds set.Strings) (map[string][]*state.Machine, error) {
   244  	v := make(map[string][]*state.Machine)
   245  	machines, err := st.AllMachines()
   246  	if err != nil {
   247  		return nil, err
   248  	}
   249  	// AllMachines gives us machines sorted by id.
   250  	for _, m := range machines {
   251  		if machineIds != nil && !machineIds.Contains(m.Id()) {
   252  			continue
   253  		}
   254  		parentId, ok := m.ParentId()
   255  		if !ok {
   256  			// Only top level host machines go directly into the machine map.
   257  			v[m.Id()] = []*state.Machine{m}
   258  		} else {
   259  			topParentId := state.TopParentId(m.Id())
   260  			machines, ok := v[topParentId]
   261  			if !ok {
   262  				panic(fmt.Errorf("unexpected machine id %q", parentId))
   263  			}
   264  			machines = append(machines, m)
   265  			v[topParentId] = machines
   266  		}
   267  	}
   268  	return v, nil
   269  }
   270  
   271  // fetchAllServicesAndUnits returns a map from service name to service,
   272  // a map from service name to unit name to unit, and a map from base charm URL to latest URL.
   273  func fetchAllServicesAndUnits(
   274  	st *state.State,
   275  	matchAny bool,
   276  ) (map[string]*state.Service, map[string]map[string]*state.Unit, map[charm.URL]string, error) {
   277  
   278  	svcMap := make(map[string]*state.Service)
   279  	unitMap := make(map[string]map[string]*state.Unit)
   280  	latestCharms := make(map[charm.URL]string)
   281  	services, err := st.AllServices()
   282  	if err != nil {
   283  		return nil, nil, nil, err
   284  	}
   285  	for _, s := range services {
   286  		units, err := s.AllUnits()
   287  		if err != nil {
   288  			return nil, nil, nil, err
   289  		}
   290  		svcUnitMap := make(map[string]*state.Unit)
   291  		for _, u := range units {
   292  			svcUnitMap[u.Name()] = u
   293  		}
   294  		if matchAny || len(svcUnitMap) > 0 {
   295  			unitMap[s.Name()] = svcUnitMap
   296  			svcMap[s.Name()] = s
   297  			// Record the base URL for the service's charm so that
   298  			// the latest store revision can be looked up.
   299  			charmURL, _ := s.CharmURL()
   300  			if charmURL.Schema == "cs" {
   301  				latestCharms[*charmURL.WithRevision(-1)] = ""
   302  			}
   303  		}
   304  	}
   305  	for baseURL := range latestCharms {
   306  		ch, err := st.LatestPlaceholderCharm(&baseURL)
   307  		if errors.IsNotFound(err) {
   308  			continue
   309  		}
   310  		if err != nil {
   311  			return nil, nil, nil, err
   312  		}
   313  		latestCharms[baseURL] = ch.String()
   314  	}
   315  	return svcMap, unitMap, latestCharms, nil
   316  }
   317  
   318  // fetchUnitMachineIds returns a set of IDs for machines that
   319  // the specified units reside on, and those machines' ancestors.
   320  func fetchUnitMachineIds(units map[string]map[string]*state.Unit) (set.Strings, error) {
   321  	machineIds := make(set.Strings)
   322  	for _, svcUnitMap := range units {
   323  		for _, unit := range svcUnitMap {
   324  			if !unit.IsPrincipal() {
   325  				continue
   326  			}
   327  			mid, err := unit.AssignedMachineId()
   328  			if err != nil {
   329  				return nil, err
   330  			}
   331  			for mid != "" {
   332  				machineIds.Add(mid)
   333  				mid = state.ParentId(mid)
   334  			}
   335  		}
   336  	}
   337  	return machineIds, nil
   338  }
   339  
   340  // fetchRelations returns a map of all relations keyed by service name.
   341  //
   342  // This structure is useful for processServiceRelations() which needs
   343  // to have the relations for each service. Reading them once here
   344  // avoids the repeated DB hits to retrieve the relations for each
   345  // service that used to happen in processServiceRelations().
   346  func fetchRelations(st *state.State) (map[string][]*state.Relation, error) {
   347  	relations, err := st.AllRelations()
   348  	if err != nil {
   349  		return nil, err
   350  	}
   351  	out := make(map[string][]*state.Relation)
   352  	for _, relation := range relations {
   353  		for _, ep := range relation.Endpoints() {
   354  			out[ep.ServiceName] = append(out[ep.ServiceName], relation)
   355  		}
   356  	}
   357  	return out, nil
   358  }
   359  
   360  // fetchNetworks returns a map from network name to network.
   361  func fetchNetworks(st *state.State) (map[string]*state.Network, error) {
   362  	networks, err := st.AllNetworks()
   363  	if err != nil {
   364  		return nil, err
   365  	}
   366  	out := make(map[string]*state.Network)
   367  	for _, n := range networks {
   368  		out[n.Name()] = n
   369  	}
   370  	return out, nil
   371  }
   372  
   373  type machineAndContainers map[string][]*state.Machine
   374  
   375  func (m machineAndContainers) HostForMachineId(id string) *state.Machine {
   376  	// Element 0 is assumed to be the top-level machine.
   377  	return m[id][0]
   378  }
   379  
   380  func (m machineAndContainers) Containers(id string) []*state.Machine {
   381  	return m[id][1:]
   382  }
   383  
   384  func processMachines(idToMachines map[string][]*state.Machine) map[string]api.MachineStatus {
   385  	machinesMap := make(map[string]api.MachineStatus)
   386  	cache := make(map[string]api.MachineStatus)
   387  	for id, machines := range idToMachines {
   388  
   389  		if len(machines) <= 0 {
   390  			continue
   391  		}
   392  
   393  		// Element 0 is assumed to be the top-level machine.
   394  		hostStatus := makeMachineStatus(machines[0])
   395  		machinesMap[id] = hostStatus
   396  		cache[id] = hostStatus
   397  
   398  		for _, machine := range machines[1:] {
   399  			parent, ok := cache[state.ParentId(machine.Id())]
   400  			if !ok {
   401  				panic("We've broken an assumpution.")
   402  			}
   403  
   404  			status := makeMachineStatus(machine)
   405  			parent.Containers[machine.Id()] = status
   406  			cache[machine.Id()] = status
   407  		}
   408  	}
   409  	return machinesMap
   410  }
   411  
   412  func makeMachineStatus(machine *state.Machine) (status api.MachineStatus) {
   413  	status.Id = machine.Id()
   414  	agentStatus, compatStatus := processMachine(machine)
   415  	status.Agent = agentStatus
   416  
   417  	// These legacy status values will be deprecated for Juju 2.0.
   418  	status.AgentState = compatStatus.Status
   419  	status.AgentStateInfo = compatStatus.Info
   420  	status.AgentVersion = compatStatus.Version
   421  	status.Life = compatStatus.Life
   422  	status.Err = compatStatus.Err
   423  
   424  	status.Series = machine.Series()
   425  	status.Jobs = paramsJobsFromJobs(machine.Jobs())
   426  	status.WantsVote = machine.WantsVote()
   427  	status.HasVote = machine.HasVote()
   428  	instid, err := machine.InstanceId()
   429  	if err == nil {
   430  		status.InstanceId = instid
   431  		status.InstanceState, err = machine.InstanceStatus()
   432  		if err != nil {
   433  			status.InstanceState = "error"
   434  		}
   435  		status.DNSName = network.SelectPublicAddress(machine.Addresses())
   436  	} else {
   437  		if errors.IsNotProvisioned(err) {
   438  			status.InstanceId = "pending"
   439  		} else {
   440  			status.InstanceId = "error"
   441  		}
   442  		// There's no point in reporting a pending agent state
   443  		// if the machine hasn't been provisioned. This
   444  		// also makes unprovisioned machines visually distinct
   445  		// in the output.
   446  		status.AgentState = ""
   447  	}
   448  	hc, err := machine.HardwareCharacteristics()
   449  	if err != nil {
   450  		if !errors.IsNotFound(err) {
   451  			status.Hardware = "error"
   452  		}
   453  	} else {
   454  		status.Hardware = hc.String()
   455  	}
   456  	status.Containers = make(map[string]api.MachineStatus)
   457  	return
   458  }
   459  
   460  func (context *statusContext) processRelations() []api.RelationStatus {
   461  	var out []api.RelationStatus
   462  	relations := context.getAllRelations()
   463  	for _, relation := range relations {
   464  		var eps []api.EndpointStatus
   465  		var scope charm.RelationScope
   466  		var relationInterface string
   467  		for _, ep := range relation.Endpoints() {
   468  			eps = append(eps, api.EndpointStatus{
   469  				ServiceName: ep.ServiceName,
   470  				Name:        ep.Name,
   471  				Role:        ep.Role,
   472  				Subordinate: context.isSubordinate(&ep),
   473  			})
   474  			// these should match on both sides so use the last
   475  			relationInterface = ep.Interface
   476  			scope = ep.Scope
   477  		}
   478  		relStatus := api.RelationStatus{
   479  			Id:        relation.Id(),
   480  			Key:       relation.String(),
   481  			Interface: relationInterface,
   482  			Scope:     scope,
   483  			Endpoints: eps,
   484  		}
   485  		out = append(out, relStatus)
   486  	}
   487  	return out
   488  }
   489  
   490  // This method exists only to dedup the loaded relations as they will
   491  // appear multiple times in context.relations.
   492  func (context *statusContext) getAllRelations() []*state.Relation {
   493  	var out []*state.Relation
   494  	seenRelations := make(map[int]bool)
   495  	for _, relations := range context.relations {
   496  		for _, relation := range relations {
   497  			if _, found := seenRelations[relation.Id()]; !found {
   498  				out = append(out, relation)
   499  				seenRelations[relation.Id()] = true
   500  			}
   501  		}
   502  	}
   503  	return out
   504  }
   505  
   506  func (context *statusContext) processNetworks() map[string]api.NetworkStatus {
   507  	networksMap := make(map[string]api.NetworkStatus)
   508  	for name, network := range context.networks {
   509  		networksMap[name] = context.makeNetworkStatus(network)
   510  	}
   511  	return networksMap
   512  }
   513  
   514  func (context *statusContext) makeNetworkStatus(network *state.Network) api.NetworkStatus {
   515  	return api.NetworkStatus{
   516  		ProviderId: network.ProviderId(),
   517  		CIDR:       network.CIDR(),
   518  		VLANTag:    network.VLANTag(),
   519  	}
   520  }
   521  
   522  func (context *statusContext) isSubordinate(ep *state.Endpoint) bool {
   523  	service := context.services[ep.ServiceName]
   524  	if service == nil {
   525  		return false
   526  	}
   527  	return isSubordinate(ep, service)
   528  }
   529  
   530  func isSubordinate(ep *state.Endpoint, service *state.Service) bool {
   531  	return ep.Scope == charm.ScopeContainer && !service.IsPrincipal()
   532  }
   533  
   534  // paramsJobsFromJobs converts state jobs to params jobs.
   535  func paramsJobsFromJobs(jobs []state.MachineJob) []multiwatcher.MachineJob {
   536  	paramsJobs := make([]multiwatcher.MachineJob, len(jobs))
   537  	for i, machineJob := range jobs {
   538  		paramsJobs[i] = machineJob.ToParams()
   539  	}
   540  	return paramsJobs
   541  }
   542  
   543  func (context *statusContext) processServices() map[string]api.ServiceStatus {
   544  	servicesMap := make(map[string]api.ServiceStatus)
   545  	for _, s := range context.services {
   546  		servicesMap[s.Name()] = context.processService(s)
   547  	}
   548  	return servicesMap
   549  }
   550  
   551  func (context *statusContext) processService(service *state.Service) (status api.ServiceStatus) {
   552  	serviceCharmURL, _ := service.CharmURL()
   553  	status.Charm = serviceCharmURL.String()
   554  	status.Exposed = service.IsExposed()
   555  	status.Life = processLife(service)
   556  
   557  	latestCharm, ok := context.latestCharms[*serviceCharmURL.WithRevision(-1)]
   558  	if ok && latestCharm != serviceCharmURL.String() {
   559  		status.CanUpgradeTo = latestCharm
   560  	}
   561  	var err error
   562  	status.Relations, status.SubordinateTo, err = context.processServiceRelations(service)
   563  	if err != nil {
   564  		status.Err = err
   565  		return
   566  	}
   567  	networks, err := service.Networks()
   568  	if err != nil {
   569  		status.Err = err
   570  		return
   571  	}
   572  	var cons constraints.Value
   573  	if service.IsPrincipal() {
   574  		// Only principals can have constraints.
   575  		cons, err = service.Constraints()
   576  		if err != nil {
   577  			status.Err = err
   578  			return
   579  		}
   580  	}
   581  	if len(networks) > 0 || cons.HaveNetworks() {
   582  		// Only the explicitly requested networks (using "juju deploy
   583  		// <svc> --networks=...") will be enabled, and altough when
   584  		// specified, networks constraints will be used for instance
   585  		// selection, they won't be actually enabled.
   586  		status.Networks = api.NetworksSpecification{
   587  			Enabled:  networks,
   588  			Disabled: append(cons.IncludeNetworks(), cons.ExcludeNetworks()...),
   589  		}
   590  	}
   591  	if service.IsPrincipal() {
   592  		status.Units = context.processUnits(context.units[service.Name()], serviceCharmURL.String())
   593  		serviceStatus, err := service.Status()
   594  		if err != nil {
   595  			status.Err = err
   596  			return
   597  		}
   598  		status.Status.Status = params.Status(serviceStatus.Status)
   599  		status.Status.Info = serviceStatus.Message
   600  		status.Status.Data = serviceStatus.Data
   601  		status.Status.Since = serviceStatus.Since
   602  	}
   603  	return status
   604  }
   605  
   606  func (context *statusContext) processUnits(units map[string]*state.Unit, serviceCharm string) map[string]api.UnitStatus {
   607  	unitsMap := make(map[string]api.UnitStatus)
   608  	for _, unit := range units {
   609  		unitsMap[unit.Name()] = context.processUnit(unit, serviceCharm)
   610  	}
   611  	return unitsMap
   612  }
   613  
   614  func (context *statusContext) processUnit(unit *state.Unit, serviceCharm string) api.UnitStatus {
   615  	var result api.UnitStatus
   616  	result.PublicAddress, _ = unit.PublicAddress()
   617  	unitPorts, _ := unit.OpenedPorts()
   618  	for _, port := range unitPorts {
   619  		result.OpenedPorts = append(result.OpenedPorts, port.String())
   620  	}
   621  	if unit.IsPrincipal() {
   622  		result.Machine, _ = unit.AssignedMachineId()
   623  	}
   624  	curl, _ := unit.CharmURL()
   625  	if serviceCharm != "" && curl != nil && curl.String() != serviceCharm {
   626  		result.Charm = curl.String()
   627  	}
   628  	processUnitAndAgentStatus(unit, &result)
   629  
   630  	if subUnits := unit.SubordinateNames(); len(subUnits) > 0 {
   631  		result.Subordinates = make(map[string]api.UnitStatus)
   632  		for _, name := range subUnits {
   633  			subUnit := context.unitByName(name)
   634  			// subUnit may be nil if subordinate was filtered out.
   635  			if subUnit != nil {
   636  				result.Subordinates[name] = context.processUnit(subUnit, serviceCharm)
   637  			}
   638  		}
   639  	}
   640  	return result
   641  }
   642  
   643  func (context *statusContext) unitByName(name string) *state.Unit {
   644  	serviceName := strings.Split(name, "/")[0]
   645  	return context.units[serviceName][name]
   646  }
   647  
   648  func (context *statusContext) processServiceRelations(service *state.Service) (related map[string][]string, subord []string, err error) {
   649  	subordSet := make(set.Strings)
   650  	related = make(map[string][]string)
   651  	relations := context.relations[service.Name()]
   652  	for _, relation := range relations {
   653  		ep, err := relation.Endpoint(service.Name())
   654  		if err != nil {
   655  			return nil, nil, err
   656  		}
   657  		relationName := ep.Relation.Name
   658  		eps, err := relation.RelatedEndpoints(service.Name())
   659  		if err != nil {
   660  			return nil, nil, err
   661  		}
   662  		for _, ep := range eps {
   663  			if isSubordinate(&ep, service) {
   664  				subordSet.Add(ep.ServiceName)
   665  			}
   666  			related[relationName] = append(related[relationName], ep.ServiceName)
   667  		}
   668  	}
   669  	for relationName, serviceNames := range related {
   670  		sn := set.NewStrings(serviceNames...)
   671  		related[relationName] = sn.SortedValues()
   672  	}
   673  	return related, subordSet.SortedValues(), nil
   674  }
   675  
   676  type lifer interface {
   677  	Life() state.Life
   678  }
   679  
   680  // processUnitAndAgentStatus retrieves status information for both unit and unitAgents.
   681  func processUnitAndAgentStatus(unit *state.Unit, status *api.UnitStatus) {
   682  	status.UnitAgent, status.Workload = processUnitStatus(unit)
   683  
   684  	// Legacy fields required until Juju 2.0.
   685  	// We only display pending, started, error, stopped.
   686  	var ok bool
   687  	legacyState, ok := state.TranslateToLegacyAgentState(
   688  		state.Status(status.UnitAgent.Status),
   689  		state.Status(status.Workload.Status),
   690  		status.Workload.Info,
   691  	)
   692  	if !ok {
   693  		logger.Warningf(
   694  			"translate to legacy status encounted unexpected workload status %q and agent status %q",
   695  			status.Workload.Status, status.UnitAgent.Status)
   696  	}
   697  	status.AgentState = params.Status(legacyState)
   698  	if status.AgentState == params.StatusError {
   699  		status.AgentStateInfo = status.Workload.Info
   700  	}
   701  	status.AgentVersion = status.UnitAgent.Version
   702  	status.Life = status.UnitAgent.Life
   703  	status.Err = status.UnitAgent.Err
   704  
   705  	processUnitLost(unit, status)
   706  
   707  	return
   708  }
   709  
   710  // makeStatusForEntity creates status information for machines, units.
   711  func makeStatusForEntity(agent *api.AgentStatus, getter state.StatusGetter) {
   712  	statusInfo, err := getter.Status()
   713  	agent.Err = err
   714  	agent.Status = params.Status(statusInfo.Status)
   715  	agent.Info = statusInfo.Message
   716  	agent.Data = filterStatusData(statusInfo.Data)
   717  	agent.Since = statusInfo.Since
   718  }
   719  
   720  // processMachine retrieves version and status information for the given machine.
   721  // It also returns deprecated legacy status information.
   722  func processMachine(machine *state.Machine) (out api.AgentStatus, compat api.AgentStatus) {
   723  	out.Life = processLife(machine)
   724  
   725  	if t, err := machine.AgentTools(); err == nil {
   726  		out.Version = t.Version.Number.String()
   727  	}
   728  
   729  	makeStatusForEntity(&out, machine)
   730  	compat = out
   731  
   732  	if out.Err != nil {
   733  		return
   734  	}
   735  	if out.Status == params.StatusPending {
   736  		// The status is pending - there's no point
   737  		// in enquiring about the agent liveness.
   738  		return
   739  	}
   740  	agentAlive, err := machine.AgentPresence()
   741  	if err != nil {
   742  		return
   743  	}
   744  
   745  	if machine.Life() != state.Dead && !agentAlive {
   746  		// The agent *should* be alive but is not. Set status to
   747  		// StatusDown and munge Info to indicate the previous status and
   748  		// info. This is unfortunately making presentation decisions
   749  		// on behalf of the client (crappy).
   750  		//
   751  		// This is munging is only being left in place for
   752  		// compatibility with older clients.  TODO: At some point we
   753  		// should change this so that Info left alone. API version may
   754  		// help here.
   755  		//
   756  		// Better yet, Status shouldn't be changed here in the API at
   757  		// all! Status changes should only happen in State. One
   758  		// problem caused by this is that this status change won't be
   759  		// seen by clients using a watcher because it didn't happen in
   760  		// State.
   761  		if out.Info != "" {
   762  			compat.Info = fmt.Sprintf("(%s: %s)", out.Status, out.Info)
   763  		} else {
   764  			compat.Info = fmt.Sprintf("(%s)", out.Status)
   765  		}
   766  		compat.Status = params.StatusDown
   767  	}
   768  
   769  	return
   770  }
   771  
   772  // processUnit retrieves version and status information for the given unit.
   773  func processUnitStatus(unit *state.Unit) (agentStatus, workloadStatus api.AgentStatus) {
   774  	// First determine the agent status information.
   775  	unitAgent := unit.Agent().(*state.UnitAgent)
   776  	makeStatusForEntity(&agentStatus, unitAgent)
   777  	agentStatus.Life = processLife(unit)
   778  	if t, err := unit.AgentTools(); err == nil {
   779  		agentStatus.Version = t.Version.Number.String()
   780  	}
   781  
   782  	// Second, determine the workload (unit) status.
   783  	makeStatusForEntity(&workloadStatus, unit)
   784  	return
   785  }
   786  
   787  func canBeLost(status *api.UnitStatus) bool {
   788  	// Pending and Installing are deprecated.
   789  	// Need to still check pending for existing deployments.
   790  	switch status.UnitAgent.Status {
   791  	case params.StatusPending, params.StatusInstalling, params.StatusAllocating:
   792  		return false
   793  	case params.StatusExecuting:
   794  		return status.UnitAgent.Info != operation.RunningHookMessage(string(hooks.Install))
   795  	}
   796  	// TODO(wallyworld) - use status history to see if start hook has run.
   797  	isInstalled := status.Workload.Status != params.StatusMaintenance || status.Workload.Info != state.MessageInstalling
   798  	return isInstalled
   799  }
   800  
   801  // processUnitLost determines whether the given unit should be marked as lost.
   802  // TODO(wallyworld) - move this to state and the canBeLost() code can be simplified.
   803  func processUnitLost(unit *state.Unit, status *api.UnitStatus) {
   804  	if !canBeLost(status) {
   805  		// The status is allocating or installing - there's no point
   806  		// in enquiring about the agent liveness.
   807  		return
   808  	}
   809  	agentAlive, err := unit.AgentPresence()
   810  	if err != nil {
   811  		return
   812  	}
   813  
   814  	if unit.Life() != state.Dead && !agentAlive {
   815  		// If the unit is in error, it would be bad to throw away
   816  		// the error information as when the agent reconnects, that
   817  		// error information would then be lost.
   818  		if status.Workload.Status != params.StatusError {
   819  			status.Workload.Status = params.StatusUnknown
   820  			status.Workload.Info = fmt.Sprintf("agent is lost, sorry! See 'juju status-history %s'", unit.Name())
   821  		}
   822  		status.UnitAgent.Status = params.StatusLost
   823  		status.UnitAgent.Info = "agent is not communicating with the server"
   824  	}
   825  }
   826  
   827  // filterStatusData limits what agent StatusData data is passed over
   828  // the API. This prevents unintended leakage of internal-only data.
   829  func filterStatusData(status map[string]interface{}) map[string]interface{} {
   830  	out := make(map[string]interface{})
   831  	for name, value := range status {
   832  		// use a set here if we end up with a larger whitelist
   833  		if name == "relation-id" {
   834  			out[name] = value
   835  		}
   836  	}
   837  	return out
   838  }
   839  
   840  func processLife(entity lifer) string {
   841  	if life := entity.Life(); life != state.Alive {
   842  		// alive is the usual state so omit it by default.
   843  		return life.String()
   844  	}
   845  	return ""
   846  }