github.com/axw/juju@v0.0.0-20161005053422-4bd6544d08d4/apiserver/common/networkingcommon/subnets.go (about)

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