github.com/makyo/juju@v0.0.0-20160425123129-2608902037e9/apiserver/subnets/subnets_test.go (about)

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