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