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 }