github.com/wallyworld/juju@v0.0.0-20161013125918-6cf1bc9d917a/apiserver/common/networkingcommon/subnets_test.go (about)

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