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