github.com/mhilton/juju-juju@v0.0.0-20150901100907-a94dd2c73455/apiserver/subnets/subnets_test.go (about)

     1  // Copyright 2015 Canonical Ltd.
     2  // Licensed under the AGPLv3, see LICENCE file for details.
     3  
     4  package subnets_test
     5  
     6  import (
     7  	"github.com/juju/errors"
     8  	"github.com/juju/names"
     9  	jc "github.com/juju/testing/checkers"
    10  	"github.com/juju/utils/set"
    11  	gc "gopkg.in/check.v1"
    12  
    13  	"github.com/juju/juju/apiserver/common"
    14  	"github.com/juju/juju/apiserver/params"
    15  	"github.com/juju/juju/apiserver/subnets"
    16  	apiservertesting "github.com/juju/juju/apiserver/testing"
    17  	"github.com/juju/juju/instance"
    18  	"github.com/juju/juju/network"
    19  	providercommon "github.com/juju/juju/provider/common"
    20  	coretesting "github.com/juju/juju/testing"
    21  )
    22  
    23  type SubnetsSuite struct {
    24  	coretesting.BaseSuite
    25  	apiservertesting.StubNetwork
    26  
    27  	resources  *common.Resources
    28  	authorizer apiservertesting.FakeAuthorizer
    29  	facade     subnets.API
    30  }
    31  
    32  var _ = gc.Suite(&SubnetsSuite{})
    33  
    34  func (s *SubnetsSuite) SetUpSuite(c *gc.C) {
    35  	s.StubNetwork.SetUpSuite(c)
    36  	s.BaseSuite.SetUpSuite(c)
    37  }
    38  
    39  func (s *SubnetsSuite) TearDownSuite(c *gc.C) {
    40  	s.BaseSuite.TearDownSuite(c)
    41  }
    42  
    43  func (s *SubnetsSuite) SetUpTest(c *gc.C) {
    44  	s.BaseSuite.SetUpTest(c)
    45  	apiservertesting.BackingInstance.SetUp(c, apiservertesting.StubZonedEnvironName, apiservertesting.WithZones, apiservertesting.WithSpaces, apiservertesting.WithSubnets)
    46  
    47  	s.resources = common.NewResources()
    48  	s.authorizer = apiservertesting.FakeAuthorizer{
    49  		Tag:            names.NewUserTag("admin"),
    50  		EnvironManager: false,
    51  	}
    52  
    53  	var err error
    54  	s.facade, err = subnets.NewAPIWithBacking(
    55  		apiservertesting.BackingInstance, s.resources, s.authorizer,
    56  	)
    57  	c.Assert(err, jc.ErrorIsNil)
    58  	c.Assert(s.facade, gc.NotNil)
    59  }
    60  
    61  func (s *SubnetsSuite) TearDownTest(c *gc.C) {
    62  	if s.resources != nil {
    63  		s.resources.StopAll()
    64  	}
    65  	s.BaseSuite.TearDownTest(c)
    66  }
    67  
    68  // AssertAllZonesResult makes it easier to verify AllZones results.
    69  func (s *SubnetsSuite) AssertAllZonesResult(c *gc.C, got params.ZoneResults, expected []providercommon.AvailabilityZone) {
    70  	results := make([]params.ZoneResult, len(expected))
    71  	for i, zone := range expected {
    72  		results[i].Name = zone.Name()
    73  		results[i].Available = zone.Available()
    74  	}
    75  	c.Assert(got, jc.DeepEquals, params.ZoneResults{Results: results})
    76  }
    77  
    78  // AssertAllSpacesResult makes it easier to verify AllSpaces results.
    79  func (s *SubnetsSuite) AssertAllSpacesResult(c *gc.C, got params.SpaceResults, expected []common.BackingSpace) {
    80  	seen := set.Strings{}
    81  	results := []params.SpaceResult{}
    82  	for _, space := range expected {
    83  		if seen.Contains(space.Name()) {
    84  			continue
    85  		}
    86  		seen.Add(space.Name())
    87  		result := params.SpaceResult{}
    88  		result.Tag = names.NewSpaceTag(space.Name()).String()
    89  		results = append(results, result)
    90  	}
    91  	c.Assert(got, jc.DeepEquals, params.SpaceResults{Results: results})
    92  }
    93  
    94  func (s *SubnetsSuite) TestNewAPIWithBacking(c *gc.C) {
    95  	// Clients are allowed.
    96  	facade, err := subnets.NewAPIWithBacking(
    97  		apiservertesting.BackingInstance, s.resources, s.authorizer,
    98  	)
    99  	c.Assert(err, jc.ErrorIsNil)
   100  	c.Assert(facade, gc.NotNil)
   101  	// No calls so far.
   102  	apiservertesting.CheckMethodCalls(c, apiservertesting.SharedStub)
   103  
   104  	// Agents are not allowed
   105  	agentAuthorizer := s.authorizer
   106  	agentAuthorizer.Tag = names.NewMachineTag("42")
   107  	facade, err = subnets.NewAPIWithBacking(
   108  		apiservertesting.BackingInstance, s.resources, agentAuthorizer,
   109  	)
   110  	c.Assert(err, jc.DeepEquals, common.ErrPerm)
   111  	c.Assert(facade, gc.IsNil)
   112  	// No calls so far.
   113  	apiservertesting.CheckMethodCalls(c, apiservertesting.SharedStub)
   114  }
   115  
   116  func (s *SubnetsSuite) TestAllZonesWhenBackingAvailabilityZonesFails(c *gc.C) {
   117  	apiservertesting.SharedStub.SetErrors(errors.NotSupportedf("zones"))
   118  
   119  	results, err := s.facade.AllZones()
   120  	c.Assert(err, gc.ErrorMatches, "zones not supported")
   121  	// Verify the cause is not obscured.
   122  	c.Assert(err, jc.Satisfies, errors.IsNotSupported)
   123  	c.Assert(results, jc.DeepEquals, params.ZoneResults{})
   124  
   125  	apiservertesting.CheckMethodCalls(c, apiservertesting.SharedStub,
   126  		apiservertesting.BackingCall("AvailabilityZones"),
   127  	)
   128  }
   129  
   130  func (s *SubnetsSuite) TestAllZonesUsesBackingZonesWhenAvailable(c *gc.C) {
   131  	results, err := s.facade.AllZones()
   132  	c.Assert(err, jc.ErrorIsNil)
   133  	s.AssertAllZonesResult(c, results, apiservertesting.BackingInstance.Zones)
   134  
   135  	apiservertesting.CheckMethodCalls(c, apiservertesting.SharedStub,
   136  		apiservertesting.BackingCall("AvailabilityZones"),
   137  	)
   138  }
   139  
   140  func (s *SubnetsSuite) TestAllZonesWithNoBackingZonesUpdates(c *gc.C) {
   141  	apiservertesting.BackingInstance.SetUp(c, apiservertesting.StubZonedEnvironName, apiservertesting.WithoutZones, apiservertesting.WithSpaces, apiservertesting.WithSubnets)
   142  
   143  	results, err := s.facade.AllZones()
   144  	c.Assert(err, jc.ErrorIsNil)
   145  	s.AssertAllZonesResult(c, results, apiservertesting.ProviderInstance.Zones)
   146  
   147  	apiservertesting.CheckMethodCalls(c, apiservertesting.SharedStub,
   148  		apiservertesting.BackingCall("AvailabilityZones"),
   149  		apiservertesting.BackingCall("EnvironConfig"),
   150  		apiservertesting.ProviderCall("Open", apiservertesting.BackingInstance.EnvConfig),
   151  		apiservertesting.ZonedEnvironCall("AvailabilityZones"),
   152  		apiservertesting.BackingCall("SetAvailabilityZones", apiservertesting.ProviderInstance.Zones),
   153  	)
   154  }
   155  
   156  func (s *SubnetsSuite) TestAllZonesWithNoBackingZonesAndSetFails(c *gc.C) {
   157  	apiservertesting.BackingInstance.SetUp(c, apiservertesting.StubZonedEnvironName, apiservertesting.WithoutZones, apiservertesting.WithSpaces, apiservertesting.WithSubnets)
   158  	apiservertesting.SharedStub.SetErrors(
   159  		nil, // Backing.AvailabilityZones
   160  		nil, // Backing.EnvironConfig
   161  		nil, // Provider.Open
   162  		nil, // ZonedEnviron.AvailabilityZones
   163  		errors.NotSupportedf("setting"), // Backing.SetAvailabilityZones
   164  	)
   165  
   166  	results, err := s.facade.AllZones()
   167  	c.Assert(err, gc.ErrorMatches,
   168  		`cannot update known zones: setting not supported`,
   169  	)
   170  	// Verify the cause is not obscured.
   171  	c.Assert(err, jc.Satisfies, errors.IsNotSupported)
   172  	c.Assert(results, jc.DeepEquals, params.ZoneResults{})
   173  
   174  	apiservertesting.CheckMethodCalls(c, apiservertesting.SharedStub,
   175  		apiservertesting.BackingCall("AvailabilityZones"),
   176  		apiservertesting.BackingCall("EnvironConfig"),
   177  		apiservertesting.ProviderCall("Open", apiservertesting.BackingInstance.EnvConfig),
   178  		apiservertesting.ZonedEnvironCall("AvailabilityZones"),
   179  		apiservertesting.BackingCall("SetAvailabilityZones", apiservertesting.ProviderInstance.Zones),
   180  	)
   181  }
   182  
   183  func (s *SubnetsSuite) TestAllZonesWithNoBackingZonesAndFetchingZonesFails(c *gc.C) {
   184  	apiservertesting.BackingInstance.SetUp(c, apiservertesting.StubZonedEnvironName, apiservertesting.WithoutZones, apiservertesting.WithSpaces, apiservertesting.WithSubnets)
   185  	apiservertesting.SharedStub.SetErrors(
   186  		nil, // Backing.AvailabilityZones
   187  		nil, // Backing.EnvironConfig
   188  		nil, // Provider.Open
   189  		errors.NotValidf("foo"), // ZonedEnviron.AvailabilityZones
   190  	)
   191  
   192  	results, err := s.facade.AllZones()
   193  	c.Assert(err, gc.ErrorMatches,
   194  		`cannot update known zones: foo not valid`,
   195  	)
   196  	// Verify the cause is not obscured.
   197  	c.Assert(err, jc.Satisfies, errors.IsNotValid)
   198  	c.Assert(results, jc.DeepEquals, params.ZoneResults{})
   199  
   200  	apiservertesting.CheckMethodCalls(c, apiservertesting.SharedStub,
   201  		apiservertesting.BackingCall("AvailabilityZones"),
   202  		apiservertesting.BackingCall("EnvironConfig"),
   203  		apiservertesting.ProviderCall("Open", apiservertesting.BackingInstance.EnvConfig),
   204  		apiservertesting.ZonedEnvironCall("AvailabilityZones"),
   205  	)
   206  }
   207  
   208  func (s *SubnetsSuite) TestAllZonesWithNoBackingZonesAndEnvironConfigFails(c *gc.C) {
   209  	apiservertesting.BackingInstance.SetUp(c, apiservertesting.StubZonedEnvironName, apiservertesting.WithoutZones, apiservertesting.WithSpaces, apiservertesting.WithSubnets)
   210  	apiservertesting.SharedStub.SetErrors(
   211  		nil, // Backing.AvailabilityZones
   212  		errors.NotFoundf("config"), // Backing.EnvironConfig
   213  	)
   214  
   215  	results, err := s.facade.AllZones()
   216  	c.Assert(err, gc.ErrorMatches,
   217  		`cannot update known zones: getting environment config: config not found`,
   218  	)
   219  	// Verify the cause is not obscured.
   220  	c.Assert(err, jc.Satisfies, errors.IsNotFound)
   221  	c.Assert(results, jc.DeepEquals, params.ZoneResults{})
   222  
   223  	apiservertesting.CheckMethodCalls(c, apiservertesting.SharedStub,
   224  		apiservertesting.BackingCall("AvailabilityZones"),
   225  		apiservertesting.BackingCall("EnvironConfig"),
   226  	)
   227  }
   228  
   229  func (s *SubnetsSuite) TestAllZonesWithNoBackingZonesAndOpenFails(c *gc.C) {
   230  	apiservertesting.BackingInstance.SetUp(c, apiservertesting.StubZonedEnvironName, apiservertesting.WithoutZones, apiservertesting.WithSpaces, apiservertesting.WithSubnets)
   231  	apiservertesting.SharedStub.SetErrors(
   232  		nil, // Backing.AvailabilityZones
   233  		nil, // Backing.EnvironConfig
   234  		errors.NotValidf("config"), // Provider.Open
   235  	)
   236  
   237  	results, err := s.facade.AllZones()
   238  	c.Assert(err, gc.ErrorMatches,
   239  		`cannot update known zones: opening environment: config not valid`,
   240  	)
   241  	// Verify the cause is not obscured.
   242  	c.Assert(err, jc.Satisfies, errors.IsNotValid)
   243  	c.Assert(results, jc.DeepEquals, params.ZoneResults{})
   244  
   245  	apiservertesting.CheckMethodCalls(c, apiservertesting.SharedStub,
   246  		apiservertesting.BackingCall("AvailabilityZones"),
   247  		apiservertesting.BackingCall("EnvironConfig"),
   248  		apiservertesting.ProviderCall("Open", apiservertesting.BackingInstance.EnvConfig),
   249  	)
   250  }
   251  
   252  func (s *SubnetsSuite) TestAllZonesWithNoBackingZonesAndZonesNotSupported(c *gc.C) {
   253  	apiservertesting.BackingInstance.SetUp(c, apiservertesting.StubEnvironName, apiservertesting.WithoutZones, apiservertesting.WithSpaces, apiservertesting.WithSubnets)
   254  	// ZonedEnviron not supported
   255  
   256  	results, err := s.facade.AllZones()
   257  	c.Assert(err, gc.ErrorMatches,
   258  		`cannot update known zones: availability zones not supported`,
   259  	)
   260  	// Verify the cause is not obscured.
   261  	c.Assert(err, jc.Satisfies, errors.IsNotSupported)
   262  	c.Assert(results, jc.DeepEquals, params.ZoneResults{})
   263  
   264  	apiservertesting.CheckMethodCalls(c, apiservertesting.SharedStub,
   265  		apiservertesting.BackingCall("AvailabilityZones"),
   266  		apiservertesting.BackingCall("EnvironConfig"),
   267  		apiservertesting.ProviderCall("Open", apiservertesting.BackingInstance.EnvConfig),
   268  	)
   269  }
   270  
   271  func (s *SubnetsSuite) TestAllSpacesWithExistingSuccess(c *gc.C) {
   272  	s.testAllSpacesSuccess(c, apiservertesting.WithSpaces)
   273  }
   274  
   275  func (s *SubnetsSuite) TestAllSpacesNoExistingSuccess(c *gc.C) {
   276  	s.testAllSpacesSuccess(c, apiservertesting.WithoutSpaces)
   277  }
   278  
   279  func (s *SubnetsSuite) testAllSpacesSuccess(c *gc.C, withBackingSpaces apiservertesting.SetUpFlag) {
   280  	apiservertesting.BackingInstance.SetUp(c, apiservertesting.StubZonedEnvironName, apiservertesting.WithZones, withBackingSpaces, apiservertesting.WithSubnets)
   281  
   282  	results, err := s.facade.AllSpaces()
   283  	c.Assert(err, jc.ErrorIsNil)
   284  	s.AssertAllSpacesResult(c, results, apiservertesting.BackingInstance.Spaces)
   285  
   286  	apiservertesting.CheckMethodCalls(c, apiservertesting.SharedStub,
   287  		apiservertesting.BackingCall("AllSpaces"),
   288  	)
   289  }
   290  
   291  func (s *SubnetsSuite) TestAllSpacesFailure(c *gc.C) {
   292  	apiservertesting.SharedStub.SetErrors(errors.NotFoundf("boom"))
   293  
   294  	results, err := s.facade.AllSpaces()
   295  	c.Assert(err, gc.ErrorMatches, "boom not found")
   296  	// Verify the cause is not obscured.
   297  	c.Assert(err, jc.Satisfies, errors.IsNotFound)
   298  	c.Assert(results, jc.DeepEquals, params.SpaceResults{})
   299  
   300  	apiservertesting.CheckMethodCalls(c, apiservertesting.SharedStub,
   301  		apiservertesting.BackingCall("AllSpaces"),
   302  	)
   303  }
   304  
   305  func (s *SubnetsSuite) TestAddSubnetsParamsCombinations(c *gc.C) {
   306  	apiservertesting.BackingInstance.SetUp(c, apiservertesting.StubNetworkingEnvironName, apiservertesting.WithZones, apiservertesting.WithSpaces, apiservertesting.WithSubnets)
   307  
   308  	args := params.AddSubnetsParams{Subnets: []params.AddSubnetParams{{
   309  	// nothing set; early exit: no calls
   310  	}, {
   311  		// neither tag nor id set: the rest is ignored; same as above
   312  		SpaceTag: "any",
   313  		Zones:    []string{"any", "ignored"},
   314  	}, {
   315  		// both tag and id set; same as above
   316  		SubnetTag:        "any",
   317  		SubnetProviderId: "any",
   318  	}, {
   319  		// lookup by id needed, no cached subnets; EnvironConfig(): error
   320  		SubnetProviderId: "any",
   321  	}, {
   322  		// same as above, need to cache subnets; EnvironConfig(): ok; Open(): error
   323  		SubnetProviderId: "ignored",
   324  	}, {
   325  		// as above, caching again; EnvironConfig(), Open(): ok; Subnets(): error
   326  		SubnetProviderId: "unimportant",
   327  	}, {
   328  		// exactly as above, except all 3 calls ok; cached lookup: id not found
   329  		SubnetProviderId: "missing",
   330  	}, {
   331  		// cached lookup by id (no calls): not found error
   332  		SubnetProviderId: "void",
   333  	}, {
   334  		// cached lookup by id: ok; parsing space tag: invalid tag error
   335  		SubnetProviderId: "sn-deadbeef",
   336  		SpaceTag:         "invalid",
   337  	}, {
   338  		// as above, but slightly different error: invalid space tag error
   339  		SubnetProviderId: "sn-zadf00d",
   340  		SpaceTag:         "unit-foo",
   341  	}, {
   342  		// as above; yet another similar error (valid tag with another kind)
   343  		SubnetProviderId: "vlan-42",
   344  		SpaceTag:         "unit-foo-0",
   345  	}, {
   346  		// invalid tag (no kind): error (no calls)
   347  		SubnetTag: "invalid",
   348  	}, {
   349  		// invalid subnet tag (another kind): same as above
   350  		SubnetTag: "service-bar",
   351  	}, {
   352  		// cached lookup by missing CIDR: not found error
   353  		SubnetTag: "subnet-1.2.3.0/24",
   354  	}, {
   355  		// cached lookup by duplicate CIDR: multiple choices error
   356  		SubnetTag: "subnet-10.10.0.0/24",
   357  	}, {
   358  		// cached lookup by CIDR with empty provider id: ok; space tag is required error
   359  		SubnetTag: "subnet-10.20.0.0/16",
   360  	}, {
   361  		// cached lookup by id with invalid CIDR: cannot be added error
   362  		SubnetProviderId: "sn-invalid",
   363  	}, {
   364  		// cached lookup by id with empty CIDR: cannot be added error
   365  		SubnetProviderId: "sn-empty",
   366  	}, {
   367  		// cached lookup by id with incorrectly specified CIDR: cannot be added error
   368  		SubnetProviderId: "sn-awesome",
   369  	}, {
   370  		// cached lookup by CIDR: ok; valid tag; caching spaces: AllSpaces(): error
   371  		SubnetTag: "subnet-10.30.1.0/24",
   372  		SpaceTag:  "space-unverified",
   373  	}, {
   374  		// exactly as above, except AllSpaces(): ok; cached lookup: space not found
   375  		SubnetTag: "subnet-2001:db8::/32",
   376  		SpaceTag:  "space-missing",
   377  	}, {
   378  		// both cached lookups (CIDR, space): ok; no provider or given zones: error
   379  		SubnetTag: "subnet-10.42.0.0/16",
   380  		SpaceTag:  "space-dmz",
   381  	}, {
   382  		// like above; with provider zones, extra given: error
   383  		SubnetProviderId: "vlan-42",
   384  		SpaceTag:         "space-private",
   385  		Zones: []string{
   386  			"zone2",   // not allowed, existing, unavailable
   387  			"zone3",   // allowed, existing, available
   388  			"missing", // not allowed, non-existing
   389  			"zone3",   // duplicates are ignored (should they ?)
   390  			"zone1",   // not allowed, existing, available
   391  		},
   392  	}, {
   393  		// like above; no provider, only given zones; caching: AllZones(): error
   394  		SubnetTag: "subnet-10.42.0.0/16",
   395  		SpaceTag:  "space-dmz",
   396  		Zones:     []string{"any", "ignored"},
   397  	}, {
   398  		// as above, but unknown zones given: cached: AllZones(): ok; unknown zones error
   399  		SubnetTag: "subnet-10.42.0.0/16",
   400  		SpaceTag:  "space-dmz",
   401  		Zones:     []string{"missing", "gone"},
   402  	}, {
   403  		// as above, but unknown and unavailable zones given: same error (no calls)
   404  		SubnetTag: "subnet-10.42.0.0/16",
   405  		SpaceTag:  "space-dmz",
   406  		Zones:     []string{"zone4", "missing", "zone2"},
   407  	}, {
   408  		// as above, but unavailable zones given: Zones contains unavailable error
   409  		SubnetTag: "subnet-10.42.0.0/16",
   410  		SpaceTag:  "space-dmz",
   411  		Zones:     []string{"zone2", "zone4"},
   412  	}, {
   413  		// as above, but available and unavailable zones given: same error as above
   414  		SubnetTag: "subnet-10.42.0.0/16",
   415  		SpaceTag:  "space-dmz",
   416  		Zones:     []string{"zone4", "zone3"},
   417  	}, {
   418  		// everything succeeds, using caches as needed, until: AddSubnet(): error
   419  		SubnetProviderId: "sn-ipv6",
   420  		SpaceTag:         "space-dmz",
   421  		Zones:            []string{"zone1"},
   422  		// restriction of provider zones [zone1, zone3]
   423  	}, {
   424  		// cached lookups by CIDR, space: ok; duplicated provider id: unavailable zone2
   425  		SubnetTag: "subnet-10.99.88.0/24",
   426  		SpaceTag:  "space-dmz",
   427  		Zones:     []string{"zone2"},
   428  		// due to the duplicate ProviderId provider zones from subnet
   429  		// with the last ProviderId=sn-deadbeef are used
   430  		// (10.10.0.0/24); [zone2], not the 10.99.88.0/24 provider
   431  		// zones: [zone1, zone2].
   432  	}, {
   433  		// same as above, but AddSubnet(): ok; success (backing verified later)
   434  		SubnetProviderId: "sn-ipv6",
   435  		SpaceTag:         "space-dmz",
   436  		Zones:            []string{"zone1"},
   437  		// restriction of provider zones [zone1, zone3]
   438  	}, {
   439  		// success (CIDR lookup; with provider (no given) zones): AddSubnet(): ok
   440  		SubnetTag: "subnet-10.30.1.0/24",
   441  		SpaceTag:  "space-private",
   442  		// Zones not given, so provider zones are used instead: [zone3]
   443  	}, {
   444  		// success (id lookup; given zones match provider zones) AddSubnet(): ok
   445  		SubnetProviderId: "sn-zadf00d",
   446  		SpaceTag:         "space-private",
   447  		Zones:            []string{"zone1"},
   448  	}}}
   449  	apiservertesting.SharedStub.SetErrors(
   450  		// caching subnets (1st attempt): fails
   451  		errors.NotFoundf("config"), // BackingInstance.EnvironConfig (1st call)
   452  
   453  		// caching subnets (2nd attepmt): fails
   454  		nil, // BackingInstance.EnvironConfig (2nd call)
   455  		errors.NotFoundf("provider"), // ProviderInstance.Open (1st call)
   456  
   457  		// caching subnets (3rd attempt): fails
   458  		nil, // BackingInstance.EnvironConfig (3rd call)
   459  		nil, // ProviderInstance.Open (2nd call)
   460  		errors.NotFoundf("subnets"), // NetworkingEnvironInstance.Subnets (1st call)
   461  
   462  		// caching subnets (4th attempt): succeeds
   463  		nil, // BackingInstance.EnvironConfig (4th call)
   464  		nil, // ProviderInstance.Open (3rd call)
   465  		nil, // NetworkingEnvironInstance.Subnets (2nd call)
   466  
   467  		// caching spaces (1st and 2nd attempts)
   468  		errors.NotFoundf("spaces"), // BackingInstance.AllSpaces (1st call)
   469  		nil, // BackingInstance.AllSpaces (2nd call)
   470  
   471  		// cacing zones (1st and 2nd attempts)
   472  		errors.NotFoundf("zones"), // BackingInstance.AvailabilityZones (1st call)
   473  		nil, // BackingInstance.AvailabilityZones (2nd call)
   474  
   475  		// validation done; adding subnets to backing store
   476  		errors.NotFoundf("state"), // BackingInstance.AddSubnet (1st call)
   477  		// the next 3 BackingInstance.AddSubnet calls succeed(2nd call)
   478  	)
   479  
   480  	expectedErrors := []struct {
   481  		message   string
   482  		satisfier func(error) bool
   483  	}{
   484  		{"either SubnetTag or SubnetProviderId is required", nil},
   485  		{"either SubnetTag or SubnetProviderId is required", nil},
   486  		{"SubnetTag and SubnetProviderId cannot be both set", nil},
   487  		{"getting environment config: config not found", params.IsCodeNotFound},
   488  		{"opening environment: provider not found", params.IsCodeNotFound},
   489  		{"cannot get provider subnets: subnets not found", params.IsCodeNotFound},
   490  		{`subnet with CIDR "" and ProviderId "missing" not found`, params.IsCodeNotFound},
   491  		{`subnet with CIDR "" and ProviderId "void" not found`, params.IsCodeNotFound},
   492  		{`given SpaceTag is invalid: "invalid" is not a valid tag`, nil},
   493  		{`given SpaceTag is invalid: "unit-foo" is not a valid unit tag`, nil},
   494  		{`given SpaceTag is invalid: "unit-foo-0" is not a valid space tag`, nil},
   495  		{`given SubnetTag is invalid: "invalid" is not a valid tag`, nil},
   496  		{`given SubnetTag is invalid: "service-bar" is not a valid subnet tag`, nil},
   497  		{`subnet with CIDR "1.2.3.0/24" not found`, params.IsCodeNotFound},
   498  		{
   499  			`multiple subnets with CIDR "10.10.0.0/24": ` +
   500  				`retry using ProviderId from: "sn-deadbeef", "sn-zadf00d"`, nil,
   501  		},
   502  		{"SpaceTag is required", nil},
   503  		{`subnet with CIDR "invalid" and ProviderId "sn-invalid": invalid CIDR`, nil},
   504  		{`subnet with ProviderId "sn-empty": empty CIDR`, nil},
   505  		{
   506  			`subnet with ProviderId "sn-awesome": ` +
   507  				`incorrect CIDR format "0.1.2.3/4", expected "0.0.0.0/4"`, nil,
   508  		},
   509  		{"cannot validate given SpaceTag: spaces not found", params.IsCodeNotFound},
   510  		{`space "missing" not found`, params.IsCodeNotFound},
   511  		{"Zones cannot be discovered from the provider and must be set", nil},
   512  		{`Zones contain zones not allowed by the provider: "missing", "zone1", "zone2"`, nil},
   513  		{"given Zones cannot be validated: zones not found", params.IsCodeNotFound},
   514  		{`Zones contain unknown zones: "gone", "missing"`, nil},
   515  		{`Zones contain unknown zones: "missing"`, nil},
   516  		{`Zones contain unavailable zones: "zone2", "zone4"`, nil},
   517  		{`Zones contain unavailable zones: "zone4"`, nil},
   518  		{"state not found", params.IsCodeNotFound},
   519  		{`Zones contain unavailable zones: "zone2"`, nil},
   520  		{"", nil},
   521  		{"", nil},
   522  		{"", nil},
   523  	}
   524  	expectedBackingInfos := []common.BackingSubnetInfo{{
   525  		ProviderId:        "sn-ipv6",
   526  		CIDR:              "2001:db8::/32",
   527  		VLANTag:           0,
   528  		AllocatableIPHigh: "",
   529  		AllocatableIPLow:  "",
   530  		AvailabilityZones: []string{"zone1"},
   531  		SpaceName:         "dmz",
   532  	}, {
   533  		ProviderId:        "vlan-42",
   534  		CIDR:              "10.30.1.0/24",
   535  		VLANTag:           42,
   536  		AllocatableIPHigh: "",
   537  		AllocatableIPLow:  "",
   538  		AvailabilityZones: []string{"zone3"},
   539  		SpaceName:         "private",
   540  	}, {
   541  		ProviderId:        "sn-zadf00d",
   542  		CIDR:              "10.10.0.0/24",
   543  		VLANTag:           0,
   544  		AllocatableIPHigh: "10.10.0.100",
   545  		AllocatableIPLow:  "10.10.0.10",
   546  		AvailabilityZones: []string{"zone1"},
   547  		SpaceName:         "private",
   548  	}}
   549  	c.Check(expectedErrors, gc.HasLen, len(args.Subnets))
   550  	results, err := s.facade.AddSubnets(args)
   551  	c.Assert(err, jc.ErrorIsNil)
   552  	c.Assert(len(results.Results), gc.Equals, len(args.Subnets))
   553  	for i, result := range results.Results {
   554  		c.Logf("result #%d: expected: %q", i, expectedErrors[i].message)
   555  		if expectedErrors[i].message == "" {
   556  			if !c.Check(result.Error, gc.IsNil) {
   557  				c.Logf("unexpected error: %v; args: %#v", result.Error, args.Subnets[i])
   558  			}
   559  			continue
   560  		}
   561  		if !c.Check(result.Error, gc.NotNil) {
   562  			c.Logf("unexpected success; args: %#v", args.Subnets[i])
   563  			continue
   564  		}
   565  		c.Check(result.Error.Message, gc.Equals, expectedErrors[i].message)
   566  		if expectedErrors[i].satisfier != nil {
   567  			c.Check(result.Error, jc.Satisfies, expectedErrors[i].satisfier)
   568  		} else {
   569  			c.Check(result.Error.Code, gc.Equals, "")
   570  		}
   571  	}
   572  
   573  	apiservertesting.CheckMethodCalls(c, apiservertesting.SharedStub,
   574  		// caching subnets (1st attempt): fails
   575  		apiservertesting.BackingCall("EnvironConfig"),
   576  
   577  		// caching subnets (2nd attepmt): fails
   578  		apiservertesting.BackingCall("EnvironConfig"),
   579  		apiservertesting.ProviderCall("Open", apiservertesting.BackingInstance.EnvConfig),
   580  
   581  		// caching subnets (3rd attempt): fails
   582  		apiservertesting.BackingCall("EnvironConfig"),
   583  		apiservertesting.ProviderCall("Open", apiservertesting.BackingInstance.EnvConfig),
   584  		apiservertesting.NetworkingEnvironCall("Subnets", instance.UnknownId, []network.Id(nil)),
   585  
   586  		// caching subnets (4th attempt): succeeds
   587  		apiservertesting.BackingCall("EnvironConfig"),
   588  		apiservertesting.ProviderCall("Open", apiservertesting.BackingInstance.EnvConfig),
   589  		apiservertesting.NetworkingEnvironCall("Subnets", instance.UnknownId, []network.Id(nil)),
   590  
   591  		// caching spaces (1st and 2nd attempts)
   592  		apiservertesting.BackingCall("AllSpaces"),
   593  		apiservertesting.BackingCall("AllSpaces"),
   594  
   595  		// cacing zones (1st and 2nd attempts)
   596  		apiservertesting.BackingCall("AvailabilityZones"),
   597  		apiservertesting.BackingCall("AvailabilityZones"),
   598  
   599  		// validation done; adding subnets to backing store
   600  		apiservertesting.BackingCall("AddSubnet", expectedBackingInfos[0]),
   601  		apiservertesting.BackingCall("AddSubnet", expectedBackingInfos[0]),
   602  		apiservertesting.BackingCall("AddSubnet", expectedBackingInfos[1]),
   603  		apiservertesting.BackingCall("AddSubnet", expectedBackingInfos[2]),
   604  	)
   605  	apiservertesting.ResetStub(apiservertesting.SharedStub)
   606  
   607  	// Finally, check that no params yields no results.
   608  	results, err = s.facade.AddSubnets(params.AddSubnetsParams{})
   609  	c.Assert(err, jc.ErrorIsNil)
   610  	c.Assert(results.Results, gc.NotNil)
   611  	c.Assert(results.Results, gc.HasLen, 0)
   612  
   613  	apiservertesting.CheckMethodCalls(c, apiservertesting.SharedStub)
   614  }
   615  
   616  func (s *SubnetsSuite) CheckAddSubnetsFails(
   617  	c *gc.C, envName string,
   618  	withZones, withSpaces, withSubnets apiservertesting.SetUpFlag,
   619  	expectedError string,
   620  ) {
   621  
   622  	apiservertesting.BackingInstance.SetUp(c, envName, withZones, withSpaces, withSubnets)
   623  
   624  	// These calls always happen.
   625  	expectedCalls := []apiservertesting.StubMethodCall{
   626  		apiservertesting.BackingCall("EnvironConfig"),
   627  		apiservertesting.ProviderCall("Open", apiservertesting.BackingInstance.EnvConfig),
   628  	}
   629  
   630  	// Subnets is also always called. but the receiver is different.
   631  	switch envName {
   632  	case apiservertesting.StubNetworkingEnvironName:
   633  		expectedCalls = append(
   634  			expectedCalls,
   635  			apiservertesting.NetworkingEnvironCall("Subnets", instance.UnknownId, []network.Id(nil)),
   636  		)
   637  	case apiservertesting.StubZonedNetworkingEnvironName:
   638  		expectedCalls = append(
   639  			expectedCalls,
   640  			apiservertesting.ZonedNetworkingEnvironCall("Subnets", instance.UnknownId, []network.Id(nil)),
   641  		)
   642  	}
   643  
   644  	if !withSubnets {
   645  		// Set provider subnets to empty for this test.
   646  		originalSubnets := make([]network.SubnetInfo, len(apiservertesting.ProviderInstance.Subnets))
   647  		copy(originalSubnets, apiservertesting.ProviderInstance.Subnets)
   648  		apiservertesting.ProviderInstance.Subnets = []network.SubnetInfo{}
   649  
   650  		defer func() {
   651  			apiservertesting.ProviderInstance.Subnets = make([]network.SubnetInfo, len(originalSubnets))
   652  			copy(apiservertesting.ProviderInstance.Subnets, originalSubnets)
   653  		}()
   654  
   655  		if envName == apiservertesting.StubEnvironName || envName == apiservertesting.StubNetworkingEnvironName {
   656  			// networking is either not supported or no subnets are
   657  			// defined, so expected the same calls for each of the two
   658  			// arguments to AddSubnets() below.
   659  			expectedCalls = append(expectedCalls, expectedCalls...)
   660  		}
   661  	} else {
   662  		// Having subnets implies spaces will be cached as well.
   663  		expectedCalls = append(expectedCalls, apiservertesting.BackingCall("AllSpaces"))
   664  	}
   665  
   666  	if withSpaces && withSubnets {
   667  		// Having both subnets and spaces means we'll also cache zones.
   668  		expectedCalls = append(expectedCalls, apiservertesting.BackingCall("AvailabilityZones"))
   669  	}
   670  
   671  	if !withZones && withSpaces {
   672  		// Set provider zones to empty for this test.
   673  		originalZones := make([]providercommon.AvailabilityZone, len(apiservertesting.ProviderInstance.Zones))
   674  		copy(originalZones, apiservertesting.ProviderInstance.Zones)
   675  		apiservertesting.ProviderInstance.Zones = []providercommon.AvailabilityZone{}
   676  
   677  		defer func() {
   678  			apiservertesting.ProviderInstance.Zones = make([]providercommon.AvailabilityZone, len(originalZones))
   679  			copy(apiservertesting.ProviderInstance.Zones, originalZones)
   680  		}()
   681  
   682  		// updateZones tries to constructs a ZonedEnviron with these calls.
   683  		zoneCalls := append([]apiservertesting.StubMethodCall{},
   684  			apiservertesting.BackingCall("EnvironConfig"),
   685  			apiservertesting.ProviderCall("Open", apiservertesting.BackingInstance.EnvConfig),
   686  		)
   687  		// Receiver can differ according to envName, but
   688  		// AvailabilityZones() will be called on either receiver.
   689  		switch envName {
   690  		case apiservertesting.StubZonedEnvironName:
   691  			zoneCalls = append(
   692  				zoneCalls,
   693  				apiservertesting.ZonedEnvironCall("AvailabilityZones"),
   694  			)
   695  		case apiservertesting.StubZonedNetworkingEnvironName:
   696  			zoneCalls = append(
   697  				zoneCalls,
   698  				apiservertesting.ZonedNetworkingEnvironCall("AvailabilityZones"),
   699  			)
   700  		}
   701  		// Finally after caching provider zones backing zones are
   702  		// updated.
   703  		zoneCalls = append(
   704  			zoneCalls,
   705  			apiservertesting.BackingCall("SetAvailabilityZones", apiservertesting.ProviderInstance.Zones),
   706  		)
   707  
   708  		// Now, because we have 2 arguments to AddSubnets() below, we
   709  		// need to expect the same zoneCalls twice, with a
   710  		// AvailabilityZones backing lookup between them.
   711  		expectedCalls = append(expectedCalls, zoneCalls...)
   712  		expectedCalls = append(expectedCalls, apiservertesting.BackingCall("AvailabilityZones"))
   713  		expectedCalls = append(expectedCalls, zoneCalls...)
   714  	}
   715  
   716  	// Pass 2 arguments covering all cases we need.
   717  	args := params.AddSubnetsParams{
   718  		Subnets: []params.AddSubnetParams{{
   719  			SubnetTag: "subnet-10.42.0.0/16",
   720  			SpaceTag:  "space-dmz",
   721  			Zones:     []string{"zone1"},
   722  		}, {
   723  			SubnetProviderId: "vlan-42",
   724  			SpaceTag:         "space-private",
   725  			Zones:            []string{"zone3"},
   726  		}},
   727  	}
   728  	results, err := s.facade.AddSubnets(args)
   729  	c.Assert(err, jc.ErrorIsNil)
   730  	c.Assert(results.Results, gc.HasLen, len(args.Subnets))
   731  	for _, result := range results.Results {
   732  		if !c.Check(result.Error, gc.NotNil) {
   733  			continue
   734  		}
   735  		c.Check(result.Error, gc.ErrorMatches, expectedError)
   736  		c.Check(result.Error.Code, gc.Equals, "")
   737  	}
   738  
   739  	apiservertesting.CheckMethodCalls(c, apiservertesting.SharedStub, expectedCalls...)
   740  }
   741  
   742  func (s *SubnetsSuite) TestAddSubnetsWithNoProviderSubnetsFails(c *gc.C) {
   743  	s.CheckAddSubnetsFails(
   744  		c, apiservertesting.StubNetworkingEnvironName,
   745  		apiservertesting.WithoutZones, apiservertesting.WithoutSpaces, apiservertesting.WithoutSubnets,
   746  		"no subnets defined",
   747  	)
   748  }
   749  
   750  func (s *SubnetsSuite) TestAddSubnetsWithNoBackingSpacesFails(c *gc.C) {
   751  	s.CheckAddSubnetsFails(
   752  		c, apiservertesting.StubNetworkingEnvironName,
   753  		apiservertesting.WithoutZones, apiservertesting.WithoutSpaces, apiservertesting.WithSubnets,
   754  		"no spaces defined",
   755  	)
   756  }
   757  
   758  func (s *SubnetsSuite) TestAddSubnetsWithNoProviderZonesFails(c *gc.C) {
   759  	s.CheckAddSubnetsFails(
   760  		c, apiservertesting.StubZonedNetworkingEnvironName,
   761  		apiservertesting.WithoutZones, apiservertesting.WithSpaces, apiservertesting.WithSubnets,
   762  		"no zones defined",
   763  	)
   764  }
   765  
   766  func (s *SubnetsSuite) TestAddSubnetsWhenNetworkingEnvironNotSupported(c *gc.C) {
   767  	s.CheckAddSubnetsFails(
   768  		c, apiservertesting.StubEnvironName,
   769  		apiservertesting.WithoutZones, apiservertesting.WithoutSpaces, apiservertesting.WithoutSubnets,
   770  		"environment networking features not supported",
   771  	)
   772  }
   773  
   774  func (s *SubnetsSuite) TestListSubnetsAndFiltering(c *gc.C) {
   775  	expected := []params.Subnet{{
   776  		CIDR:       "10.10.0.0/24",
   777  		ProviderId: "sn-zadf00d",
   778  		VLANTag:    0,
   779  		Life:       "",
   780  		SpaceTag:   "space-private",
   781  		Zones:      []string{"zone1"},
   782  		Status:     "",
   783  	}, {
   784  		CIDR:       "2001:db8::/32",
   785  		ProviderId: "sn-ipv6",
   786  		VLANTag:    0,
   787  		Life:       "",
   788  		SpaceTag:   "space-dmz",
   789  		Zones:      []string{"zone1", "zone3"},
   790  		Status:     "",
   791  	}}
   792  	// No filtering.
   793  	args := params.SubnetsFilters{}
   794  	subnets, err := s.facade.ListSubnets(args)
   795  	c.Assert(err, jc.ErrorIsNil)
   796  	c.Assert(subnets.Results, jc.DeepEquals, expected)
   797  
   798  	// Filter by space only.
   799  	args.SpaceTag = "space-dmz"
   800  	subnets, err = s.facade.ListSubnets(args)
   801  	c.Assert(err, jc.ErrorIsNil)
   802  	c.Assert(subnets.Results, jc.DeepEquals, expected[1:])
   803  
   804  	// Filter by zone only.
   805  	args.SpaceTag = ""
   806  	args.Zone = "zone3"
   807  	subnets, err = s.facade.ListSubnets(args)
   808  	c.Assert(err, jc.ErrorIsNil)
   809  	c.Assert(subnets.Results, jc.DeepEquals, expected[1:])
   810  
   811  	// Filter by both space and zone.
   812  	args.SpaceTag = "space-private"
   813  	args.Zone = "zone1"
   814  	subnets, err = s.facade.ListSubnets(args)
   815  	c.Assert(err, jc.ErrorIsNil)
   816  	c.Assert(subnets.Results, jc.DeepEquals, expected[:1])
   817  }
   818  
   819  func (s *SubnetsSuite) TestListSubnetsInvalidSpaceTag(c *gc.C) {
   820  	args := params.SubnetsFilters{SpaceTag: "invalid"}
   821  	_, err := s.facade.ListSubnets(args)
   822  	c.Assert(err, gc.ErrorMatches, `"invalid" is not a valid tag`)
   823  }
   824  
   825  func (s *SubnetsSuite) TestListSubnetsAllSubnetError(c *gc.C) {
   826  	boom := errors.New("no subnets for you")
   827  	apiservertesting.BackingInstance.SetErrors(boom)
   828  	_, err := s.facade.ListSubnets(params.SubnetsFilters{})
   829  	c.Assert(err, gc.ErrorMatches, "no subnets for you")
   830  }