github.com/mhilton/juju-juju@v0.0.0-20150901100907-a94dd2c73455/apiserver/subnets/subnets.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  	"fmt"
     8  	"net"
     9  	"strings"
    10  
    11  	"github.com/juju/errors"
    12  	"github.com/juju/loggo"
    13  	"github.com/juju/names"
    14  	"github.com/juju/utils/set"
    15  
    16  	"github.com/juju/juju/apiserver/common"
    17  	"github.com/juju/juju/apiserver/params"
    18  	"github.com/juju/juju/environs"
    19  	"github.com/juju/juju/instance"
    20  	"github.com/juju/juju/network"
    21  	providercommon "github.com/juju/juju/provider/common"
    22  	"github.com/juju/juju/state"
    23  )
    24  
    25  var logger = loggo.GetLogger("juju.apiserver.subnets")
    26  
    27  func init() {
    28  	common.RegisterStandardFacade("Subnets", 1, NewAPI)
    29  }
    30  
    31  // API defines the methods the Subnets API facade implements.
    32  type API interface {
    33  	// AllZones returns all availability zones known to Juju. If a
    34  	// zone is unusable, unavailable, or deprecated the Available
    35  	// field will be false.
    36  	AllZones() (params.ZoneResults, error)
    37  
    38  	// AllSpaces returns the tags of all network spaces known to Juju.
    39  	AllSpaces() (params.SpaceResults, error)
    40  
    41  	// AddSubnets adds existing subnets to Juju.
    42  	AddSubnets(args params.AddSubnetsParams) (params.ErrorResults, error)
    43  
    44  	// ListSubnets returns the matching subnets after applying
    45  	// optional filters.
    46  	ListSubnets(args params.SubnetsFilters) (params.ListSubnetsResults, error)
    47  }
    48  
    49  // subnetsAPI implements the API interface.
    50  type subnetsAPI struct {
    51  	backing    common.NetworkBacking
    52  	resources  *common.Resources
    53  	authorizer common.Authorizer
    54  }
    55  
    56  // NewAPI creates a new Subnets API server-side facade with a
    57  // state.State backing.
    58  func NewAPI(st *state.State, res *common.Resources, auth common.Authorizer) (API, error) {
    59  	return newAPIWithBacking(&stateShim{st: st}, res, auth)
    60  }
    61  
    62  // newAPIWithBacking creates a new server-side Subnets API facade with
    63  // a common.NetworkBacking
    64  func newAPIWithBacking(backing common.NetworkBacking, resources *common.Resources, authorizer common.Authorizer) (API, error) {
    65  	// Only clients can access the Subnets facade.
    66  	if !authorizer.AuthClient() {
    67  		return nil, common.ErrPerm
    68  	}
    69  	return &subnetsAPI{
    70  		backing:    backing,
    71  		resources:  resources,
    72  		authorizer: authorizer,
    73  	}, nil
    74  }
    75  
    76  // AllZones is defined on the API interface.
    77  func (api *subnetsAPI) AllZones() (params.ZoneResults, error) {
    78  	var results params.ZoneResults
    79  
    80  	zonesAsString := func(zones []providercommon.AvailabilityZone) string {
    81  		results := make([]string, len(zones))
    82  		for i, zone := range zones {
    83  			results[i] = zone.Name()
    84  		}
    85  		return `"` + strings.Join(results, `", "`) + `"`
    86  	}
    87  
    88  	// Try fetching cached zones first.
    89  	zones, err := api.backing.AvailabilityZones()
    90  	if err != nil {
    91  		return results, errors.Trace(err)
    92  	}
    93  
    94  	if len(zones) == 0 {
    95  		// This is likely the first time we're called.
    96  		// Fetch all zones from the provider and update.
    97  		zones, err = api.updateZones()
    98  		if err != nil {
    99  			return results, errors.Annotate(err, "cannot update known zones")
   100  		}
   101  		logger.Debugf(
   102  			"updated the list of known zones from the environment: %s", zonesAsString(zones),
   103  		)
   104  	} else {
   105  		logger.Debugf("using cached list of known zones: %s", zonesAsString(zones))
   106  	}
   107  
   108  	results.Results = make([]params.ZoneResult, len(zones))
   109  	for i, zone := range zones {
   110  		results.Results[i].Name = zone.Name()
   111  		results.Results[i].Available = zone.Available()
   112  	}
   113  	return results, nil
   114  }
   115  
   116  // AllSpaces is defined on the API interface.
   117  func (api *subnetsAPI) AllSpaces() (params.SpaceResults, error) {
   118  	var results params.SpaceResults
   119  
   120  	spaces, err := api.backing.AllSpaces()
   121  	if err != nil {
   122  		return results, errors.Trace(err)
   123  	}
   124  
   125  	results.Results = make([]params.SpaceResult, len(spaces))
   126  	for i, space := range spaces {
   127  		// TODO(dimitern): Add a Tag() a method and use it here. Too
   128  		// early to do it now as it will just complicate the tests.
   129  		tag := names.NewSpaceTag(space.Name())
   130  		results.Results[i].Tag = tag.String()
   131  	}
   132  	return results, nil
   133  }
   134  
   135  // zonedEnviron returns a providercommon.ZonedEnviron instance from
   136  // the current environment config. If the environment does not support
   137  // zones, an error satisfying errors.IsNotSupported() will be
   138  // returned.
   139  func (api *subnetsAPI) zonedEnviron() (providercommon.ZonedEnviron, error) {
   140  	envConfig, err := api.backing.EnvironConfig()
   141  	if err != nil {
   142  		return nil, errors.Annotate(err, "getting environment config")
   143  	}
   144  
   145  	env, err := environs.New(envConfig)
   146  	if err != nil {
   147  		return nil, errors.Annotate(err, "opening environment")
   148  	}
   149  	if zonedEnv, ok := env.(providercommon.ZonedEnviron); ok {
   150  		return zonedEnv, nil
   151  	}
   152  	return nil, errors.NotSupportedf("availability zones")
   153  }
   154  
   155  // networkingEnviron returns a environs.NetworkingEnviron instance
   156  // from the current environment config, if supported. If the
   157  // environment does not support environs.Networking, an error
   158  // satisfying errors.IsNotSupported() will be returned.
   159  func (api *subnetsAPI) networkingEnviron() (environs.NetworkingEnviron, error) {
   160  	envConfig, err := api.backing.EnvironConfig()
   161  	if err != nil {
   162  		return nil, errors.Annotate(err, "getting environment config")
   163  	}
   164  
   165  	env, err := environs.New(envConfig)
   166  	if err != nil {
   167  		return nil, errors.Annotate(err, "opening environment")
   168  	}
   169  	if netEnv, ok := environs.SupportsNetworking(env); ok {
   170  		return netEnv, nil
   171  	}
   172  	return nil, errors.NotSupportedf("environment networking features") // " not supported"
   173  }
   174  
   175  // updateZones attempts to retrieve all availability zones from the
   176  // environment provider (if supported) and then updates the persisted
   177  // list of zones in state, returning them as well on success.
   178  func (api *subnetsAPI) updateZones() ([]providercommon.AvailabilityZone, error) {
   179  	zoned, err := api.zonedEnviron()
   180  	if err != nil {
   181  		return nil, errors.Trace(err)
   182  	}
   183  	zones, err := zoned.AvailabilityZones()
   184  	if err != nil {
   185  		return nil, errors.Trace(err)
   186  	}
   187  
   188  	if err := api.backing.SetAvailabilityZones(zones); err != nil {
   189  		return nil, errors.Trace(err)
   190  	}
   191  	return zones, nil
   192  }
   193  
   194  // addSubnetsCache holds cached lists of spaces, zones, and subnets,
   195  // used for fast lookups while adding subnets.
   196  type addSubnetsCache struct {
   197  	api            *subnetsAPI
   198  	allSpaces      set.Strings          // all defined backing spaces
   199  	allZones       set.Strings          // all known provider zones
   200  	availableZones set.Strings          // all the available zones
   201  	allSubnets     []network.SubnetInfo // all (valid) provider subnets
   202  	// providerIdsByCIDR maps possibly duplicated CIDRs to one or more ids.
   203  	providerIdsByCIDR map[string]set.Strings
   204  	// subnetsByProviderId maps unique subnet ProviderIds to pointers
   205  	// to entries in allSubnets.
   206  	subnetsByProviderId map[string]*network.SubnetInfo
   207  }
   208  
   209  func newAddSubnetsCache(api *subnetsAPI) *addSubnetsCache {
   210  	// Empty cache initially.
   211  	return &addSubnetsCache{
   212  		api:                 api,
   213  		allSpaces:           nil,
   214  		allZones:            nil,
   215  		availableZones:      nil,
   216  		allSubnets:          nil,
   217  		providerIdsByCIDR:   nil,
   218  		subnetsByProviderId: nil,
   219  	}
   220  }
   221  
   222  // validateSpace parses the given spaceTag and verifies it exists by
   223  // looking it up in the cache (or populates the cache if empty).
   224  func (cache *addSubnetsCache) validateSpace(spaceTag string) (*names.SpaceTag, error) {
   225  	if spaceTag == "" {
   226  		return nil, errors.Errorf("SpaceTag is required")
   227  	}
   228  	tag, err := names.ParseSpaceTag(spaceTag)
   229  	if err != nil {
   230  		return nil, errors.Annotate(err, "given SpaceTag is invalid")
   231  	}
   232  
   233  	// Otherwise we need the cache to validate.
   234  	if cache.allSpaces == nil {
   235  		// Not yet cached.
   236  		logger.Debugf("caching known spaces")
   237  
   238  		allSpaces, err := cache.api.backing.AllSpaces()
   239  		if err != nil {
   240  			return nil, errors.Annotate(err, "cannot validate given SpaceTag")
   241  		}
   242  		cache.allSpaces = set.NewStrings()
   243  		for _, space := range allSpaces {
   244  			if cache.allSpaces.Contains(space.Name()) {
   245  				logger.Warningf("ignoring duplicated space %q", space.Name())
   246  				continue
   247  			}
   248  			cache.allSpaces.Add(space.Name())
   249  		}
   250  	}
   251  	if cache.allSpaces.IsEmpty() {
   252  		return nil, errors.Errorf("no spaces defined")
   253  	}
   254  	logger.Tracef("using cached spaces: %v", cache.allSpaces.SortedValues())
   255  
   256  	if !cache.allSpaces.Contains(tag.Id()) {
   257  		return nil, errors.NotFoundf("space %q", tag.Id()) // " not found"
   258  	}
   259  	return &tag, nil
   260  }
   261  
   262  // cacheZones populates the allZones and availableZones cache, if it's
   263  // empty.
   264  func (cache *addSubnetsCache) cacheZones() error {
   265  	if cache.allZones != nil {
   266  		// Already cached.
   267  		logger.Tracef("using cached zones: %v", cache.allZones.SortedValues())
   268  		return nil
   269  	}
   270  
   271  	allZones, err := cache.api.AllZones()
   272  	if err != nil {
   273  		return errors.Annotate(err, "given Zones cannot be validated")
   274  	}
   275  	cache.allZones = set.NewStrings()
   276  	cache.availableZones = set.NewStrings()
   277  	for _, zone := range allZones.Results {
   278  		// AllZones() does not use the Error result field, so no
   279  		// need to check it here.
   280  		if cache.allZones.Contains(zone.Name) {
   281  			logger.Warningf("ignoring duplicated zone %q", zone.Name)
   282  			continue
   283  		}
   284  
   285  		if zone.Available {
   286  			cache.availableZones.Add(zone.Name)
   287  		}
   288  		cache.allZones.Add(zone.Name)
   289  	}
   290  	logger.Debugf(
   291  		"%d known and %d available zones cached: %v",
   292  		cache.allZones.Size(), cache.availableZones.Size(), cache.allZones.SortedValues(),
   293  	)
   294  	if cache.allZones.IsEmpty() {
   295  		cache.allZones = nil
   296  		// Cached an empty list.
   297  		return errors.Errorf("no zones defined")
   298  	}
   299  	return nil
   300  }
   301  
   302  // validateZones ensures givenZones are valid. When providerZones are
   303  // also set, givenZones must be a subset of them or match exactly.
   304  // With non-empty providerZones and empty givenZones, it returns the
   305  // providerZones (i.e. trusts the provider to know better). When no
   306  // providerZones and only givenZones are set, only then the cache is
   307  // used to validate givenZones.
   308  func (cache *addSubnetsCache) validateZones(providerZones, givenZones []string) ([]string, error) {
   309  	givenSet := set.NewStrings(givenZones...)
   310  	providerSet := set.NewStrings(providerZones...)
   311  
   312  	// First check if we can validate without using the cache.
   313  	switch {
   314  	case providerSet.IsEmpty() && givenSet.IsEmpty():
   315  		return nil, errors.Errorf("Zones cannot be discovered from the provider and must be set")
   316  	case !providerSet.IsEmpty() && givenSet.IsEmpty():
   317  		// Use provider zones when none given.
   318  		return providerSet.SortedValues(), nil
   319  	case !providerSet.IsEmpty() && !givenSet.IsEmpty():
   320  		// Ensure givenZones either match providerZones or are a
   321  		// subset of them.
   322  		extraGiven := givenSet.Difference(providerSet)
   323  		if !extraGiven.IsEmpty() {
   324  			extra := `"` + strings.Join(extraGiven.SortedValues(), `", "`) + `"`
   325  			msg := fmt.Sprintf("Zones contain zones not allowed by the provider: %s", extra)
   326  			return nil, errors.Errorf(msg)
   327  		}
   328  	}
   329  
   330  	// Otherwise we need the cache to validate.
   331  	if err := cache.cacheZones(); err != nil {
   332  		return nil, errors.Trace(err)
   333  	}
   334  
   335  	diffAvailable := givenSet.Difference(cache.availableZones)
   336  	diffAll := givenSet.Difference(cache.allZones)
   337  
   338  	if !diffAll.IsEmpty() {
   339  		extra := `"` + strings.Join(diffAll.SortedValues(), `", "`) + `"`
   340  		return nil, errors.Errorf("Zones contain unknown zones: %s", extra)
   341  	}
   342  	if !diffAvailable.IsEmpty() {
   343  		extra := `"` + strings.Join(diffAvailable.SortedValues(), `", "`) + `"`
   344  		return nil, errors.Errorf("Zones contain unavailable zones: %s", extra)
   345  	}
   346  	// All good - given zones are a subset and none are
   347  	// unavailable.
   348  	return givenSet.SortedValues(), nil
   349  }
   350  
   351  // cacheSubnets tries to get and cache once all known provider
   352  // subnets. It handles the case when subnets have duplicated CIDRs but
   353  // distinct ProviderIds. It also handles weird edge cases, like no
   354  // CIDR and/or ProviderId set for a subnet.
   355  func (cache *addSubnetsCache) cacheSubnets() error {
   356  	if cache.allSubnets != nil {
   357  		// Already cached.
   358  		logger.Tracef("using %d cached subnets", len(cache.allSubnets))
   359  		return nil
   360  	}
   361  
   362  	netEnv, err := cache.api.networkingEnviron()
   363  	if err != nil {
   364  		return errors.Trace(err)
   365  	}
   366  	subnetInfo, err := netEnv.Subnets(instance.UnknownId, nil)
   367  	if err != nil {
   368  		return errors.Annotate(err, "cannot get provider subnets")
   369  	}
   370  	logger.Debugf("got %d subnets to cache from the provider", len(subnetInfo))
   371  
   372  	if len(subnetInfo) > 0 {
   373  		// Trying to avoid reallocations.
   374  		cache.allSubnets = make([]network.SubnetInfo, 0, len(subnetInfo))
   375  	}
   376  	cache.providerIdsByCIDR = make(map[string]set.Strings)
   377  	cache.subnetsByProviderId = make(map[string]*network.SubnetInfo)
   378  
   379  	for i, _ := range subnetInfo {
   380  		subnet := subnetInfo[i]
   381  		cidr := subnet.CIDR
   382  		providerId := string(subnet.ProviderId)
   383  		logger.Debugf(
   384  			"caching subnet with CIDR %q, ProviderId %q, Zones: %q",
   385  			cidr, providerId, subnet.AvailabilityZones,
   386  		)
   387  
   388  		if providerId == "" && cidr == "" {
   389  			logger.Warningf("found subnet with empty CIDR and ProviderId")
   390  			// But we still save it for lookups, which will probably fail anyway.
   391  		} else if providerId == "" {
   392  			logger.Warningf("found subnet with CIDR %q and empty ProviderId", cidr)
   393  			// But we still save it for lookups.
   394  		} else {
   395  			_, ok := cache.subnetsByProviderId[providerId]
   396  			if ok {
   397  				logger.Warningf(
   398  					"found subnet with CIDR %q and duplicated ProviderId %q",
   399  					cidr, providerId,
   400  				)
   401  				// We just overwrite what's there for the same id.
   402  				// It's a weird case and it shouldn't happen with
   403  				// properly written providers, but anyway..
   404  			}
   405  		}
   406  		cache.subnetsByProviderId[providerId] = &subnet
   407  
   408  		if ids, ok := cache.providerIdsByCIDR[cidr]; !ok {
   409  			cache.providerIdsByCIDR[cidr] = set.NewStrings(providerId)
   410  		} else {
   411  			ids.Add(providerId)
   412  			logger.Debugf(
   413  				"duplicated subnet CIDR %q; collected ProviderIds so far: %v",
   414  				cidr, ids.SortedValues(),
   415  			)
   416  			cache.providerIdsByCIDR[cidr] = ids
   417  		}
   418  
   419  		cache.allSubnets = append(cache.allSubnets, subnet)
   420  	}
   421  	logger.Debugf("%d provider subnets cached", len(cache.allSubnets))
   422  	if len(cache.allSubnets) == 0 {
   423  		// Cached an empty list.
   424  		return errors.Errorf("no subnets defined")
   425  	}
   426  	return nil
   427  }
   428  
   429  // validateSubnet ensures either subnetTag or providerId is valid (not
   430  // both), then uses the cache to validate and lookup the provider
   431  // SubnetInfo for the subnet, if found.
   432  func (cache *addSubnetsCache) validateSubnet(subnetTag, providerId string) (*network.SubnetInfo, error) {
   433  	haveTag := subnetTag != ""
   434  	haveProviderId := providerId != ""
   435  
   436  	if !haveTag && !haveProviderId {
   437  		return nil, errors.Errorf("either SubnetTag or SubnetProviderId is required")
   438  	} else if haveTag && haveProviderId {
   439  		return nil, errors.Errorf("SubnetTag and SubnetProviderId cannot be both set")
   440  	}
   441  	var tag names.SubnetTag
   442  	if haveTag {
   443  		var err error
   444  		tag, err = names.ParseSubnetTag(subnetTag)
   445  		if err != nil {
   446  			return nil, errors.Annotate(err, "given SubnetTag is invalid")
   447  		}
   448  	}
   449  
   450  	// Otherwise we need the cache to validate.
   451  	if err := cache.cacheSubnets(); err != nil {
   452  		return nil, errors.Trace(err)
   453  	}
   454  
   455  	if haveTag {
   456  		providerIds, ok := cache.providerIdsByCIDR[tag.Id()]
   457  		if !ok || providerIds.IsEmpty() {
   458  			return nil, errors.NotFoundf("subnet with CIDR %q", tag.Id())
   459  		}
   460  		if providerIds.Size() > 1 {
   461  			ids := `"` + strings.Join(providerIds.SortedValues(), `", "`) + `"`
   462  			return nil, errors.Errorf(
   463  				"multiple subnets with CIDR %q: retry using ProviderId from: %s",
   464  				tag.Id(), ids,
   465  			)
   466  		}
   467  		// A single CIDR matched.
   468  		providerId = providerIds.Values()[0]
   469  	}
   470  
   471  	info, ok := cache.subnetsByProviderId[providerId]
   472  	if !ok || info == nil {
   473  		return nil, errors.NotFoundf(
   474  			"subnet with CIDR %q and ProviderId %q",
   475  			tag.Id(), providerId,
   476  		)
   477  	}
   478  	// Do last-call validation.
   479  	if !names.IsValidSubnet(info.CIDR) {
   480  		_, ipnet, err := net.ParseCIDR(info.CIDR)
   481  		if err != nil && info.CIDR != "" {
   482  			// The underlying error is not important here, just that
   483  			// the CIDR is invalid.
   484  			return nil, errors.Errorf(
   485  				"subnet with CIDR %q and ProviderId %q: invalid CIDR",
   486  				info.CIDR, providerId,
   487  			)
   488  		}
   489  		if info.CIDR == "" {
   490  			return nil, errors.Errorf(
   491  				"subnet with ProviderId %q: empty CIDR", providerId,
   492  			)
   493  		}
   494  		return nil, errors.Errorf(
   495  			"subnet with ProviderId %q: incorrect CIDR format %q, expected %q",
   496  			providerId, info.CIDR, ipnet.String(),
   497  		)
   498  	}
   499  	return info, nil
   500  }
   501  
   502  // addOneSubnet validates the given arguments, using cache for lookups
   503  // (initialized on first use), then adds it to the backing store, if
   504  // successful.
   505  func (api *subnetsAPI) addOneSubnet(args params.AddSubnetParams, cache *addSubnetsCache) error {
   506  	subnetInfo, err := cache.validateSubnet(args.SubnetTag, args.SubnetProviderId)
   507  	if err != nil {
   508  		return errors.Trace(err)
   509  	}
   510  	spaceTag, err := cache.validateSpace(args.SpaceTag)
   511  	if err != nil {
   512  		return errors.Trace(err)
   513  	}
   514  	zones, err := cache.validateZones(subnetInfo.AvailabilityZones, args.Zones)
   515  	if err != nil {
   516  		return errors.Trace(err)
   517  	}
   518  
   519  	// Try adding the subnet.
   520  	backingInfo := common.BackingSubnetInfo{
   521  		ProviderId:        string(subnetInfo.ProviderId),
   522  		CIDR:              subnetInfo.CIDR,
   523  		VLANTag:           subnetInfo.VLANTag,
   524  		AvailabilityZones: zones,
   525  		SpaceName:         spaceTag.Id(),
   526  	}
   527  	if subnetInfo.AllocatableIPLow != nil {
   528  		backingInfo.AllocatableIPLow = subnetInfo.AllocatableIPLow.String()
   529  	}
   530  	if subnetInfo.AllocatableIPHigh != nil {
   531  		backingInfo.AllocatableIPHigh = subnetInfo.AllocatableIPHigh.String()
   532  	}
   533  	if _, err := api.backing.AddSubnet(backingInfo); err != nil {
   534  		return errors.Trace(err)
   535  	}
   536  	return nil
   537  }
   538  
   539  // AddSubnets is defined on the API interface.
   540  func (api *subnetsAPI) AddSubnets(args params.AddSubnetsParams) (params.ErrorResults, error) {
   541  	results := params.ErrorResults{
   542  		Results: make([]params.ErrorResult, len(args.Subnets)),
   543  	}
   544  
   545  	if len(args.Subnets) == 0 {
   546  		return results, nil
   547  	}
   548  
   549  	cache := newAddSubnetsCache(api)
   550  	for i, arg := range args.Subnets {
   551  		err := api.addOneSubnet(arg, cache)
   552  		if err != nil {
   553  			results.Results[i].Error = common.ServerError(err)
   554  		}
   555  	}
   556  	return results, nil
   557  }
   558  
   559  // ListSubnets lists all the available subnets or only those matching
   560  // all given optional filters.
   561  func (api *subnetsAPI) ListSubnets(args params.SubnetsFilters) (results params.ListSubnetsResults, err error) {
   562  	subnets, err := api.backing.AllSubnets()
   563  	if err != nil {
   564  		return results, errors.Trace(err)
   565  	}
   566  
   567  	var spaceFilter string
   568  	if args.SpaceTag != "" {
   569  		tag, err := names.ParseSpaceTag(args.SpaceTag)
   570  		if err != nil {
   571  			return results, errors.Trace(err)
   572  		}
   573  		spaceFilter = tag.Id()
   574  	}
   575  	zoneFilter := args.Zone
   576  
   577  	for _, subnet := range subnets {
   578  		if spaceFilter != "" && subnet.SpaceName() != spaceFilter {
   579  			logger.Tracef(
   580  				"filtering subnet %q from space %q not matching filter %q",
   581  				subnet.CIDR(), subnet.SpaceName(), spaceFilter,
   582  			)
   583  			continue
   584  		}
   585  		zoneSet := set.NewStrings(subnet.AvailabilityZones()...)
   586  		if zoneFilter != "" && !zoneSet.IsEmpty() && !zoneSet.Contains(zoneFilter) {
   587  			logger.Tracef(
   588  				"filtering subnet %q with zones %v not matching filter %q",
   589  				subnet.CIDR(), subnet.AvailabilityZones(), zoneFilter,
   590  			)
   591  			continue
   592  		}
   593  		result := params.Subnet{
   594  			CIDR:       subnet.CIDR(),
   595  			ProviderId: subnet.ProviderId(),
   596  			VLANTag:    subnet.VLANTag(),
   597  			Life:       subnet.Life(),
   598  			SpaceTag:   names.NewSpaceTag(subnet.SpaceName()).String(),
   599  			Zones:      subnet.AvailabilityZones(),
   600  			Status:     subnet.Status(),
   601  		}
   602  		results.Results = append(results.Results, result)
   603  	}
   604  	return results, nil
   605  }