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