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