github.com/mwhudson/juju@v0.0.0-20160512215208-90ff01f3497f/provider/ec2/environ_vpc_test.go (about)

     1  // Copyright 2016 Canonical Ltd.
     2  // Licensed under the AGPLv3, see LICENCE file for details.
     3  
     4  package ec2
     5  
     6  import (
     7  	"fmt"
     8  
     9  	"github.com/juju/errors"
    10  	"github.com/juju/testing"
    11  	jc "github.com/juju/testing/checkers"
    12  	"gopkg.in/amz.v3/ec2"
    13  	gc "gopkg.in/check.v1"
    14  
    15  	"github.com/juju/juju/network"
    16  )
    17  
    18  type vpcSuite struct {
    19  	testing.IsolationSuite
    20  
    21  	stubAPI *stubVPCAPIClient
    22  }
    23  
    24  var _ = gc.Suite(&vpcSuite{})
    25  
    26  func (s *vpcSuite) SetUpTest(c *gc.C) {
    27  	s.IsolationSuite.SetUpTest(c)
    28  
    29  	s.stubAPI = &stubVPCAPIClient{Stub: &testing.Stub{}}
    30  }
    31  
    32  // NOTE: validateVPC tests only verify expected error types for all code paths,
    33  // but do not check passed API arguments or exact error messages, as those are
    34  // extensively tested separately below.
    35  
    36  func (s *vpcSuite) TestValidateVPCWithEmptyVPCIDOrNilAPIClient(c *gc.C) {
    37  	err := validateVPC(s.stubAPI, "")
    38  	c.Assert(err, gc.ErrorMatches, "invalid arguments: empty VPC ID or nil client")
    39  
    40  	err = validateVPC(nil, anyVPCID)
    41  	c.Assert(err, gc.ErrorMatches, "invalid arguments: empty VPC ID or nil client")
    42  
    43  	s.stubAPI.CheckNoCalls(c)
    44  }
    45  
    46  func (s *vpcSuite) TestValidateVPCWhenVPCIDNotFound(c *gc.C) {
    47  	s.stubAPI.SetErrors(makeVPCNotFoundError("foo"))
    48  
    49  	err := validateVPC(s.stubAPI, anyVPCID)
    50  	c.Check(err, jc.Satisfies, isVPCNotUsableError)
    51  
    52  	s.stubAPI.CheckCallNames(c, "VPCs")
    53  }
    54  
    55  func (s *vpcSuite) TestValidateVPCWhenVPCHasNoSubnets(c *gc.C) {
    56  	s.stubAPI.SetVPCsResponse(1, availableState, notDefaultVPC)
    57  	s.stubAPI.SetSubnetsResponse(noResults, anyZone, noPublicIPOnLaunch)
    58  
    59  	err := validateVPC(s.stubAPI, anyVPCID)
    60  	c.Check(err, jc.Satisfies, isVPCNotUsableError)
    61  
    62  	s.stubAPI.CheckCallNames(c, "VPCs", "Subnets")
    63  }
    64  func (s *vpcSuite) TestValidateVPCWhenVPCNotAvailable(c *gc.C) {
    65  	s.stubAPI.PrepareValidateVPCResponses()
    66  	s.stubAPI.SetVPCsResponse(1, "bad-state", notDefaultVPC)
    67  
    68  	s.stubAPI.CallValidateVPCAndCheckCallsUpToExpectingVPCNotRecommendedError(c, "VPCs")
    69  }
    70  
    71  func (s *vpcSuite) TestValidateVPCWhenVPCHasNoPublicSubnets(c *gc.C) {
    72  	s.stubAPI.PrepareValidateVPCResponses()
    73  	s.stubAPI.SetSubnetsResponse(1, anyZone, noPublicIPOnLaunch)
    74  
    75  	s.stubAPI.CallValidateVPCAndCheckCallsUpToExpectingVPCNotRecommendedError(c, "Subnets")
    76  }
    77  
    78  func (s *vpcSuite) TestValidateVPCWhenVPCHasNoGateway(c *gc.C) {
    79  	s.stubAPI.PrepareValidateVPCResponses()
    80  	s.stubAPI.SetGatewaysResponse(noResults, anyState)
    81  
    82  	s.stubAPI.CallValidateVPCAndCheckCallsUpToExpectingVPCNotRecommendedError(c, "InternetGateways")
    83  }
    84  
    85  func (s *vpcSuite) TestValidateVPCWhenVPCHasNoAttachedGateway(c *gc.C) {
    86  	s.stubAPI.PrepareValidateVPCResponses()
    87  	s.stubAPI.SetGatewaysResponse(1, "pending")
    88  
    89  	s.stubAPI.CallValidateVPCAndCheckCallsUpToExpectingVPCNotRecommendedError(c, "InternetGateways")
    90  }
    91  
    92  func (s *vpcSuite) TestValidateVPCWhenVPCHasNoRouteTables(c *gc.C) {
    93  	s.stubAPI.PrepareValidateVPCResponses()
    94  	s.stubAPI.SetRouteTablesResponse() // no route tables at all
    95  
    96  	s.stubAPI.CallValidateVPCAndCheckCallsUpToExpectingVPCNotRecommendedError(c, "RouteTables")
    97  }
    98  
    99  func (s *vpcSuite) TestValidateVPCWhenVPCHasNoMainRouteTable(c *gc.C) {
   100  	s.stubAPI.PrepareValidateVPCResponses()
   101  	s.stubAPI.SetRouteTablesResponse(
   102  		makeEC2RouteTable(anyTableID, notMainRouteTable, nil, nil),
   103  	)
   104  
   105  	s.stubAPI.CallValidateVPCAndCheckCallsUpToExpectingVPCNotRecommendedError(c, "RouteTables")
   106  }
   107  
   108  func (s *vpcSuite) TestValidateVPCWhenVPCHasMainRouteTableWithoutRoutes(c *gc.C) {
   109  	s.stubAPI.PrepareValidateVPCResponses()
   110  	s.stubAPI.SetRouteTablesResponse(
   111  		makeEC2RouteTable(anyTableID, mainRouteTable, nil, nil),
   112  	)
   113  
   114  	s.stubAPI.CallValidateVPCAndCheckCallsUpToExpectingVPCNotRecommendedError(c, "RouteTables")
   115  }
   116  
   117  func (s *vpcSuite) TestValidateVPCSuccess(c *gc.C) {
   118  	s.stubAPI.PrepareValidateVPCResponses()
   119  
   120  	err := validateVPC(s.stubAPI, anyVPCID)
   121  	c.Assert(err, jc.ErrorIsNil)
   122  
   123  	s.stubAPI.CheckCallNames(c, "VPCs", "Subnets", "InternetGateways", "RouteTables")
   124  }
   125  
   126  func (s *vpcSuite) TestGetVPCByIDWithMissingID(c *gc.C) {
   127  	s.stubAPI.SetErrors(makeVPCNotFoundError("foo"))
   128  
   129  	vpc, err := getVPCByID(s.stubAPI, "foo")
   130  	c.Assert(err, gc.ErrorMatches, `The vpc ID 'foo' does not exist \(InvalidVpcID.NotFound\)`)
   131  	c.Check(err, jc.Satisfies, isVPCNotUsableError)
   132  	c.Check(vpc, gc.IsNil)
   133  
   134  	s.stubAPI.CheckSingleVPCsCall(c, "foo")
   135  }
   136  
   137  func (s *vpcSuite) TestGetVPCByIDUnexpectedAWSError(c *gc.C) {
   138  	s.stubAPI.SetErrors(errors.New("AWS failed!"))
   139  
   140  	vpc, err := getVPCByID(s.stubAPI, "bar")
   141  	c.Assert(err, gc.ErrorMatches, `unexpected AWS response getting VPC "bar": AWS failed!`)
   142  	c.Check(vpc, gc.IsNil)
   143  
   144  	s.stubAPI.CheckSingleVPCsCall(c, "bar")
   145  }
   146  
   147  func (s *vpcSuite) TestGetVPCByIDNoResults(c *gc.C) {
   148  	s.stubAPI.SetVPCsResponse(noResults, anyState, notDefaultVPC)
   149  
   150  	vpc, err := getVPCByID(s.stubAPI, "vpc-42")
   151  	c.Assert(err, gc.ErrorMatches, `VPC "vpc-42" not found`)
   152  	c.Check(err, jc.Satisfies, isVPCNotUsableError)
   153  	c.Check(vpc, gc.IsNil)
   154  
   155  	s.stubAPI.CheckSingleVPCsCall(c, "vpc-42")
   156  }
   157  
   158  func (s *vpcSuite) TestGetVPCByIDMultipleResults(c *gc.C) {
   159  	s.stubAPI.SetVPCsResponse(5, anyState, notDefaultVPC)
   160  
   161  	vpc, err := getVPCByID(s.stubAPI, "vpc-33")
   162  	c.Assert(err, gc.ErrorMatches, "expected 1 result from AWS, got 5")
   163  	c.Check(vpc, gc.IsNil)
   164  
   165  	s.stubAPI.CheckSingleVPCsCall(c, "vpc-33")
   166  }
   167  
   168  func (s *vpcSuite) TestGetVPCByIDSuccess(c *gc.C) {
   169  	s.stubAPI.SetVPCsResponse(1, anyState, notDefaultVPC)
   170  
   171  	vpc, err := getVPCByID(s.stubAPI, "vpc-1")
   172  	c.Assert(err, jc.ErrorIsNil)
   173  	c.Check(vpc, jc.DeepEquals, &s.stubAPI.vpcsResponse.VPCs[0])
   174  
   175  	s.stubAPI.CheckSingleVPCsCall(c, "vpc-1")
   176  }
   177  
   178  func (s *vpcSuite) TestIsVPCNotFoundError(c *gc.C) {
   179  	c.Check(isVPCNotFoundError(nil), jc.IsFalse)
   180  
   181  	nonEC2Error := errors.New("boom")
   182  	c.Check(isVPCNotFoundError(nonEC2Error), jc.IsFalse)
   183  
   184  	ec2Error := makeEC2Error(444, "code", "bad stuff", "req-id")
   185  	c.Check(isVPCNotFoundError(ec2Error), jc.IsFalse)
   186  
   187  	ec2Error = makeVPCNotFoundError("some-id")
   188  	c.Check(isVPCNotFoundError(ec2Error), jc.IsTrue)
   189  }
   190  
   191  func (s *vpcSuite) TestCheckVPCIsAvailable(c *gc.C) {
   192  	availableVPC := makeEC2VPC(anyVPCID, availableState)
   193  	c.Check(checkVPCIsAvailable(availableVPC), jc.ErrorIsNil)
   194  
   195  	defaultVPC := makeEC2VPC(anyVPCID, availableState)
   196  	defaultVPC.IsDefault = true
   197  	c.Check(checkVPCIsAvailable(defaultVPC), jc.ErrorIsNil)
   198  
   199  	notAvailableVPC := makeEC2VPC(anyVPCID, anyState)
   200  	err := checkVPCIsAvailable(notAvailableVPC)
   201  	c.Assert(err, gc.ErrorMatches, `VPC has unexpected state "any state"`)
   202  	c.Check(err, jc.Satisfies, isVPCNotRecommendedError)
   203  }
   204  
   205  func (s *vpcSuite) TestGetVPCSubnetUnexpectedAWSError(c *gc.C) {
   206  	s.stubAPI.SetErrors(errors.New("AWS failed!"))
   207  
   208  	anyVPC := makeEC2VPC(anyVPCID, anyState)
   209  	subnets, err := getVPCSubnets(s.stubAPI, anyVPC)
   210  	c.Assert(err, gc.ErrorMatches, `unexpected AWS response getting subnets of VPC "vpc-anything": AWS failed!`)
   211  	c.Check(subnets, gc.IsNil)
   212  
   213  	s.stubAPI.CheckSingleSubnetsCall(c, anyVPC)
   214  }
   215  
   216  func (s *vpcSuite) TestGetVPCSubnetsNoResults(c *gc.C) {
   217  	s.stubAPI.SetSubnetsResponse(noResults, anyZone, noPublicIPOnLaunch)
   218  
   219  	anyVPC := makeEC2VPC(anyVPCID, anyState)
   220  	subnets, err := getVPCSubnets(s.stubAPI, anyVPC)
   221  	c.Assert(err, gc.ErrorMatches, `no subnets found for VPC "vpc-anything"`)
   222  	c.Check(err, jc.Satisfies, isVPCNotUsableError)
   223  	c.Check(subnets, gc.IsNil)
   224  
   225  	s.stubAPI.CheckSingleSubnetsCall(c, anyVPC)
   226  }
   227  
   228  func (s *vpcSuite) TestGetVPCSubnetsSuccess(c *gc.C) {
   229  	s.stubAPI.SetSubnetsResponse(3, anyZone, noPublicIPOnLaunch)
   230  
   231  	anyVPC := makeEC2VPC(anyVPCID, anyState)
   232  	subnets, err := getVPCSubnets(s.stubAPI, anyVPC)
   233  	c.Assert(err, jc.ErrorIsNil)
   234  	c.Check(subnets, jc.DeepEquals, s.stubAPI.subnetsResponse.Subnets)
   235  
   236  	s.stubAPI.CheckSingleSubnetsCall(c, anyVPC)
   237  }
   238  
   239  func (s *vpcSuite) TestFindFirstPublicSubnetSuccess(c *gc.C) {
   240  	s.stubAPI.SetSubnetsResponse(3, anyZone, withPublicIPOnLaunch)
   241  	s.stubAPI.subnetsResponse.Subnets[0].MapPublicIPOnLaunch = false
   242  
   243  	subnet, err := findFirstPublicSubnet(s.stubAPI.subnetsResponse.Subnets)
   244  	c.Assert(err, jc.ErrorIsNil)
   245  	c.Check(subnet, jc.DeepEquals, &s.stubAPI.subnetsResponse.Subnets[1])
   246  }
   247  
   248  func (s *vpcSuite) TestFindFirstPublicSubnetNoneFound(c *gc.C) {
   249  	s.stubAPI.SetSubnetsResponse(3, anyZone, noPublicIPOnLaunch)
   250  
   251  	subnet, err := findFirstPublicSubnet(s.stubAPI.subnetsResponse.Subnets)
   252  	c.Assert(err, gc.ErrorMatches, "VPC contains no public subnets")
   253  	c.Check(err, jc.Satisfies, isVPCNotRecommendedError)
   254  	c.Check(subnet, gc.IsNil)
   255  }
   256  
   257  func (s *vpcSuite) TestGetVPCInternetGatewayNoResults(c *gc.C) {
   258  	s.stubAPI.SetGatewaysResponse(noResults, anyState)
   259  
   260  	anyVPC := makeEC2VPC(anyVPCID, anyState)
   261  	gateway, err := getVPCInternetGateway(s.stubAPI, anyVPC)
   262  	c.Assert(err, gc.ErrorMatches, `VPC has no Internet Gateway attached`)
   263  	c.Check(err, jc.Satisfies, isVPCNotRecommendedError)
   264  	c.Check(gateway, gc.IsNil)
   265  
   266  	s.stubAPI.CheckSingleInternetGatewaysCall(c, anyVPC)
   267  }
   268  
   269  func (s *vpcSuite) TestGetVPCInternetGatewayUnexpectedAWSError(c *gc.C) {
   270  	s.stubAPI.SetErrors(errors.New("AWS failed!"))
   271  
   272  	anyVPC := makeEC2VPC(anyVPCID, anyState)
   273  	gateway, err := getVPCInternetGateway(s.stubAPI, anyVPC)
   274  	c.Assert(err, gc.ErrorMatches, `unexpected AWS response getting Internet Gateway of VPC "vpc-anything": AWS failed!`)
   275  	c.Check(gateway, gc.IsNil)
   276  
   277  	s.stubAPI.CheckSingleInternetGatewaysCall(c, anyVPC)
   278  }
   279  
   280  func (s *vpcSuite) TestGetVPCInternetGatewayMultipleResults(c *gc.C) {
   281  	s.stubAPI.SetGatewaysResponse(3, anyState)
   282  
   283  	anyVPC := makeEC2VPC(anyVPCID, anyState)
   284  	gateway, err := getVPCInternetGateway(s.stubAPI, anyVPC)
   285  	c.Assert(err, gc.ErrorMatches, "expected 1 result from AWS, got 3")
   286  	c.Check(gateway, gc.IsNil)
   287  
   288  	s.stubAPI.CheckSingleInternetGatewaysCall(c, anyVPC)
   289  }
   290  
   291  func (s *vpcSuite) TestGetVPCInternetGatewaySuccess(c *gc.C) {
   292  	s.stubAPI.SetGatewaysResponse(1, anyState)
   293  
   294  	anyVPC := makeEC2VPC(anyVPCID, anyState)
   295  	gateway, err := getVPCInternetGateway(s.stubAPI, anyVPC)
   296  	c.Assert(err, jc.ErrorIsNil)
   297  	c.Check(gateway, jc.DeepEquals, &s.stubAPI.gatewaysResponse.InternetGateways[0])
   298  
   299  	s.stubAPI.CheckSingleInternetGatewaysCall(c, anyVPC)
   300  }
   301  
   302  func (s *vpcSuite) TestCheckInternetGatewayIsAvailable(c *gc.C) {
   303  	availableIGW := makeEC2InternetGateway(anyGatewayID, availableState)
   304  	c.Check(checkInternetGatewayIsAvailable(availableIGW), jc.ErrorIsNil)
   305  
   306  	pendingIGW := makeEC2InternetGateway(anyGatewayID, "pending")
   307  	err := checkInternetGatewayIsAvailable(pendingIGW)
   308  	c.Assert(err, gc.ErrorMatches, `VPC has Internet Gateway "igw-anything" in unexpected state "pending"`)
   309  	c.Check(err, jc.Satisfies, isVPCNotRecommendedError)
   310  }
   311  
   312  func (s *vpcSuite) TestGetVPCRouteTablesNoResults(c *gc.C) {
   313  	s.stubAPI.SetRouteTablesResponse() // no results
   314  
   315  	anyVPC := makeEC2VPC(anyVPCID, anyState)
   316  	tables, err := getVPCRouteTables(s.stubAPI, anyVPC)
   317  	c.Assert(err, gc.ErrorMatches, `VPC has no route tables`)
   318  	c.Check(err, jc.Satisfies, isVPCNotRecommendedError)
   319  	c.Check(tables, gc.IsNil)
   320  
   321  	s.stubAPI.CheckSingleRouteTablesCall(c, anyVPC)
   322  }
   323  
   324  func (s *vpcSuite) TestGetVPCRouteTablesUnexpectedAWSError(c *gc.C) {
   325  	s.stubAPI.SetErrors(errors.New("AWS failed!"))
   326  
   327  	anyVPC := makeEC2VPC(anyVPCID, anyState)
   328  	tables, err := getVPCRouteTables(s.stubAPI, anyVPC)
   329  	c.Assert(err, gc.ErrorMatches, `unexpected AWS response getting route tables of VPC "vpc-anything": AWS failed!`)
   330  	c.Check(tables, gc.IsNil)
   331  
   332  	s.stubAPI.CheckSingleRouteTablesCall(c, anyVPC)
   333  }
   334  
   335  func (s *vpcSuite) TestGetVPCRouteTablesSuccess(c *gc.C) {
   336  	givenVPC := makeEC2VPC("vpc-given", anyState)
   337  	givenVPC.CIDRBlock = "0.1.0.0/16"
   338  	givenGateway := makeEC2InternetGateway("igw-given", availableState)
   339  
   340  	s.stubAPI.SetRouteTablesResponse(
   341  		makeEC2RouteTable("rtb-other", notMainRouteTable, []string{"subnet-1", "subnet-2"}, nil),
   342  		makeEC2RouteTable("rtb-main", mainRouteTable, nil, makeEC2Routes(
   343  			givenGateway.Id, givenVPC.CIDRBlock, activeState, 3, // 3 extra routes
   344  		)),
   345  	)
   346  
   347  	tables, err := getVPCRouteTables(s.stubAPI, givenVPC)
   348  	c.Assert(err, jc.ErrorIsNil)
   349  	c.Check(tables, jc.DeepEquals, s.stubAPI.routeTablesResponse.Tables)
   350  
   351  	s.stubAPI.CheckSingleRouteTablesCall(c, givenVPC)
   352  }
   353  
   354  func (s *vpcSuite) TestFindVPCMainRouteTableWithMainAndPerSubnetTables(c *gc.C) {
   355  	givenTables := []ec2.RouteTable{
   356  		*makeEC2RouteTable("rtb-main", mainRouteTable, nil, nil),
   357  		*makeEC2RouteTable("rtb-2-subnets", notMainRouteTable, []string{"subnet-1", "subnet-2"}, nil),
   358  	}
   359  
   360  	mainTable, err := findVPCMainRouteTable(givenTables)
   361  	c.Assert(err, gc.ErrorMatches, `subnet "subnet-1" not associated with VPC "vpc-anything" main route table`)
   362  	c.Check(err, jc.Satisfies, isVPCNotRecommendedError)
   363  	c.Check(mainTable, gc.IsNil)
   364  }
   365  
   366  func (s *vpcSuite) TestFindVPCMainRouteTableWithOnlyNonAssociatedTables(c *gc.C) {
   367  	givenTables := []ec2.RouteTable{
   368  		*makeEC2RouteTable("rtb-1", notMainRouteTable, nil, nil),
   369  		*makeEC2RouteTable("rtb-2", notMainRouteTable, nil, nil),
   370  		*makeEC2RouteTable("rtb-3", notMainRouteTable, nil, nil),
   371  	}
   372  
   373  	mainTable, err := findVPCMainRouteTable(givenTables)
   374  	c.Assert(err, gc.ErrorMatches, "VPC has no associated main route table")
   375  	c.Check(err, jc.Satisfies, isVPCNotRecommendedError)
   376  	c.Check(mainTable, gc.IsNil)
   377  }
   378  
   379  func (s *vpcSuite) TestFindVPCMainRouteTableWithSingleMainTable(c *gc.C) {
   380  	givenTables := []ec2.RouteTable{
   381  		*makeEC2RouteTable("rtb-main", mainRouteTable, nil, nil),
   382  	}
   383  
   384  	mainTable, err := findVPCMainRouteTable(givenTables)
   385  	c.Assert(err, jc.ErrorIsNil)
   386  	c.Check(mainTable, jc.DeepEquals, &givenTables[0])
   387  }
   388  
   389  func (s *vpcSuite) TestFindVPCMainRouteTableWithExtraMainTables(c *gc.C) {
   390  	givenTables := []ec2.RouteTable{
   391  		*makeEC2RouteTable("rtb-non-associated", notMainRouteTable, nil, nil),
   392  		*makeEC2RouteTable("rtb-main", mainRouteTable, nil, nil),
   393  		*makeEC2RouteTable("rtb-main-extra", mainRouteTable, nil, nil),
   394  	}
   395  
   396  	mainTable, err := findVPCMainRouteTable(givenTables)
   397  	c.Assert(err, jc.ErrorIsNil)
   398  	c.Check(mainTable, jc.DeepEquals, &givenTables[1]) // first found counts
   399  }
   400  
   401  func (s *vpcSuite) TestCheckVPCRouteTableRoutesWithNoDefaultRoute(c *gc.C) {
   402  	vpc, table, gateway := prepareCheckVPCRouteTableRoutesArgs()
   403  	c.Check(table.Routes, gc.HasLen, 0) // no routes at all
   404  
   405  	checkFailed := func() {
   406  		err := checkVPCRouteTableRoutes(vpc, table, gateway)
   407  		c.Assert(err, gc.ErrorMatches, `missing default route via gateway "igw-anything"`)
   408  		c.Check(err, jc.Satisfies, isVPCNotRecommendedError)
   409  	}
   410  	checkFailed()
   411  
   412  	table.Routes = makeEC2Routes(gateway.Id, vpc.CIDRBlock, "blackhole", 3) // inactive routes only
   413  	checkFailed()
   414  
   415  	table.Routes = makeEC2Routes("", vpc.CIDRBlock, activeState, 1) // local and 1 extra route
   416  	checkFailed()
   417  
   418  	table.Routes = makeEC2Routes("", vpc.CIDRBlock, activeState, 0) // local route only
   419  	checkFailed()
   420  }
   421  
   422  func (s *vpcSuite) TestCheckVPCRouteTableRoutesWithDefaultButNoLocalRoutes(c *gc.C) {
   423  	vpc, table, gateway := prepareCheckVPCRouteTableRoutesArgs()
   424  	table.Routes = makeEC2Routes(gateway.Id, "", activeState, 3) // default and 3 extra routes; no local route
   425  
   426  	checkFailed := func() {
   427  		err := checkVPCRouteTableRoutes(vpc, table, gateway)
   428  		c.Assert(err, gc.ErrorMatches, `missing local route with destination "0.1.0.0/16"`)
   429  		c.Check(err, jc.Satisfies, isVPCNotRecommendedError)
   430  	}
   431  	checkFailed()
   432  
   433  	table.Routes = makeEC2Routes(gateway.Id, "", activeState, 0) // only default route
   434  	checkFailed()
   435  }
   436  
   437  func (s *vpcSuite) TestCheckVPCRouteTableRoutesSuccess(c *gc.C) {
   438  	vpc, table, gateway := prepareCheckVPCRouteTableRoutesArgs()
   439  	table.Routes = makeEC2Routes(gateway.Id, vpc.CIDRBlock, activeState, 3) // default, local and 3 extra routes
   440  
   441  	err := checkVPCRouteTableRoutes(vpc, table, gateway)
   442  	c.Assert(err, jc.ErrorIsNil)
   443  }
   444  
   445  func (s *vpcSuite) TestFindDefaultVPCIDUnexpectedAWSError(c *gc.C) {
   446  	s.stubAPI.SetErrors(errors.New("AWS failed!"))
   447  
   448  	vpcID, err := findDefaultVPCID(s.stubAPI)
   449  	c.Assert(err, gc.ErrorMatches, "unexpected AWS response getting default-vpc account attribute: AWS failed!")
   450  	c.Check(vpcID, gc.Equals, "")
   451  
   452  	s.stubAPI.CheckSingleAccountAttributesCall(c, "default-vpc")
   453  }
   454  
   455  func (s *vpcSuite) TestFindDefaultVPCIDNoAttributeOrNoValue(c *gc.C) {
   456  	s.stubAPI.SetAttributesResponse(nil) // no attributes at all
   457  
   458  	checkFailed := func() {
   459  		vpcID, err := findDefaultVPCID(s.stubAPI)
   460  		c.Assert(err, gc.ErrorMatches, "default-vpc account attribute not found")
   461  		c.Check(err, jc.Satisfies, errors.IsNotFound)
   462  		c.Check(vpcID, gc.Equals, "")
   463  
   464  		s.stubAPI.CheckSingleAccountAttributesCall(c, "default-vpc")
   465  	}
   466  	checkFailed()
   467  
   468  	s.stubAPI.SetAttributesResponse(map[string][]string{
   469  		"any-attribute": nil, // no values
   470  	})
   471  	checkFailed()
   472  
   473  	s.stubAPI.SetAttributesResponse(map[string][]string{
   474  		"not-default-vpc-attribute": []string{"foo", "bar"}, // wrong name
   475  	})
   476  	checkFailed()
   477  
   478  	s.stubAPI.SetAttributesResponse(map[string][]string{
   479  		"default-vpc": nil, // name ok, no values
   480  	})
   481  	checkFailed()
   482  
   483  	s.stubAPI.SetAttributesResponse(map[string][]string{
   484  		"default-vpc": []string{}, // name ok, empty values
   485  	})
   486  	checkFailed()
   487  }
   488  
   489  func (s *vpcSuite) TestFindDefaultVPCIDWithExplicitNoneValue(c *gc.C) {
   490  	s.stubAPI.SetAttributesResponse(map[string][]string{
   491  		"default-vpc": []string{"none"},
   492  	})
   493  
   494  	vpcID, err := findDefaultVPCID(s.stubAPI)
   495  	c.Assert(err, gc.ErrorMatches, "default VPC not found")
   496  	c.Check(err, jc.Satisfies, errors.IsNotFound)
   497  	c.Check(vpcID, gc.Equals, "")
   498  
   499  	s.stubAPI.CheckSingleAccountAttributesCall(c, "default-vpc")
   500  }
   501  
   502  func (s *vpcSuite) TestFindDefaultVPCIDSuccess(c *gc.C) {
   503  	s.stubAPI.SetAttributesResponse(map[string][]string{
   504  		"default-vpc": []string{"vpc-foo", "vpc-bar"},
   505  	})
   506  
   507  	vpcID, err := findDefaultVPCID(s.stubAPI)
   508  	c.Assert(err, jc.ErrorIsNil)
   509  	c.Check(vpcID, gc.Equals, "vpc-foo") // always the first value is used.
   510  
   511  	s.stubAPI.CheckSingleAccountAttributesCall(c, "default-vpc")
   512  }
   513  
   514  func (s *vpcSuite) TestGetVPCSubnetIDsForAvailabilityZoneWithSubnetsError(c *gc.C) {
   515  	s.stubAPI.SetErrors(errors.New("too cloudy"))
   516  
   517  	anyVPC := makeEC2VPC(anyVPCID, anyState)
   518  	subnetIDs, err := getVPCSubnetIDsForAvailabilityZone(s.stubAPI, anyVPC.Id, anyZone)
   519  	c.Assert(err, gc.ErrorMatches, `cannot get VPC "vpc-anything" subnets: unexpected AWS .*: too cloudy`)
   520  	c.Check(subnetIDs, gc.IsNil)
   521  
   522  	s.stubAPI.CheckSingleSubnetsCall(c, anyVPC)
   523  }
   524  
   525  func (s *vpcSuite) TestGetVPCSubnetIDsForAvailabilityZoneNoSubnetsAtAll(c *gc.C) {
   526  	s.stubAPI.SetSubnetsResponse(noResults, anyZone, noPublicIPOnLaunch)
   527  
   528  	anyVPC := makeEC2VPC(anyVPCID, anyState)
   529  	subnetIDs, err := getVPCSubnetIDsForAvailabilityZone(s.stubAPI, anyVPC.Id, anyZone)
   530  	c.Assert(err, gc.ErrorMatches, `VPC "vpc-anything" has no subnets in AZ "any-zone": no subnets found for VPC.*`)
   531  	c.Check(err, jc.Satisfies, errors.IsNotFound)
   532  	c.Check(subnetIDs, gc.IsNil)
   533  
   534  	s.stubAPI.CheckSingleSubnetsCall(c, anyVPC)
   535  }
   536  
   537  func (s *vpcSuite) TestGetVPCSubnetIDsForAvailabilityZoneNoSubnetsInAZ(c *gc.C) {
   538  	s.stubAPI.SetSubnetsResponse(3, "other-zone", noPublicIPOnLaunch)
   539  
   540  	anyVPC := makeEC2VPC(anyVPCID, anyState)
   541  	subnetIDs, err := getVPCSubnetIDsForAvailabilityZone(s.stubAPI, anyVPC.Id, "given-zone")
   542  	c.Assert(err, gc.ErrorMatches, `VPC "vpc-anything" has no subnets in AZ "given-zone"`)
   543  	c.Check(err, jc.Satisfies, errors.IsNotFound)
   544  	c.Check(subnetIDs, gc.IsNil)
   545  
   546  	s.stubAPI.CheckSingleSubnetsCall(c, anyVPC)
   547  }
   548  
   549  func (s *vpcSuite) TestGetVPCSubnetIDsForAvailabilityZoneSuccess(c *gc.C) {
   550  	s.stubAPI.SetSubnetsResponse(2, "my-zone", noPublicIPOnLaunch)
   551  
   552  	anyVPC := makeEC2VPC(anyVPCID, anyState)
   553  	subnetIDs, err := getVPCSubnetIDsForAvailabilityZone(s.stubAPI, anyVPC.Id, "my-zone")
   554  	c.Assert(err, jc.ErrorIsNil)
   555  	// Result slice of IDs is always sorted.
   556  	c.Check(subnetIDs, jc.DeepEquals, []string{"subnet-0", "subnet-1"})
   557  
   558  	s.stubAPI.CheckSingleSubnetsCall(c, anyVPC)
   559  }
   560  
   561  var fakeSubnetsToZones = map[network.Id][]string{
   562  	"subnet-foo": []string{"az1", "az2"},
   563  	"subnet-bar": []string{"az1"},
   564  	"subnet-oof": []string{"az3"},
   565  }
   566  
   567  func (s *vpcSuite) TestFindSubnetIDsForAvailabilityZoneNoneFound(c *gc.C) {
   568  	subnetIDs, err := findSubnetIDsForAvailabilityZone("unknown-zone", fakeSubnetsToZones)
   569  	c.Assert(err, gc.ErrorMatches, `subnets in AZ "unknown-zone" not found`)
   570  	c.Check(err, jc.Satisfies, errors.IsNotFound)
   571  	c.Check(subnetIDs, gc.IsNil)
   572  }
   573  
   574  func (s *vpcSuite) TestFindSubnetIDsForAvailabilityOneMatched(c *gc.C) {
   575  	subnetIDs, err := findSubnetIDsForAvailabilityZone("az3", fakeSubnetsToZones)
   576  	c.Assert(err, jc.ErrorIsNil)
   577  	c.Check(subnetIDs, gc.DeepEquals, []string{"subnet-oof"})
   578  }
   579  
   580  func (s *vpcSuite) TestFindSubnetIDsForAvailabilityMultipleMatched(c *gc.C) {
   581  	subnetIDs, err := findSubnetIDsForAvailabilityZone("az1", fakeSubnetsToZones)
   582  	c.Assert(err, jc.ErrorIsNil)
   583  	// Result slice of IDs is always sorted.
   584  	c.Check(subnetIDs, gc.DeepEquals, []string{"subnet-bar", "subnet-foo"})
   585  }
   586  
   587  const (
   588  	notDefaultVPC = false
   589  	defaultVPC    = true
   590  
   591  	notMainRouteTable = false
   592  	mainRouteTable    = true
   593  
   594  	noResults = 0
   595  
   596  	anyState     = "any state"
   597  	anyVPCID     = "vpc-anything"
   598  	anyGatewayID = "igw-anything"
   599  	anyTableID   = "rtb-anything"
   600  	anyZone      = "any-zone"
   601  
   602  	noPublicIPOnLaunch   = false
   603  	withPublicIPOnLaunch = true
   604  )
   605  
   606  type stubVPCAPIClient struct {
   607  	*testing.Stub
   608  	vpcAPIClient // embedded mostly for documentation
   609  
   610  	attributesResponse  *ec2.AccountAttributesResp
   611  	vpcsResponse        *ec2.VPCsResp
   612  	subnetsResponse     *ec2.SubnetsResp
   613  	gatewaysResponse    *ec2.InternetGatewaysResp
   614  	routeTablesResponse *ec2.RouteTablesResp
   615  }
   616  
   617  // AccountAttributes implements vpcAPIClient and is used to test finding the
   618  // default VPC from the "default-vpc"" attribute.
   619  func (s *stubVPCAPIClient) AccountAttributes(attributeNames ...string) (*ec2.AccountAttributesResp, error) {
   620  	s.Stub.AddCall("AccountAttributes", makeArgsFromStrings(attributeNames...)...)
   621  	return s.attributesResponse, s.Stub.NextErr()
   622  }
   623  
   624  // VPCs implements vpcAPIClient and is used to test getting the details of a
   625  // VPC.
   626  func (s *stubVPCAPIClient) VPCs(ids []string, filter *ec2.Filter) (*ec2.VPCsResp, error) {
   627  	s.Stub.AddCall("VPCs", ids, filter)
   628  	return s.vpcsResponse, s.Stub.NextErr()
   629  }
   630  
   631  // Subnets implements vpcAPIClient and is used to test getting a VPC's subnets.
   632  func (s *stubVPCAPIClient) Subnets(ids []string, filter *ec2.Filter) (*ec2.SubnetsResp, error) {
   633  	s.Stub.AddCall("Subnets", ids, filter)
   634  	return s.subnetsResponse, s.Stub.NextErr()
   635  }
   636  
   637  // InternetGateways implements vpcAPIClient and is used to test getting the
   638  // attached IGW of a VPC.
   639  func (s *stubVPCAPIClient) InternetGateways(ids []string, filter *ec2.Filter) (*ec2.InternetGatewaysResp, error) {
   640  	s.Stub.AddCall("InternetGateways", ids, filter)
   641  	return s.gatewaysResponse, s.Stub.NextErr()
   642  }
   643  
   644  // RouteTables implements vpcAPIClient and is used to test getting all route
   645  // tables of a VPC, alond with their routes.
   646  func (s *stubVPCAPIClient) RouteTables(ids []string, filter *ec2.Filter) (*ec2.RouteTablesResp, error) {
   647  	s.Stub.AddCall("RouteTables", ids, filter)
   648  	return s.routeTablesResponse, s.Stub.NextErr()
   649  }
   650  
   651  func (s *stubVPCAPIClient) SetAttributesResponse(attributeNameToValues map[string][]string) {
   652  	s.attributesResponse = &ec2.AccountAttributesResp{
   653  		RequestId:  "fake-request-id",
   654  		Attributes: make([]ec2.AccountAttribute, 0, len(attributeNameToValues)),
   655  	}
   656  
   657  	for name, values := range attributeNameToValues {
   658  		attribute := ec2.AccountAttribute{
   659  			Name:   name,
   660  			Values: values,
   661  		}
   662  		s.attributesResponse.Attributes = append(s.attributesResponse.Attributes, attribute)
   663  	}
   664  }
   665  func (s *stubVPCAPIClient) CheckSingleAccountAttributesCall(c *gc.C, attributeNames ...string) {
   666  	s.Stub.CheckCallNames(c, "AccountAttributes")
   667  	s.Stub.CheckCall(c, 0, "AccountAttributes", makeArgsFromStrings(attributeNames...)...)
   668  	s.Stub.ResetCalls()
   669  }
   670  
   671  func (s *stubVPCAPIClient) SetVPCsResponse(numResults int, state string, isDefault bool) {
   672  	s.vpcsResponse = &ec2.VPCsResp{
   673  		RequestId: "fake-request-id",
   674  		VPCs:      make([]ec2.VPC, numResults),
   675  	}
   676  
   677  	for i := range s.vpcsResponse.VPCs {
   678  		id := fmt.Sprintf("vpc-%d", i)
   679  		vpc := makeEC2VPC(id, state)
   680  		vpc.IsDefault = isDefault
   681  		s.vpcsResponse.VPCs[i] = *vpc
   682  	}
   683  }
   684  
   685  func (s *stubVPCAPIClient) CheckSingleVPCsCall(c *gc.C, vpcID string) {
   686  	var nilFilter *ec2.Filter
   687  	s.Stub.CheckCallNames(c, "VPCs")
   688  	s.Stub.CheckCall(c, 0, "VPCs", []string{vpcID}, nilFilter)
   689  	s.Stub.ResetCalls()
   690  }
   691  
   692  func (s *stubVPCAPIClient) SetSubnetsResponse(numResults int, zone string, mapPublicIpOnLaunch bool) {
   693  	s.subnetsResponse = &ec2.SubnetsResp{
   694  		RequestId: "fake-request-id",
   695  		Subnets:   make([]ec2.Subnet, numResults),
   696  	}
   697  
   698  	for i := range s.subnetsResponse.Subnets {
   699  		s.subnetsResponse.Subnets[i] = ec2.Subnet{
   700  			Id:                  fmt.Sprintf("subnet-%d", i),
   701  			VPCId:               anyVPCID,
   702  			State:               anyState,
   703  			AvailZone:           zone,
   704  			CIDRBlock:           fmt.Sprintf("0.1.%d.0/20", i),
   705  			MapPublicIPOnLaunch: mapPublicIpOnLaunch,
   706  		}
   707  	}
   708  }
   709  
   710  func (s *stubVPCAPIClient) CheckSingleSubnetsCall(c *gc.C, vpc *ec2.VPC) {
   711  	var nilIDs []string
   712  	filter := ec2.NewFilter()
   713  	filter.Add("vpc-id", vpc.Id)
   714  
   715  	s.Stub.CheckCallNames(c, "Subnets")
   716  	s.Stub.CheckCall(c, 0, "Subnets", nilIDs, filter)
   717  	s.Stub.ResetCalls()
   718  }
   719  
   720  func (s *stubVPCAPIClient) SetGatewaysResponse(numResults int, attachmentState string) {
   721  	s.gatewaysResponse = &ec2.InternetGatewaysResp{
   722  		RequestId:        "fake-request-id",
   723  		InternetGateways: make([]ec2.InternetGateway, numResults),
   724  	}
   725  
   726  	for i := range s.gatewaysResponse.InternetGateways {
   727  		id := fmt.Sprintf("igw-%d", i)
   728  		gateway := makeEC2InternetGateway(id, attachmentState)
   729  		s.gatewaysResponse.InternetGateways[i] = *gateway
   730  	}
   731  }
   732  
   733  func (s *stubVPCAPIClient) CheckSingleInternetGatewaysCall(c *gc.C, vpc *ec2.VPC) {
   734  	var nilIDs []string
   735  	filter := ec2.NewFilter()
   736  	filter.Add("attachment.vpc-id", vpc.Id)
   737  
   738  	s.Stub.CheckCallNames(c, "InternetGateways")
   739  	s.Stub.CheckCall(c, 0, "InternetGateways", nilIDs, filter)
   740  	s.Stub.ResetCalls()
   741  }
   742  
   743  func (s *stubVPCAPIClient) SetRouteTablesResponse(tables ...*ec2.RouteTable) {
   744  	s.routeTablesResponse = &ec2.RouteTablesResp{
   745  		RequestId: "fake-request-id",
   746  		Tables:    make([]ec2.RouteTable, len(tables)),
   747  	}
   748  
   749  	for i := range s.routeTablesResponse.Tables {
   750  		s.routeTablesResponse.Tables[i] = *tables[i]
   751  	}
   752  }
   753  
   754  func (s *stubVPCAPIClient) CheckSingleRouteTablesCall(c *gc.C, vpc *ec2.VPC) {
   755  	var nilIDs []string
   756  	filter := ec2.NewFilter()
   757  	filter.Add("vpc-id", vpc.Id)
   758  
   759  	s.Stub.CheckCallNames(c, "RouteTables")
   760  	s.Stub.CheckCall(c, 0, "RouteTables", nilIDs, filter)
   761  	s.Stub.ResetCalls()
   762  }
   763  
   764  func (s *stubVPCAPIClient) PrepareValidateVPCResponses() {
   765  	s.SetVPCsResponse(1, availableState, notDefaultVPC)
   766  	s.vpcsResponse.VPCs[0].CIDRBlock = "0.1.0.0/16"
   767  	s.SetSubnetsResponse(1, anyZone, withPublicIPOnLaunch)
   768  	s.SetGatewaysResponse(1, availableState)
   769  	onlyDefaultAndLocalRoutes := makeEC2Routes(
   770  		s.gatewaysResponse.InternetGateways[0].Id,
   771  		s.vpcsResponse.VPCs[0].CIDRBlock,
   772  		activeState,
   773  		0, // no extra routes
   774  	)
   775  	s.SetRouteTablesResponse(
   776  		makeEC2RouteTable(anyTableID, mainRouteTable, nil, onlyDefaultAndLocalRoutes),
   777  	)
   778  }
   779  
   780  func (s *stubVPCAPIClient) CallValidateVPCAndCheckCallsUpToExpectingVPCNotRecommendedError(c *gc.C, lastExpectedCallName string) {
   781  	err := validateVPC(s, anyVPCID)
   782  	c.Assert(err, jc.Satisfies, isVPCNotRecommendedError)
   783  
   784  	allCalls := []string{"VPCs", "Subnets", "InternetGateways", "RouteTables"}
   785  	var expectedCalls []string
   786  	for i := range allCalls {
   787  		expectedCalls = append(expectedCalls, allCalls[i])
   788  		if allCalls[i] == lastExpectedCallName {
   789  			break
   790  		}
   791  	}
   792  	s.CheckCallNames(c, expectedCalls...)
   793  }
   794  
   795  func makeEC2VPC(vpcID, state string) *ec2.VPC {
   796  	return &ec2.VPC{
   797  		Id:    vpcID,
   798  		State: state,
   799  	}
   800  }
   801  
   802  func makeEC2InternetGateway(gatewayID, attachmentState string) *ec2.InternetGateway {
   803  	return &ec2.InternetGateway{
   804  		Id:              gatewayID,
   805  		VPCId:           anyVPCID,
   806  		AttachmentState: attachmentState,
   807  	}
   808  }
   809  
   810  func makeEC2RouteTable(tableID string, isMain bool, associatedSubnetIDs []string, routes []ec2.Route) *ec2.RouteTable {
   811  	table := &ec2.RouteTable{
   812  		Id:     tableID,
   813  		VPCId:  anyVPCID,
   814  		Routes: routes,
   815  	}
   816  
   817  	if isMain {
   818  		table.Associations = []ec2.RouteTableAssociation{{
   819  			Id:      "rtbassoc-main",
   820  			TableId: tableID,
   821  			IsMain:  true,
   822  		}}
   823  	} else {
   824  		table.Associations = make([]ec2.RouteTableAssociation, len(associatedSubnetIDs))
   825  		for i := range associatedSubnetIDs {
   826  			table.Associations[i] = ec2.RouteTableAssociation{
   827  				Id:       fmt.Sprintf("rtbassoc-%d", i),
   828  				TableId:  tableID,
   829  				SubnetId: associatedSubnetIDs[i],
   830  			}
   831  		}
   832  	}
   833  	return table
   834  }
   835  
   836  func makeEC2Routes(defaultRouteGatewayID, localRouteCIDRBlock, state string, numExtraRoutes int) []ec2.Route {
   837  	var routes []ec2.Route
   838  
   839  	if defaultRouteGatewayID != "" {
   840  		routes = append(routes, ec2.Route{
   841  			DestinationCIDRBlock: defaultRouteCIDRBlock,
   842  			GatewayId:            defaultRouteGatewayID,
   843  			State:                state,
   844  		})
   845  	}
   846  
   847  	if localRouteCIDRBlock != "" {
   848  		routes = append(routes, ec2.Route{
   849  			DestinationCIDRBlock: localRouteCIDRBlock,
   850  			GatewayId:            localRouteGatewayID,
   851  			State:                state,
   852  		})
   853  	}
   854  
   855  	if numExtraRoutes > 0 {
   856  		for i := 0; i < numExtraRoutes; i++ {
   857  			routes = append(routes, ec2.Route{
   858  				DestinationCIDRBlock: fmt.Sprintf("0.1.%d.0/24", i),
   859  				State:                state,
   860  			})
   861  		}
   862  	}
   863  
   864  	return routes
   865  }
   866  
   867  func prepareCheckVPCRouteTableRoutesArgs() (*ec2.VPC, *ec2.RouteTable, *ec2.InternetGateway) {
   868  	anyVPC := makeEC2VPC(anyVPCID, anyState)
   869  	anyVPC.CIDRBlock = "0.1.0.0/16"
   870  	anyTable := makeEC2RouteTable(anyTableID, notMainRouteTable, nil, nil)
   871  	anyGateway := makeEC2InternetGateway(anyGatewayID, anyState)
   872  
   873  	return anyVPC, anyTable, anyGateway
   874  }
   875  
   876  func makeEC2Error(statusCode int, code, message, requestID string) error {
   877  	return &ec2.Error{
   878  		StatusCode: statusCode,
   879  		Code:       code,
   880  		Message:    message,
   881  		RequestId:  requestID,
   882  	}
   883  }
   884  
   885  func makeVPCNotFoundError(vpcID string) error {
   886  	return makeEC2Error(
   887  		400,
   888  		"InvalidVpcID.NotFound",
   889  		fmt.Sprintf("The vpc ID '%s' does not exist", vpcID),
   890  		"fake-request-id",
   891  	)
   892  }
   893  
   894  func makeArgsFromStrings(strings ...string) []interface{} {
   895  	args := make([]interface{}, len(strings))
   896  	for i := range strings {
   897  		args[i] = strings[i]
   898  	}
   899  	return args
   900  }