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