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