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