github.com/juju/juju@v0.0.0-20240430160146-1752b71fcf00/apiserver/facades/controller/firewaller/firewaller.go (about) 1 // Copyright 2014 Canonical Ltd. 2 // Licensed under the AGPLv3, see LICENCE file for details. 3 4 package firewaller 5 6 import ( 7 "sort" 8 "strconv" 9 10 "github.com/juju/collections/set" 11 "github.com/juju/errors" 12 "github.com/juju/loggo" 13 "github.com/juju/names/v5" 14 15 "github.com/juju/juju/apiserver/common" 16 "github.com/juju/juju/apiserver/common/cloudspec" 17 "github.com/juju/juju/apiserver/common/firewall" 18 apiservererrors "github.com/juju/juju/apiserver/errors" 19 "github.com/juju/juju/apiserver/facade" 20 "github.com/juju/juju/core/network" 21 "github.com/juju/juju/core/status" 22 "github.com/juju/juju/rpc/params" 23 "github.com/juju/juju/state" 24 "github.com/juju/juju/state/watcher" 25 ) 26 27 var logger = loggo.GetLogger("juju.apiserver.firewaller") 28 29 // FirewallerAPI provides access to the Firewaller API facade. 30 type FirewallerAPI struct { 31 *common.LifeGetter 32 *common.ModelWatcher 33 *common.AgentEntityWatcher 34 *common.UnitsWatcher 35 *common.ModelMachinesWatcher 36 *common.InstanceIdGetter 37 ControllerConfigAPI 38 cloudspec.CloudSpecer 39 40 st State 41 resources facade.Resources 42 authorizer facade.Authorizer 43 accessUnit common.GetAuthFunc 44 accessApplication common.GetAuthFunc 45 accessMachine common.GetAuthFunc 46 accessModel common.GetAuthFunc 47 48 // Fetched on demand and memoized 49 spaceInfos network.SpaceInfos 50 appEndpointBindings map[string]map[string]string 51 } 52 53 // NewStateFirewallerAPI creates a new server-side FirewallerAPIV7 facade. 54 func NewStateFirewallerAPI( 55 st State, 56 resources facade.Resources, 57 authorizer facade.Authorizer, 58 cloudSpecAPI cloudspec.CloudSpecer, 59 controllerConfigAPI ControllerConfigAPI, 60 ) (*FirewallerAPI, error) { 61 if !authorizer.AuthController() { 62 // Firewaller must run as a controller. 63 return nil, apiservererrors.ErrPerm 64 } 65 // Set up the various authorization checkers. 66 accessModel := common.AuthFuncForTagKind(names.ModelTagKind) 67 accessUnit := common.AuthFuncForTagKind(names.UnitTagKind) 68 accessApplication := common.AuthFuncForTagKind(names.ApplicationTagKind) 69 accessMachine := common.AuthFuncForTagKind(names.MachineTagKind) 70 accessRelation := common.AuthFuncForTagKind(names.RelationTagKind) 71 accessUnitApplicationOrMachineOrRelation := common.AuthAny(accessUnit, accessApplication, accessMachine, accessRelation) 72 73 // Life() is supported for units, applications or machines. 74 lifeGetter := common.NewLifeGetter( 75 st, 76 accessUnitApplicationOrMachineOrRelation, 77 ) 78 // ModelConfig() and WatchForModelConfigChanges() are allowed 79 // with unrestricted access. 80 modelWatcher := common.NewModelWatcher( 81 st, 82 resources, 83 authorizer, 84 ) 85 // Watch() is supported for applications only. 86 entityWatcher := common.NewAgentEntityWatcher( 87 st, 88 resources, 89 accessApplication, 90 ) 91 // WatchUnits() is supported for machines. 92 unitsWatcher := common.NewUnitsWatcher(st, 93 resources, 94 accessMachine, 95 ) 96 // WatchModelMachines() is allowed with unrestricted access. 97 machinesWatcher := common.NewModelMachinesWatcher( 98 st, 99 resources, 100 authorizer, 101 ) 102 // InstanceId() is supported for machines. 103 instanceIdGetter := common.NewInstanceIdGetter( 104 st, 105 accessMachine, 106 ) 107 108 return &FirewallerAPI{ 109 LifeGetter: lifeGetter, 110 ModelWatcher: modelWatcher, 111 AgentEntityWatcher: entityWatcher, 112 UnitsWatcher: unitsWatcher, 113 ModelMachinesWatcher: machinesWatcher, 114 InstanceIdGetter: instanceIdGetter, 115 CloudSpecer: cloudSpecAPI, 116 ControllerConfigAPI: controllerConfigAPI, 117 st: st, 118 resources: resources, 119 authorizer: authorizer, 120 accessUnit: accessUnit, 121 accessApplication: accessApplication, 122 accessMachine: accessMachine, 123 accessModel: accessModel, 124 }, nil 125 } 126 127 // WatchOpenedPorts returns a new StringsWatcher for each given 128 // model tag. 129 func (f *FirewallerAPI) WatchOpenedPorts(args params.Entities) (params.StringsWatchResults, error) { 130 result := params.StringsWatchResults{ 131 Results: make([]params.StringsWatchResult, len(args.Entities)), 132 } 133 if len(args.Entities) == 0 { 134 return result, nil 135 } 136 canWatch, err := f.accessModel() 137 if err != nil { 138 return params.StringsWatchResults{}, errors.Trace(err) 139 } 140 for i, entity := range args.Entities { 141 tag, err := names.ParseTag(entity.Tag) 142 if err != nil { 143 result.Results[i].Error = apiservererrors.ServerError(apiservererrors.ErrPerm) 144 continue 145 } 146 if !canWatch(tag) { 147 result.Results[i].Error = apiservererrors.ServerError(apiservererrors.ErrPerm) 148 continue 149 } 150 watcherId, initial, err := f.watchOneModelOpenedPorts(tag) 151 if err != nil { 152 result.Results[i].Error = apiservererrors.ServerError(err) 153 continue 154 } 155 result.Results[i].StringsWatcherId = watcherId 156 result.Results[i].Changes = initial 157 } 158 return result, nil 159 } 160 161 func (f *FirewallerAPI) watchOneModelOpenedPorts(tag names.Tag) (string, []string, error) { 162 // NOTE: tag is ignored, as there is only one model in the 163 // state DB. Once this changes, change the code below accordingly. 164 watch := f.st.WatchOpenedPorts() 165 // Consume the initial event and forward it to the result. 166 if changes, ok := <-watch.Changes(); ok { 167 return f.resources.Register(watch), changes, nil 168 } 169 return "", nil, watcher.EnsureErr(watch) 170 } 171 172 // ModelFirewallRules returns the firewall rules that this model is 173 // configured to open 174 func (f *FirewallerAPI) ModelFirewallRules() (params.IngressRulesResult, error) { 175 cfg, err := f.st.ModelConfig() 176 if err != nil { 177 return params.IngressRulesResult{Error: apiservererrors.ServerError(err)}, nil 178 } 179 ctrlCfg, err := f.st.ControllerConfig() 180 if err != nil { 181 return params.IngressRulesResult{Error: apiservererrors.ServerError(err)}, nil 182 } 183 isController := f.st.IsController() 184 185 var rules []params.IngressRule 186 sshAllow := cfg.SSHAllow() 187 if len(sshAllow) != 0 { 188 portRange := params.FromNetworkPortRange(network.MustParsePortRange("22")) 189 rules = append(rules, params.IngressRule{PortRange: portRange, SourceCIDRs: sshAllow}) 190 } 191 if isController { 192 portRange := params.FromNetworkPortRange(network.MustParsePortRange(strconv.Itoa(ctrlCfg.APIPort()))) 193 rules = append(rules, params.IngressRule{PortRange: portRange, SourceCIDRs: []string{"0.0.0.0/0", "::/0"}}) 194 } 195 if isController && ctrlCfg.AutocertDNSName() != "" { 196 portRange := params.FromNetworkPortRange(network.MustParsePortRange("80")) 197 rules = append(rules, params.IngressRule{PortRange: portRange, SourceCIDRs: []string{"0.0.0.0/0", "::/0"}}) 198 } 199 return params.IngressRulesResult{ 200 Rules: rules, 201 }, nil 202 } 203 204 // WatchModelFirewallRules returns a NotifyWatcher that notifies of 205 // potential changes to a model's configured firewall rules 206 func (f *FirewallerAPI) WatchModelFirewallRules() (params.NotifyWatchResult, error) { 207 watch, err := NewModelFirewallRulesWatcher(f.st) 208 if err != nil { 209 return params.NotifyWatchResult{Error: apiservererrors.ServerError(err)}, nil 210 } 211 212 if _, ok := <-watch.Changes(); ok { 213 return params.NotifyWatchResult{NotifyWatcherId: f.resources.Register(watch)}, nil 214 } else { 215 err := watcher.EnsureErr(watch) 216 return params.NotifyWatchResult{Error: apiservererrors.ServerError(err)}, nil 217 } 218 } 219 220 // GetAssignedMachine returns the assigned machine tag (if any) for 221 // each given unit. 222 func (f *FirewallerAPI) GetAssignedMachine(args params.Entities) (params.StringResults, error) { 223 result := params.StringResults{ 224 Results: make([]params.StringResult, len(args.Entities)), 225 } 226 canAccess, err := f.accessUnit() 227 if err != nil { 228 return params.StringResults{}, err 229 } 230 for i, entity := range args.Entities { 231 tag, err := names.ParseUnitTag(entity.Tag) 232 if err != nil { 233 result.Results[i].Error = apiservererrors.ServerError(apiservererrors.ErrPerm) 234 continue 235 } 236 unit, err := f.getUnit(canAccess, tag) 237 if err == nil { 238 var machineId string 239 machineId, err = unit.AssignedMachineId() 240 if err == nil { 241 result.Results[i].Result = names.NewMachineTag(machineId).String() 242 } 243 } 244 result.Results[i].Error = apiservererrors.ServerError(err) 245 } 246 return result, nil 247 } 248 249 // getSpaceInfos returns the cached SpaceInfos or retrieves them from state 250 // and memoizes it for future invocations. 251 func (f *FirewallerAPI) getSpaceInfos() (network.SpaceInfos, error) { 252 if f.spaceInfos != nil { 253 return f.spaceInfos, nil 254 } 255 256 si, err := f.st.SpaceInfos() 257 if err != nil { 258 return nil, errors.Trace(err) 259 } 260 261 return si, nil 262 } 263 264 // getApplicationBindings returns the cached endpoint bindings for all model 265 // applications grouped by app name. If the application endpoints have not yet 266 // been retrieved they will be retrieved and memoized for future calls. 267 func (f *FirewallerAPI) getApplicationBindings() (map[string]map[string]string, error) { 268 if f.appEndpointBindings == nil { 269 bindings, err := f.st.AllEndpointBindings() 270 if err != nil { 271 return nil, errors.Trace(err) 272 } 273 f.appEndpointBindings = bindings 274 } 275 276 return f.appEndpointBindings, nil 277 } 278 279 func (f *FirewallerAPI) getEntity(canAccess common.AuthFunc, tag names.Tag) (state.Entity, error) { 280 if !canAccess(tag) { 281 return nil, apiservererrors.ErrPerm 282 } 283 return f.st.FindEntity(tag) 284 } 285 286 func (f *FirewallerAPI) getUnit(canAccess common.AuthFunc, tag names.UnitTag) (*state.Unit, error) { 287 entity, err := f.getEntity(canAccess, tag) 288 if err != nil { 289 return nil, err 290 } 291 // The authorization function guarantees that the tag represents a 292 // unit. 293 return entity.(*state.Unit), nil 294 } 295 296 func (f *FirewallerAPI) getApplication(canAccess common.AuthFunc, tag names.ApplicationTag) (*state.Application, error) { 297 entity, err := f.getEntity(canAccess, tag) 298 if err != nil { 299 return nil, err 300 } 301 // The authorization function guarantees that the tag represents a 302 // application. 303 return entity.(*state.Application), nil 304 } 305 306 func (f *FirewallerAPI) getMachine(canAccess common.AuthFunc, tag names.MachineTag) (firewall.Machine, error) { 307 if !canAccess(tag) { 308 return nil, apiservererrors.ErrPerm 309 } 310 return f.st.Machine(tag.Id()) 311 } 312 313 // WatchEgressAddressesForRelations creates a watcher that notifies when addresses, from which 314 // connections will originate for the relation, change. 315 // Each event contains the entire set of addresses which are required for ingress for the relation. 316 func (f *FirewallerAPI) WatchEgressAddressesForRelations(relations params.Entities) (params.StringsWatchResults, error) { 317 return firewall.WatchEgressAddressesForRelations(f.resources, f.st, relations) 318 } 319 320 // WatchIngressAddressesForRelations creates a watcher that returns the ingress networks 321 // that have been recorded against the specified relations. 322 func (f *FirewallerAPI) WatchIngressAddressesForRelations(relations params.Entities) (params.StringsWatchResults, error) { 323 results := params.StringsWatchResults{ 324 make([]params.StringsWatchResult, len(relations.Entities)), 325 } 326 327 one := func(tag string) (id string, changes []string, _ error) { 328 logger.Debugf("Watching ingress addresses for %+v from model %v", tag, f.st.ModelUUID()) 329 330 relationTag, err := names.ParseRelationTag(tag) 331 if err != nil { 332 return "", nil, errors.Trace(err) 333 } 334 rel, err := f.st.KeyRelation(relationTag.Id()) 335 if err != nil { 336 return "", nil, errors.Trace(err) 337 } 338 w := rel.WatchRelationIngressNetworks() 339 changes, ok := <-w.Changes() 340 if !ok { 341 return "", nil, apiservererrors.ServerError(watcher.EnsureErr(w)) 342 } 343 return f.resources.Register(w), changes, nil 344 } 345 346 for i, e := range relations.Entities { 347 watcherId, changes, err := one(e.Tag) 348 if err != nil { 349 results.Results[i].Error = apiservererrors.ServerError(err) 350 continue 351 } 352 results.Results[i].StringsWatcherId = watcherId 353 results.Results[i].Changes = changes 354 } 355 return results, nil 356 } 357 358 // MacaroonForRelations returns the macaroon for the specified relations. 359 func (f *FirewallerAPI) MacaroonForRelations(args params.Entities) (params.MacaroonResults, error) { 360 var result params.MacaroonResults 361 result.Results = make([]params.MacaroonResult, len(args.Entities)) 362 for i, entity := range args.Entities { 363 relationTag, err := names.ParseRelationTag(entity.Tag) 364 if err != nil { 365 result.Results[i].Error = apiservererrors.ServerError(err) 366 continue 367 } 368 mac, err := f.st.GetMacaroon(relationTag) 369 if err != nil { 370 result.Results[i].Error = apiservererrors.ServerError(err) 371 continue 372 } 373 result.Results[i].Result = mac 374 } 375 return result, nil 376 } 377 378 // SetRelationsStatus sets the status for the specified relations. 379 func (f *FirewallerAPI) SetRelationsStatus(args params.SetStatus) (params.ErrorResults, error) { 380 var result params.ErrorResults 381 result.Results = make([]params.ErrorResult, len(args.Entities)) 382 for i, entity := range args.Entities { 383 relationTag, err := names.ParseRelationTag(entity.Tag) 384 if err != nil { 385 result.Results[i].Error = apiservererrors.ServerError(err) 386 continue 387 } 388 rel, err := f.st.KeyRelation(relationTag.Id()) 389 if err != nil { 390 result.Results[i].Error = apiservererrors.ServerError(err) 391 continue 392 } 393 err = rel.SetStatus(status.StatusInfo{ 394 Status: status.Status(entity.Status), 395 Message: entity.Info, 396 }) 397 result.Results[i].Error = apiservererrors.ServerError(err) 398 } 399 return result, nil 400 } 401 402 // AreManuallyProvisioned returns whether each given entity is 403 // manually provisioned or not. Only machine tags are accepted. 404 func (f *FirewallerAPI) AreManuallyProvisioned(args params.Entities) (params.BoolResults, error) { 405 result := params.BoolResults{ 406 Results: make([]params.BoolResult, len(args.Entities)), 407 } 408 canAccess, err := f.accessMachine() 409 if err != nil { 410 return result, err 411 } 412 for i, arg := range args.Entities { 413 machineTag, err := names.ParseMachineTag(arg.Tag) 414 if err != nil { 415 result.Results[i].Error = apiservererrors.ServerError(err) 416 continue 417 } 418 machine, err := f.getMachine(canAccess, machineTag) 419 if err == nil { 420 result.Results[i].Result, err = machine.IsManual() 421 } 422 result.Results[i].Error = apiservererrors.ServerError(err) 423 } 424 return result, nil 425 } 426 427 // OpenedMachinePortRanges returns a list of the opened port ranges for the 428 // specified machines where each result is broken down by unit. The list of 429 // opened ports for each unit is further grouped by endpoint name and includes 430 // the subnet CIDRs that belong to the space that each endpoint is bound to. 431 func (f *FirewallerAPI) OpenedMachinePortRanges(args params.Entities) (params.OpenMachinePortRangesResults, error) { 432 result := params.OpenMachinePortRangesResults{ 433 Results: make([]params.OpenMachinePortRangesResult, len(args.Entities)), 434 } 435 canAccess, err := f.accessMachine() 436 if err != nil { 437 return result, err 438 } 439 440 for i, arg := range args.Entities { 441 machineTag, err := names.ParseMachineTag(arg.Tag) 442 if err != nil { 443 result.Results[i].Error = apiservererrors.ServerError(err) 444 continue 445 } 446 447 machine, err := f.getMachine(canAccess, machineTag) 448 if err != nil { 449 result.Results[i].Error = apiservererrors.ServerError(err) 450 continue 451 } 452 453 unitPortRanges, err := f.openedPortRangesForOneMachine(machine) 454 if err != nil { 455 result.Results[i].Error = apiservererrors.ServerError(err) 456 continue 457 458 } 459 result.Results[i].UnitPortRanges = unitPortRanges 460 } 461 return result, nil 462 } 463 464 func (f *FirewallerAPI) openedPortRangesForOneMachine(machine firewall.Machine) (map[string][]params.OpenUnitPortRanges, error) { 465 machPortRanges, err := machine.OpenedPortRanges() 466 if err != nil { 467 return nil, errors.Trace(err) 468 } 469 470 portRangesByUnit := machPortRanges.ByUnit() 471 if len(portRangesByUnit) == 0 { // no ports open 472 return nil, nil 473 } 474 475 // Look up space to subnet mappings 476 spaceInfos, err := f.getSpaceInfos() 477 if err != nil { 478 return nil, errors.Trace(err) 479 } 480 subnetCIDRsBySpaceID := spaceInfos.SubnetCIDRsBySpaceID() 481 482 // Fetch application endpoint bindings 483 allApps := set.NewStrings() 484 for unitName := range portRangesByUnit { 485 appName, err := names.UnitApplication(unitName) 486 if err != nil { 487 return nil, errors.Trace(err) 488 } 489 allApps.Add(appName) 490 } 491 allAppBindings, err := f.getApplicationBindings() 492 if err != nil { 493 return nil, errors.Trace(err) 494 } 495 496 // Map the port ranges for each unit to one or more subnet CIDRs 497 // depending on the endpoints they apply to. 498 res := make(map[string][]params.OpenUnitPortRanges) 499 for unitName, unitPortRanges := range portRangesByUnit { 500 // Already checked for validity; error can be ignored 501 appName, _ := names.UnitApplication(unitName) 502 appBindings := allAppBindings[appName] 503 504 unitTag := names.NewUnitTag(unitName).String() 505 res[unitTag] = mapUnitPortsAndResolveSubnetCIDRs(unitPortRanges.ByEndpoint(), appBindings, subnetCIDRsBySpaceID) 506 } 507 508 return res, nil 509 } 510 511 // mapUnitPortsAndResolveSubnetCIDRs maps the provided list of opened port 512 // ranges by endpoint to a params.OpenUnitPortRanges result list. Each entry in 513 // the result list also contains the subnet CIDRs that correspond to each 514 // endpoint. 515 // 516 // To resolve the subnet CIDRs, the function consults the application endpoint 517 // bindings for the unit in conjunction with the provided subnetCIDRs by 518 // spaceID map. Using this information, each endpoint from the incoming port 519 // range grouping is resolved to a space ID and the space ID is in turn 520 // resolved into a list of subnet CIDRs (the wildcard endpoint is treated as 521 // *all known* endpoints for this conversion step). 522 func mapUnitPortsAndResolveSubnetCIDRs(portRangesByEndpoint network.GroupedPortRanges, endpointBindings map[string]string, subnetCIDRsBySpaceID map[string][]string) []params.OpenUnitPortRanges { 523 var entries []params.OpenUnitPortRanges 524 525 for endpointName, portRanges := range portRangesByEndpoint { 526 entry := params.OpenUnitPortRanges{ 527 Endpoint: endpointName, 528 PortRanges: make([]params.PortRange, len(portRanges)), 529 } 530 531 // These port ranges target an explicit endpoint; just iterate 532 // the subnets that correspond to the space it is bound to and 533 // append their CIDRs. 534 if endpointName != "" { 535 entry.SubnetCIDRs = subnetCIDRsBySpaceID[endpointBindings[endpointName]] 536 sort.Strings(entry.SubnetCIDRs) 537 } else { 538 // The wildcard endpoint expands to all known endpoints. 539 for boundEndpoint, spaceID := range endpointBindings { 540 if boundEndpoint == "" { // ignore default endpoint entry in the set of app bindings 541 continue 542 } 543 entry.SubnetCIDRs = append(entry.SubnetCIDRs, subnetCIDRsBySpaceID[spaceID]...) 544 } 545 546 // Ensure that any duplicate CIDRs are removed. 547 entry.SubnetCIDRs = set.NewStrings(entry.SubnetCIDRs...).SortedValues() 548 } 549 550 // Finally, map the port ranges to params.PortRange and 551 network.SortPortRanges(portRanges) 552 for i, pr := range portRanges { 553 entry.PortRanges[i] = params.FromNetworkPortRange(pr) 554 } 555 556 entries = append(entries, entry) 557 } 558 559 // Ensure results are sorted by endpoint name to be consistent. 560 sort.Slice(entries, func(a, b int) bool { 561 return entries[a].Endpoint < entries[b].Endpoint 562 }) 563 564 return entries 565 } 566 567 // GetExposeInfo returns the expose flag and per-endpoint expose settings 568 // for the specified applications. 569 func (f *FirewallerAPI) GetExposeInfo(args params.Entities) (params.ExposeInfoResults, error) { 570 canAccess, err := f.accessApplication() 571 if err != nil { 572 return params.ExposeInfoResults{}, err 573 } 574 575 result := params.ExposeInfoResults{ 576 Results: make([]params.ExposeInfoResult, len(args.Entities)), 577 } 578 579 for i, entity := range args.Entities { 580 tag, err := names.ParseApplicationTag(entity.Tag) 581 if err != nil { 582 result.Results[i].Error = apiservererrors.ServerError(apiservererrors.ErrPerm) 583 continue 584 } 585 application, err := f.getApplication(canAccess, tag) 586 if err != nil { 587 result.Results[i].Error = apiservererrors.ServerError(err) 588 continue 589 } 590 591 if !application.IsExposed() { 592 continue 593 } 594 595 result.Results[i].Exposed = true 596 if exposedEndpoints := application.ExposedEndpoints(); len(exposedEndpoints) != 0 { 597 mappedEndpoints := make(map[string]params.ExposedEndpoint) 598 for endpoint, exposeDetails := range exposedEndpoints { 599 mappedEndpoints[endpoint] = params.ExposedEndpoint{ 600 ExposeToSpaces: exposeDetails.ExposeToSpaceIDs, 601 ExposeToCIDRs: exposeDetails.ExposeToCIDRs, 602 } 603 } 604 result.Results[i].ExposedEndpoints = mappedEndpoints 605 } 606 } 607 return result, nil 608 } 609 610 // SpaceInfos returns a comprehensive representation of either all spaces or 611 // a filtered subset of the known spaces and their associated subnet details. 612 func (f *FirewallerAPI) SpaceInfos(args params.SpaceInfosParams) (params.SpaceInfos, error) { 613 if !f.authorizer.AuthController() { 614 return params.SpaceInfos{}, apiservererrors.ServerError(apiservererrors.ErrPerm) 615 } 616 617 allSpaceInfos, err := f.getSpaceInfos() 618 if err != nil { 619 return params.SpaceInfos{}, apiservererrors.ServerError(err) 620 } 621 622 // Apply filtering if required 623 if len(args.FilterBySpaceIDs) != 0 { 624 var ( 625 filteredList network.SpaceInfos 626 selectList = set.NewStrings(args.FilterBySpaceIDs...) 627 ) 628 for _, si := range allSpaceInfos { 629 if selectList.Contains(si.ID) { 630 filteredList = append(filteredList, si) 631 } 632 } 633 634 allSpaceInfos = filteredList 635 } 636 637 return params.FromNetworkSpaceInfos(allSpaceInfos), nil 638 } 639 640 // WatchSubnets returns a new StringsWatcher that watches the specified 641 // subnet tags or all tags if no entities are specified. 642 func (f *FirewallerAPI) WatchSubnets(args params.Entities) (params.StringsWatchResult, error) { 643 if !f.authorizer.AuthController() { 644 return params.StringsWatchResult{}, apiservererrors.ServerError(apiservererrors.ErrPerm) 645 } 646 647 var ( 648 filterFn func(id interface{}) bool 649 filterSet set.Strings 650 result = params.StringsWatchResult{} 651 ) 652 653 if len(args.Entities) != 0 { 654 filterSet = set.NewStrings() 655 for _, arg := range args.Entities { 656 subnetTag, err := names.ParseSubnetTag(arg.Tag) 657 if err != nil { 658 return params.StringsWatchResult{}, apiservererrors.ServerError(err) 659 } 660 661 filterSet.Add(subnetTag.Id()) 662 } 663 664 filterFn = func(id interface{}) bool { 665 return filterSet.Contains(id.(string)) 666 } 667 } 668 669 watcherId, initial, err := f.watchModelSubnets(filterFn) 670 if err != nil { 671 result.Error = apiservererrors.ServerError(err) 672 return result, nil 673 } 674 result.StringsWatcherId = watcherId 675 result.Changes = initial 676 return result, nil 677 } 678 679 func (f *FirewallerAPI) watchModelSubnets(filterFn func(interface{}) bool) (string, []string, error) { 680 watch := f.st.WatchSubnets(filterFn) 681 682 // Consume the initial event and forward it to the result. 683 if changes, ok := <-watch.Changes(); ok { 684 return f.resources.Register(watch), changes, nil 685 } 686 return "", nil, watcher.EnsureErr(watch) 687 } 688 689 func setEquals(a, b set.Strings) bool { 690 if a.Size() != b.Size() { 691 return false 692 } 693 return a.Intersection(b).Size() == a.Size() 694 }