github.com/makyo/juju@v0.0.0-20160425123129-2608902037e9/state/allwatcher.go (about) 1 // Copyright 2013 Canonical Ltd. 2 // Licensed under the AGPLv3, see LICENCE file for details. 3 4 package state 5 6 import ( 7 "reflect" 8 "strings" 9 "time" 10 11 "github.com/juju/errors" 12 "github.com/juju/names" 13 "gopkg.in/mgo.v2" 14 15 "github.com/juju/juju/network" 16 "github.com/juju/juju/state/multiwatcher" 17 "github.com/juju/juju/state/watcher" 18 "github.com/juju/juju/status" 19 ) 20 21 // allWatcherStateBacking implements Backing by fetching entities for 22 // a single model from the State. 23 type allWatcherStateBacking struct { 24 st *State 25 collectionByName map[string]allWatcherStateCollection 26 } 27 28 // allModelWatcherStateBacking implements Backing by fetching entities 29 // for all models from the State. 30 type allModelWatcherStateBacking struct { 31 st *State 32 stPool *StatePool 33 collectionByName map[string]allWatcherStateCollection 34 } 35 36 // allWatcherStateCollection holds information about a 37 // collection watched by an allWatcher and the 38 // type of value we use to store entity information 39 // for that collection. 40 type allWatcherStateCollection struct { 41 // name stores the name of the collection. 42 name string 43 44 // docType stores the type of document 45 // that we use for this collection. 46 docType reflect.Type 47 48 // subsidiary is true if the collection is used only 49 // to modify a primary entity. 50 subsidiary bool 51 } 52 53 // makeAllWatcherCollectionInfo returns a name indexed map of 54 // allWatcherStateCollection instances for the collections specified. 55 func makeAllWatcherCollectionInfo(collNames ...string) map[string]allWatcherStateCollection { 56 seenTypes := make(map[reflect.Type]struct{}) 57 collectionByName := make(map[string]allWatcherStateCollection) 58 59 for _, collName := range collNames { 60 collection := allWatcherStateCollection{name: collName} 61 switch collName { 62 case modelsC: 63 collection.docType = reflect.TypeOf(backingModel{}) 64 case machinesC: 65 collection.docType = reflect.TypeOf(backingMachine{}) 66 case unitsC: 67 collection.docType = reflect.TypeOf(backingUnit{}) 68 case servicesC: 69 collection.docType = reflect.TypeOf(backingService{}) 70 case actionsC: 71 collection.docType = reflect.TypeOf(backingAction{}) 72 case relationsC: 73 collection.docType = reflect.TypeOf(backingRelation{}) 74 case annotationsC: 75 collection.docType = reflect.TypeOf(backingAnnotation{}) 76 case blocksC: 77 collection.docType = reflect.TypeOf(backingBlock{}) 78 case statusesC: 79 collection.docType = reflect.TypeOf(backingStatus{}) 80 collection.subsidiary = true 81 case constraintsC: 82 collection.docType = reflect.TypeOf(backingConstraints{}) 83 collection.subsidiary = true 84 case settingsC: 85 collection.docType = reflect.TypeOf(backingSettings{}) 86 collection.subsidiary = true 87 case openedPortsC: 88 collection.docType = reflect.TypeOf(backingOpenedPorts{}) 89 collection.subsidiary = true 90 default: 91 panic(errors.Errorf("unknown collection %q", collName)) 92 } 93 94 docType := collection.docType 95 if _, ok := seenTypes[docType]; ok { 96 panic(errors.Errorf("duplicate collection type %s", docType)) 97 } 98 seenTypes[docType] = struct{}{} 99 100 if _, ok := collectionByName[collName]; ok { 101 panic(errors.Errorf("duplicate collection name %q", collName)) 102 } 103 collectionByName[collName] = collection 104 } 105 106 return collectionByName 107 } 108 109 type backingModel modelDoc 110 111 func (e *backingModel) updated(st *State, store *multiwatcherStore, id string) error { 112 store.Update(&multiwatcher.ModelInfo{ 113 ModelUUID: e.UUID, 114 Name: e.Name, 115 Life: multiwatcher.Life(e.Life.String()), 116 Owner: e.Owner, 117 ServerUUID: e.ServerUUID, 118 }) 119 return nil 120 } 121 122 func (e *backingModel) removed(store *multiwatcherStore, modelUUID, _ string, _ *State) error { 123 store.Remove(multiwatcher.EntityId{ 124 Kind: "model", 125 ModelUUID: modelUUID, 126 Id: modelUUID, 127 }) 128 return nil 129 } 130 131 func (e *backingModel) mongoId() string { 132 return e.UUID 133 } 134 135 type backingMachine machineDoc 136 137 func (m *backingMachine) updated(st *State, store *multiwatcherStore, id string) error { 138 info := &multiwatcher.MachineInfo{ 139 ModelUUID: st.ModelUUID(), 140 Id: m.Id, 141 Life: multiwatcher.Life(m.Life.String()), 142 Series: m.Series, 143 Jobs: paramsJobsFromJobs(m.Jobs), 144 Addresses: network.MergedAddresses(networkAddresses(m.MachineAddresses), networkAddresses(m.Addresses)), 145 SupportedContainers: m.SupportedContainers, 146 SupportedContainersKnown: m.SupportedContainersKnown, 147 HasVote: m.HasVote, 148 WantsVote: wantsVote(m.Jobs, m.NoVote), 149 } 150 151 oldInfo := store.Get(info.EntityId()) 152 if oldInfo == nil { 153 // We're adding the entry for the first time, 154 // so fetch the associated machine status. 155 entity, err := st.FindEntity(names.NewMachineTag(m.Id)) 156 if err != nil { 157 return errors.Annotatef(err, "retrieving machine %q", m.Id) 158 } 159 machine, ok := entity.(status.StatusGetter) 160 if !ok { 161 return errors.Errorf("the given entity does not support Status %v", entity) 162 } 163 jujuStatus, err := machine.Status() 164 if err != nil { 165 return errors.Annotatef(err, "retrieving juju status for machine %q", m.Id) 166 } 167 info.JujuStatus = multiwatcher.NewStatusInfo(jujuStatus, err) 168 169 inst := machine.(status.InstanceStatusGetter) 170 if !ok { 171 return errors.Errorf("the given entity does not support InstanceStatus %v", entity) 172 } 173 174 machineStatus, err := inst.InstanceStatus() 175 if err != nil { 176 return errors.Annotatef(err, "retrieving instance status for machine %q", m.Id) 177 } 178 info.MachineStatus = multiwatcher.NewStatusInfo(machineStatus, err) 179 } else { 180 // The entry already exists, so preserve the current status and 181 // instance data. 182 oldInfo := oldInfo.(*multiwatcher.MachineInfo) 183 info.JujuStatus = oldInfo.JujuStatus 184 info.MachineStatus = oldInfo.MachineStatus 185 info.InstanceId = oldInfo.InstanceId 186 info.HardwareCharacteristics = oldInfo.HardwareCharacteristics 187 } 188 // If the machine is been provisioned, fetch the instance id as required, 189 // and set instance id and hardware characteristics. 190 if m.Nonce != "" && info.InstanceId == "" { 191 instanceData, err := getInstanceData(st, m.Id) 192 if err == nil { 193 info.InstanceId = string(instanceData.InstanceId) 194 info.HardwareCharacteristics = hardwareCharacteristics(instanceData) 195 } else if !errors.IsNotFound(err) { 196 return err 197 } 198 } 199 store.Update(info) 200 return nil 201 } 202 203 func (m *backingMachine) removed(store *multiwatcherStore, modelUUID, id string, _ *State) error { 204 store.Remove(multiwatcher.EntityId{ 205 Kind: "machine", 206 ModelUUID: modelUUID, 207 Id: id, 208 }) 209 return nil 210 } 211 212 func (m *backingMachine) mongoId() string { 213 return m.DocID 214 } 215 216 type backingUnit unitDoc 217 218 func getUnitPortRangesAndPorts(st *State, unitName string) ([]network.PortRange, []network.Port, error) { 219 // Get opened port ranges for the unit and convert them to ports, 220 // as older clients/servers do not know about ranges). See bug 221 // http://pad.lv/1418344 for more info. 222 unit, err := st.Unit(unitName) 223 if errors.IsNotFound(err) { 224 // Empty slices ensure backwards compatibility with older clients. 225 // See Bug #1425435. 226 return []network.PortRange{}, []network.Port{}, nil 227 } else if err != nil { 228 return nil, nil, errors.Annotatef(err, "failed to get unit %q", unitName) 229 } 230 portRanges, err := unit.OpenedPorts() 231 // Since the port ranges are associated with the unit's machine, 232 // we need to check for NotAssignedError. 233 if errors.IsNotAssigned(err) { 234 // Not assigned, so there won't be any ports opened. 235 // Empty slices ensure backwards compatibility with older clients. 236 // See Bug #1425435. 237 return []network.PortRange{}, []network.Port{}, nil 238 } else if err != nil { 239 return nil, nil, errors.Annotate(err, "failed to get unit port ranges") 240 } 241 // For backward compatibility, if there are no ports opened, return an 242 // empty slice rather than a nil slice. Use a len(portRanges) capacity to 243 // avoid unnecessary allocations, since most of the times only specific 244 // ports are opened by charms. 245 compatiblePorts := make([]network.Port, 0, len(portRanges)) 246 for _, portRange := range portRanges { 247 for j := portRange.FromPort; j <= portRange.ToPort; j++ { 248 compatiblePorts = append(compatiblePorts, network.Port{ 249 Number: j, 250 Protocol: portRange.Protocol, 251 }) 252 } 253 } 254 return portRanges, compatiblePorts, nil 255 } 256 257 func unitAndAgentStatus(st *State, name string) (unitStatus, agentStatus *status.StatusInfo, err error) { 258 unit, err := st.Unit(name) 259 if err != nil { 260 return nil, nil, errors.Trace(err) 261 } 262 unitStatusResult, err := unit.Status() 263 if err != nil { 264 return nil, nil, errors.Trace(err) 265 } 266 agentStatusResult, err := unit.AgentStatus() 267 if err != nil { 268 return nil, nil, errors.Trace(err) 269 } 270 return &unitStatusResult, &agentStatusResult, nil 271 } 272 273 func (u *backingUnit) updated(st *State, store *multiwatcherStore, id string) error { 274 info := &multiwatcher.UnitInfo{ 275 ModelUUID: st.ModelUUID(), 276 Name: u.Name, 277 Service: u.Service, 278 Series: u.Series, 279 MachineId: u.MachineId, 280 Subordinate: u.Principal != "", 281 } 282 if u.CharmURL != nil { 283 info.CharmURL = u.CharmURL.String() 284 } 285 oldInfo := store.Get(info.EntityId()) 286 if oldInfo == nil { 287 logger.Debugf("new unit %q added to backing state", u.Name) 288 // We're adding the entry for the first time, 289 // so fetch the associated unit status and opened ports. 290 unitStatus, agentStatus, err := unitAndAgentStatus(st, u.Name) 291 if err != nil { 292 return errors.Annotatef(err, "reading unit and agent status for %q", u.Name) 293 } 294 // Unit and workload status. 295 info.WorkloadStatus = multiwatcher.StatusInfo{ 296 Current: status.Status(unitStatus.Status), 297 Message: unitStatus.Message, 298 Data: normaliseStatusData(unitStatus.Data), 299 Since: unitStatus.Since, 300 } 301 if u.Tools != nil { 302 info.JujuStatus.Version = u.Tools.Version.Number.String() 303 } 304 info.JujuStatus = multiwatcher.StatusInfo{ 305 Current: status.Status(agentStatus.Status), 306 Message: agentStatus.Message, 307 Data: normaliseStatusData(agentStatus.Data), 308 Since: agentStatus.Since, 309 } 310 311 portRanges, compatiblePorts, err := getUnitPortRangesAndPorts(st, u.Name) 312 if err != nil { 313 return errors.Trace(err) 314 } 315 info.PortRanges = portRanges 316 info.Ports = compatiblePorts 317 318 } else { 319 // The entry already exists, so preserve the current status and ports. 320 oldInfo := oldInfo.(*multiwatcher.UnitInfo) 321 // Unit and workload status. 322 info.JujuStatus = oldInfo.JujuStatus 323 info.WorkloadStatus = oldInfo.WorkloadStatus 324 info.Ports = oldInfo.Ports 325 info.PortRanges = oldInfo.PortRanges 326 } 327 publicAddress, privateAddress, err := getUnitAddresses(st, u.Name) 328 if err != nil { 329 return err 330 } 331 info.PublicAddress = publicAddress 332 info.PrivateAddress = privateAddress 333 store.Update(info) 334 return nil 335 } 336 337 // getUnitAddresses returns the public and private addresses on a given unit. 338 // As of 1.18, the addresses are stored on the assigned machine but we retain 339 // this approach for backwards compatibility. 340 func getUnitAddresses(st *State, unitName string) (string, string, error) { 341 u, err := st.Unit(unitName) 342 if errors.IsNotFound(err) { 343 // Not found, so there won't be any addresses. 344 return "", "", nil 345 } else if err != nil { 346 return "", "", err 347 } 348 publicAddress, err := u.PublicAddress() 349 if err != nil { 350 logger.Warningf("getting a public address for unit %q failed: %q", u.Name(), err) 351 } 352 privateAddress, err := u.PrivateAddress() 353 if err != nil { 354 logger.Warningf("getting a private address for unit %q failed: %q", u.Name(), err) 355 } 356 return publicAddress.Value, privateAddress.Value, nil 357 } 358 359 func (u *backingUnit) removed(store *multiwatcherStore, modelUUID, id string, _ *State) error { 360 store.Remove(multiwatcher.EntityId{ 361 Kind: "unit", 362 ModelUUID: modelUUID, 363 Id: id, 364 }) 365 return nil 366 } 367 368 func (u *backingUnit) mongoId() string { 369 return u.DocID 370 } 371 372 type backingService serviceDoc 373 374 func (svc *backingService) updated(st *State, store *multiwatcherStore, id string) error { 375 if svc.CharmURL == nil { 376 return errors.Errorf("charm url is nil") 377 } 378 env, err := st.Model() 379 if err != nil { 380 return errors.Trace(err) 381 } 382 info := &multiwatcher.ServiceInfo{ 383 ModelUUID: st.ModelUUID(), 384 Name: svc.Name, 385 Exposed: svc.Exposed, 386 CharmURL: svc.CharmURL.String(), 387 OwnerTag: svc.fixOwnerTag(env), 388 Life: multiwatcher.Life(svc.Life.String()), 389 MinUnits: svc.MinUnits, 390 Subordinate: svc.Subordinate, 391 } 392 oldInfo := store.Get(info.EntityId()) 393 needConfig := false 394 if oldInfo == nil { 395 logger.Debugf("new service %q added to backing state", svc.Name) 396 key := serviceGlobalKey(svc.Name) 397 // We're adding the entry for the first time, 398 // so fetch the associated child documents. 399 c, err := readConstraints(st, key) 400 if err != nil { 401 return errors.Trace(err) 402 } 403 info.Constraints = c 404 needConfig = true 405 // Fetch the status. 406 service, err := st.Service(svc.Name) 407 if err != nil { 408 return errors.Trace(err) 409 } 410 serviceStatus, err := service.Status() 411 if err != nil { 412 logger.Warningf("reading service status for key %s: %v", key, err) 413 } 414 if err != nil && !errors.IsNotFound(err) { 415 return errors.Annotatef(err, "reading service status for key %s", key) 416 } 417 if err == nil { 418 info.Status = multiwatcher.StatusInfo{ 419 Current: serviceStatus.Status, 420 Message: serviceStatus.Message, 421 Data: normaliseStatusData(serviceStatus.Data), 422 Since: serviceStatus.Since, 423 } 424 } else { 425 // TODO(wallyworld) - bug http://pad.lv/1451283 426 // return an error here once we figure out what's happening 427 // Not sure how status can even return NotFound as it is created 428 // with the service initially. For now, we'll log the error as per 429 // the above and return Unknown. 430 // TODO(fwereade): 2016-03-17 lp:1558657 431 now := time.Now() 432 info.Status = multiwatcher.StatusInfo{ 433 Current: status.StatusUnknown, 434 Since: &now, 435 Data: normaliseStatusData(nil), 436 } 437 } 438 } else { 439 // The entry already exists, so preserve the current status. 440 oldInfo := oldInfo.(*multiwatcher.ServiceInfo) 441 info.Constraints = oldInfo.Constraints 442 if info.CharmURL == oldInfo.CharmURL { 443 // The charm URL remains the same - we can continue to 444 // use the same config settings. 445 info.Config = oldInfo.Config 446 } else { 447 // The charm URL has changed - we need to fetch the 448 // settings from the new charm's settings doc. 449 needConfig = true 450 } 451 } 452 if needConfig { 453 doc, err := readSettingsDoc(st, serviceSettingsKey(svc.Name, svc.CharmURL)) 454 if err != nil { 455 return errors.Trace(err) 456 } 457 info.Config = doc.Settings 458 } 459 store.Update(info) 460 return nil 461 } 462 463 func (svc *backingService) removed(store *multiwatcherStore, modelUUID, id string, _ *State) error { 464 store.Remove(multiwatcher.EntityId{ 465 Kind: "service", 466 ModelUUID: modelUUID, 467 Id: id, 468 }) 469 return nil 470 } 471 472 // SCHEMACHANGE 473 // TODO(mattyw) remove when schema upgrades are possible 474 func (svc *backingService) fixOwnerTag(env *Model) string { 475 if svc.OwnerTag != "" { 476 return svc.OwnerTag 477 } 478 return env.Owner().String() 479 } 480 481 func (svc *backingService) mongoId() string { 482 return svc.DocID 483 } 484 485 type backingAction actionDoc 486 487 func (a *backingAction) mongoId() string { 488 return a.DocId 489 } 490 491 func (a *backingAction) removed(store *multiwatcherStore, modelUUID, id string, _ *State) error { 492 store.Remove(multiwatcher.EntityId{ 493 Kind: "action", 494 ModelUUID: modelUUID, 495 Id: id, 496 }) 497 return nil 498 } 499 500 func (a *backingAction) updated(st *State, store *multiwatcherStore, id string) error { 501 info := &multiwatcher.ActionInfo{ 502 ModelUUID: st.ModelUUID(), 503 Id: id, 504 Receiver: a.Receiver, 505 Name: a.Name, 506 Parameters: a.Parameters, 507 Status: string(a.Status), 508 Message: a.Message, 509 Results: a.Results, 510 Enqueued: a.Enqueued, 511 Started: a.Started, 512 Completed: a.Completed, 513 } 514 store.Update(info) 515 return nil 516 } 517 518 type backingRelation relationDoc 519 520 func (r *backingRelation) updated(st *State, store *multiwatcherStore, id string) error { 521 eps := make([]multiwatcher.Endpoint, len(r.Endpoints)) 522 for i, ep := range r.Endpoints { 523 eps[i] = multiwatcher.Endpoint{ 524 ServiceName: ep.ServiceName, 525 Relation: ep.Relation, 526 } 527 } 528 info := &multiwatcher.RelationInfo{ 529 ModelUUID: st.ModelUUID(), 530 Key: r.Key, 531 Id: r.Id, 532 Endpoints: eps, 533 } 534 store.Update(info) 535 return nil 536 } 537 538 func (r *backingRelation) removed(store *multiwatcherStore, modelUUID, id string, _ *State) error { 539 store.Remove(multiwatcher.EntityId{ 540 Kind: "relation", 541 ModelUUID: modelUUID, 542 Id: id, 543 }) 544 return nil 545 } 546 547 func (r *backingRelation) mongoId() string { 548 return r.DocID 549 } 550 551 type backingAnnotation annotatorDoc 552 553 func (a *backingAnnotation) updated(st *State, store *multiwatcherStore, id string) error { 554 info := &multiwatcher.AnnotationInfo{ 555 ModelUUID: st.ModelUUID(), 556 Tag: a.Tag, 557 Annotations: a.Annotations, 558 } 559 store.Update(info) 560 return nil 561 } 562 563 func (a *backingAnnotation) removed(store *multiwatcherStore, modelUUID, id string, _ *State) error { 564 tag, ok := tagForGlobalKey(id) 565 if !ok { 566 return errors.Errorf("could not parse global key: %q", id) 567 } 568 store.Remove(multiwatcher.EntityId{ 569 Kind: "annotation", 570 ModelUUID: modelUUID, 571 Id: tag, 572 }) 573 return nil 574 } 575 576 func (a *backingAnnotation) mongoId() string { 577 return a.GlobalKey 578 } 579 580 type backingBlock blockDoc 581 582 func (a *backingBlock) updated(st *State, store *multiwatcherStore, id string) error { 583 info := &multiwatcher.BlockInfo{ 584 ModelUUID: st.ModelUUID(), 585 Id: id, 586 Tag: a.Tag, 587 Type: a.Type.ToParams(), 588 Message: a.Message, 589 } 590 store.Update(info) 591 return nil 592 } 593 594 func (a *backingBlock) removed(store *multiwatcherStore, modelUUID, id string, _ *State) error { 595 store.Remove(multiwatcher.EntityId{ 596 Kind: "block", 597 ModelUUID: modelUUID, 598 Id: id, 599 }) 600 return nil 601 } 602 603 func (a *backingBlock) mongoId() string { 604 return a.DocID 605 } 606 607 type backingStatus statusDoc 608 609 func (s *backingStatus) updated(st *State, store *multiwatcherStore, id string) error { 610 parentID, ok := backingEntityIdForGlobalKey(st.ModelUUID(), id) 611 if !ok { 612 return nil 613 } 614 info0 := store.Get(parentID) 615 switch info := info0.(type) { 616 case nil: 617 // The parent info doesn't exist. Ignore the status until it does. 618 return nil 619 case *multiwatcher.UnitInfo: 620 newInfo := *info 621 // Get the unit's current recorded status from state. 622 // It's needed to reset the unit status when a unit comes off error. 623 statusInfo, err := getStatus(st, unitGlobalKey(newInfo.Name), "unit") 624 if err != nil { 625 return err 626 } 627 if err := s.updatedUnitStatus(st, store, id, statusInfo, &newInfo); err != nil { 628 return err 629 } 630 info0 = &newInfo 631 case *multiwatcher.ServiceInfo: 632 newInfo := *info 633 newInfo.Status.Current = s.Status 634 newInfo.Status.Message = s.StatusInfo 635 newInfo.Status.Data = normaliseStatusData(s.StatusData) 636 newInfo.Status.Since = unixNanoToTime(s.Updated) 637 info0 = &newInfo 638 case *multiwatcher.MachineInfo: 639 newInfo := *info 640 // lets dissambiguate between juju machine agent and provider instance statuses. 641 if strings.HasSuffix(id, "#instance") { 642 newInfo.MachineStatus.Current = s.Status 643 newInfo.MachineStatus.Message = s.StatusInfo 644 newInfo.MachineStatus.Data = normaliseStatusData(s.StatusData) 645 newInfo.MachineStatus.Since = unixNanoToTime(s.Updated) 646 647 } else { 648 newInfo.JujuStatus.Current = s.Status 649 newInfo.JujuStatus.Message = s.StatusInfo 650 newInfo.JujuStatus.Data = normaliseStatusData(s.StatusData) 651 newInfo.JujuStatus.Since = unixNanoToTime(s.Updated) 652 } 653 info0 = &newInfo 654 default: 655 return errors.Errorf("status for unexpected entity with id %q; type %T", id, info) 656 } 657 store.Update(info0) 658 return nil 659 } 660 661 func (s *backingStatus) updatedUnitStatus(st *State, store *multiwatcherStore, id string, unitStatus status.StatusInfo, newInfo *multiwatcher.UnitInfo) error { 662 // Unit or workload status - display the agent status or any error. 663 if strings.HasSuffix(id, "#charm") || s.Status == status.StatusError { 664 newInfo.WorkloadStatus.Current = s.Status 665 newInfo.WorkloadStatus.Message = s.StatusInfo 666 newInfo.WorkloadStatus.Data = normaliseStatusData(s.StatusData) 667 newInfo.WorkloadStatus.Since = unixNanoToTime(s.Updated) 668 } else { 669 newInfo.JujuStatus.Current = s.Status 670 newInfo.JujuStatus.Message = s.StatusInfo 671 newInfo.JujuStatus.Data = normaliseStatusData(s.StatusData) 672 newInfo.JujuStatus.Since = unixNanoToTime(s.Updated) 673 // If the unit was in error and now it's not, we need to reset its 674 // status back to what was previously recorded. 675 if newInfo.WorkloadStatus.Current == status.StatusError { 676 newInfo.WorkloadStatus.Current = unitStatus.Status 677 newInfo.WorkloadStatus.Message = unitStatus.Message 678 newInfo.WorkloadStatus.Data = normaliseStatusData(unitStatus.Data) 679 newInfo.WorkloadStatus.Since = unixNanoToTime(s.Updated) 680 } 681 } 682 683 // A change in a unit's status might also affect it's service. 684 service, err := st.Service(newInfo.Service) 685 if err != nil { 686 return errors.Trace(err) 687 } 688 serviceId, ok := backingEntityIdForGlobalKey(st.ModelUUID(), service.globalKey()) 689 if !ok { 690 return nil 691 } 692 serviceInfo := store.Get(serviceId) 693 if serviceInfo == nil { 694 return nil 695 } 696 status, err := service.Status() 697 if err != nil { 698 return errors.Trace(err) 699 } 700 newServiceInfo := *serviceInfo.(*multiwatcher.ServiceInfo) 701 newServiceInfo.Status.Current = status.Status 702 newServiceInfo.Status.Message = status.Message 703 newServiceInfo.Status.Data = normaliseStatusData(status.Data) 704 newServiceInfo.Status.Since = status.Since 705 store.Update(&newServiceInfo) 706 return nil 707 } 708 709 func (s *backingStatus) removed(*multiwatcherStore, string, string, *State) error { 710 // If the status is removed, the parent will follow not long after, 711 // so do nothing. 712 return nil 713 } 714 715 func (s *backingStatus) mongoId() string { 716 panic("cannot find mongo id from status document") 717 } 718 719 type backingConstraints constraintsDoc 720 721 func (c *backingConstraints) updated(st *State, store *multiwatcherStore, id string) error { 722 parentID, ok := backingEntityIdForGlobalKey(st.ModelUUID(), id) 723 if !ok { 724 return nil 725 } 726 info0 := store.Get(parentID) 727 switch info := info0.(type) { 728 case nil: 729 // The parent info doesn't exist. Ignore the status until it does. 730 return nil 731 case *multiwatcher.UnitInfo, *multiwatcher.MachineInfo: 732 // We don't (yet) publish unit or machine constraints. 733 return nil 734 case *multiwatcher.ServiceInfo: 735 newInfo := *info 736 newInfo.Constraints = constraintsDoc(*c).value() 737 info0 = &newInfo 738 default: 739 return errors.Errorf("status for unexpected entity with id %q; type %T", id, info) 740 } 741 store.Update(info0) 742 return nil 743 } 744 745 func (c *backingConstraints) removed(*multiwatcherStore, string, string, *State) error { 746 return nil 747 } 748 749 func (c *backingConstraints) mongoId() string { 750 panic("cannot find mongo id from constraints document") 751 } 752 753 type backingSettings settingsDoc 754 755 func (s *backingSettings) updated(st *State, store *multiwatcherStore, id string) error { 756 parentID, url, ok := backingEntityIdForSettingsKey(st.ModelUUID(), id) 757 if !ok { 758 return nil 759 } 760 info0 := store.Get(parentID) 761 switch info := info0.(type) { 762 case nil: 763 // The parent info doesn't exist. Ignore the status until it does. 764 return nil 765 case *multiwatcher.ServiceInfo: 766 // If we're seeing settings for the service with a different 767 // charm URL, we ignore them - we will fetch 768 // them again when the service charm changes. 769 // By doing this we make sure that the settings in the 770 // ServiceInfo are always consistent with the charm URL. 771 if info.CharmURL != url { 772 break 773 } 774 newInfo := *info 775 newInfo.Config = s.Settings 776 info0 = &newInfo 777 default: 778 return nil 779 } 780 store.Update(info0) 781 return nil 782 } 783 784 func (s *backingSettings) removed(store *multiwatcherStore, modelUUID, id string, _ *State) error { 785 parentID, url, ok := backingEntityIdForSettingsKey(modelUUID, id) 786 if !ok { 787 // Service is already gone along with its settings. 788 return nil 789 } 790 parent := store.Get(parentID) 791 if info, ok := parent.(*multiwatcher.ServiceInfo); ok { 792 if info.CharmURL != url { 793 return nil 794 } 795 newInfo := *info 796 newInfo.Config = s.Settings 797 parent = &newInfo 798 store.Update(parent) 799 } 800 return nil 801 } 802 803 func (s *backingSettings) mongoId() string { 804 panic("cannot find mongo id from settings document") 805 } 806 807 // backingEntityIdForSettingsKey returns the entity id for the given 808 // settings key. Any extra information in the key is returned in 809 // extra. 810 func backingEntityIdForSettingsKey(modelUUID, key string) (eid multiwatcher.EntityId, extra string, ok bool) { 811 if !strings.HasPrefix(key, "s#") { 812 eid, ok = backingEntityIdForGlobalKey(modelUUID, key) 813 return 814 } 815 key = key[2:] 816 i := strings.Index(key, "#") 817 if i == -1 { 818 return multiwatcher.EntityId{}, "", false 819 } 820 eid = (&multiwatcher.ServiceInfo{ 821 ModelUUID: modelUUID, 822 Name: key[0:i], 823 }).EntityId() 824 extra = key[i+1:] 825 ok = true 826 return 827 } 828 829 type backingOpenedPorts map[string]interface{} 830 831 func (p *backingOpenedPorts) updated(st *State, store *multiwatcherStore, id string) error { 832 parentID, ok := backingEntityIdForOpenedPortsKey(st.ModelUUID(), id) 833 if !ok { 834 return nil 835 } 836 switch info := store.Get(parentID).(type) { 837 case nil: 838 // The parent info doesn't exist. This is unexpected because the port 839 // always refers to a machine. Anyway, ignore the ports for now. 840 return nil 841 case *multiwatcher.MachineInfo: 842 // Retrieve the units placed in the machine. 843 units, err := st.UnitsFor(info.Id) 844 if err != nil { 845 return errors.Trace(err) 846 } 847 // Update the ports on all units assigned to the machine. 848 for _, u := range units { 849 if err := updateUnitPorts(st, store, u); err != nil { 850 return errors.Trace(err) 851 } 852 } 853 } 854 return nil 855 } 856 857 func (p *backingOpenedPorts) removed(store *multiwatcherStore, modelUUID, id string, st *State) error { 858 if st == nil { 859 return nil 860 } 861 parentID, ok := backingEntityIdForOpenedPortsKey(st.ModelUUID(), id) 862 if !ok { 863 return nil 864 } 865 switch info := store.Get(parentID).(type) { 866 case nil: 867 // The parent info doesn't exist. This is unexpected because the port 868 // always refers to a machine. Anyway, ignore the ports for now. 869 return nil 870 case *multiwatcher.MachineInfo: 871 // Retrieve the units placed in the machine. 872 units, err := st.UnitsFor(info.Id) 873 if err != nil { 874 // An error isn't returned here because the watcher is 875 // always acting a little behind reality. It is reasonable 876 // that entities have been deleted from State but we're 877 // still seeing events related to them from the watcher. 878 logger.Warningf("cannot retrieve units for %q: %v", info.Id, err) 879 return nil 880 } 881 // Update the ports on all units assigned to the machine. 882 for _, u := range units { 883 if err := updateUnitPorts(st, store, u); err != nil { 884 logger.Warningf("cannot update unit ports for %q: %v", u.Name(), err) 885 } 886 } 887 } 888 return nil 889 } 890 891 func (p *backingOpenedPorts) mongoId() string { 892 panic("cannot find mongo id from openedPorts document") 893 } 894 895 // updateUnitPorts updates the Ports and PortRanges info of the given unit. 896 func updateUnitPorts(st *State, store *multiwatcherStore, u *Unit) error { 897 eid, ok := backingEntityIdForGlobalKey(st.ModelUUID(), u.globalKey()) 898 if !ok { 899 // This should never happen. 900 return errors.New("cannot retrieve entity id for unit") 901 } 902 switch oldInfo := store.Get(eid).(type) { 903 case nil: 904 // The unit info doesn't exist. This is unlikely to happen, but ignore 905 // the status until a unitInfo is included in the store. 906 return nil 907 case *multiwatcher.UnitInfo: 908 portRanges, compatiblePorts, err := getUnitPortRangesAndPorts(st, oldInfo.Name) 909 if err != nil { 910 return errors.Trace(err) 911 } 912 unitInfo := *oldInfo 913 unitInfo.PortRanges = portRanges 914 unitInfo.Ports = compatiblePorts 915 store.Update(&unitInfo) 916 default: 917 return nil 918 } 919 return nil 920 } 921 922 // backingEntityIdForOpenedPortsKey returns the entity id for the given 923 // openedPorts key. Any extra information in the key is discarded. 924 func backingEntityIdForOpenedPortsKey(modelUUID, key string) (multiwatcher.EntityId, bool) { 925 parts, err := extractPortsIDParts(key) 926 if err != nil { 927 logger.Debugf("cannot parse ports key %q: %v", key, err) 928 return multiwatcher.EntityId{}, false 929 } 930 return backingEntityIdForGlobalKey(modelUUID, machineGlobalKey(parts[1])) 931 } 932 933 // backingEntityIdForGlobalKey returns the entity id for the given global key. 934 // It returns false if the key is not recognized. 935 func backingEntityIdForGlobalKey(modelUUID, key string) (multiwatcher.EntityId, bool) { 936 if len(key) < 3 || key[1] != '#' { 937 return multiwatcher.EntityId{}, false 938 } 939 id := key[2:] 940 switch key[0] { 941 case 'm': 942 return (&multiwatcher.MachineInfo{ 943 ModelUUID: modelUUID, 944 Id: id, 945 }).EntityId(), true 946 case 'u': 947 id = strings.TrimSuffix(id, "#charm") 948 return (&multiwatcher.UnitInfo{ 949 ModelUUID: modelUUID, 950 Name: id, 951 }).EntityId(), true 952 case 's': 953 return (&multiwatcher.ServiceInfo{ 954 ModelUUID: modelUUID, 955 Name: id, 956 }).EntityId(), true 957 default: 958 return multiwatcher.EntityId{}, false 959 } 960 } 961 962 // backingEntityDoc is implemented by the documents in 963 // collections that the allWatcherStateBacking watches. 964 type backingEntityDoc interface { 965 // updated is called when the document has changed. 966 // The mongo _id value of the document is provided in id. 967 updated(st *State, store *multiwatcherStore, id string) error 968 969 // removed is called when the document has changed. 970 // The receiving instance will not contain any data. 971 // 972 // The mongo _id value of the document is provided in id. 973 // 974 // In some cases st may be nil. If the implementation requires st 975 // then it should do nothing. 976 removed(store *multiwatcherStore, modelUUID, id string, st *State) error 977 978 // mongoId returns the mongo _id field of the document. 979 // It is currently never called for subsidiary documents. 980 mongoId() string 981 } 982 983 func newAllWatcherStateBacking(st *State) Backing { 984 collections := makeAllWatcherCollectionInfo( 985 machinesC, 986 unitsC, 987 servicesC, 988 relationsC, 989 annotationsC, 990 statusesC, 991 constraintsC, 992 settingsC, 993 openedPortsC, 994 actionsC, 995 blocksC, 996 ) 997 return &allWatcherStateBacking{ 998 st: st, 999 collectionByName: collections, 1000 } 1001 } 1002 1003 func (b *allWatcherStateBacking) filterEnv(docID interface{}) bool { 1004 _, err := b.st.strictLocalID(docID.(string)) 1005 return err == nil 1006 } 1007 1008 // Watch watches all the collections. 1009 func (b *allWatcherStateBacking) Watch(in chan<- watcher.Change) { 1010 for _, c := range b.collectionByName { 1011 b.st.watcher.WatchCollectionWithFilter(c.name, in, b.filterEnv) 1012 } 1013 } 1014 1015 // Unwatch unwatches all the collections. 1016 func (b *allWatcherStateBacking) Unwatch(in chan<- watcher.Change) { 1017 for _, c := range b.collectionByName { 1018 b.st.watcher.UnwatchCollection(c.name, in) 1019 } 1020 } 1021 1022 // GetAll fetches all items that we want to watch from the state. 1023 func (b *allWatcherStateBacking) GetAll(all *multiwatcherStore) error { 1024 err := loadAllWatcherEntities(b.st, b.collectionByName, all) 1025 return errors.Trace(err) 1026 } 1027 1028 // Changed updates the allWatcher's idea of the current state 1029 // in response to the given change. 1030 func (b *allWatcherStateBacking) Changed(all *multiwatcherStore, change watcher.Change) error { 1031 c, ok := b.collectionByName[change.C] 1032 if !ok { 1033 return errors.Errorf("unknown collection %q in fetch request", change.C) 1034 } 1035 col, closer := b.st.getCollection(c.name) 1036 defer closer() 1037 doc := reflect.New(c.docType).Interface().(backingEntityDoc) 1038 1039 id := b.st.localID(change.Id.(string)) 1040 1041 // TODO(rog) investigate ways that this can be made more efficient 1042 // than simply fetching each entity in turn. 1043 // TODO(rog) avoid fetching documents that we have no interest 1044 // in, such as settings changes to entities we don't care about. 1045 err := col.FindId(id).One(doc) 1046 if err == mgo.ErrNotFound { 1047 err := doc.removed(all, b.st.ModelUUID(), id, b.st) 1048 return errors.Trace(err) 1049 } 1050 if err != nil { 1051 return err 1052 } 1053 return doc.updated(b.st, all, id) 1054 } 1055 1056 // Release implements the Backing interface. 1057 func (b *allWatcherStateBacking) Release() error { 1058 // allWatcherStateBacking doesn't need to release anything. 1059 return nil 1060 } 1061 1062 func NewAllModelWatcherStateBacking(st *State) Backing { 1063 collections := makeAllWatcherCollectionInfo( 1064 modelsC, 1065 machinesC, 1066 unitsC, 1067 servicesC, 1068 relationsC, 1069 annotationsC, 1070 statusesC, 1071 constraintsC, 1072 settingsC, 1073 openedPortsC, 1074 ) 1075 return &allModelWatcherStateBacking{ 1076 st: st, 1077 stPool: NewStatePool(st), 1078 collectionByName: collections, 1079 } 1080 } 1081 1082 // Watch watches all the collections. 1083 func (b *allModelWatcherStateBacking) Watch(in chan<- watcher.Change) { 1084 for _, c := range b.collectionByName { 1085 b.st.watcher.WatchCollection(c.name, in) 1086 } 1087 } 1088 1089 // Unwatch unwatches all the collections. 1090 func (b *allModelWatcherStateBacking) Unwatch(in chan<- watcher.Change) { 1091 for _, c := range b.collectionByName { 1092 b.st.watcher.UnwatchCollection(c.name, in) 1093 } 1094 } 1095 1096 // GetAll fetches all items that we want to watch from the state. 1097 func (b *allModelWatcherStateBacking) GetAll(all *multiwatcherStore) error { 1098 envs, err := b.st.AllModels() 1099 if err != nil { 1100 return errors.Annotate(err, "error loading models") 1101 } 1102 for _, env := range envs { 1103 st, err := b.st.ForModel(env.ModelTag()) 1104 if err != nil { 1105 return errors.Trace(err) 1106 } 1107 defer st.Close() 1108 1109 err = loadAllWatcherEntities(st, b.collectionByName, all) 1110 if err != nil { 1111 return errors.Annotatef(err, "error loading entities for model %v", env.UUID()) 1112 } 1113 } 1114 return nil 1115 } 1116 1117 // Changed updates the allWatcher's idea of the current state 1118 // in response to the given change. 1119 func (b *allModelWatcherStateBacking) Changed(all *multiwatcherStore, change watcher.Change) error { 1120 c, ok := b.collectionByName[change.C] 1121 if !ok { 1122 return errors.Errorf("unknown collection %q in fetch request", change.C) 1123 } 1124 1125 modelUUID, id, err := b.idForChange(change) 1126 if err != nil { 1127 return errors.Trace(err) 1128 } 1129 1130 doc := reflect.New(c.docType).Interface().(backingEntityDoc) 1131 1132 st, err := b.getState(change.C, modelUUID) 1133 if err != nil { 1134 _, envErr := b.st.GetModel(names.NewModelTag(modelUUID)) 1135 if errors.IsNotFound(envErr) { 1136 // The entity's model is gone so remove the entity 1137 // from the store. 1138 doc.removed(all, modelUUID, id, nil) 1139 return nil 1140 } 1141 return errors.Trace(err) 1142 } 1143 1144 col, closer := st.getCollection(c.name) 1145 defer closer() 1146 1147 // TODO - see TODOs in allWatcherStateBacking.Changed() 1148 err = col.FindId(id).One(doc) 1149 if err == mgo.ErrNotFound { 1150 err := doc.removed(all, modelUUID, id, st) 1151 return errors.Trace(err) 1152 } 1153 if err != nil { 1154 return err 1155 } 1156 return doc.updated(st, all, id) 1157 } 1158 1159 func (b *allModelWatcherStateBacking) idForChange(change watcher.Change) (string, string, error) { 1160 if change.C == modelsC { 1161 modelUUID := change.Id.(string) 1162 return modelUUID, modelUUID, nil 1163 } 1164 1165 modelUUID, id, ok := splitDocID(change.Id.(string)) 1166 if !ok { 1167 return "", "", errors.Errorf("unknown id format: %v", change.Id.(string)) 1168 } 1169 return modelUUID, id, nil 1170 } 1171 1172 func (b *allModelWatcherStateBacking) getState(collName, modelUUID string) (*State, error) { 1173 if collName == modelsC { 1174 return b.st, nil 1175 } 1176 1177 st, err := b.stPool.Get(modelUUID) 1178 if err != nil { 1179 return nil, errors.Trace(err) 1180 } 1181 return st, nil 1182 } 1183 1184 // Release implements the Backing interface. 1185 func (b *allModelWatcherStateBacking) Release() error { 1186 err := b.stPool.Close() 1187 return errors.Trace(err) 1188 } 1189 1190 func loadAllWatcherEntities(st *State, collectionByName map[string]allWatcherStateCollection, all *multiwatcherStore) error { 1191 // Use a single new MongoDB connection for all the work here. 1192 db, closer := st.newDB() 1193 defer closer() 1194 1195 // TODO(rog) fetch collections concurrently? 1196 for _, c := range collectionByName { 1197 if c.subsidiary { 1198 continue 1199 } 1200 col, closer := db.GetCollection(c.name) 1201 defer closer() 1202 infoSlicePtr := reflect.New(reflect.SliceOf(c.docType)) 1203 if err := col.Find(nil).All(infoSlicePtr.Interface()); err != nil { 1204 return errors.Errorf("cannot get all %s: %v", c.name, err) 1205 } 1206 infos := infoSlicePtr.Elem() 1207 for i := 0; i < infos.Len(); i++ { 1208 info := infos.Index(i).Addr().Interface().(backingEntityDoc) 1209 id := info.mongoId() 1210 err := info.updated(st, all, id) 1211 if err != nil { 1212 return errors.Annotatef(err, "failed to initialise backing for %s:%v", c.name, id) 1213 } 1214 } 1215 } 1216 1217 return nil 1218 } 1219 1220 func normaliseStatusData(data map[string]interface{}) map[string]interface{} { 1221 if data == nil { 1222 return make(map[string]interface{}) 1223 } 1224 return data 1225 }