github.com/altoros/juju-vmware@v0.0.0-20150312064031-f19ae857ccca/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  	"strings"
     9  
    10  	"github.com/juju/errors"
    11  	"github.com/juju/utils/set"
    12  	"gopkg.in/juju/charm.v4"
    13  
    14  	"github.com/juju/juju/api"
    15  	"github.com/juju/juju/apiserver/params"
    16  	"github.com/juju/juju/constraints"
    17  	"github.com/juju/juju/network"
    18  	"github.com/juju/juju/state"
    19  	"github.com/juju/juju/state/multiwatcher"
    20  	"github.com/juju/juju/tools"
    21  )
    22  
    23  // FullStatus gives the information needed for juju status over the api
    24  func (c *Client) FullStatus(args params.StatusParams) (api.Status, error) {
    25  	cfg, err := c.api.state.EnvironConfig()
    26  	if err != nil {
    27  		return api.Status{}, errors.Annotate(err, "could not get environ config")
    28  	}
    29  	var noStatus api.Status
    30  	var context statusContext
    31  	if context.services, context.units, context.latestCharms, err =
    32  		fetchAllServicesAndUnits(c.api.state, len(args.Patterns) <= 0); err != nil {
    33  		return noStatus, errors.Annotate(err, "could not fetch services and units")
    34  	} else if context.machines, err = fetchMachines(c.api.state, nil); err != nil {
    35  		return noStatus, errors.Annotate(err, "could not fetch machines")
    36  	} else if context.relations, err = fetchRelations(c.api.state); err != nil {
    37  		return noStatus, errors.Annotate(err, "could not fetch relations")
    38  	} else if context.networks, err = fetchNetworks(c.api.state); err != nil {
    39  		return noStatus, errors.Annotate(err, "could not fetch networks")
    40  	}
    41  
    42  	logger.Debugf("Services: %v", context.services)
    43  
    44  	if len(args.Patterns) > 0 {
    45  		predicate := BuildPredicateFor(args.Patterns)
    46  
    47  		// Filter units
    48  		unfilteredSvcs := make(set.Strings)
    49  		unfilteredMachines := make(set.Strings)
    50  		unitChainPredicate := UnitChainPredicateFn(predicate, context.unitByName)
    51  		for _, unitMap := range context.units {
    52  			for name, unit := range unitMap {
    53  				// Always start examining at the top-level. This
    54  				// prevents a situation where we filter a subordinate
    55  				// before we discover its parent is a match.
    56  				if !unit.IsPrincipal() {
    57  					continue
    58  				} else if matches, err := unitChainPredicate(unit); err != nil {
    59  					return noStatus, errors.Annotate(err, "could not filter units")
    60  				} else if !matches {
    61  					delete(unitMap, name)
    62  					continue
    63  				}
    64  
    65  				// Track which services are utilized by the units so
    66  				// that we can be sure to not filter that service out.
    67  				unfilteredSvcs.Add(unit.ServiceName())
    68  				machineId, err := unit.AssignedMachineId()
    69  				if err != nil {
    70  					return noStatus, err
    71  				}
    72  				unfilteredMachines.Add(machineId)
    73  			}
    74  		}
    75  
    76  		// Filter services
    77  		for svcName, svc := range context.services {
    78  			if unfilteredSvcs.Contains(svcName) {
    79  				// Don't filter services which have units that were
    80  				// not filtered.
    81  				continue
    82  			} else if matches, err := predicate(svc); err != nil {
    83  				return noStatus, errors.Annotate(err, "could not filter services")
    84  			} else if !matches {
    85  				delete(context.services, svcName)
    86  			}
    87  		}
    88  
    89  		// Filter machines
    90  		for status, machineList := range context.machines {
    91  			filteredList := make([]*state.Machine, 0, len(machineList))
    92  			for _, m := range machineList {
    93  				machineContainers, err := m.Containers()
    94  				if err != nil {
    95  					return noStatus, err
    96  				}
    97  				machineContainersSet := set.NewStrings(machineContainers...)
    98  
    99  				if unfilteredMachines.Contains(m.Id()) || !unfilteredMachines.Intersection(machineContainersSet).IsEmpty() {
   100  					// Don't filter machines which have an unfiltered
   101  					// unit running on them.
   102  					logger.Debugf("mid %s is hosting something.", m.Id())
   103  					filteredList = append(filteredList, m)
   104  					continue
   105  				} else if matches, err := predicate(m); err != nil {
   106  					return noStatus, errors.Annotate(err, "could not filter machines")
   107  				} else if matches {
   108  					filteredList = append(filteredList, m)
   109  				}
   110  			}
   111  			context.machines[status] = filteredList
   112  		}
   113  	}
   114  
   115  	return api.Status{
   116  		EnvironmentName: cfg.Name(),
   117  		Machines:        processMachines(context.machines),
   118  		Services:        context.processServices(),
   119  		Networks:        context.processNetworks(),
   120  		Relations:       context.processRelations(),
   121  	}, nil
   122  }
   123  
   124  // Status is a stub version of FullStatus that was introduced in 1.16
   125  func (c *Client) Status() (api.LegacyStatus, error) {
   126  	var legacyStatus api.LegacyStatus
   127  	status, err := c.FullStatus(params.StatusParams{})
   128  	if err != nil {
   129  		return legacyStatus, err
   130  	}
   131  
   132  	legacyStatus.Machines = make(map[string]api.LegacyMachineStatus)
   133  	for machineName, machineStatus := range status.Machines {
   134  		legacyStatus.Machines[machineName] = api.LegacyMachineStatus{
   135  			InstanceId: string(machineStatus.InstanceId),
   136  		}
   137  	}
   138  	return legacyStatus, nil
   139  }
   140  
   141  type statusContext struct {
   142  	// machines: top-level machine id -> list of machines nested in
   143  	// this machine.
   144  	machines map[string][]*state.Machine
   145  	// services: service name -> service
   146  	services     map[string]*state.Service
   147  	relations    map[string][]*state.Relation
   148  	units        map[string]map[string]*state.Unit
   149  	networks     map[string]*state.Network
   150  	latestCharms map[charm.URL]string
   151  }
   152  
   153  // fetchMachines returns a map from top level machine id to machines, where machines[0] is the host
   154  // machine and machines[1..n] are any containers (including nested ones).
   155  //
   156  // If machineIds is non-nil, only machines whose IDs are in the set are returned.
   157  func fetchMachines(st *state.State, machineIds set.Strings) (map[string][]*state.Machine, error) {
   158  	v := make(map[string][]*state.Machine)
   159  	machines, err := st.AllMachines()
   160  	if err != nil {
   161  		return nil, err
   162  	}
   163  	// AllMachines gives us machines sorted by id.
   164  	for _, m := range machines {
   165  		if machineIds != nil && !machineIds.Contains(m.Id()) {
   166  			continue
   167  		}
   168  		parentId, ok := m.ParentId()
   169  		if !ok {
   170  			// Only top level host machines go directly into the machine map.
   171  			v[m.Id()] = []*state.Machine{m}
   172  		} else {
   173  			topParentId := state.TopParentId(m.Id())
   174  			machines, ok := v[topParentId]
   175  			if !ok {
   176  				panic(fmt.Errorf("unexpected machine id %q", parentId))
   177  			}
   178  			machines = append(machines, m)
   179  			v[topParentId] = machines
   180  		}
   181  	}
   182  	return v, nil
   183  }
   184  
   185  // fetchAllServicesAndUnits returns a map from service name to service,
   186  // a map from service name to unit name to unit, and a map from base charm URL to latest URL.
   187  func fetchAllServicesAndUnits(
   188  	st *state.State,
   189  	matchAny bool,
   190  ) (map[string]*state.Service, map[string]map[string]*state.Unit, map[charm.URL]string, error) {
   191  
   192  	svcMap := make(map[string]*state.Service)
   193  	unitMap := make(map[string]map[string]*state.Unit)
   194  	latestCharms := make(map[charm.URL]string)
   195  	services, err := st.AllServices()
   196  	if err != nil {
   197  		return nil, nil, nil, err
   198  	}
   199  	for _, s := range services {
   200  		units, err := s.AllUnits()
   201  		if err != nil {
   202  			return nil, nil, nil, err
   203  		}
   204  		svcUnitMap := make(map[string]*state.Unit)
   205  		for _, u := range units {
   206  			svcUnitMap[u.Name()] = u
   207  		}
   208  		if matchAny || len(svcUnitMap) > 0 {
   209  			unitMap[s.Name()] = svcUnitMap
   210  			svcMap[s.Name()] = s
   211  			// Record the base URL for the service's charm so that
   212  			// the latest store revision can be looked up.
   213  			charmURL, _ := s.CharmURL()
   214  			if charmURL.Schema == "cs" {
   215  				latestCharms[*charmURL.WithRevision(-1)] = ""
   216  			}
   217  		}
   218  	}
   219  	for baseURL := range latestCharms {
   220  		ch, err := st.LatestPlaceholderCharm(&baseURL)
   221  		if errors.IsNotFound(err) {
   222  			continue
   223  		}
   224  		if err != nil {
   225  			return nil, nil, nil, err
   226  		}
   227  		latestCharms[baseURL] = ch.String()
   228  	}
   229  	return svcMap, unitMap, latestCharms, nil
   230  }
   231  
   232  // fetchUnitMachineIds returns a set of IDs for machines that
   233  // the specified units reside on, and those machines' ancestors.
   234  func fetchUnitMachineIds(units map[string]map[string]*state.Unit) (set.Strings, error) {
   235  	machineIds := make(set.Strings)
   236  	for _, svcUnitMap := range units {
   237  		for _, unit := range svcUnitMap {
   238  			if !unit.IsPrincipal() {
   239  				continue
   240  			}
   241  			mid, err := unit.AssignedMachineId()
   242  			if err != nil {
   243  				return nil, err
   244  			}
   245  			for mid != "" {
   246  				machineIds.Add(mid)
   247  				mid = state.ParentId(mid)
   248  			}
   249  		}
   250  	}
   251  	return machineIds, nil
   252  }
   253  
   254  // fetchRelations returns a map of all relations keyed by service name.
   255  //
   256  // This structure is useful for processServiceRelations() which needs
   257  // to have the relations for each service. Reading them once here
   258  // avoids the repeated DB hits to retrieve the relations for each
   259  // service that used to happen in processServiceRelations().
   260  func fetchRelations(st *state.State) (map[string][]*state.Relation, error) {
   261  	relations, err := st.AllRelations()
   262  	if err != nil {
   263  		return nil, err
   264  	}
   265  	out := make(map[string][]*state.Relation)
   266  	for _, relation := range relations {
   267  		for _, ep := range relation.Endpoints() {
   268  			out[ep.ServiceName] = append(out[ep.ServiceName], relation)
   269  		}
   270  	}
   271  	return out, nil
   272  }
   273  
   274  // fetchNetworks returns a map from network name to network.
   275  func fetchNetworks(st *state.State) (map[string]*state.Network, error) {
   276  	networks, err := st.AllNetworks()
   277  	if err != nil {
   278  		return nil, err
   279  	}
   280  	out := make(map[string]*state.Network)
   281  	for _, n := range networks {
   282  		out[n.Name()] = n
   283  	}
   284  	return out, nil
   285  }
   286  
   287  type machineAndContainers map[string][]*state.Machine
   288  
   289  func (m machineAndContainers) HostForMachineId(id string) *state.Machine {
   290  	// Element 0 is assumed to be the top-level machine.
   291  	return m[id][0]
   292  }
   293  
   294  func (m machineAndContainers) Containers(id string) []*state.Machine {
   295  	return m[id][1:]
   296  }
   297  
   298  func processMachines(idToMachines map[string][]*state.Machine) map[string]api.MachineStatus {
   299  	machinesMap := make(map[string]api.MachineStatus)
   300  	cache := make(map[string]api.MachineStatus)
   301  	for id, machines := range idToMachines {
   302  
   303  		if len(machines) <= 0 {
   304  			continue
   305  		}
   306  
   307  		// Element 0 is assumed to be the top-level machine.
   308  		hostStatus := makeMachineStatus(machines[0])
   309  		machinesMap[id] = hostStatus
   310  		cache[id] = hostStatus
   311  
   312  		for _, machine := range machines[1:] {
   313  			parent, ok := cache[state.ParentId(machine.Id())]
   314  			if !ok {
   315  				panic("We've broken an assumpution.")
   316  			}
   317  
   318  			status := makeMachineStatus(machine)
   319  			parent.Containers[machine.Id()] = status
   320  			cache[machine.Id()] = status
   321  		}
   322  	}
   323  	return machinesMap
   324  }
   325  
   326  func makeMachineStatus(machine *state.Machine) (status api.MachineStatus) {
   327  	status.Id = machine.Id()
   328  	status.Agent, status.AgentState, status.AgentStateInfo = processAgent(machine)
   329  	status.AgentVersion = status.Agent.Version
   330  	status.Life = status.Agent.Life
   331  	status.Err = status.Agent.Err
   332  	status.Series = machine.Series()
   333  	status.Jobs = paramsJobsFromJobs(machine.Jobs())
   334  	status.WantsVote = machine.WantsVote()
   335  	status.HasVote = machine.HasVote()
   336  	instid, err := machine.InstanceId()
   337  	if err == nil {
   338  		status.InstanceId = instid
   339  		status.InstanceState, err = machine.InstanceStatus()
   340  		if err != nil {
   341  			status.InstanceState = "error"
   342  		}
   343  		status.DNSName = network.SelectPublicAddress(machine.Addresses())
   344  	} else {
   345  		if errors.IsNotProvisioned(err) {
   346  			status.InstanceId = "pending"
   347  		} else {
   348  			status.InstanceId = "error"
   349  		}
   350  		// There's no point in reporting a pending agent state
   351  		// if the machine hasn't been provisioned. This
   352  		// also makes unprovisioned machines visually distinct
   353  		// in the output.
   354  		status.AgentState = ""
   355  	}
   356  	hc, err := machine.HardwareCharacteristics()
   357  	if err != nil {
   358  		if !errors.IsNotFound(err) {
   359  			status.Hardware = "error"
   360  		}
   361  	} else {
   362  		status.Hardware = hc.String()
   363  	}
   364  	status.Containers = make(map[string]api.MachineStatus)
   365  	return
   366  }
   367  
   368  func (context *statusContext) processRelations() []api.RelationStatus {
   369  	var out []api.RelationStatus
   370  	relations := context.getAllRelations()
   371  	for _, relation := range relations {
   372  		var eps []api.EndpointStatus
   373  		var scope charm.RelationScope
   374  		var relationInterface string
   375  		for _, ep := range relation.Endpoints() {
   376  			eps = append(eps, api.EndpointStatus{
   377  				ServiceName: ep.ServiceName,
   378  				Name:        ep.Name,
   379  				Role:        ep.Role,
   380  				Subordinate: context.isSubordinate(&ep),
   381  			})
   382  			// these should match on both sides so use the last
   383  			relationInterface = ep.Interface
   384  			scope = ep.Scope
   385  		}
   386  		relStatus := api.RelationStatus{
   387  			Id:        relation.Id(),
   388  			Key:       relation.String(),
   389  			Interface: relationInterface,
   390  			Scope:     scope,
   391  			Endpoints: eps,
   392  		}
   393  		out = append(out, relStatus)
   394  	}
   395  	return out
   396  }
   397  
   398  // This method exists only to dedup the loaded relations as they will
   399  // appear multiple times in context.relations.
   400  func (context *statusContext) getAllRelations() []*state.Relation {
   401  	var out []*state.Relation
   402  	seenRelations := make(map[int]bool)
   403  	for _, relations := range context.relations {
   404  		for _, relation := range relations {
   405  			if _, found := seenRelations[relation.Id()]; !found {
   406  				out = append(out, relation)
   407  				seenRelations[relation.Id()] = true
   408  			}
   409  		}
   410  	}
   411  	return out
   412  }
   413  
   414  func (context *statusContext) processNetworks() map[string]api.NetworkStatus {
   415  	networksMap := make(map[string]api.NetworkStatus)
   416  	for name, network := range context.networks {
   417  		networksMap[name] = context.makeNetworkStatus(network)
   418  	}
   419  	return networksMap
   420  }
   421  
   422  func (context *statusContext) makeNetworkStatus(network *state.Network) api.NetworkStatus {
   423  	return api.NetworkStatus{
   424  		ProviderId: network.ProviderId(),
   425  		CIDR:       network.CIDR(),
   426  		VLANTag:    network.VLANTag(),
   427  	}
   428  }
   429  
   430  func (context *statusContext) isSubordinate(ep *state.Endpoint) bool {
   431  	service := context.services[ep.ServiceName]
   432  	if service == nil {
   433  		return false
   434  	}
   435  	return isSubordinate(ep, service)
   436  }
   437  
   438  func isSubordinate(ep *state.Endpoint, service *state.Service) bool {
   439  	return ep.Scope == charm.ScopeContainer && !service.IsPrincipal()
   440  }
   441  
   442  // paramsJobsFromJobs converts state jobs to params jobs.
   443  func paramsJobsFromJobs(jobs []state.MachineJob) []multiwatcher.MachineJob {
   444  	paramsJobs := make([]multiwatcher.MachineJob, len(jobs))
   445  	for i, machineJob := range jobs {
   446  		paramsJobs[i] = machineJob.ToParams()
   447  	}
   448  	return paramsJobs
   449  }
   450  
   451  func (context *statusContext) processServices() map[string]api.ServiceStatus {
   452  	servicesMap := make(map[string]api.ServiceStatus)
   453  	for _, s := range context.services {
   454  		servicesMap[s.Name()] = context.processService(s)
   455  	}
   456  	return servicesMap
   457  }
   458  
   459  func (context *statusContext) processService(service *state.Service) (status api.ServiceStatus) {
   460  	serviceCharmURL, _ := service.CharmURL()
   461  	status.Charm = serviceCharmURL.String()
   462  	status.Exposed = service.IsExposed()
   463  	status.Life = processLife(service)
   464  
   465  	latestCharm, ok := context.latestCharms[*serviceCharmURL.WithRevision(-1)]
   466  	if ok && latestCharm != serviceCharmURL.String() {
   467  		status.CanUpgradeTo = latestCharm
   468  	}
   469  	var err error
   470  	status.Relations, status.SubordinateTo, err = context.processServiceRelations(service)
   471  	if err != nil {
   472  		status.Err = err
   473  		return
   474  	}
   475  	networks, err := service.Networks()
   476  	if err != nil {
   477  		status.Err = err
   478  		return
   479  	}
   480  	var cons constraints.Value
   481  	if service.IsPrincipal() {
   482  		// Only principals can have constraints.
   483  		cons, err = service.Constraints()
   484  		if err != nil {
   485  			status.Err = err
   486  			return
   487  		}
   488  	}
   489  	if len(networks) > 0 || cons.HaveNetworks() {
   490  		// Only the explicitly requested networks (using "juju deploy
   491  		// <svc> --networks=...") will be enabled, and altough when
   492  		// specified, networks constraints will be used for instance
   493  		// selection, they won't be actually enabled.
   494  		status.Networks = api.NetworksSpecification{
   495  			Enabled:  networks,
   496  			Disabled: append(cons.IncludeNetworks(), cons.ExcludeNetworks()...),
   497  		}
   498  	}
   499  	if service.IsPrincipal() {
   500  		status.Units = context.processUnits(context.units[service.Name()], serviceCharmURL.String())
   501  	}
   502  	return status
   503  }
   504  
   505  func (context *statusContext) processUnits(units map[string]*state.Unit, serviceCharm string) map[string]api.UnitStatus {
   506  	unitsMap := make(map[string]api.UnitStatus)
   507  	for _, unit := range units {
   508  		unitsMap[unit.Name()] = context.processUnit(unit, serviceCharm)
   509  	}
   510  	return unitsMap
   511  }
   512  
   513  func (context *statusContext) processUnit(unit *state.Unit, serviceCharm string) (status api.UnitStatus) {
   514  	status.PublicAddress, _ = unit.PublicAddress()
   515  	unitPorts, _ := unit.OpenedPorts()
   516  	for _, port := range unitPorts {
   517  		status.OpenedPorts = append(status.OpenedPorts, port.String())
   518  	}
   519  	if unit.IsPrincipal() {
   520  		status.Machine, _ = unit.AssignedMachineId()
   521  	}
   522  	curl, _ := unit.CharmURL()
   523  	if serviceCharm != "" && curl != nil && curl.String() != serviceCharm {
   524  		status.Charm = curl.String()
   525  	}
   526  	status.Agent, status.AgentState, status.AgentStateInfo = processAgent(unit)
   527  
   528  	// Until Juju 2.0, we need to continue to display legacy status values.
   529  	status.Agent.Status = params.TranslateLegacyStatus(status.Agent.Status)
   530  	status.AgentState = params.TranslateLegacyStatus(status.AgentState)
   531  
   532  	status.AgentVersion = status.Agent.Version
   533  	status.Life = status.Agent.Life
   534  	status.Err = status.Agent.Err
   535  	if subUnits := unit.SubordinateNames(); len(subUnits) > 0 {
   536  		status.Subordinates = make(map[string]api.UnitStatus)
   537  		for _, name := range subUnits {
   538  			subUnit := context.unitByName(name)
   539  			// subUnit may be nil if subordinate was filtered out.
   540  			if subUnit != nil {
   541  				status.Subordinates[name] = context.processUnit(subUnit, serviceCharm)
   542  			}
   543  		}
   544  	}
   545  	return
   546  }
   547  
   548  func (context *statusContext) unitByName(name string) *state.Unit {
   549  	serviceName := strings.Split(name, "/")[0]
   550  	return context.units[serviceName][name]
   551  }
   552  
   553  func (context *statusContext) processServiceRelations(service *state.Service) (related map[string][]string, subord []string, err error) {
   554  	subordSet := make(set.Strings)
   555  	related = make(map[string][]string)
   556  	relations := context.relations[service.Name()]
   557  	for _, relation := range relations {
   558  		ep, err := relation.Endpoint(service.Name())
   559  		if err != nil {
   560  			return nil, nil, err
   561  		}
   562  		relationName := ep.Relation.Name
   563  		eps, err := relation.RelatedEndpoints(service.Name())
   564  		if err != nil {
   565  			return nil, nil, err
   566  		}
   567  		for _, ep := range eps {
   568  			if isSubordinate(&ep, service) {
   569  				subordSet.Add(ep.ServiceName)
   570  			}
   571  			related[relationName] = append(related[relationName], ep.ServiceName)
   572  		}
   573  	}
   574  	for relationName, serviceNames := range related {
   575  		sn := set.NewStrings(serviceNames...)
   576  		related[relationName] = sn.SortedValues()
   577  	}
   578  	return related, subordSet.SortedValues(), nil
   579  }
   580  
   581  type lifer interface {
   582  	Life() state.Life
   583  }
   584  
   585  type stateAgent interface {
   586  	lifer
   587  	AgentPresence() (bool, error)
   588  	AgentTools() (*tools.Tools, error)
   589  	Status() (state.Status, string, map[string]interface{}, error)
   590  }
   591  
   592  // processAgent retrieves version and status information from the given entity.
   593  func processAgent(entity stateAgent) (out api.AgentStatus, compatStatus params.Status, compatInfo string) {
   594  	out.Life = processLife(entity)
   595  
   596  	if t, err := entity.AgentTools(); err == nil {
   597  		out.Version = t.Version.Number.String()
   598  	}
   599  
   600  	var st state.Status
   601  	st, out.Info, out.Data, out.Err = entity.Status()
   602  	out.Status = params.Status(st)
   603  	compatStatus = out.Status
   604  	compatInfo = out.Info
   605  	out.Data = filterStatusData(out.Data)
   606  	if out.Err != nil {
   607  		return
   608  	}
   609  
   610  	if out.Status == params.StatusPending || // Need to still check pending for existing deployments.
   611  		out.Status == params.StatusAllocating ||
   612  		out.Status == params.StatusInstalling {
   613  		// The status is allocating or installing - there's no point
   614  		// in enquiring about the agent liveness.
   615  		return
   616  	}
   617  	agentAlive, err := entity.AgentPresence()
   618  	if err != nil {
   619  		return
   620  	}
   621  
   622  	if entity.Life() != state.Dead && !agentAlive {
   623  		// The agent *should* be alive but is not. Set status to
   624  		// StatusDown and munge Info to indicate the previous status and
   625  		// info. This is unfortunately making presentation decisions
   626  		// on behalf of the client (crappy).
   627  		//
   628  		// This is munging is only being left in place for
   629  		// compatibility with older clients.  TODO: At some point we
   630  		// should change this so that Info left alone. API version may
   631  		// help here.
   632  		//
   633  		// Better yet, Status shouldn't be changed here in the API at
   634  		// all! Status changes should only happen in State. One
   635  		// problem caused by this is that this status change won't be
   636  		// seen by clients using a watcher because it didn't happen in
   637  		// State.
   638  		if out.Info != "" {
   639  			compatInfo = fmt.Sprintf("(%s: %s)", out.Status, out.Info)
   640  		} else {
   641  			compatInfo = fmt.Sprintf("(%s)", out.Status)
   642  		}
   643  		compatStatus = params.StatusDown
   644  	}
   645  
   646  	return
   647  }
   648  
   649  // filterStatusData limits what agent StatusData data is passed over
   650  // the API. This prevents unintended leakage of internal-only data.
   651  func filterStatusData(status map[string]interface{}) map[string]interface{} {
   652  	out := make(map[string]interface{})
   653  	for name, value := range status {
   654  		// use a set here if we end up with a larger whitelist
   655  		if name == "relation-id" {
   656  			out[name] = value
   657  		}
   658  	}
   659  	return out
   660  }
   661  
   662  func processLife(entity lifer) string {
   663  	if life := entity.Life(); life != state.Alive {
   664  		// alive is the usual state so omit it by default.
   665  		return life.String()
   666  	}
   667  	return ""
   668  }