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