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