github.com/axw/juju@v0.0.0-20161005053422-4bd6544d08d4/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/names.v2" 15 16 "github.com/juju/juju/apiserver/common" 17 "github.com/juju/juju/apiserver/params" 18 "github.com/juju/juju/network" 19 "github.com/juju/juju/state" 20 "github.com/juju/juju/state/multiwatcher" 21 "github.com/juju/juju/status" 22 ) 23 24 func agentStatusFromStatusInfo(s []status.StatusInfo, kind status.HistoryKind) []params.DetailedStatus { 25 result := []params.DetailedStatus{} 26 for _, v := range s { 27 result = append(result, params.DetailedStatus{ 28 Status: string(v.Status), 29 Info: v.Message, 30 Data: v.Data, 31 Since: v.Since, 32 Kind: string(kind), 33 }) 34 } 35 return result 36 37 } 38 39 type byTime []params.DetailedStatus 40 41 func (s byTime) Len() int { 42 return len(s) 43 } 44 func (s byTime) Swap(i, j int) { 45 s[i], s[j] = s[j], s[i] 46 } 47 func (s byTime) 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(unitTag names.UnitTag, filter status.StatusHistoryFilter, kind status.HistoryKind) ([]params.DetailedStatus, error) { 53 unit, err := c.api.stateAccessor.Unit(unitTag.Id()) 54 if err != nil { 55 return nil, errors.Trace(err) 56 } 57 statuses := []params.DetailedStatus{} 58 if kind == status.KindUnit || kind == status.KindWorkload { 59 unitStatuses, err := unit.StatusHistory(filter) 60 if err != nil { 61 return nil, errors.Trace(err) 62 } 63 statuses = agentStatusFromStatusInfo(unitStatuses, status.KindWorkload) 64 65 } 66 if kind == status.KindUnit || kind == status.KindUnitAgent { 67 agentStatuses, err := unit.AgentHistory().StatusHistory(filter) 68 if err != nil { 69 return nil, errors.Trace(err) 70 } 71 statuses = append(statuses, agentStatusFromStatusInfo(agentStatuses, status.KindUnitAgent)...) 72 } 73 74 sort.Sort(byTime(statuses)) 75 if kind == status.KindUnit && filter.Size > 0 { 76 if len(statuses) > filter.Size { 77 statuses = statuses[len(statuses)-filter.Size:] 78 } 79 } 80 81 return statuses, nil 82 } 83 84 // machineStatusHistory returns status history for the given machine. 85 func (c *Client) machineStatusHistory(machineTag names.MachineTag, filter status.StatusHistoryFilter, kind status.HistoryKind) ([]params.DetailedStatus, error) { 86 machine, err := c.api.stateAccessor.Machine(machineTag.Id()) 87 if err != nil { 88 return nil, errors.Trace(err) 89 } 90 var sInfo []status.StatusInfo 91 if kind == status.KindMachineInstance || kind == status.KindContainerInstance { 92 sInfo, err = machine.InstanceStatusHistory(filter) 93 } else { 94 sInfo, err = machine.StatusHistory(filter) 95 } 96 if err != nil { 97 return nil, errors.Trace(err) 98 } 99 return agentStatusFromStatusInfo(sInfo, kind), nil 100 } 101 102 // StatusHistory returns a slice of past statuses for several entities. 103 func (c *Client) StatusHistory(request params.StatusHistoryRequests) params.StatusHistoryResults { 104 105 results := params.StatusHistoryResults{} 106 // TODO(perrito666) the contents of the loop could be split into 107 // a oneHistory method for clarity. 108 for _, request := range request.Requests { 109 filter := status.StatusHistoryFilter{ 110 Size: request.Filter.Size, 111 Date: request.Filter.Date, 112 Delta: request.Filter.Delta, 113 } 114 if err := c.checkCanRead(); err != nil { 115 history := params.StatusHistoryResult{ 116 Error: common.ServerError(err), 117 } 118 results.Results = append(results.Results, history) 119 continue 120 121 } 122 123 if err := filter.Validate(); err != nil { 124 history := params.StatusHistoryResult{ 125 Error: common.ServerError(errors.Annotate(err, "cannot validate status history filter")), 126 } 127 results.Results = append(results.Results, history) 128 continue 129 } 130 131 var ( 132 err error 133 hist []params.DetailedStatus 134 ) 135 kind := status.HistoryKind(request.Kind) 136 err = errors.NotValidf("%q requires a unit, got %T", kind, request.Tag) 137 switch kind { 138 case status.KindUnit, status.KindWorkload, status.KindUnitAgent: 139 var u names.UnitTag 140 if u, err = names.ParseUnitTag(request.Tag); err == nil { 141 hist, err = c.unitStatusHistory(u, filter, kind) 142 } 143 default: 144 var m names.MachineTag 145 if m, err = names.ParseMachineTag(request.Tag); err == nil { 146 hist, err = c.machineStatusHistory(m, filter, kind) 147 } 148 } 149 150 if err == nil { 151 sort.Sort(byTime(hist)) 152 } 153 154 results.Results = append(results.Results, 155 params.StatusHistoryResult{ 156 History: params.History{Statuses: hist}, 157 Error: common.ServerError(errors.Annotatef(err, "fetching status history for %q", request.Tag)), 158 }) 159 } 160 return results 161 } 162 163 // FullStatus gives the information needed for juju status over the api 164 func (c *Client) FullStatus(args params.StatusParams) (params.FullStatus, error) { 165 if err := c.checkCanRead(); err != nil { 166 return params.FullStatus{}, err 167 } 168 169 var noStatus params.FullStatus 170 var context statusContext 171 var err error 172 if context.services, context.units, context.latestCharms, err = 173 fetchAllApplicationsAndUnits(c.api.stateAccessor, len(args.Patterns) <= 0); err != nil { 174 return noStatus, errors.Annotate(err, "could not fetch services and units") 175 } 176 if context.machines, err = fetchMachines(c.api.stateAccessor, nil); err != nil { 177 return noStatus, errors.Annotate(err, "could not fetch machines") 178 } 179 if context.relations, err = fetchRelations(c.api.stateAccessor); err != nil { 180 return noStatus, errors.Annotate(err, "could not fetch relations") 181 } 182 if len(context.services) > 0 { 183 if context.leaders, err = c.api.stateAccessor.ApplicationLeaders(); err != nil { 184 return noStatus, errors.Annotate(err, " could not fetch leaders") 185 } 186 } 187 188 logger.Debugf("Applications: %v", context.services) 189 190 if len(args.Patterns) > 0 { 191 predicate := BuildPredicateFor(args.Patterns) 192 193 // First, attempt to match machines. Any units on those 194 // machines are implicitly matched. 195 matchedMachines := make(set.Strings) 196 for _, machineList := range context.machines { 197 for _, m := range machineList { 198 matches, err := predicate(m) 199 if err != nil { 200 return noStatus, errors.Annotate( 201 err, "could not filter machines", 202 ) 203 } 204 if matches { 205 matchedMachines.Add(m.Id()) 206 } 207 } 208 } 209 210 // Filter units 211 matchedSvcs := make(set.Strings) 212 unitChainPredicate := UnitChainPredicateFn(predicate, context.unitByName) 213 for _, unitMap := range context.units { 214 for name, unit := range unitMap { 215 machineId, err := unit.AssignedMachineId() 216 if err != nil { 217 machineId = "" 218 } else if matchedMachines.Contains(machineId) { 219 // Unit is on a matching machine. 220 matchedSvcs.Add(unit.ApplicationName()) 221 continue 222 } 223 224 // Always start examining at the top-level. This 225 // prevents a situation where we filter a subordinate 226 // before we discover its parent is a match. 227 if !unit.IsPrincipal() { 228 continue 229 } else if matches, err := unitChainPredicate(unit); err != nil { 230 return noStatus, errors.Annotate(err, "could not filter units") 231 } else if !matches { 232 delete(unitMap, name) 233 continue 234 } 235 matchedSvcs.Add(unit.ApplicationName()) 236 if machineId != "" { 237 matchedMachines.Add(machineId) 238 } 239 } 240 } 241 242 // Filter services 243 for svcName, svc := range context.services { 244 if matchedSvcs.Contains(svcName) { 245 // There are matched units for this service. 246 continue 247 } else if matches, err := predicate(svc); err != nil { 248 return noStatus, errors.Annotate(err, "could not filter applications") 249 } else if !matches { 250 delete(context.services, svcName) 251 } 252 } 253 254 // Filter machines 255 for status, machineList := range context.machines { 256 matched := make([]*state.Machine, 0, len(machineList)) 257 for _, m := range machineList { 258 machineContainers, err := m.Containers() 259 if err != nil { 260 return noStatus, err 261 } 262 machineContainersSet := set.NewStrings(machineContainers...) 263 264 if matchedMachines.Contains(m.Id()) || !matchedMachines.Intersection(machineContainersSet).IsEmpty() { 265 // The machine is matched directly, or contains a unit 266 // or container that matches. 267 logger.Tracef("machine %s is hosting something.", m.Id()) 268 matched = append(matched, m) 269 continue 270 } 271 } 272 context.machines[status] = matched 273 } 274 } 275 276 modelStatus, err := c.modelStatus() 277 if err != nil { 278 return noStatus, errors.Annotate(err, "cannot determine model status") 279 } 280 return params.FullStatus{ 281 Model: modelStatus, 282 Machines: processMachines(context.machines), 283 Applications: context.processApplications(), 284 Relations: context.processRelations(), 285 }, nil 286 } 287 288 // newToolsVersionAvailable will return a string representing a tools 289 // version only if the latest check is newer than current tools. 290 func (c *Client) modelStatus() (params.ModelStatusInfo, error) { 291 var info params.ModelStatusInfo 292 293 m, err := c.api.stateAccessor.Model() 294 if err != nil { 295 return info, errors.Annotate(err, "cannot get model") 296 } 297 info.Name = m.Name() 298 info.CloudTag = names.NewCloudTag(m.Cloud()).String() 299 info.CloudRegion = m.CloudRegion() 300 301 cfg, err := m.Config() 302 if err != nil { 303 return info, errors.Annotate(err, "cannot obtain current model config") 304 } 305 306 latestVersion := m.LatestToolsVersion() 307 current, ok := cfg.AgentVersion() 308 if ok { 309 info.Version = current.String() 310 if current.Compare(latestVersion) < 0 { 311 info.AvailableVersion = latestVersion.String() 312 } 313 } 314 315 migStatus, err := c.getMigrationStatus() 316 if err != nil { 317 // It's not worth killing the entire status out if migration 318 // status can't be retrieved. 319 logger.Errorf("error retrieving migration status: %v", err) 320 info.Migration = "error retrieving migration status" 321 } else { 322 info.Migration = migStatus 323 } 324 325 return info, nil 326 } 327 328 func (c *Client) getMigrationStatus() (string, error) { 329 mig, err := c.api.stateAccessor.LatestMigration() 330 if err != nil { 331 if errors.IsNotFound(err) { 332 return "", nil 333 } 334 return "", errors.Trace(err) 335 } 336 337 phase, err := mig.Phase() 338 if err != nil { 339 return "", errors.Trace(err) 340 } 341 if phase.IsTerminal() { 342 // There has been a migration attempt but it's no longer 343 // active - don't include this in status. 344 return "", nil 345 } 346 347 return mig.StatusMessage(), nil 348 } 349 350 type statusContext struct { 351 // machines: top-level machine id -> list of machines nested in 352 // this machine. 353 machines map[string][]*state.Machine 354 // services: service name -> service 355 services map[string]*state.Application 356 relations map[string][]*state.Relation 357 units map[string]map[string]*state.Unit 358 latestCharms map[charm.URL]*state.Charm 359 leaders map[string]string 360 } 361 362 // fetchMachines returns a map from top level machine id to machines, where machines[0] is the host 363 // machine and machines[1..n] are any containers (including nested ones). 364 // 365 // If machineIds is non-nil, only machines whose IDs are in the set are returned. 366 func fetchMachines(st Backend, machineIds set.Strings) (map[string][]*state.Machine, error) { 367 v := make(map[string][]*state.Machine) 368 machines, err := st.AllMachines() 369 if err != nil { 370 return nil, err 371 } 372 // AllMachines gives us machines sorted by id. 373 for _, m := range machines { 374 if machineIds != nil && !machineIds.Contains(m.Id()) { 375 continue 376 } 377 parentId, ok := m.ParentId() 378 if !ok { 379 // Only top level host machines go directly into the machine map. 380 v[m.Id()] = []*state.Machine{m} 381 } else { 382 topParentId := state.TopParentId(m.Id()) 383 machines, ok := v[topParentId] 384 if !ok { 385 panic(fmt.Errorf("unexpected machine id %q", parentId)) 386 } 387 machines = append(machines, m) 388 v[topParentId] = machines 389 } 390 } 391 return v, nil 392 } 393 394 // fetchAllApplicationsAndUnits returns a map from service name to service, 395 // a map from service name to unit name to unit, and a map from base charm URL to latest URL. 396 func fetchAllApplicationsAndUnits( 397 st Backend, 398 matchAny bool, 399 ) (map[string]*state.Application, map[string]map[string]*state.Unit, map[charm.URL]*state.Charm, error) { 400 401 svcMap := make(map[string]*state.Application) 402 unitMap := make(map[string]map[string]*state.Unit) 403 latestCharms := make(map[charm.URL]*state.Charm) 404 services, err := st.AllApplications() 405 if err != nil { 406 return nil, nil, nil, err 407 } 408 for _, s := range services { 409 units, err := s.AllUnits() 410 if err != nil { 411 return nil, nil, nil, err 412 } 413 svcUnitMap := make(map[string]*state.Unit) 414 for _, u := range units { 415 svcUnitMap[u.Name()] = u 416 } 417 if matchAny || len(svcUnitMap) > 0 { 418 unitMap[s.Name()] = svcUnitMap 419 svcMap[s.Name()] = s 420 // Record the base URL for the application's charm so that 421 // the latest store revision can be looked up. 422 charmURL, _ := s.CharmURL() 423 if charmURL.Schema == "cs" { 424 latestCharms[*charmURL.WithRevision(-1)] = nil 425 } 426 } 427 } 428 for baseURL := range latestCharms { 429 ch, err := st.LatestPlaceholderCharm(&baseURL) 430 if errors.IsNotFound(err) { 431 continue 432 } 433 if err != nil { 434 return nil, nil, nil, err 435 } 436 latestCharms[baseURL] = ch 437 } 438 439 return svcMap, unitMap, latestCharms, nil 440 } 441 442 // fetchRelations returns a map of all relations keyed by service name. 443 // 444 // This structure is useful for processServiceRelations() which needs 445 // to have the relations for each service. Reading them once here 446 // avoids the repeated DB hits to retrieve the relations for each 447 // service that used to happen in processServiceRelations(). 448 func fetchRelations(st Backend) (map[string][]*state.Relation, error) { 449 relations, err := st.AllRelations() 450 if err != nil { 451 return nil, err 452 } 453 out := make(map[string][]*state.Relation) 454 for _, relation := range relations { 455 for _, ep := range relation.Endpoints() { 456 out[ep.ApplicationName] = append(out[ep.ApplicationName], relation) 457 } 458 } 459 return out, nil 460 } 461 462 type machineAndContainers map[string][]*state.Machine 463 464 func (m machineAndContainers) HostForMachineId(id string) *state.Machine { 465 // Element 0 is assumed to be the top-level machine. 466 return m[id][0] 467 } 468 469 func (m machineAndContainers) Containers(id string) []*state.Machine { 470 return m[id][1:] 471 } 472 473 func processMachines(idToMachines map[string][]*state.Machine) map[string]params.MachineStatus { 474 machinesMap := make(map[string]params.MachineStatus) 475 cache := make(map[string]params.MachineStatus) 476 for id, machines := range idToMachines { 477 478 if len(machines) <= 0 { 479 continue 480 } 481 482 // Element 0 is assumed to be the top-level machine. 483 tlMachine := machines[0] 484 hostStatus := makeMachineStatus(tlMachine) 485 machinesMap[id] = hostStatus 486 cache[id] = hostStatus 487 488 for _, machine := range machines[1:] { 489 parent, ok := cache[state.ParentId(machine.Id())] 490 if !ok { 491 panic("We've broken an assumpution.") 492 } 493 494 status := makeMachineStatus(machine) 495 parent.Containers[machine.Id()] = status 496 cache[machine.Id()] = status 497 } 498 } 499 return machinesMap 500 } 501 502 func makeMachineStatus(machine *state.Machine) (status params.MachineStatus) { 503 var err error 504 status.Id = machine.Id() 505 agentStatus := processMachine(machine) 506 status.AgentStatus = agentStatus 507 508 status.Series = machine.Series() 509 status.Jobs = paramsJobsFromJobs(machine.Jobs()) 510 status.WantsVote = machine.WantsVote() 511 status.HasVote = machine.HasVote() 512 sInfo, err := machine.InstanceStatus() 513 populateStatusFromStatusInfoAndErr(&status.InstanceStatus, sInfo, err) 514 instid, err := machine.InstanceId() 515 if err == nil { 516 status.InstanceId = instid 517 addr, err := machine.PublicAddress() 518 if err != nil { 519 // Usually this indicates that no addresses have been set on the 520 // machine yet. 521 addr = network.Address{} 522 logger.Debugf("error fetching public address: %q", err) 523 } 524 status.DNSName = addr.Value 525 } else { 526 if errors.IsNotProvisioned(err) { 527 status.InstanceId = "pending" 528 } else { 529 status.InstanceId = "error" 530 } 531 } 532 hc, err := machine.HardwareCharacteristics() 533 if err != nil { 534 if !errors.IsNotFound(err) { 535 status.Hardware = "error" 536 } 537 } else { 538 status.Hardware = hc.String() 539 } 540 status.Containers = make(map[string]params.MachineStatus) 541 return 542 } 543 544 func (context *statusContext) processRelations() []params.RelationStatus { 545 var out []params.RelationStatus 546 relations := context.getAllRelations() 547 for _, relation := range relations { 548 var eps []params.EndpointStatus 549 var scope charm.RelationScope 550 var relationInterface string 551 for _, ep := range relation.Endpoints() { 552 eps = append(eps, params.EndpointStatus{ 553 ApplicationName: ep.ApplicationName, 554 Name: ep.Name, 555 Role: string(ep.Role), 556 Subordinate: context.isSubordinate(&ep), 557 }) 558 // these should match on both sides so use the last 559 relationInterface = ep.Interface 560 scope = ep.Scope 561 } 562 relStatus := params.RelationStatus{ 563 Id: relation.Id(), 564 Key: relation.String(), 565 Interface: relationInterface, 566 Scope: string(scope), 567 Endpoints: eps, 568 } 569 out = append(out, relStatus) 570 } 571 return out 572 } 573 574 // This method exists only to dedup the loaded relations as they will 575 // appear multiple times in context.relations. 576 func (context *statusContext) getAllRelations() []*state.Relation { 577 var out []*state.Relation 578 seenRelations := make(map[int]bool) 579 for _, relations := range context.relations { 580 for _, relation := range relations { 581 if _, found := seenRelations[relation.Id()]; !found { 582 out = append(out, relation) 583 seenRelations[relation.Id()] = true 584 } 585 } 586 } 587 return out 588 } 589 590 func (context *statusContext) isSubordinate(ep *state.Endpoint) bool { 591 service := context.services[ep.ApplicationName] 592 if service == nil { 593 return false 594 } 595 return isSubordinate(ep, service) 596 } 597 598 func isSubordinate(ep *state.Endpoint, service *state.Application) bool { 599 return ep.Scope == charm.ScopeContainer && !service.IsPrincipal() 600 } 601 602 // paramsJobsFromJobs converts state jobs to params jobs. 603 func paramsJobsFromJobs(jobs []state.MachineJob) []multiwatcher.MachineJob { 604 paramsJobs := make([]multiwatcher.MachineJob, len(jobs)) 605 for i, machineJob := range jobs { 606 paramsJobs[i] = machineJob.ToParams() 607 } 608 return paramsJobs 609 } 610 611 func (context *statusContext) processApplications() map[string]params.ApplicationStatus { 612 servicesMap := make(map[string]params.ApplicationStatus) 613 for _, s := range context.services { 614 servicesMap[s.Name()] = context.processApplication(s) 615 } 616 return servicesMap 617 } 618 619 func (context *statusContext) processApplication(service *state.Application) params.ApplicationStatus { 620 serviceCharm, _, err := service.Charm() 621 if err != nil { 622 return params.ApplicationStatus{Err: common.ServerError(err)} 623 } 624 625 var processedStatus = params.ApplicationStatus{ 626 Charm: serviceCharm.URL().String(), 627 Series: service.Series(), 628 Exposed: service.IsExposed(), 629 Life: processLife(service), 630 } 631 632 if latestCharm, ok := context.latestCharms[*serviceCharm.URL().WithRevision(-1)]; ok && latestCharm != nil { 633 if latestCharm.Revision() > serviceCharm.URL().Revision { 634 processedStatus.CanUpgradeTo = latestCharm.String() 635 } 636 } 637 638 processedStatus.Relations, processedStatus.SubordinateTo, err = context.processServiceRelations(service) 639 if err != nil { 640 processedStatus.Err = common.ServerError(err) 641 return processedStatus 642 } 643 units := context.units[service.Name()] 644 if service.IsPrincipal() { 645 processedStatus.Units = context.processUnits(units, serviceCharm.URL().String()) 646 } 647 applicationStatus, err := service.Status() 648 if err != nil { 649 processedStatus.Err = common.ServerError(err) 650 return processedStatus 651 } 652 processedStatus.Status.Status = applicationStatus.Status.String() 653 processedStatus.Status.Info = applicationStatus.Message 654 processedStatus.Status.Data = applicationStatus.Data 655 processedStatus.Status.Since = applicationStatus.Since 656 657 metrics := serviceCharm.Metrics() 658 planRequired := metrics != nil && metrics.Plan != nil && metrics.Plan.Required 659 if planRequired || len(service.MetricCredentials()) > 0 { 660 processedStatus.MeterStatuses = context.processUnitMeterStatuses(units) 661 } 662 663 versions := make([]status.StatusInfo, 0, len(units)) 664 for _, unit := range units { 665 statuses, err := unit.WorkloadVersionHistory().StatusHistory( 666 status.StatusHistoryFilter{Size: 1}, 667 ) 668 if err != nil { 669 processedStatus.Err = common.ServerError(err) 670 return processedStatus 671 } 672 // Even though we fully expect there to be historical values there, 673 // even the first should be the empty string, the status history 674 // collection is not added to in a transactional manner, so it may be 675 // not there even though we'd really like it to be. Such is mongo. 676 if len(statuses) > 0 { 677 versions = append(versions, statuses[0]) 678 } 679 } 680 if len(versions) > 0 { 681 sort.Sort(bySinceDescending(versions)) 682 processedStatus.WorkloadVersion = versions[0].Message 683 } 684 685 return processedStatus 686 } 687 688 func isColorStatus(code state.MeterStatusCode) bool { 689 return code == state.MeterGreen || code == state.MeterAmber || code == state.MeterRed 690 } 691 692 func (context *statusContext) processUnitMeterStatuses(units map[string]*state.Unit) map[string]params.MeterStatus { 693 unitsMap := make(map[string]params.MeterStatus) 694 for _, unit := range units { 695 meterStatus, err := unit.GetMeterStatus() 696 if err != nil { 697 continue 698 } 699 if isColorStatus(meterStatus.Code) { 700 unitsMap[unit.Name()] = params.MeterStatus{Color: strings.ToLower(meterStatus.Code.String()), Message: meterStatus.Info} 701 } 702 } 703 if len(unitsMap) > 0 { 704 return unitsMap 705 } 706 return nil 707 } 708 709 func (context *statusContext) processUnits(units map[string]*state.Unit, serviceCharm string) map[string]params.UnitStatus { 710 unitsMap := make(map[string]params.UnitStatus) 711 for _, unit := range units { 712 unitsMap[unit.Name()] = context.processUnit(unit, serviceCharm) 713 } 714 return unitsMap 715 } 716 717 func (context *statusContext) processUnit(unit *state.Unit, serviceCharm string) params.UnitStatus { 718 var result params.UnitStatus 719 addr, err := unit.PublicAddress() 720 if err != nil { 721 // Usually this indicates that no addresses have been set on the 722 // machine yet. 723 addr = network.Address{} 724 logger.Debugf("error fetching public address: %v", err) 725 } 726 result.PublicAddress = addr.Value 727 unitPorts, _ := unit.OpenedPorts() 728 for _, port := range unitPorts { 729 result.OpenedPorts = append(result.OpenedPorts, port.String()) 730 } 731 if unit.IsPrincipal() { 732 result.Machine, _ = unit.AssignedMachineId() 733 } 734 curl, _ := unit.CharmURL() 735 if serviceCharm != "" && curl != nil && curl.String() != serviceCharm { 736 result.Charm = curl.String() 737 } 738 workloadVersion, err := unit.WorkloadVersion() 739 if err == nil { 740 result.WorkloadVersion = workloadVersion 741 } else { 742 logger.Debugf("error fetching workload version: %v", err) 743 } 744 745 processUnitAndAgentStatus(unit, &result) 746 747 if subUnits := unit.SubordinateNames(); len(subUnits) > 0 { 748 result.Subordinates = make(map[string]params.UnitStatus) 749 for _, name := range subUnits { 750 subUnit := context.unitByName(name) 751 // subUnit may be nil if subordinate was filtered out. 752 if subUnit != nil { 753 result.Subordinates[name] = context.processUnit(subUnit, serviceCharm) 754 } 755 } 756 } 757 if leader := context.leaders[unit.ApplicationName()]; leader == unit.Name() { 758 result.Leader = true 759 } 760 return result 761 } 762 763 func (context *statusContext) unitByName(name string) *state.Unit { 764 serviceName := strings.Split(name, "/")[0] 765 return context.units[serviceName][name] 766 } 767 768 func (context *statusContext) processServiceRelations(service *state.Application) (related map[string][]string, subord []string, err error) { 769 subordSet := make(set.Strings) 770 related = make(map[string][]string) 771 relations := context.relations[service.Name()] 772 for _, relation := range relations { 773 ep, err := relation.Endpoint(service.Name()) 774 if err != nil { 775 return nil, nil, err 776 } 777 relationName := ep.Relation.Name 778 eps, err := relation.RelatedEndpoints(service.Name()) 779 if err != nil { 780 return nil, nil, err 781 } 782 for _, ep := range eps { 783 if isSubordinate(&ep, service) { 784 subordSet.Add(ep.ApplicationName) 785 } 786 related[relationName] = append(related[relationName], ep.ApplicationName) 787 } 788 } 789 for relationName, serviceNames := range related { 790 sn := set.NewStrings(serviceNames...) 791 related[relationName] = sn.SortedValues() 792 } 793 return related, subordSet.SortedValues(), nil 794 } 795 796 type lifer interface { 797 Life() state.Life 798 } 799 800 // processUnitAndAgentStatus retrieves status information for both unit and unitAgents. 801 func processUnitAndAgentStatus(unit *state.Unit, unitStatus *params.UnitStatus) { 802 unitStatus.AgentStatus, unitStatus.WorkloadStatus = processUnit(unit) 803 } 804 805 // populateStatusFromStatusInfoAndErr creates AgentStatus from the typical output 806 // of a status getter. 807 func populateStatusFromStatusInfoAndErr(agent *params.DetailedStatus, statusInfo status.StatusInfo, err error) { 808 agent.Err = err 809 agent.Status = statusInfo.Status.String() 810 agent.Info = statusInfo.Message 811 agent.Data = filterStatusData(statusInfo.Data) 812 agent.Since = statusInfo.Since 813 } 814 815 // processMachine retrieves version and status information for the given machine. 816 // It also returns deprecated legacy status information. 817 func processMachine(machine *state.Machine) (out params.DetailedStatus) { 818 statusInfo, err := common.MachineStatus(machine) 819 populateStatusFromStatusInfoAndErr(&out, statusInfo, err) 820 821 out.Life = processLife(machine) 822 823 if t, err := machine.AgentTools(); err == nil { 824 out.Version = t.Version.Number.String() 825 } 826 return 827 } 828 829 // processUnit retrieves version and status information for the given unit. 830 func processUnit(unit *state.Unit) (agentStatus, workloadStatus params.DetailedStatus) { 831 agent, workload := common.UnitStatus(unit) 832 populateStatusFromStatusInfoAndErr(&agentStatus, agent.Status, agent.Err) 833 populateStatusFromStatusInfoAndErr(&workloadStatus, workload.Status, workload.Err) 834 835 agentStatus.Life = processLife(unit) 836 837 if t, err := unit.AgentTools(); err == nil { 838 agentStatus.Version = t.Version.Number.String() 839 } 840 return 841 } 842 843 // filterStatusData limits what agent StatusData data is passed over 844 // the API. This prevents unintended leakage of internal-only data. 845 func filterStatusData(status map[string]interface{}) map[string]interface{} { 846 out := make(map[string]interface{}) 847 for name, value := range status { 848 // use a set here if we end up with a larger whitelist 849 if name == "relation-id" { 850 out[name] = value 851 } 852 } 853 return out 854 } 855 856 func processLife(entity lifer) string { 857 if life := entity.Life(); life != state.Alive { 858 // alive is the usual state so omit it by default. 859 return life.String() 860 } 861 return "" 862 } 863 864 type bySinceDescending []status.StatusInfo 865 866 // Len implements sort.Interface. 867 func (s bySinceDescending) Len() int { return len(s) } 868 869 // Swap implements sort.Interface. 870 func (s bySinceDescending) Swap(a, b int) { s[a], s[b] = s[b], s[a] } 871 872 // Less implements sort.Interface. 873 func (s bySinceDescending) Less(a, b int) bool { return s[a].Since.After(*s[b].Since) }