github.com/juju/juju@v0.0.0-20240430160146-1752b71fcf00/apiserver/facades/client/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/collections/set"
     8  	"github.com/juju/errors"
     9  	"github.com/juju/names/v5"
    10  	jc "github.com/juju/testing/checkers"
    11  	"go.uber.org/mock/gomock"
    12  	gc "gopkg.in/check.v1"
    13  
    14  	"github.com/juju/juju/apiserver/common"
    15  	"github.com/juju/juju/apiserver/common/networkingcommon"
    16  	networkcommonmocks "github.com/juju/juju/apiserver/common/networkingcommon/mocks"
    17  	apiservererrors "github.com/juju/juju/apiserver/errors"
    18  	facademocks "github.com/juju/juju/apiserver/facade/mocks"
    19  	"github.com/juju/juju/apiserver/facades/client/subnets"
    20  	"github.com/juju/juju/apiserver/facades/client/subnets/mocks"
    21  	apiservertesting "github.com/juju/juju/apiserver/testing"
    22  	"github.com/juju/juju/core/life"
    23  	"github.com/juju/juju/core/network"
    24  	"github.com/juju/juju/environs/context"
    25  	"github.com/juju/juju/rpc/params"
    26  	"github.com/juju/juju/state"
    27  	coretesting "github.com/juju/juju/testing"
    28  )
    29  
    30  // SubnetSuite uses mocks for testing.
    31  // All future facade tests should be added to this suite.
    32  type SubnetSuite struct {
    33  	mockBacking          *mocks.MockBacking
    34  	mockResource         *facademocks.MockResources
    35  	mockCloudCallContext *context.CloudCallContext
    36  	mockAuthorizer       *facademocks.MockAuthorizer
    37  
    38  	api *subnets.API
    39  }
    40  
    41  var _ = gc.Suite(&SubnetSuite{})
    42  
    43  func (s *SubnetSuite) TearDownTest(c *gc.C) {
    44  	s.api = nil
    45  }
    46  
    47  func (s *SubnetSuite) TestSubnetsByCIDR(c *gc.C) {
    48  	ctrl := s.setupSubnetsAPI(c)
    49  	defer ctrl.Finish()
    50  
    51  	cidrs := []string{"10.10.10.0/24", "10.10.20.0/24", "not-a-cidr"}
    52  
    53  	subnet := networkcommonmocks.NewMockBackingSubnet(ctrl)
    54  	sExp := subnet.EXPECT()
    55  	sExp.ID().Return("1")
    56  	sExp.CIDR().Return("10.10.20.0/24")
    57  	sExp.SpaceName().Return("space")
    58  	sExp.VLANTag().Return(0)
    59  	sExp.ProviderId().Return(network.Id("0"))
    60  	sExp.ProviderNetworkId().Return(network.Id("1"))
    61  	sExp.AvailabilityZones().Return([]string{"bar", "bam"})
    62  	sExp.Life().Return(state.Alive)
    63  
    64  	bExp := s.mockBacking.EXPECT()
    65  	gomock.InOrder(
    66  		bExp.SubnetsByCIDR(cidrs[0]).Return(nil, errors.New("bad-mongo")),
    67  		bExp.SubnetsByCIDR(cidrs[1]).Return([]networkingcommon.BackingSubnet{subnet}, nil),
    68  		// No call for cidrs[2]; the input is invalidated.
    69  	)
    70  
    71  	arg := params.CIDRParams{CIDRS: cidrs}
    72  	res, err := s.api.SubnetsByCIDR(arg)
    73  	c.Assert(err, jc.ErrorIsNil)
    74  
    75  	results := res.Results
    76  	c.Assert(results, gc.HasLen, 3)
    77  
    78  	c.Check(results[0].Error.Message, gc.Equals, "bad-mongo")
    79  	c.Check(results[1].Subnets, gc.HasLen, 1)
    80  	c.Check(results[2].Error.Message, gc.Equals, `CIDR "not-a-cidr" not valid`)
    81  }
    82  
    83  func (s *SubnetSuite) setupSubnetsAPI(c *gc.C) *gomock.Controller {
    84  	ctrl := gomock.NewController(c)
    85  	s.mockResource = facademocks.NewMockResources(ctrl)
    86  	s.mockCloudCallContext = context.NewEmptyCloudCallContext()
    87  	s.mockBacking = mocks.NewMockBacking(ctrl)
    88  
    89  	s.mockAuthorizer = facademocks.NewMockAuthorizer(ctrl)
    90  	s.mockAuthorizer.EXPECT().HasPermission(gomock.Any(), gomock.Any()).Return(nil).AnyTimes()
    91  	s.mockAuthorizer.EXPECT().AuthClient().Return(true)
    92  
    93  	s.mockBacking.EXPECT().ModelTag().Return(names.NewModelTag("123"))
    94  
    95  	var err error
    96  	s.api, err = subnets.NewAPIWithBacking(s.mockBacking, s.mockCloudCallContext, s.mockResource, s.mockAuthorizer)
    97  	c.Assert(err, jc.ErrorIsNil)
    98  	return ctrl
    99  }
   100  
   101  // SubnetsSuite is the old testing suite based on testing stubs.
   102  // This should be phased out in favour of mock-based tests.
   103  // The testing network infrastructure should also be removed as soon as can be
   104  // managed.
   105  type SubnetsSuite struct {
   106  	coretesting.BaseSuite
   107  	apiservertesting.StubNetwork
   108  
   109  	resources  *common.Resources
   110  	authorizer apiservertesting.FakeAuthorizer
   111  	facade     *subnets.API
   112  
   113  	callContext context.ProviderCallContext
   114  }
   115  
   116  type stubBacking struct {
   117  	*apiservertesting.StubBacking
   118  }
   119  
   120  func (sb *stubBacking) SubnetsByCIDR(_ string) ([]networkingcommon.BackingSubnet, error) {
   121  	panic("should not be called")
   122  }
   123  
   124  var _ = gc.Suite(&SubnetsSuite{})
   125  
   126  func (s *SubnetsSuite) SetUpSuite(c *gc.C) {
   127  	s.StubNetwork.SetUpSuite(c)
   128  	s.BaseSuite.SetUpSuite(c)
   129  }
   130  
   131  func (s *SubnetsSuite) TearDownSuite(c *gc.C) {
   132  	s.BaseSuite.TearDownSuite(c)
   133  }
   134  
   135  func (s *SubnetsSuite) SetUpTest(c *gc.C) {
   136  	s.BaseSuite.SetUpTest(c)
   137  	apiservertesting.BackingInstance.SetUp(c, apiservertesting.StubZonedEnvironName, apiservertesting.WithZones, apiservertesting.WithSpaces, apiservertesting.WithSubnets)
   138  
   139  	s.resources = common.NewResources()
   140  	s.authorizer = apiservertesting.FakeAuthorizer{
   141  		Tag:        names.NewUserTag("admin"),
   142  		Controller: false,
   143  	}
   144  
   145  	s.callContext = context.NewEmptyCloudCallContext()
   146  	var err error
   147  	s.facade, err = subnets.NewAPIWithBacking(
   148  		&stubBacking{apiservertesting.BackingInstance},
   149  		s.callContext,
   150  		s.resources, s.authorizer,
   151  	)
   152  	c.Assert(err, jc.ErrorIsNil)
   153  	c.Assert(s.facade, gc.NotNil)
   154  }
   155  
   156  func (s *SubnetsSuite) TearDownTest(c *gc.C) {
   157  	if s.resources != nil {
   158  		s.resources.StopAll()
   159  	}
   160  	s.BaseSuite.TearDownTest(c)
   161  }
   162  
   163  // AssertAllZonesResult makes it easier to verify AllZones results.
   164  func (s *SubnetsSuite) AssertAllZonesResult(c *gc.C, got params.ZoneResults, expected network.AvailabilityZones) {
   165  	results := make([]params.ZoneResult, len(expected))
   166  	for i, zone := range expected {
   167  		results[i].Name = zone.Name()
   168  		results[i].Available = zone.Available()
   169  	}
   170  	c.Assert(got, jc.DeepEquals, params.ZoneResults{Results: results})
   171  }
   172  
   173  // AssertAllSpacesResult makes it easier to verify AllSpaces results.
   174  func (s *SubnetsSuite) AssertAllSpacesResult(c *gc.C, got params.SpaceResults, expected []networkingcommon.BackingSpace) {
   175  	seen := set.Strings{}
   176  	results := []params.SpaceResult{}
   177  	for _, space := range expected {
   178  		if seen.Contains(space.Name()) {
   179  			continue
   180  		}
   181  		seen.Add(space.Name())
   182  		result := params.SpaceResult{}
   183  		result.Tag = names.NewSpaceTag(space.Name()).String()
   184  		results = append(results, result)
   185  	}
   186  	c.Assert(got, jc.DeepEquals, params.SpaceResults{Results: results})
   187  }
   188  
   189  func (s *SubnetsSuite) TestNewAPIWithBacking(c *gc.C) {
   190  	// Clients are allowed.
   191  	facade, err := subnets.NewAPIWithBacking(
   192  		&stubBacking{apiservertesting.BackingInstance},
   193  		s.callContext,
   194  		s.resources, s.authorizer,
   195  	)
   196  	c.Assert(err, jc.ErrorIsNil)
   197  	c.Assert(facade, gc.NotNil)
   198  	// No calls so far.
   199  	apiservertesting.CheckMethodCalls(c, apiservertesting.SharedStub)
   200  
   201  	// Agents are not allowed
   202  	agentAuthorizer := s.authorizer
   203  	agentAuthorizer.Tag = names.NewMachineTag("42")
   204  	facade, err = subnets.NewAPIWithBacking(
   205  		&stubBacking{apiservertesting.BackingInstance},
   206  		s.callContext,
   207  		s.resources, agentAuthorizer,
   208  	)
   209  	c.Assert(err, jc.DeepEquals, apiservererrors.ErrPerm)
   210  	c.Assert(facade, gc.IsNil)
   211  	// No calls so far.
   212  	apiservertesting.CheckMethodCalls(c, apiservertesting.SharedStub)
   213  }
   214  
   215  func (s *SubnetsSuite) TestAllZonesWhenBackingAvailabilityZonesFails(c *gc.C) {
   216  	apiservertesting.SharedStub.SetErrors(errors.NotSupportedf("zones"))
   217  
   218  	results, err := s.facade.AllZones()
   219  	c.Assert(err, gc.ErrorMatches, "zones not supported")
   220  	// Verify the cause is not obscured.
   221  	c.Assert(err, jc.Satisfies, errors.IsNotSupported)
   222  	c.Assert(results, jc.DeepEquals, params.ZoneResults{})
   223  
   224  	apiservertesting.CheckMethodCalls(c, apiservertesting.SharedStub,
   225  		apiservertesting.BackingCall("AvailabilityZones"),
   226  	)
   227  }
   228  
   229  func (s *SubnetsSuite) TestAllZonesUsesBackingZonesWhenAvailable(c *gc.C) {
   230  	results, err := s.facade.AllZones()
   231  	c.Assert(err, jc.ErrorIsNil)
   232  	s.AssertAllZonesResult(c, results, apiservertesting.BackingInstance.Zones)
   233  
   234  	apiservertesting.CheckMethodCalls(c, apiservertesting.SharedStub,
   235  		apiservertesting.BackingCall("AvailabilityZones"),
   236  	)
   237  }
   238  
   239  func (s *SubnetsSuite) TestAllZonesWithNoBackingZonesUpdates(c *gc.C) {
   240  	apiservertesting.BackingInstance.SetUp(c, apiservertesting.StubZonedEnvironName, apiservertesting.WithoutZones, apiservertesting.WithSpaces, apiservertesting.WithSubnets)
   241  
   242  	results, err := s.facade.AllZones()
   243  	c.Assert(err, jc.ErrorIsNil)
   244  	s.AssertAllZonesResult(c, results, apiservertesting.ProviderInstance.Zones)
   245  
   246  	apiservertesting.CheckMethodCalls(c, apiservertesting.SharedStub,
   247  		apiservertesting.BackingCall("AvailabilityZones"),
   248  		apiservertesting.BackingCall("ModelConfig"),
   249  		apiservertesting.BackingCall("CloudSpec"),
   250  		apiservertesting.ProviderCall("Open", apiservertesting.BackingInstance.EnvConfig),
   251  		apiservertesting.ZonedEnvironCall("AvailabilityZones", s.callContext),
   252  		apiservertesting.BackingCall("SetAvailabilityZones", apiservertesting.ProviderInstance.Zones),
   253  	)
   254  }
   255  
   256  func (s *SubnetsSuite) TestAllZonesWithNoBackingZonesAndSetFails(c *gc.C) {
   257  	apiservertesting.BackingInstance.SetUp(c, apiservertesting.StubZonedEnvironName, apiservertesting.WithoutZones, apiservertesting.WithSpaces, apiservertesting.WithSubnets)
   258  	apiservertesting.SharedStub.SetErrors(
   259  		nil,                             // Backing.AvailabilityZones
   260  		nil,                             // Backing.ModelConfig
   261  		nil,                             // Backing.CloudSpec
   262  		nil,                             // Provider.Open
   263  		nil,                             // ZonedEnviron.AvailabilityZones
   264  		errors.NotSupportedf("setting"), // Backing.SetAvailabilityZones
   265  	)
   266  
   267  	results, err := s.facade.AllZones()
   268  	c.Assert(err, gc.ErrorMatches,
   269  		`cannot update known zones: setting not supported`,
   270  	)
   271  	// Verify the cause is not obscured.
   272  	c.Assert(err, jc.Satisfies, errors.IsNotSupported)
   273  	c.Assert(results, jc.DeepEquals, params.ZoneResults{})
   274  
   275  	apiservertesting.CheckMethodCalls(c, apiservertesting.SharedStub,
   276  		apiservertesting.BackingCall("AvailabilityZones"),
   277  		apiservertesting.BackingCall("ModelConfig"),
   278  		apiservertesting.BackingCall("CloudSpec"),
   279  		apiservertesting.ProviderCall("Open", apiservertesting.BackingInstance.EnvConfig),
   280  		apiservertesting.ZonedEnvironCall("AvailabilityZones", s.callContext),
   281  		apiservertesting.BackingCall("SetAvailabilityZones", apiservertesting.ProviderInstance.Zones),
   282  	)
   283  }
   284  
   285  func (s *SubnetsSuite) TestAllZonesWithNoBackingZonesAndFetchingZonesFails(c *gc.C) {
   286  	apiservertesting.BackingInstance.SetUp(c, apiservertesting.StubZonedEnvironName, apiservertesting.WithoutZones, apiservertesting.WithSpaces, apiservertesting.WithSubnets)
   287  	apiservertesting.SharedStub.SetErrors(
   288  		nil,                     // Backing.AvailabilityZones
   289  		nil,                     // Backing.ModelConfig
   290  		nil,                     // Backing.CloudSpec
   291  		nil,                     // Provider.Open
   292  		errors.NotValidf("foo"), // ZonedEnviron.AvailabilityZones
   293  	)
   294  
   295  	results, err := s.facade.AllZones()
   296  	c.Assert(err, gc.ErrorMatches,
   297  		`cannot update known zones: foo not valid`,
   298  	)
   299  	// Verify the cause is not obscured.
   300  	c.Assert(err, jc.Satisfies, errors.IsNotValid)
   301  	c.Assert(results, jc.DeepEquals, params.ZoneResults{})
   302  
   303  	apiservertesting.CheckMethodCalls(c, apiservertesting.SharedStub,
   304  		apiservertesting.BackingCall("AvailabilityZones"),
   305  		apiservertesting.BackingCall("ModelConfig"),
   306  		apiservertesting.BackingCall("CloudSpec"),
   307  		apiservertesting.ProviderCall("Open", apiservertesting.BackingInstance.EnvConfig),
   308  		apiservertesting.ZonedEnvironCall("AvailabilityZones", s.callContext),
   309  	)
   310  }
   311  
   312  func (s *SubnetsSuite) TestAllZonesWithNoBackingZonesAndModelConfigFails(c *gc.C) {
   313  	apiservertesting.BackingInstance.SetUp(c, apiservertesting.StubZonedEnvironName, apiservertesting.WithoutZones, apiservertesting.WithSpaces, apiservertesting.WithSubnets)
   314  	apiservertesting.SharedStub.SetErrors(
   315  		nil,                        // Backing.AvailabilityZones
   316  		errors.NotFoundf("config"), // Backing.ModelConfig
   317  	)
   318  
   319  	results, err := s.facade.AllZones()
   320  	c.Assert(err, gc.ErrorMatches,
   321  		`cannot update known zones: opening environment: retrieving model config: config not found`,
   322  	)
   323  	// Verify the cause is not obscured.
   324  	c.Assert(err, jc.Satisfies, errors.IsNotFound)
   325  	c.Assert(results, jc.DeepEquals, params.ZoneResults{})
   326  
   327  	apiservertesting.CheckMethodCalls(c, apiservertesting.SharedStub,
   328  		apiservertesting.BackingCall("AvailabilityZones"),
   329  		apiservertesting.BackingCall("ModelConfig"),
   330  	)
   331  }
   332  
   333  func (s *SubnetsSuite) TestAllZonesWithNoBackingZonesAndOpenFails(c *gc.C) {
   334  	apiservertesting.BackingInstance.SetUp(c, apiservertesting.StubZonedEnvironName, apiservertesting.WithoutZones, apiservertesting.WithSpaces, apiservertesting.WithSubnets)
   335  	apiservertesting.SharedStub.SetErrors(
   336  		nil,                        // Backing.AvailabilityZones
   337  		nil,                        // Backing.ModelConfig
   338  		nil,                        // Backing.CloudSpec
   339  		errors.NotValidf("config"), // Provider.Open
   340  	)
   341  
   342  	results, err := s.facade.AllZones()
   343  	c.Assert(err, gc.ErrorMatches,
   344  		`cannot update known zones: opening environment: creating environ for model \"stub-zoned-environ\" \(.*\): config not valid`,
   345  	)
   346  	// Verify the cause is not obscured.
   347  	c.Assert(err, jc.Satisfies, errors.IsNotValid)
   348  	c.Assert(results, jc.DeepEquals, params.ZoneResults{})
   349  
   350  	apiservertesting.CheckMethodCalls(c, apiservertesting.SharedStub,
   351  		apiservertesting.BackingCall("AvailabilityZones"),
   352  		apiservertesting.BackingCall("ModelConfig"),
   353  		apiservertesting.BackingCall("CloudSpec"),
   354  		apiservertesting.ProviderCall("Open", apiservertesting.BackingInstance.EnvConfig),
   355  	)
   356  }
   357  
   358  func (s *SubnetsSuite) TestAllZonesWithNoBackingZonesAndZonesNotSupported(c *gc.C) {
   359  	apiservertesting.BackingInstance.SetUp(c, apiservertesting.StubEnvironName, apiservertesting.WithoutZones, apiservertesting.WithSpaces, apiservertesting.WithSubnets)
   360  	// ZonedEnviron not supported
   361  
   362  	results, err := s.facade.AllZones()
   363  	c.Assert(err, gc.ErrorMatches,
   364  		`cannot update known zones: availability zones not supported`,
   365  	)
   366  	// Verify the cause is not obscured.
   367  	c.Assert(err, jc.Satisfies, errors.IsNotSupported)
   368  	c.Assert(results, jc.DeepEquals, params.ZoneResults{})
   369  
   370  	apiservertesting.CheckMethodCalls(c, apiservertesting.SharedStub,
   371  		apiservertesting.BackingCall("AvailabilityZones"),
   372  		apiservertesting.BackingCall("ModelConfig"),
   373  		apiservertesting.BackingCall("CloudSpec"),
   374  		apiservertesting.ProviderCall("Open", apiservertesting.BackingInstance.EnvConfig),
   375  	)
   376  }
   377  
   378  func (s *SubnetsSuite) TestListSubnetsAndFiltering(c *gc.C) {
   379  	expected := []params.Subnet{{
   380  		CIDR:              "10.10.0.0/24",
   381  		ProviderId:        "sn-zadf00d",
   382  		ProviderNetworkId: "godspeed",
   383  		VLANTag:           0,
   384  		Life:              life.Alive,
   385  		SpaceTag:          "space-private",
   386  		Zones:             []string{"zone1"},
   387  	}, {
   388  		CIDR:              "2001:db8::/32",
   389  		ProviderId:        "sn-ipv6",
   390  		ProviderNetworkId: "",
   391  		VLANTag:           0,
   392  		Life:              life.Alive,
   393  		SpaceTag:          "space-dmz",
   394  		Zones:             []string{"zone1", "zone3"},
   395  	}}
   396  	// No filtering.
   397  	args := params.SubnetsFilters{}
   398  	subnets, err := s.facade.ListSubnets(args)
   399  	c.Assert(err, jc.ErrorIsNil)
   400  	c.Assert(subnets.Results, jc.DeepEquals, expected)
   401  
   402  	// Filter by space only.
   403  	args.SpaceTag = "space-dmz"
   404  	subnets, err = s.facade.ListSubnets(args)
   405  	c.Assert(err, jc.ErrorIsNil)
   406  	c.Assert(subnets.Results, jc.DeepEquals, expected[1:])
   407  
   408  	// Filter by zone only.
   409  	args.SpaceTag = ""
   410  	args.Zone = "zone3"
   411  	subnets, err = s.facade.ListSubnets(args)
   412  	c.Assert(err, jc.ErrorIsNil)
   413  	c.Assert(subnets.Results, jc.DeepEquals, expected[1:])
   414  
   415  	// Filter by both space and zone.
   416  	args.SpaceTag = "space-private"
   417  	args.Zone = "zone1"
   418  	subnets, err = s.facade.ListSubnets(args)
   419  	c.Assert(err, jc.ErrorIsNil)
   420  	c.Assert(subnets.Results, jc.DeepEquals, expected[:1])
   421  }
   422  
   423  func (s *SubnetsSuite) TestListSubnetsInvalidSpaceTag(c *gc.C) {
   424  	args := params.SubnetsFilters{SpaceTag: "invalid"}
   425  	_, err := s.facade.ListSubnets(args)
   426  	c.Assert(err, gc.ErrorMatches, `"invalid" is not a valid tag`)
   427  }
   428  
   429  func (s *SubnetsSuite) TestListSubnetsAllSubnetError(c *gc.C) {
   430  	boom := errors.New("no subnets for you")
   431  	apiservertesting.BackingInstance.SetErrors(boom)
   432  	_, err := s.facade.ListSubnets(params.SubnetsFilters{})
   433  	c.Assert(err, gc.ErrorMatches, "no subnets for you")
   434  }