github.com/altoros/juju-vmware@v0.0.0-20150312064031-f19ae857ccca/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 "strings" 9 10 "github.com/juju/errors" 11 "github.com/juju/utils/set" 12 "gopkg.in/juju/charm.v4" 13 14 "github.com/juju/juju/api" 15 "github.com/juju/juju/apiserver/params" 16 "github.com/juju/juju/constraints" 17 "github.com/juju/juju/network" 18 "github.com/juju/juju/state" 19 "github.com/juju/juju/state/multiwatcher" 20 "github.com/juju/juju/tools" 21 ) 22 23 // FullStatus gives the information needed for juju status over the api 24 func (c *Client) FullStatus(args params.StatusParams) (api.Status, error) { 25 cfg, err := c.api.state.EnvironConfig() 26 if err != nil { 27 return api.Status{}, errors.Annotate(err, "could not get environ config") 28 } 29 var noStatus api.Status 30 var context statusContext 31 if context.services, context.units, context.latestCharms, err = 32 fetchAllServicesAndUnits(c.api.state, len(args.Patterns) <= 0); err != nil { 33 return noStatus, errors.Annotate(err, "could not fetch services and units") 34 } else if context.machines, err = fetchMachines(c.api.state, nil); err != nil { 35 return noStatus, errors.Annotate(err, "could not fetch machines") 36 } else if context.relations, err = fetchRelations(c.api.state); err != nil { 37 return noStatus, errors.Annotate(err, "could not fetch relations") 38 } else if context.networks, err = fetchNetworks(c.api.state); err != nil { 39 return noStatus, errors.Annotate(err, "could not fetch networks") 40 } 41 42 logger.Debugf("Services: %v", context.services) 43 44 if len(args.Patterns) > 0 { 45 predicate := BuildPredicateFor(args.Patterns) 46 47 // Filter units 48 unfilteredSvcs := make(set.Strings) 49 unfilteredMachines := make(set.Strings) 50 unitChainPredicate := UnitChainPredicateFn(predicate, context.unitByName) 51 for _, unitMap := range context.units { 52 for name, unit := range unitMap { 53 // Always start examining at the top-level. This 54 // prevents a situation where we filter a subordinate 55 // before we discover its parent is a match. 56 if !unit.IsPrincipal() { 57 continue 58 } else if matches, err := unitChainPredicate(unit); err != nil { 59 return noStatus, errors.Annotate(err, "could not filter units") 60 } else if !matches { 61 delete(unitMap, name) 62 continue 63 } 64 65 // Track which services are utilized by the units so 66 // that we can be sure to not filter that service out. 67 unfilteredSvcs.Add(unit.ServiceName()) 68 machineId, err := unit.AssignedMachineId() 69 if err != nil { 70 return noStatus, err 71 } 72 unfilteredMachines.Add(machineId) 73 } 74 } 75 76 // Filter services 77 for svcName, svc := range context.services { 78 if unfilteredSvcs.Contains(svcName) { 79 // Don't filter services which have units that were 80 // not filtered. 81 continue 82 } else if matches, err := predicate(svc); err != nil { 83 return noStatus, errors.Annotate(err, "could not filter services") 84 } else if !matches { 85 delete(context.services, svcName) 86 } 87 } 88 89 // Filter machines 90 for status, machineList := range context.machines { 91 filteredList := make([]*state.Machine, 0, len(machineList)) 92 for _, m := range machineList { 93 machineContainers, err := m.Containers() 94 if err != nil { 95 return noStatus, err 96 } 97 machineContainersSet := set.NewStrings(machineContainers...) 98 99 if unfilteredMachines.Contains(m.Id()) || !unfilteredMachines.Intersection(machineContainersSet).IsEmpty() { 100 // Don't filter machines which have an unfiltered 101 // unit running on them. 102 logger.Debugf("mid %s is hosting something.", m.Id()) 103 filteredList = append(filteredList, m) 104 continue 105 } else if matches, err := predicate(m); err != nil { 106 return noStatus, errors.Annotate(err, "could not filter machines") 107 } else if matches { 108 filteredList = append(filteredList, m) 109 } 110 } 111 context.machines[status] = filteredList 112 } 113 } 114 115 return api.Status{ 116 EnvironmentName: cfg.Name(), 117 Machines: processMachines(context.machines), 118 Services: context.processServices(), 119 Networks: context.processNetworks(), 120 Relations: context.processRelations(), 121 }, nil 122 } 123 124 // Status is a stub version of FullStatus that was introduced in 1.16 125 func (c *Client) Status() (api.LegacyStatus, error) { 126 var legacyStatus api.LegacyStatus 127 status, err := c.FullStatus(params.StatusParams{}) 128 if err != nil { 129 return legacyStatus, err 130 } 131 132 legacyStatus.Machines = make(map[string]api.LegacyMachineStatus) 133 for machineName, machineStatus := range status.Machines { 134 legacyStatus.Machines[machineName] = api.LegacyMachineStatus{ 135 InstanceId: string(machineStatus.InstanceId), 136 } 137 } 138 return legacyStatus, nil 139 } 140 141 type statusContext struct { 142 // machines: top-level machine id -> list of machines nested in 143 // this machine. 144 machines map[string][]*state.Machine 145 // services: service name -> service 146 services map[string]*state.Service 147 relations map[string][]*state.Relation 148 units map[string]map[string]*state.Unit 149 networks map[string]*state.Network 150 latestCharms map[charm.URL]string 151 } 152 153 // fetchMachines returns a map from top level machine id to machines, where machines[0] is the host 154 // machine and machines[1..n] are any containers (including nested ones). 155 // 156 // If machineIds is non-nil, only machines whose IDs are in the set are returned. 157 func fetchMachines(st *state.State, machineIds set.Strings) (map[string][]*state.Machine, error) { 158 v := make(map[string][]*state.Machine) 159 machines, err := st.AllMachines() 160 if err != nil { 161 return nil, err 162 } 163 // AllMachines gives us machines sorted by id. 164 for _, m := range machines { 165 if machineIds != nil && !machineIds.Contains(m.Id()) { 166 continue 167 } 168 parentId, ok := m.ParentId() 169 if !ok { 170 // Only top level host machines go directly into the machine map. 171 v[m.Id()] = []*state.Machine{m} 172 } else { 173 topParentId := state.TopParentId(m.Id()) 174 machines, ok := v[topParentId] 175 if !ok { 176 panic(fmt.Errorf("unexpected machine id %q", parentId)) 177 } 178 machines = append(machines, m) 179 v[topParentId] = machines 180 } 181 } 182 return v, nil 183 } 184 185 // fetchAllServicesAndUnits returns a map from service name to service, 186 // a map from service name to unit name to unit, and a map from base charm URL to latest URL. 187 func fetchAllServicesAndUnits( 188 st *state.State, 189 matchAny bool, 190 ) (map[string]*state.Service, map[string]map[string]*state.Unit, map[charm.URL]string, error) { 191 192 svcMap := make(map[string]*state.Service) 193 unitMap := make(map[string]map[string]*state.Unit) 194 latestCharms := make(map[charm.URL]string) 195 services, err := st.AllServices() 196 if err != nil { 197 return nil, nil, nil, err 198 } 199 for _, s := range services { 200 units, err := s.AllUnits() 201 if err != nil { 202 return nil, nil, nil, err 203 } 204 svcUnitMap := make(map[string]*state.Unit) 205 for _, u := range units { 206 svcUnitMap[u.Name()] = u 207 } 208 if matchAny || len(svcUnitMap) > 0 { 209 unitMap[s.Name()] = svcUnitMap 210 svcMap[s.Name()] = s 211 // Record the base URL for the service's charm so that 212 // the latest store revision can be looked up. 213 charmURL, _ := s.CharmURL() 214 if charmURL.Schema == "cs" { 215 latestCharms[*charmURL.WithRevision(-1)] = "" 216 } 217 } 218 } 219 for baseURL := range latestCharms { 220 ch, err := st.LatestPlaceholderCharm(&baseURL) 221 if errors.IsNotFound(err) { 222 continue 223 } 224 if err != nil { 225 return nil, nil, nil, err 226 } 227 latestCharms[baseURL] = ch.String() 228 } 229 return svcMap, unitMap, latestCharms, nil 230 } 231 232 // fetchUnitMachineIds returns a set of IDs for machines that 233 // the specified units reside on, and those machines' ancestors. 234 func fetchUnitMachineIds(units map[string]map[string]*state.Unit) (set.Strings, error) { 235 machineIds := make(set.Strings) 236 for _, svcUnitMap := range units { 237 for _, unit := range svcUnitMap { 238 if !unit.IsPrincipal() { 239 continue 240 } 241 mid, err := unit.AssignedMachineId() 242 if err != nil { 243 return nil, err 244 } 245 for mid != "" { 246 machineIds.Add(mid) 247 mid = state.ParentId(mid) 248 } 249 } 250 } 251 return machineIds, nil 252 } 253 254 // fetchRelations returns a map of all relations keyed by service name. 255 // 256 // This structure is useful for processServiceRelations() which needs 257 // to have the relations for each service. Reading them once here 258 // avoids the repeated DB hits to retrieve the relations for each 259 // service that used to happen in processServiceRelations(). 260 func fetchRelations(st *state.State) (map[string][]*state.Relation, error) { 261 relations, err := st.AllRelations() 262 if err != nil { 263 return nil, err 264 } 265 out := make(map[string][]*state.Relation) 266 for _, relation := range relations { 267 for _, ep := range relation.Endpoints() { 268 out[ep.ServiceName] = append(out[ep.ServiceName], relation) 269 } 270 } 271 return out, nil 272 } 273 274 // fetchNetworks returns a map from network name to network. 275 func fetchNetworks(st *state.State) (map[string]*state.Network, error) { 276 networks, err := st.AllNetworks() 277 if err != nil { 278 return nil, err 279 } 280 out := make(map[string]*state.Network) 281 for _, n := range networks { 282 out[n.Name()] = n 283 } 284 return out, nil 285 } 286 287 type machineAndContainers map[string][]*state.Machine 288 289 func (m machineAndContainers) HostForMachineId(id string) *state.Machine { 290 // Element 0 is assumed to be the top-level machine. 291 return m[id][0] 292 } 293 294 func (m machineAndContainers) Containers(id string) []*state.Machine { 295 return m[id][1:] 296 } 297 298 func processMachines(idToMachines map[string][]*state.Machine) map[string]api.MachineStatus { 299 machinesMap := make(map[string]api.MachineStatus) 300 cache := make(map[string]api.MachineStatus) 301 for id, machines := range idToMachines { 302 303 if len(machines) <= 0 { 304 continue 305 } 306 307 // Element 0 is assumed to be the top-level machine. 308 hostStatus := makeMachineStatus(machines[0]) 309 machinesMap[id] = hostStatus 310 cache[id] = hostStatus 311 312 for _, machine := range machines[1:] { 313 parent, ok := cache[state.ParentId(machine.Id())] 314 if !ok { 315 panic("We've broken an assumpution.") 316 } 317 318 status := makeMachineStatus(machine) 319 parent.Containers[machine.Id()] = status 320 cache[machine.Id()] = status 321 } 322 } 323 return machinesMap 324 } 325 326 func makeMachineStatus(machine *state.Machine) (status api.MachineStatus) { 327 status.Id = machine.Id() 328 status.Agent, status.AgentState, status.AgentStateInfo = processAgent(machine) 329 status.AgentVersion = status.Agent.Version 330 status.Life = status.Agent.Life 331 status.Err = status.Agent.Err 332 status.Series = machine.Series() 333 status.Jobs = paramsJobsFromJobs(machine.Jobs()) 334 status.WantsVote = machine.WantsVote() 335 status.HasVote = machine.HasVote() 336 instid, err := machine.InstanceId() 337 if err == nil { 338 status.InstanceId = instid 339 status.InstanceState, err = machine.InstanceStatus() 340 if err != nil { 341 status.InstanceState = "error" 342 } 343 status.DNSName = network.SelectPublicAddress(machine.Addresses()) 344 } else { 345 if errors.IsNotProvisioned(err) { 346 status.InstanceId = "pending" 347 } else { 348 status.InstanceId = "error" 349 } 350 // There's no point in reporting a pending agent state 351 // if the machine hasn't been provisioned. This 352 // also makes unprovisioned machines visually distinct 353 // in the output. 354 status.AgentState = "" 355 } 356 hc, err := machine.HardwareCharacteristics() 357 if err != nil { 358 if !errors.IsNotFound(err) { 359 status.Hardware = "error" 360 } 361 } else { 362 status.Hardware = hc.String() 363 } 364 status.Containers = make(map[string]api.MachineStatus) 365 return 366 } 367 368 func (context *statusContext) processRelations() []api.RelationStatus { 369 var out []api.RelationStatus 370 relations := context.getAllRelations() 371 for _, relation := range relations { 372 var eps []api.EndpointStatus 373 var scope charm.RelationScope 374 var relationInterface string 375 for _, ep := range relation.Endpoints() { 376 eps = append(eps, api.EndpointStatus{ 377 ServiceName: ep.ServiceName, 378 Name: ep.Name, 379 Role: ep.Role, 380 Subordinate: context.isSubordinate(&ep), 381 }) 382 // these should match on both sides so use the last 383 relationInterface = ep.Interface 384 scope = ep.Scope 385 } 386 relStatus := api.RelationStatus{ 387 Id: relation.Id(), 388 Key: relation.String(), 389 Interface: relationInterface, 390 Scope: scope, 391 Endpoints: eps, 392 } 393 out = append(out, relStatus) 394 } 395 return out 396 } 397 398 // This method exists only to dedup the loaded relations as they will 399 // appear multiple times in context.relations. 400 func (context *statusContext) getAllRelations() []*state.Relation { 401 var out []*state.Relation 402 seenRelations := make(map[int]bool) 403 for _, relations := range context.relations { 404 for _, relation := range relations { 405 if _, found := seenRelations[relation.Id()]; !found { 406 out = append(out, relation) 407 seenRelations[relation.Id()] = true 408 } 409 } 410 } 411 return out 412 } 413 414 func (context *statusContext) processNetworks() map[string]api.NetworkStatus { 415 networksMap := make(map[string]api.NetworkStatus) 416 for name, network := range context.networks { 417 networksMap[name] = context.makeNetworkStatus(network) 418 } 419 return networksMap 420 } 421 422 func (context *statusContext) makeNetworkStatus(network *state.Network) api.NetworkStatus { 423 return api.NetworkStatus{ 424 ProviderId: network.ProviderId(), 425 CIDR: network.CIDR(), 426 VLANTag: network.VLANTag(), 427 } 428 } 429 430 func (context *statusContext) isSubordinate(ep *state.Endpoint) bool { 431 service := context.services[ep.ServiceName] 432 if service == nil { 433 return false 434 } 435 return isSubordinate(ep, service) 436 } 437 438 func isSubordinate(ep *state.Endpoint, service *state.Service) bool { 439 return ep.Scope == charm.ScopeContainer && !service.IsPrincipal() 440 } 441 442 // paramsJobsFromJobs converts state jobs to params jobs. 443 func paramsJobsFromJobs(jobs []state.MachineJob) []multiwatcher.MachineJob { 444 paramsJobs := make([]multiwatcher.MachineJob, len(jobs)) 445 for i, machineJob := range jobs { 446 paramsJobs[i] = machineJob.ToParams() 447 } 448 return paramsJobs 449 } 450 451 func (context *statusContext) processServices() map[string]api.ServiceStatus { 452 servicesMap := make(map[string]api.ServiceStatus) 453 for _, s := range context.services { 454 servicesMap[s.Name()] = context.processService(s) 455 } 456 return servicesMap 457 } 458 459 func (context *statusContext) processService(service *state.Service) (status api.ServiceStatus) { 460 serviceCharmURL, _ := service.CharmURL() 461 status.Charm = serviceCharmURL.String() 462 status.Exposed = service.IsExposed() 463 status.Life = processLife(service) 464 465 latestCharm, ok := context.latestCharms[*serviceCharmURL.WithRevision(-1)] 466 if ok && latestCharm != serviceCharmURL.String() { 467 status.CanUpgradeTo = latestCharm 468 } 469 var err error 470 status.Relations, status.SubordinateTo, err = context.processServiceRelations(service) 471 if err != nil { 472 status.Err = err 473 return 474 } 475 networks, err := service.Networks() 476 if err != nil { 477 status.Err = err 478 return 479 } 480 var cons constraints.Value 481 if service.IsPrincipal() { 482 // Only principals can have constraints. 483 cons, err = service.Constraints() 484 if err != nil { 485 status.Err = err 486 return 487 } 488 } 489 if len(networks) > 0 || cons.HaveNetworks() { 490 // Only the explicitly requested networks (using "juju deploy 491 // <svc> --networks=...") will be enabled, and altough when 492 // specified, networks constraints will be used for instance 493 // selection, they won't be actually enabled. 494 status.Networks = api.NetworksSpecification{ 495 Enabled: networks, 496 Disabled: append(cons.IncludeNetworks(), cons.ExcludeNetworks()...), 497 } 498 } 499 if service.IsPrincipal() { 500 status.Units = context.processUnits(context.units[service.Name()], serviceCharmURL.String()) 501 } 502 return status 503 } 504 505 func (context *statusContext) processUnits(units map[string]*state.Unit, serviceCharm string) map[string]api.UnitStatus { 506 unitsMap := make(map[string]api.UnitStatus) 507 for _, unit := range units { 508 unitsMap[unit.Name()] = context.processUnit(unit, serviceCharm) 509 } 510 return unitsMap 511 } 512 513 func (context *statusContext) processUnit(unit *state.Unit, serviceCharm string) (status api.UnitStatus) { 514 status.PublicAddress, _ = unit.PublicAddress() 515 unitPorts, _ := unit.OpenedPorts() 516 for _, port := range unitPorts { 517 status.OpenedPorts = append(status.OpenedPorts, port.String()) 518 } 519 if unit.IsPrincipal() { 520 status.Machine, _ = unit.AssignedMachineId() 521 } 522 curl, _ := unit.CharmURL() 523 if serviceCharm != "" && curl != nil && curl.String() != serviceCharm { 524 status.Charm = curl.String() 525 } 526 status.Agent, status.AgentState, status.AgentStateInfo = processAgent(unit) 527 528 // Until Juju 2.0, we need to continue to display legacy status values. 529 status.Agent.Status = params.TranslateLegacyStatus(status.Agent.Status) 530 status.AgentState = params.TranslateLegacyStatus(status.AgentState) 531 532 status.AgentVersion = status.Agent.Version 533 status.Life = status.Agent.Life 534 status.Err = status.Agent.Err 535 if subUnits := unit.SubordinateNames(); len(subUnits) > 0 { 536 status.Subordinates = make(map[string]api.UnitStatus) 537 for _, name := range subUnits { 538 subUnit := context.unitByName(name) 539 // subUnit may be nil if subordinate was filtered out. 540 if subUnit != nil { 541 status.Subordinates[name] = context.processUnit(subUnit, serviceCharm) 542 } 543 } 544 } 545 return 546 } 547 548 func (context *statusContext) unitByName(name string) *state.Unit { 549 serviceName := strings.Split(name, "/")[0] 550 return context.units[serviceName][name] 551 } 552 553 func (context *statusContext) processServiceRelations(service *state.Service) (related map[string][]string, subord []string, err error) { 554 subordSet := make(set.Strings) 555 related = make(map[string][]string) 556 relations := context.relations[service.Name()] 557 for _, relation := range relations { 558 ep, err := relation.Endpoint(service.Name()) 559 if err != nil { 560 return nil, nil, err 561 } 562 relationName := ep.Relation.Name 563 eps, err := relation.RelatedEndpoints(service.Name()) 564 if err != nil { 565 return nil, nil, err 566 } 567 for _, ep := range eps { 568 if isSubordinate(&ep, service) { 569 subordSet.Add(ep.ServiceName) 570 } 571 related[relationName] = append(related[relationName], ep.ServiceName) 572 } 573 } 574 for relationName, serviceNames := range related { 575 sn := set.NewStrings(serviceNames...) 576 related[relationName] = sn.SortedValues() 577 } 578 return related, subordSet.SortedValues(), nil 579 } 580 581 type lifer interface { 582 Life() state.Life 583 } 584 585 type stateAgent interface { 586 lifer 587 AgentPresence() (bool, error) 588 AgentTools() (*tools.Tools, error) 589 Status() (state.Status, string, map[string]interface{}, error) 590 } 591 592 // processAgent retrieves version and status information from the given entity. 593 func processAgent(entity stateAgent) (out api.AgentStatus, compatStatus params.Status, compatInfo string) { 594 out.Life = processLife(entity) 595 596 if t, err := entity.AgentTools(); err == nil { 597 out.Version = t.Version.Number.String() 598 } 599 600 var st state.Status 601 st, out.Info, out.Data, out.Err = entity.Status() 602 out.Status = params.Status(st) 603 compatStatus = out.Status 604 compatInfo = out.Info 605 out.Data = filterStatusData(out.Data) 606 if out.Err != nil { 607 return 608 } 609 610 if out.Status == params.StatusPending || // Need to still check pending for existing deployments. 611 out.Status == params.StatusAllocating || 612 out.Status == params.StatusInstalling { 613 // The status is allocating or installing - there's no point 614 // in enquiring about the agent liveness. 615 return 616 } 617 agentAlive, err := entity.AgentPresence() 618 if err != nil { 619 return 620 } 621 622 if entity.Life() != state.Dead && !agentAlive { 623 // The agent *should* be alive but is not. Set status to 624 // StatusDown and munge Info to indicate the previous status and 625 // info. This is unfortunately making presentation decisions 626 // on behalf of the client (crappy). 627 // 628 // This is munging is only being left in place for 629 // compatibility with older clients. TODO: At some point we 630 // should change this so that Info left alone. API version may 631 // help here. 632 // 633 // Better yet, Status shouldn't be changed here in the API at 634 // all! Status changes should only happen in State. One 635 // problem caused by this is that this status change won't be 636 // seen by clients using a watcher because it didn't happen in 637 // State. 638 if out.Info != "" { 639 compatInfo = fmt.Sprintf("(%s: %s)", out.Status, out.Info) 640 } else { 641 compatInfo = fmt.Sprintf("(%s)", out.Status) 642 } 643 compatStatus = params.StatusDown 644 } 645 646 return 647 } 648 649 // filterStatusData limits what agent StatusData data is passed over 650 // the API. This prevents unintended leakage of internal-only data. 651 func filterStatusData(status map[string]interface{}) map[string]interface{} { 652 out := make(map[string]interface{}) 653 for name, value := range status { 654 // use a set here if we end up with a larger whitelist 655 if name == "relation-id" { 656 out[name] = value 657 } 658 } 659 return out 660 } 661 662 func processLife(entity lifer) string { 663 if life := entity.Life(); life != state.Alive { 664 // alive is the usual state so omit it by default. 665 return life.String() 666 } 667 return "" 668 }