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