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  }