github.com/juju/juju@v0.0.0-20240430160146-1752b71fcf00/apiserver/facades/client/subnets/cache.go (about) 1 // Copyright 2015 Canonical Ltd. 2 // Licensed under the AGPLv3, see LICENCE file for details. 3 4 package subnets 5 6 import ( 7 "strings" 8 9 "github.com/juju/collections/set" 10 "github.com/juju/errors" 11 12 "github.com/juju/juju/core/network" 13 "github.com/juju/juju/environs" 14 "github.com/juju/juju/environs/context" 15 providercommon "github.com/juju/juju/provider/common" 16 "github.com/juju/juju/rpc/params" 17 ) 18 19 // addSubnetsCache holds cached lists of spaces, zones, and subnets, used for 20 // fast lookups while adding subnets. 21 type addSubnetsCache struct { 22 api Backing 23 allSpaces map[string]string // all defined backing space names to ids 24 allZones set.Strings // all known provider zones 25 availableZones set.Strings // all the available zones 26 allSubnets []network.SubnetInfo // all (valid) provider subnets 27 // providerIdsByCIDR maps possibly duplicated CIDRs to one or more ids. 28 providerIdsByCIDR map[string]set.Strings 29 // subnetsByProviderId maps unique subnet ProviderIds to pointers 30 // to entries in allSubnets. 31 subnetsByProviderId map[string]*network.SubnetInfo 32 } 33 34 func NewAddSubnetsCache(api Backing) *addSubnetsCache { 35 // Empty cache initially. 36 return &addSubnetsCache{ 37 api: api, 38 allSpaces: nil, 39 allZones: nil, 40 availableZones: nil, 41 allSubnets: nil, 42 providerIdsByCIDR: nil, 43 subnetsByProviderId: nil, 44 } 45 } 46 47 func allZones(ctx context.ProviderCallContext, api Backing) (params.ZoneResults, error) { 48 var results params.ZoneResults 49 50 zonesAsString := func(zones network.AvailabilityZones) string { 51 results := make([]string, len(zones)) 52 for i, zone := range zones { 53 results[i] = zone.Name() 54 } 55 return `"` + strings.Join(results, `", "`) + `"` 56 } 57 58 // Try fetching cached zones first. 59 zones, err := api.AvailabilityZones() 60 if err != nil { 61 return results, errors.Trace(err) 62 } 63 64 if len(zones) == 0 { 65 // This is likely the first time we're called. 66 // Fetch all zones from the provider and update. 67 zones, err = updateZones(ctx, api) 68 if err != nil { 69 return results, errors.Annotate(err, "cannot update known zones") 70 } 71 logger.Tracef( 72 "updated the list of known zones from the model: %s", zonesAsString(zones), 73 ) 74 } else { 75 logger.Tracef("using cached list of known zones: %s", zonesAsString(zones)) 76 } 77 78 results.Results = make([]params.ZoneResult, len(zones)) 79 for i, zone := range zones { 80 results.Results[i].Name = zone.Name() 81 results.Results[i].Available = zone.Available() 82 } 83 return results, nil 84 } 85 86 // updateZones attempts to retrieve all availability zones from the environment 87 // provider (if supported) and then updates the persisted list of zones in 88 // state, returning them as well on success. 89 func updateZones(ctx context.ProviderCallContext, api Backing) (network.AvailabilityZones, error) { 90 zoned, err := zonedEnviron(api) 91 if err != nil { 92 return nil, errors.Trace(err) 93 } 94 zones, err := zoned.AvailabilityZones(ctx) 95 if err != nil { 96 return nil, errors.Trace(err) 97 } 98 99 if err := api.SetAvailabilityZones(zones); err != nil { 100 return nil, errors.Trace(err) 101 } 102 return zones, nil 103 } 104 105 // zonedEnviron returns a providercommon.ZonedEnviron instance from the current 106 // model config. If the model does not support zones, an error satisfying 107 // errors.IsNotSupported() will be returned. 108 func zonedEnviron(api Backing) (providercommon.ZonedEnviron, error) { 109 env, err := environs.GetEnviron(api, environs.New) 110 if err != nil { 111 return nil, errors.Annotate(err, "opening environment") 112 } 113 if zonedEnv, ok := env.(providercommon.ZonedEnviron); ok { 114 return zonedEnv, nil 115 } 116 return nil, errors.NotSupportedf("availability zones") 117 }