Permalink
Cannot retrieve contributors at this time
Fetching contributors…
| // Copyright 2013 Canonical Ltd. | |
| // Licensed under the AGPLv3, see LICENCE file for details. | |
| package client | |
| import ( | |
| "fmt" | |
| "sort" | |
| "strings" | |
| "github.com/juju/errors" | |
| "github.com/juju/utils/featureflag" | |
| "github.com/juju/utils/set" | |
| "gopkg.in/juju/charm.v6-unstable" | |
| "gopkg.in/juju/names.v2" | |
| "github.com/juju/juju/apiserver/common" | |
| "github.com/juju/juju/apiserver/params" | |
| "github.com/juju/juju/feature" | |
| "github.com/juju/juju/network" | |
| "github.com/juju/juju/state" | |
| "github.com/juju/juju/state/multiwatcher" | |
| "github.com/juju/juju/status" | |
| ) | |
| func agentStatusFromStatusInfo(s []status.StatusInfo, kind status.HistoryKind) []params.DetailedStatus { | |
| result := []params.DetailedStatus{} | |
| for _, v := range s { | |
| result = append(result, params.DetailedStatus{ | |
| Status: string(v.Status), | |
| Info: v.Message, | |
| Data: v.Data, | |
| Since: v.Since, | |
| Kind: string(kind), | |
| }) | |
| } | |
| return result | |
| } | |
| type byTime []params.DetailedStatus | |
| func (s byTime) Len() int { | |
| return len(s) | |
| } | |
| func (s byTime) Swap(i, j int) { | |
| s[i], s[j] = s[j], s[i] | |
| } | |
| func (s byTime) Less(i, j int) bool { | |
| return s[i].Since.Before(*s[j].Since) | |
| } | |
| // unitStatusHistory returns a list of status history entries for unit agents or workloads. | |
| func (c *Client) unitStatusHistory(unitTag names.UnitTag, filter status.StatusHistoryFilter, kind status.HistoryKind) ([]params.DetailedStatus, error) { | |
| unit, err := c.api.stateAccessor.Unit(unitTag.Id()) | |
| if err != nil { | |
| return nil, errors.Trace(err) | |
| } | |
| statuses := []params.DetailedStatus{} | |
| if kind == status.KindUnit || kind == status.KindWorkload { | |
| unitStatuses, err := unit.StatusHistory(filter) | |
| if err != nil { | |
| return nil, errors.Trace(err) | |
| } | |
| statuses = agentStatusFromStatusInfo(unitStatuses, status.KindWorkload) | |
| } | |
| if kind == status.KindUnit || kind == status.KindUnitAgent { | |
| agentStatuses, err := unit.AgentHistory().StatusHistory(filter) | |
| if err != nil { | |
| return nil, errors.Trace(err) | |
| } | |
| statuses = append(statuses, agentStatusFromStatusInfo(agentStatuses, status.KindUnitAgent)...) | |
| } | |
| sort.Sort(byTime(statuses)) | |
| if kind == status.KindUnit && filter.Size > 0 { | |
| if len(statuses) > filter.Size { | |
| statuses = statuses[len(statuses)-filter.Size:] | |
| } | |
| } | |
| return statuses, nil | |
| } | |
| // machineStatusHistory returns status history for the given machine. | |
| func (c *Client) machineStatusHistory(machineTag names.MachineTag, filter status.StatusHistoryFilter, kind status.HistoryKind) ([]params.DetailedStatus, error) { | |
| machine, err := c.api.stateAccessor.Machine(machineTag.Id()) | |
| if err != nil { | |
| return nil, errors.Trace(err) | |
| } | |
| var sInfo []status.StatusInfo | |
| if kind == status.KindMachineInstance || kind == status.KindContainerInstance { | |
| sInfo, err = machine.InstanceStatusHistory(filter) | |
| } else { | |
| sInfo, err = machine.StatusHistory(filter) | |
| } | |
| if err != nil { | |
| return nil, errors.Trace(err) | |
| } | |
| return agentStatusFromStatusInfo(sInfo, kind), nil | |
| } | |
| // StatusHistory returns a slice of past statuses for several entities. | |
| func (c *Client) StatusHistory(request params.StatusHistoryRequests) params.StatusHistoryResults { | |
| results := params.StatusHistoryResults{} | |
| // TODO(perrito666) the contents of the loop could be split into | |
| // a oneHistory method for clarity. | |
| for _, request := range request.Requests { | |
| filter := status.StatusHistoryFilter{ | |
| Size: request.Filter.Size, | |
| FromDate: request.Filter.Date, | |
| Delta: request.Filter.Delta, | |
| Exclude: set.NewStrings(request.Filter.Exclude...), | |
| } | |
| if err := c.checkCanRead(); err != nil { | |
| history := params.StatusHistoryResult{ | |
| Error: common.ServerError(err), | |
| } | |
| results.Results = append(results.Results, history) | |
| continue | |
| } | |
| if err := filter.Validate(); err != nil { | |
| history := params.StatusHistoryResult{ | |
| Error: common.ServerError(errors.Annotate(err, "cannot validate status history filter")), | |
| } | |
| results.Results = append(results.Results, history) | |
| continue | |
| } | |
| var ( | |
| err error | |
| hist []params.DetailedStatus | |
| ) | |
| kind := status.HistoryKind(request.Kind) | |
| err = errors.NotValidf("%q requires a unit, got %T", kind, request.Tag) | |
| switch kind { | |
| case status.KindUnit, status.KindWorkload, status.KindUnitAgent: | |
| var u names.UnitTag | |
| if u, err = names.ParseUnitTag(request.Tag); err == nil { | |
| hist, err = c.unitStatusHistory(u, filter, kind) | |
| } | |
| default: | |
| var m names.MachineTag | |
| if m, err = names.ParseMachineTag(request.Tag); err == nil { | |
| hist, err = c.machineStatusHistory(m, filter, kind) | |
| } | |
| } | |
| if err == nil { | |
| sort.Sort(byTime(hist)) | |
| } | |
| results.Results = append(results.Results, | |
| params.StatusHistoryResult{ | |
| History: params.History{Statuses: hist}, | |
| Error: common.ServerError(errors.Annotatef(err, "fetching status history for %q", request.Tag)), | |
| }) | |
| } | |
| return results | |
| } | |
| // FullStatus gives the information needed for juju status over the api | |
| func (c *Client) FullStatus(args params.StatusParams) (params.FullStatus, error) { | |
| if err := c.checkCanRead(); err != nil { | |
| return params.FullStatus{}, err | |
| } | |
| var noStatus params.FullStatus | |
| var context statusContext | |
| var err error | |
| if context.applications, context.units, context.latestCharms, err = | |
| fetchAllApplicationsAndUnits(c.api.stateAccessor, len(args.Patterns) <= 0); err != nil { | |
| return noStatus, errors.Annotate(err, "could not fetch applications and units") | |
| } | |
| if featureflag.Enabled(feature.CrossModelRelations) { | |
| if context.remoteApplications, err = | |
| fetchRemoteApplications(c.api.stateAccessor); err != nil { | |
| return noStatus, errors.Annotate(err, "could not fetch remote applications") | |
| } | |
| } | |
| if context.machines, err = fetchMachines(c.api.stateAccessor, nil); err != nil { | |
| return noStatus, errors.Annotate(err, "could not fetch machines") | |
| } | |
| // These may be empty when machines have not finished deployment. | |
| if context.ipAddresses, context.spaces, context.linkLayerDevices, err = | |
| fetchNetworkInterfaces(c.api.stateAccessor); err != nil { | |
| return noStatus, errors.Annotate(err, "could not fetch IP addresses and link layer devices") | |
| } | |
| if context.relations, err = fetchRelations(c.api.stateAccessor); err != nil { | |
| return noStatus, errors.Annotate(err, "could not fetch relations") | |
| } | |
| if len(context.applications) > 0 { | |
| if context.leaders, err = c.api.stateAccessor.ApplicationLeaders(); err != nil { | |
| return noStatus, errors.Annotate(err, " could not fetch leaders") | |
| } | |
| } | |
| logger.Debugf("Applications: %v", context.applications) | |
| logger.Debugf("Remote applications: %v", context.remoteApplications) | |
| if len(args.Patterns) > 0 { | |
| predicate := BuildPredicateFor(args.Patterns) | |
| // First, attempt to match machines. Any units on those | |
| // machines are implicitly matched. | |
| matchedMachines := make(set.Strings) | |
| for _, machineList := range context.machines { | |
| for _, m := range machineList { | |
| matches, err := predicate(m) | |
| if err != nil { | |
| return noStatus, errors.Annotate( | |
| err, "could not filter machines", | |
| ) | |
| } | |
| if matches { | |
| matchedMachines.Add(m.Id()) | |
| } | |
| } | |
| } | |
| // Filter units | |
| matchedSvcs := make(set.Strings) | |
| unitChainPredicate := UnitChainPredicateFn(predicate, context.unitByName) | |
| for _, unitMap := range context.units { | |
| for name, unit := range unitMap { | |
| machineId, err := unit.AssignedMachineId() | |
| if err != nil { | |
| machineId = "" | |
| } else if matchedMachines.Contains(machineId) { | |
| // Unit is on a matching machine. | |
| matchedSvcs.Add(unit.ApplicationName()) | |
| continue | |
| } | |
| // Always start examining at the top-level. This | |
| // prevents a situation where we filter a subordinate | |
| // before we discover its parent is a match. | |
| if !unit.IsPrincipal() { | |
| continue | |
| } else if matches, err := unitChainPredicate(unit); err != nil { | |
| return noStatus, errors.Annotate(err, "could not filter units") | |
| } else if !matches { | |
| delete(unitMap, name) | |
| continue | |
| } | |
| matchedSvcs.Add(unit.ApplicationName()) | |
| if machineId != "" { | |
| matchedMachines.Add(machineId) | |
| } | |
| } | |
| } | |
| // Filter applications | |
| for svcName, svc := range context.applications { | |
| if matchedSvcs.Contains(svcName) { | |
| // There are matched units for this application. | |
| continue | |
| } else if matches, err := predicate(svc); err != nil { | |
| return noStatus, errors.Annotate(err, "could not filter applications") | |
| } else if !matches { | |
| delete(context.applications, svcName) | |
| } | |
| } | |
| // TODO(wallyworld) - filter remote applications | |
| // Filter machines | |
| for status, machineList := range context.machines { | |
| matched := make([]*state.Machine, 0, len(machineList)) | |
| for _, m := range machineList { | |
| machineContainers, err := m.Containers() | |
| if err != nil { | |
| return noStatus, err | |
| } | |
| machineContainersSet := set.NewStrings(machineContainers...) | |
| if matchedMachines.Contains(m.Id()) || !matchedMachines.Intersection(machineContainersSet).IsEmpty() { | |
| // The machine is matched directly, or contains a unit | |
| // or container that matches. | |
| logger.Tracef("machine %s is hosting something.", m.Id()) | |
| matched = append(matched, m) | |
| continue | |
| } | |
| } | |
| context.machines[status] = matched | |
| } | |
| } | |
| modelStatus, err := c.modelStatus() | |
| if err != nil { | |
| return noStatus, errors.Annotate(err, "cannot determine model status") | |
| } | |
| return params.FullStatus{ | |
| Model: modelStatus, | |
| Machines: processMachines( | |
| context.machines, | |
| context.ipAddresses, | |
| context.spaces, | |
| context.linkLayerDevices, | |
| ), | |
| Applications: context.processApplications(), | |
| RemoteApplications: context.processRemoteApplications(), | |
| Relations: context.processRelations(), | |
| }, nil | |
| } | |
| // newToolsVersionAvailable will return a string representing a tools | |
| // version only if the latest check is newer than current tools. | |
| func (c *Client) modelStatus() (params.ModelStatusInfo, error) { | |
| var info params.ModelStatusInfo | |
| m, err := c.api.stateAccessor.Model() | |
| if err != nil { | |
| return info, errors.Annotate(err, "cannot get model") | |
| } | |
| info.Name = m.Name() | |
| info.CloudTag = names.NewCloudTag(m.Cloud()).String() | |
| info.CloudRegion = m.CloudRegion() | |
| cfg, err := m.Config() | |
| if err != nil { | |
| return params.ModelStatusInfo{}, errors.Annotate(err, "cannot obtain current model config") | |
| } | |
| latestVersion := m.LatestToolsVersion() | |
| current, ok := cfg.AgentVersion() | |
| if ok { | |
| info.Version = current.String() | |
| if current.Compare(latestVersion) < 0 { | |
| info.AvailableVersion = latestVersion.String() | |
| } | |
| } | |
| status, err := m.Status() | |
| if err != nil { | |
| return params.ModelStatusInfo{}, errors.Annotate(err, "cannot obtain model status info") | |
| } | |
| info.ModelStatus = params.DetailedStatus{ | |
| Status: status.Status.String(), | |
| Info: status.Message, | |
| Since: status.Since, | |
| Data: status.Data, | |
| } | |
| return info, nil | |
| } | |
| type statusContext struct { | |
| // machines: top-level machine id -> list of machines nested in | |
| // this machine. | |
| machines map[string][]*state.Machine | |
| // ipAddresses: machine id -> list of ip.addresses | |
| ipAddresses map[string][]*state.Address | |
| // spaces: machine id -> deviceName -> list of spaceNames | |
| spaces map[string]map[string]set.Strings | |
| // linkLayerDevices: machine id -> list of linkLayerDevices | |
| linkLayerDevices map[string][]*state.LinkLayerDevice | |
| // applications: application name -> application | |
| applications map[string]*state.Application | |
| // remote applications: application name -> application | |
| remoteApplications map[string]*state.RemoteApplication | |
| relations map[string][]*state.Relation | |
| units map[string]map[string]*state.Unit | |
| latestCharms map[charm.URL]*state.Charm | |
| leaders map[string]string | |
| } | |
| // fetchMachines returns a map from top level machine id to machines, where machines[0] is the host | |
| // machine and machines[1..n] are any containers (including nested ones). | |
| // | |
| // If machineIds is non-nil, only machines whose IDs are in the set are returned. | |
| func fetchMachines(st Backend, machineIds set.Strings) (map[string][]*state.Machine, error) { | |
| v := make(map[string][]*state.Machine) | |
| machines, err := st.AllMachines() | |
| if err != nil { | |
| return nil, err | |
| } | |
| // AllMachines gives us machines sorted by id. | |
| for _, m := range machines { | |
| if machineIds != nil && !machineIds.Contains(m.Id()) { | |
| continue | |
| } | |
| parentId, ok := m.ParentId() | |
| if !ok { | |
| // Only top level host machines go directly into the machine map. | |
| v[m.Id()] = []*state.Machine{m} | |
| } else { | |
| topParentId := state.TopParentId(m.Id()) | |
| machines, ok := v[topParentId] | |
| if !ok { | |
| panic(fmt.Errorf("unexpected machine id %q", parentId)) | |
| } | |
| machines = append(machines, m) | |
| v[topParentId] = machines | |
| } | |
| } | |
| return v, nil | |
| } | |
| // fetchNetworkInterfaces returns maps from machine id to ip.addresses, machine | |
| // id to a map of interface names from space names, and machine id to | |
| // linklayerdevices. | |
| // | |
| // All are required to determine a machine's network interfaces configuration, | |
| // so we want all or none. | |
| func fetchNetworkInterfaces(st Backend) (map[string][]*state.Address, map[string]map[string]set.Strings, map[string][]*state.LinkLayerDevice, error) { | |
| ipAddresses := make(map[string][]*state.Address) | |
| spaces := make(map[string]map[string]set.Strings) | |
| ipAddrs, err := st.AllIPAddresses() | |
| if err != nil { | |
| return nil, nil, nil, err | |
| } | |
| for _, ipAddr := range ipAddrs { | |
| if ipAddr.LoopbackConfigMethod() { | |
| continue | |
| } | |
| machineID := ipAddr.MachineID() | |
| ipAddresses[machineID] = append(ipAddresses[machineID], ipAddr) | |
| subnet, err := st.Subnet(ipAddr.SubnetCIDR()) | |
| if errors.IsNotFound(err) { | |
| // No worries; no subnets means no spaces. | |
| continue | |
| } else if err != nil { | |
| return nil, nil, nil, err | |
| } | |
| if spaceName := subnet.SpaceName(); spaceName != "" { | |
| devices, ok := spaces[machineID] | |
| if !ok { | |
| devices = make(map[string]set.Strings) | |
| spaces[machineID] = devices | |
| } | |
| deviceName := ipAddr.DeviceName() | |
| spacesSet, ok := devices[deviceName] | |
| if !ok { | |
| spacesSet = make(set.Strings) | |
| devices[deviceName] = spacesSet | |
| } | |
| spacesSet.Add(spaceName) | |
| } | |
| } | |
| linkLayerDevices := make(map[string][]*state.LinkLayerDevice) | |
| llDevs, err := st.AllLinkLayerDevices() | |
| if err != nil { | |
| return nil, nil, nil, err | |
| } | |
| for _, llDev := range llDevs { | |
| if llDev.IsLoopbackDevice() { | |
| continue | |
| } | |
| addrs, err := llDev.Addresses() | |
| if err != nil { | |
| return nil, nil, nil, err | |
| } | |
| // We don't want to see bond slaves or bridge ports, only the | |
| // IP-addressed devices. | |
| if len(addrs) > 0 { | |
| machineID := llDev.MachineID() | |
| linkLayerDevices[machineID] = append(linkLayerDevices[machineID], llDev) | |
| } | |
| } | |
| return ipAddresses, spaces, linkLayerDevices, nil | |
| } | |
| // fetchAllApplicationsAndUnits returns a map from application name to application, | |
| // a map from application name to unit name to unit, and a map from base charm URL to latest URL. | |
| func fetchAllApplicationsAndUnits( | |
| st Backend, | |
| matchAny bool, | |
| ) (map[string]*state.Application, map[string]map[string]*state.Unit, map[charm.URL]*state.Charm, error) { | |
| appMap := make(map[string]*state.Application) | |
| unitMap := make(map[string]map[string]*state.Unit) | |
| latestCharms := make(map[charm.URL]*state.Charm) | |
| applications, err := st.AllApplications() | |
| if err != nil { | |
| return nil, nil, nil, err | |
| } | |
| for _, s := range applications { | |
| units, err := s.AllUnits() | |
| if err != nil { | |
| return nil, nil, nil, err | |
| } | |
| appUnitMap := make(map[string]*state.Unit) | |
| for _, u := range units { | |
| appUnitMap[u.Name()] = u | |
| } | |
| if matchAny || len(appUnitMap) > 0 { | |
| unitMap[s.Name()] = appUnitMap | |
| appMap[s.Name()] = s | |
| // Record the base URL for the application's charm so that | |
| // the latest store revision can be looked up. | |
| charmURL, _ := s.CharmURL() | |
| if charmURL.Schema == "cs" { | |
| latestCharms[*charmURL.WithRevision(-1)] = nil | |
| } | |
| } | |
| } | |
| for baseURL := range latestCharms { | |
| ch, err := st.LatestPlaceholderCharm(&baseURL) | |
| if errors.IsNotFound(err) { | |
| continue | |
| } | |
| if err != nil { | |
| return nil, nil, nil, err | |
| } | |
| latestCharms[baseURL] = ch | |
| } | |
| return appMap, unitMap, latestCharms, nil | |
| } | |
| // fetchRemoteApplications returns a map from application name to remote application. | |
| func fetchRemoteApplications(st Backend) (map[string]*state.RemoteApplication, error) { | |
| appMap := make(map[string]*state.RemoteApplication) | |
| applications, err := st.AllRemoteApplications() | |
| if err != nil { | |
| return nil, err | |
| } | |
| for _, a := range applications { | |
| if _, ok := a.URL(); !ok { | |
| continue | |
| } | |
| appMap[a.Name()] = a | |
| } | |
| return appMap, nil | |
| } | |
| // fetchRelations returns a map of all relations keyed by application name. | |
| // | |
| // This structure is useful for processApplicationRelations() which needs | |
| // to have the relations for each application. Reading them once here | |
| // avoids the repeated DB hits to retrieve the relations for each | |
| // application that used to happen in processApplicationRelations(). | |
| func fetchRelations(st Backend) (map[string][]*state.Relation, error) { | |
| relations, err := st.AllRelations() | |
| if err != nil { | |
| return nil, err | |
| } | |
| out := make(map[string][]*state.Relation) | |
| for _, relation := range relations { | |
| for _, ep := range relation.Endpoints() { | |
| out[ep.ApplicationName] = append(out[ep.ApplicationName], relation) | |
| } | |
| } | |
| return out, nil | |
| } | |
| type machineAndContainers map[string][]*state.Machine | |
| func (m machineAndContainers) HostForMachineId(id string) *state.Machine { | |
| // Element 0 is assumed to be the top-level machine. | |
| return m[id][0] | |
| } | |
| func (m machineAndContainers) Containers(id string) []*state.Machine { | |
| return m[id][1:] | |
| } | |
| func processMachines( | |
| idToMachines map[string][]*state.Machine, | |
| idToIpAddresses map[string][]*state.Address, | |
| idToDeviceToSpaces map[string]map[string]set.Strings, | |
| idToLinkLayerDevices map[string][]*state.LinkLayerDevice, | |
| ) map[string]params.MachineStatus { | |
| machinesMap := make(map[string]params.MachineStatus) | |
| cache := make(map[string]params.MachineStatus) | |
| for id, machines := range idToMachines { | |
| if len(machines) <= 0 { | |
| continue | |
| } | |
| // Element 0 is assumed to be the top-level machine. | |
| tlMachine := machines[0] | |
| hostStatus := makeMachineStatus( | |
| tlMachine, | |
| idToIpAddresses[tlMachine.Id()], | |
| idToDeviceToSpaces[tlMachine.Id()], | |
| idToLinkLayerDevices[tlMachine.Id()], | |
| ) | |
| machinesMap[id] = hostStatus | |
| cache[id] = hostStatus | |
| for _, machine := range machines[1:] { | |
| parent, ok := cache[state.ParentId(machine.Id())] | |
| if !ok { | |
| panic("We've broken an assumpution.") | |
| } | |
| status := makeMachineStatus( | |
| machine, | |
| idToIpAddresses[machine.Id()], | |
| idToDeviceToSpaces[machine.Id()], | |
| idToLinkLayerDevices[machine.Id()], | |
| ) | |
| parent.Containers[machine.Id()] = status | |
| cache[machine.Id()] = status | |
| } | |
| } | |
| return machinesMap | |
| } | |
| func makeMachineStatus( | |
| machine *state.Machine, | |
| ipAddresses []*state.Address, | |
| spaces map[string]set.Strings, | |
| linkLayerDevices []*state.LinkLayerDevice, | |
| ) (status params.MachineStatus) { | |
| var err error | |
| status.Id = machine.Id() | |
| agentStatus := processMachine(machine) | |
| status.AgentStatus = agentStatus | |
| status.Series = machine.Series() | |
| status.Jobs = paramsJobsFromJobs(machine.Jobs()) | |
| status.WantsVote = machine.WantsVote() | |
| status.HasVote = machine.HasVote() | |
| sInfo, err := machine.InstanceStatus() | |
| populateStatusFromStatusInfoAndErr(&status.InstanceStatus, sInfo, err) | |
| instid, err := machine.InstanceId() | |
| if err == nil { | |
| status.InstanceId = instid | |
| addr, err := machine.PublicAddress() | |
| if err != nil { | |
| // Usually this indicates that no addresses have been set on the | |
| // machine yet. | |
| addr = network.Address{} | |
| logger.Debugf("error fetching public address: %q", err) | |
| } | |
| status.DNSName = addr.Value | |
| mAddrs := machine.Addresses() | |
| if len(mAddrs) == 0 { | |
| logger.Debugf("no IP addresses fetched for machine %q", instid) | |
| // At least give it the newly created DNSName address, if it exists. | |
| if addr.Value != "" { | |
| mAddrs = append(mAddrs, addr) | |
| } | |
| } | |
| for _, mAddr := range mAddrs { | |
| switch mAddr.Scope { | |
| case network.ScopeMachineLocal, network.ScopeLinkLocal: | |
| continue | |
| } | |
| status.IPAddresses = append(status.IPAddresses, mAddr.Value) | |
| } | |
| status.NetworkInterfaces = make(map[string]params.NetworkInterface, len(linkLayerDevices)) | |
| for _, llDev := range linkLayerDevices { | |
| device := llDev.Name() | |
| ips := []string{} | |
| gw := []string{} | |
| ns := []string{} | |
| sp := make(set.Strings) | |
| for _, ipAddress := range ipAddresses { | |
| if ipAddress.DeviceName() != device { | |
| continue | |
| } | |
| ips = append(ips, ipAddress.Value()) | |
| // We don't expect to find more than one | |
| // ipAddress on a device with a list of | |
| // nameservers, but append in any case. | |
| if len(ipAddress.DNSServers()) > 0 { | |
| ns = append(ns, ipAddress.DNSServers()...) | |
| } | |
| // There should only be one gateway per device | |
| // (per machine, in fact, as we don't store | |
| // metrics). If we find more than one we should | |
| // show them all. | |
| if ipAddress.GatewayAddress() != "" { | |
| gw = append(gw, ipAddress.GatewayAddress()) | |
| } | |
| // There should only be one space per address, | |
| // but it's technically possible to have more | |
| // than one address on an interface. If we find | |
| // that happens, we need to show all spaces, to | |
| // be safe. | |
| sp = spaces[device] | |
| } | |
| status.NetworkInterfaces[device] = params.NetworkInterface{ | |
| IPAddresses: ips, | |
| MACAddress: llDev.MACAddress(), | |
| Gateway: strings.Join(gw, " "), | |
| DNSNameservers: ns, | |
| Space: strings.Join(sp.Values(), " "), | |
| IsUp: llDev.IsUp(), | |
| } | |
| } | |
| logger.Debugf("NetworkInterfaces: %+v", status.NetworkInterfaces) | |
| } else { | |
| if errors.IsNotProvisioned(err) { | |
| status.InstanceId = "pending" | |
| } else { | |
| status.InstanceId = "error" | |
| } | |
| } | |
| constraints, err := machine.Constraints() | |
| if err != nil { | |
| if !errors.IsNotFound(err) { | |
| status.Constraints = "error" | |
| } | |
| } else { | |
| status.Constraints = constraints.String() | |
| } | |
| hc, err := machine.HardwareCharacteristics() | |
| if err != nil { | |
| if !errors.IsNotFound(err) { | |
| status.Hardware = "error" | |
| } | |
| } else { | |
| status.Hardware = hc.String() | |
| } | |
| status.Containers = make(map[string]params.MachineStatus) | |
| return | |
| } | |
| func (context *statusContext) processRelations() []params.RelationStatus { | |
| var out []params.RelationStatus | |
| relations := context.getAllRelations() | |
| for _, relation := range relations { | |
| var eps []params.EndpointStatus | |
| var scope charm.RelationScope | |
| var relationInterface string | |
| for _, ep := range relation.Endpoints() { | |
| eps = append(eps, params.EndpointStatus{ | |
| ApplicationName: ep.ApplicationName, | |
| Name: ep.Name, | |
| Role: string(ep.Role), | |
| Subordinate: context.isSubordinate(&ep), | |
| }) | |
| // these should match on both sides so use the last | |
| relationInterface = ep.Interface | |
| scope = ep.Scope | |
| } | |
| relStatus := params.RelationStatus{ | |
| Id: relation.Id(), | |
| Key: relation.String(), | |
| Interface: relationInterface, | |
| Scope: string(scope), | |
| Endpoints: eps, | |
| } | |
| out = append(out, relStatus) | |
| } | |
| return out | |
| } | |
| // This method exists only to dedup the loaded relations as they will | |
| // appear multiple times in context.relations. | |
| func (context *statusContext) getAllRelations() []*state.Relation { | |
| var out []*state.Relation | |
| seenRelations := make(map[int]bool) | |
| for _, relations := range context.relations { | |
| for _, relation := range relations { | |
| if _, found := seenRelations[relation.Id()]; !found { | |
| out = append(out, relation) | |
| seenRelations[relation.Id()] = true | |
| } | |
| } | |
| } | |
| return out | |
| } | |
| func (context *statusContext) isSubordinate(ep *state.Endpoint) bool { | |
| application := context.applications[ep.ApplicationName] | |
| if application == nil { | |
| return false | |
| } | |
| return isSubordinate(ep, application) | |
| } | |
| func isSubordinate(ep *state.Endpoint, application *state.Application) bool { | |
| return ep.Scope == charm.ScopeContainer && !application.IsPrincipal() | |
| } | |
| // paramsJobsFromJobs converts state jobs to params jobs. | |
| func paramsJobsFromJobs(jobs []state.MachineJob) []multiwatcher.MachineJob { | |
| paramsJobs := make([]multiwatcher.MachineJob, len(jobs)) | |
| for i, machineJob := range jobs { | |
| paramsJobs[i] = machineJob.ToParams() | |
| } | |
| return paramsJobs | |
| } | |
| func (context *statusContext) processApplications() map[string]params.ApplicationStatus { | |
| applicationsMap := make(map[string]params.ApplicationStatus) | |
| for _, s := range context.applications { | |
| applicationsMap[s.Name()] = context.processApplication(s) | |
| } | |
| return applicationsMap | |
| } | |
| func (context *statusContext) processApplication(application *state.Application) params.ApplicationStatus { | |
| applicationCharm, _, err := application.Charm() | |
| if err != nil { | |
| return params.ApplicationStatus{Err: common.ServerError(err)} | |
| } | |
| var processedStatus = params.ApplicationStatus{ | |
| Charm: applicationCharm.URL().String(), | |
| Series: application.Series(), | |
| Exposed: application.IsExposed(), | |
| Life: processLife(application), | |
| } | |
| if latestCharm, ok := context.latestCharms[*applicationCharm.URL().WithRevision(-1)]; ok && latestCharm != nil { | |
| if latestCharm.Revision() > applicationCharm.URL().Revision { | |
| processedStatus.CanUpgradeTo = latestCharm.String() | |
| } | |
| } | |
| processedStatus.Relations, processedStatus.SubordinateTo, err = context.processApplicationRelations(application) | |
| if err != nil { | |
| processedStatus.Err = common.ServerError(err) | |
| return processedStatus | |
| } | |
| units := context.units[application.Name()] | |
| if application.IsPrincipal() { | |
| processedStatus.Units = context.processUnits(units, applicationCharm.URL().String()) | |
| } | |
| applicationStatus, err := application.Status() | |
| if err != nil { | |
| processedStatus.Err = common.ServerError(err) | |
| return processedStatus | |
| } | |
| processedStatus.Status.Status = applicationStatus.Status.String() | |
| processedStatus.Status.Info = applicationStatus.Message | |
| processedStatus.Status.Data = applicationStatus.Data | |
| processedStatus.Status.Since = applicationStatus.Since | |
| metrics := applicationCharm.Metrics() | |
| planRequired := metrics != nil && metrics.Plan != nil && metrics.Plan.Required | |
| if planRequired || len(application.MetricCredentials()) > 0 { | |
| processedStatus.MeterStatuses = context.processUnitMeterStatuses(units) | |
| } | |
| versions := make([]status.StatusInfo, 0, len(units)) | |
| for _, unit := range units { | |
| statuses, err := unit.WorkloadVersionHistory().StatusHistory( | |
| status.StatusHistoryFilter{Size: 1}, | |
| ) | |
| if err != nil { | |
| processedStatus.Err = common.ServerError(err) | |
| return processedStatus | |
| } | |
| // Even though we fully expect there to be historical values there, | |
| // even the first should be the empty string, the status history | |
| // collection is not added to in a transactional manner, so it may be | |
| // not there even though we'd really like it to be. Such is mongo. | |
| if len(statuses) > 0 { | |
| versions = append(versions, statuses[0]) | |
| } | |
| } | |
| if len(versions) > 0 { | |
| sort.Sort(bySinceDescending(versions)) | |
| processedStatus.WorkloadVersion = versions[0].Message | |
| } | |
| return processedStatus | |
| } | |
| func (context *statusContext) processRemoteApplications() map[string]params.RemoteApplicationStatus { | |
| applicationsMap := make(map[string]params.RemoteApplicationStatus) | |
| for _, s := range context.remoteApplications { | |
| applicationsMap[s.Name()] = context.processRemoteApplication(s) | |
| } | |
| return applicationsMap | |
| } | |
| func (context *statusContext) processRemoteApplication(application *state.RemoteApplication) (status params.RemoteApplicationStatus) { | |
| status.ApplicationURL, _ = application.URL() | |
| status.ApplicationName = application.Name() | |
| eps, err := application.Endpoints() | |
| if err != nil { | |
| status.Err = err | |
| return | |
| } | |
| status.Endpoints = make([]params.RemoteEndpoint, len(eps)) | |
| for i, ep := range eps { | |
| status.Endpoints[i] = params.RemoteEndpoint{ | |
| Name: ep.Name, | |
| Interface: ep.Interface, | |
| Role: ep.Role, | |
| } | |
| } | |
| status.Life = processLife(application) | |
| status.Relations, err = context.processRemoteApplicationRelations(application) | |
| if err != nil { | |
| status.Err = err | |
| return | |
| } | |
| applicationStatus, err := application.Status() | |
| populateStatusFromStatusInfoAndErr(&status.Status, applicationStatus, err) | |
| return status | |
| } | |
| func isColorStatus(code state.MeterStatusCode) bool { | |
| return code == state.MeterGreen || code == state.MeterAmber || code == state.MeterRed | |
| } | |
| func (context *statusContext) processUnitMeterStatuses(units map[string]*state.Unit) map[string]params.MeterStatus { | |
| unitsMap := make(map[string]params.MeterStatus) | |
| for _, unit := range units { | |
| meterStatus, err := unit.GetMeterStatus() | |
| if err != nil { | |
| continue | |
| } | |
| if isColorStatus(meterStatus.Code) { | |
| unitsMap[unit.Name()] = params.MeterStatus{Color: strings.ToLower(meterStatus.Code.String()), Message: meterStatus.Info} | |
| } | |
| } | |
| if len(unitsMap) > 0 { | |
| return unitsMap | |
| } | |
| return nil | |
| } | |
| func (context *statusContext) processUnits(units map[string]*state.Unit, applicationCharm string) map[string]params.UnitStatus { | |
| unitsMap := make(map[string]params.UnitStatus) | |
| for _, unit := range units { | |
| unitsMap[unit.Name()] = context.processUnit(unit, applicationCharm) | |
| } | |
| return unitsMap | |
| } | |
| func (context *statusContext) processUnit(unit *state.Unit, applicationCharm string) params.UnitStatus { | |
| var result params.UnitStatus | |
| addr, err := unit.PublicAddress() | |
| if err != nil { | |
| // Usually this indicates that no addresses have been set on the | |
| // machine yet. | |
| addr = network.Address{} | |
| logger.Debugf("error fetching public address: %v", err) | |
| } | |
| result.PublicAddress = addr.Value | |
| unitPorts, _ := unit.OpenedPorts() | |
| for _, port := range unitPorts { | |
| result.OpenedPorts = append(result.OpenedPorts, port.String()) | |
| } | |
| if unit.IsPrincipal() { | |
| result.Machine, _ = unit.AssignedMachineId() | |
| } | |
| curl, _ := unit.CharmURL() | |
| if applicationCharm != "" && curl != nil && curl.String() != applicationCharm { | |
| result.Charm = curl.String() | |
| } | |
| workloadVersion, err := unit.WorkloadVersion() | |
| if err == nil { | |
| result.WorkloadVersion = workloadVersion | |
| } else { | |
| logger.Debugf("error fetching workload version: %v", err) | |
| } | |
| processUnitAndAgentStatus(unit, &result) | |
| if subUnits := unit.SubordinateNames(); len(subUnits) > 0 { | |
| result.Subordinates = make(map[string]params.UnitStatus) | |
| for _, name := range subUnits { | |
| subUnit := context.unitByName(name) | |
| // subUnit may be nil if subordinate was filtered out. | |
| if subUnit != nil { | |
| result.Subordinates[name] = context.processUnit(subUnit, applicationCharm) | |
| } | |
| } | |
| } | |
| if leader := context.leaders[unit.ApplicationName()]; leader == unit.Name() { | |
| result.Leader = true | |
| } | |
| return result | |
| } | |
| func (context *statusContext) unitByName(name string) *state.Unit { | |
| applicationName := strings.Split(name, "/")[0] | |
| return context.units[applicationName][name] | |
| } | |
| func (context *statusContext) processApplicationRelations(application *state.Application) (related map[string][]string, subord []string, err error) { | |
| subordSet := make(set.Strings) | |
| related = make(map[string][]string) | |
| relations := context.relations[application.Name()] | |
| for _, relation := range relations { | |
| ep, err := relation.Endpoint(application.Name()) | |
| if err != nil { | |
| return nil, nil, err | |
| } | |
| relationName := ep.Relation.Name | |
| eps, err := relation.RelatedEndpoints(application.Name()) | |
| if err != nil { | |
| return nil, nil, err | |
| } | |
| for _, ep := range eps { | |
| if isSubordinate(&ep, application) { | |
| subordSet.Add(ep.ApplicationName) | |
| } | |
| related[relationName] = append(related[relationName], ep.ApplicationName) | |
| } | |
| } | |
| for relationName, applicationNames := range related { | |
| sn := set.NewStrings(applicationNames...) | |
| related[relationName] = sn.SortedValues() | |
| } | |
| return related, subordSet.SortedValues(), nil | |
| } | |
| func (context *statusContext) processRemoteApplicationRelations(application *state.RemoteApplication) (related map[string][]string, err error) { | |
| related = make(map[string][]string) | |
| relations := context.relations[application.Name()] | |
| for _, relation := range relations { | |
| ep, err := relation.Endpoint(application.Name()) | |
| if err != nil { | |
| return nil, err | |
| } | |
| relationName := ep.Relation.Name | |
| eps, err := relation.RelatedEndpoints(application.Name()) | |
| if err != nil { | |
| return nil, err | |
| } | |
| for _, ep := range eps { | |
| related[relationName] = append(related[relationName], ep.ApplicationName) | |
| } | |
| } | |
| for relationName, applicationNames := range related { | |
| sn := set.NewStrings(applicationNames...) | |
| related[relationName] = sn.SortedValues() | |
| } | |
| return related, nil | |
| } | |
| type lifer interface { | |
| Life() state.Life | |
| } | |
| // processUnitAndAgentStatus retrieves status information for both unit and unitAgents. | |
| func processUnitAndAgentStatus(unit *state.Unit, unitStatus *params.UnitStatus) { | |
| unitStatus.AgentStatus, unitStatus.WorkloadStatus = processUnit(unit) | |
| } | |
| // populateStatusFromStatusInfoAndErr creates AgentStatus from the typical output | |
| // of a status getter. | |
| func populateStatusFromStatusInfoAndErr(agent *params.DetailedStatus, statusInfo status.StatusInfo, err error) { | |
| agent.Err = err | |
| agent.Status = statusInfo.Status.String() | |
| agent.Info = statusInfo.Message | |
| agent.Data = filterStatusData(statusInfo.Data) | |
| agent.Since = statusInfo.Since | |
| } | |
| // processMachine retrieves version and status information for the given machine. | |
| // It also returns deprecated legacy status information. | |
| func processMachine(machine *state.Machine) (out params.DetailedStatus) { | |
| statusInfo, err := common.MachineStatus(machine) | |
| populateStatusFromStatusInfoAndErr(&out, statusInfo, err) | |
| out.Life = processLife(machine) | |
| if t, err := machine.AgentTools(); err == nil { | |
| out.Version = t.Version.Number.String() | |
| } | |
| return | |
| } | |
| // processUnit retrieves version and status information for the given unit. | |
| func processUnit(unit *state.Unit) (agentStatus, workloadStatus params.DetailedStatus) { | |
| agent, workload := common.UnitStatus(unit) | |
| populateStatusFromStatusInfoAndErr(&agentStatus, agent.Status, agent.Err) | |
| populateStatusFromStatusInfoAndErr(&workloadStatus, workload.Status, workload.Err) | |
| agentStatus.Life = processLife(unit) | |
| if t, err := unit.AgentTools(); err == nil { | |
| agentStatus.Version = t.Version.Number.String() | |
| } | |
| return | |
| } | |
| // filterStatusData limits what agent StatusData data is passed over | |
| // the API. This prevents unintended leakage of internal-only data. | |
| func filterStatusData(status map[string]interface{}) map[string]interface{} { | |
| out := make(map[string]interface{}) | |
| for name, value := range status { | |
| // use a set here if we end up with a larger whitelist | |
| if name == "relation-id" { | |
| out[name] = value | |
| } | |
| } | |
| return out | |
| } | |
| func processLife(entity lifer) string { | |
| if life := entity.Life(); life != state.Alive { | |
| // alive is the usual state so omit it by default. | |
| return life.String() | |
| } | |
| return "" | |
| } | |
| type bySinceDescending []status.StatusInfo | |
| // Len implements sort.Interface. | |
| func (s bySinceDescending) Len() int { return len(s) } | |
| // Swap implements sort.Interface. | |
| func (s bySinceDescending) Swap(a, b int) { s[a], s[b] = s[b], s[a] } | |
| // Less implements sort.Interface. | |
| func (s bySinceDescending) Less(a, b int) bool { return s[a].Since.After(*s[b].Since) } |