Skip to content

Commit

Permalink
chore(VpcV2): increasing test coverage for graduation
Browse files Browse the repository at this point in the history
  • Loading branch information
shikha372 committed Jan 23, 2025
1 parent d6e3c61 commit aec9218
Show file tree
Hide file tree
Showing 8 changed files with 443 additions and 13 deletions.
4 changes: 2 additions & 2 deletions packages/@aws-cdk/aws-ec2-alpha/lib/ipam.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { CfnIPAM, CfnIPAMPool, CfnIPAMPoolCidr, CfnIPAMScope } from 'aws-cdk-lib/aws-ec2';
import { Construct } from 'constructs';
import { Lazy, Names, Resource, Stack, Tags } from 'aws-cdk-lib';
import { Lazy, Names, Resource, Stack, Tags, Token } from 'aws-cdk-lib';

/**
* Represents the address family for IP addresses in an IPAM pool.
Expand Down Expand Up @@ -514,7 +514,7 @@ export class Ipam extends Resource {
if (props?.ipamName) {
Tags.of(this).add(NAME_TAG, props.ipamName);
}
if (!props?.operatingRegion && !Stack.of(this).region) {
if (!props?.operatingRegion && Token.isUnresolved(Stack.of(this).region)) {
throw new Error('Please provide at least one operating region');
}

Expand Down
12 changes: 6 additions & 6 deletions packages/@aws-cdk/aws-ec2-alpha/lib/route.ts
Original file line number Diff line number Diff line change
Expand Up @@ -608,12 +608,8 @@ export class RouteTargetType {
readonly endpoint?: IVpcEndpoint;

constructor(props: RouteTargetProps) {
if ((props.gateway && props.endpoint) || (!props.gateway && !props.endpoint)) {
throw new Error('Exactly one of `gateway` or `endpoint` must be specified.');
} else {
this.gateway = props.gateway;
this.endpoint = props.endpoint;
}
this.gateway = props.gateway;
this.endpoint = props.endpoint;
}
}

Expand Down Expand Up @@ -732,6 +728,10 @@ export class Route extends Resource implements IRouteV2 {
if (this.target.gateway?.routerType === RouterType.EGRESS_ONLY_INTERNET_GATEWAY && isDestinationIpv4) {
throw new Error('Egress only internet gateway does not support IPv4 routing');
}

if ((props.target.gateway && props.target.endpoint) || (!props.target.gateway && !props.target.endpoint)) {
throw new Error('Exactly one of `gateway` or `endpoint` must be specified.');
}
this.targetRouterType = this.target.gateway ? this.target.gateway.routerType : RouterType.VPC_ENDPOINT;
// Gateway generates route automatically via its RouteTable, thus we don't need to generate the resource for it
if (!(this.target.endpoint instanceof GatewayVpcEndpoint)) {
Expand Down
55 changes: 55 additions & 0 deletions packages/@aws-cdk/aws-ec2-alpha/test/ipam.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -155,4 +155,59 @@ describe('IPAM Test', () => {
);
});

test('IPAM throws error if awsService not provided with IPv6', () => {
// Create IPAM resources
const ipamRegion = new Ipam(stack, 'TestIpam', {
operatingRegion: ['us-west-2'],
});
const poolOptions: vpc.PoolOptions = {
addressFamily: AddressFamily.IP_V6,
publicIpSource: IpamPoolPublicIpSource.AMAZON,
locale: 'us-west-2',
};
expect(() => ipamRegion.publicScope.addPool('TestPool', poolOptions)).toThrow('awsService is required when addressFamily is set to ipv6');
});

test('IPAM throws error if operating region is not provided', () => {
const app = new cdk.App();
const stack_new = new cdk.Stack(app, 'TestStack', {});
expect(() => new Ipam(stack_new, 'TestIpam')).toThrow('Please provide at least one operating region');
});

test('IPAM does not throw error if stack region is not configured', () => {
const app = new cdk.App();
const stack_new = new cdk.Stack(app, 'TestStack');
new Ipam(stack_new, 'TestIpam', {
operatingRegion: ['us-west-2'],
});
Template.fromStack(stack_new).hasResource(
'AWS::EC2::IPAM', {},
);
});

test('IPAM does not throw error with operating region inferred from stack region', () => {
const app = new cdk.App();
const stack_new = new cdk.Stack(app, 'TestStack', {
env: {
region: 'us-west-2',
},
});
new Ipam(stack_new, 'TestIpam', {});
Template.fromStack(stack_new).hasResource(
'AWS::EC2::IPAM', {},
);
});

test('IPAM throws error if locale is not in operating regions', () => {
const ipamRegion = new Ipam(stack, 'TestIpam', {
operatingRegion: ['us-west-2'],
});
const poolOptions: vpc.PoolOptions = {
addressFamily: AddressFamily.IP_V6,
awsService: vpc.AwsServiceName.EC2,
publicIpSource: IpamPoolPublicIpSource.AMAZON,
locale: 'us-west-1',
};
expect(() => ipamRegion.publicScope.addPool('TestPool', poolOptions)).toThrow("The provided locale 'us-west-1' is not in the operating regions.");
});
});// End Test
111 changes: 107 additions & 4 deletions packages/@aws-cdk/aws-ec2-alpha/test/route.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ import * as vpc from '../lib/vpc-v2';
import * as subnet from '../lib/subnet-v2';
import { CfnEIP, GatewayVpcEndpoint, GatewayVpcEndpointAwsService, SubnetType, VpnConnectionType } from 'aws-cdk-lib/aws-ec2';
import * as route from '../lib/route';
import { Template } from 'aws-cdk-lib/assertions';
import { Match, Template } from 'aws-cdk-lib/assertions';

describe('EC2 Routing', () => {
let stack: cdk.Stack;
Expand Down Expand Up @@ -405,6 +405,32 @@ describe('EC2 Routing', () => {
});
});

test('Throws error if VPC and allocationID is not provided', () => {
expect(() => new route.NatGateway(stack, 'TestNATGW', {
subnet: mySubnet,
connectivityType: route.NatConnectivityType.PUBLIC,
maxDrainDuration: cdk.Duration.seconds(2001),
})).toThrow('Either provide vpc or allocationId');
});

test('does not create EIP or use allocationId for private NAT gateway', () => {
// WHEN
new route.NatGateway(stack, 'NGW', {
vpc: myVpc,
subnet: mySubnet,
connectivityType: route.NatConnectivityType.PRIVATE,
});

// THEN
Template.fromStack(stack).resourceCountIs('AWS::EC2::EIP', 0);
Template.fromStack(stack).hasResourceProperties('AWS::EC2::NatGateway', {
ConnectivityType: 'private',
});
Template.fromStack(stack).hasResourceProperties('AWS::EC2::NatGateway', Match.not({
AllocationId: Match.anyValue(),
}));
});

test('Route to DynamoDB Endpoint', () => {
const dynamodb = new GatewayVpcEndpoint(stack, 'TestDB', {
vpc: myVpc,
Expand Down Expand Up @@ -512,6 +538,40 @@ describe('EC2 Routing', () => {
},
});
});

test('Route throws error if no target is specified', () => {
expect(() => {
routeTable.addRoute('testRoute', '0.0.0.0', {});
}).toThrow('Exactly one of `gateway` or `endpoint` must be specified.');
});

test('Route throws error if both endpoint and gateway as target is specified', () => {
const eigw = new route.EgressOnlyInternetGateway(stack, 'TestEIGW', {
vpc: myVpc,
});

const dynamodb = new GatewayVpcEndpoint(stack, 'TestDB', {
vpc: myVpc,
service: GatewayVpcEndpointAwsService.DYNAMODB,
});
expect(() => {
routeTable.addRoute('testRoute', '::/0', {
gateway: eigw,
endpoint: dynamodb,
});
}).toThrow('Exactly one of `gateway` or `endpoint` must be specified.');
});

test('EIGW throws error for IPv4 routing', () => {
const eigw = new route.EgressOnlyInternetGateway(stack, 'TestEIGW', {
vpc: myVpc,
});
expect(() => {
routeTable.addRoute('testRoute', '0.0.0.0', {
gateway: eigw,
});
}).toThrow('Egress only internet gateway does not support IPv4 routing');
});
});

describe('VPCPeeringConnection', () => {
Expand All @@ -523,6 +583,7 @@ describe('VPCPeeringConnection', () => {
let vpcA: vpc.VpcV2;
let vpcB: vpc.VpcV2;
let vpcC: vpc.VpcV2;
let vpcD: vpc.VpcV2;

beforeEach(() => {
const app = new cdk.App({
Expand All @@ -545,7 +606,30 @@ describe('VPCPeeringConnection', () => {
vpcC = new vpc.VpcV2(stackC, 'VpcC', {
primaryAddressBlock: vpc.IpAddresses.ipv4('10.1.0.0/16'),
});
//Same Account VPC
vpcD = new vpc.VpcV2(stackC, 'VpcD', {
primaryAddressBlock: vpc.IpAddresses.ipv4('10.3.0.0/16'),
});

});

test('Creates a same account VPC peering connection', () => {
// Create VPC peering connection between vpcA and vpcB in same account
new route.VPCPeeringConnection(stackC, 'TestPeeringConnection', {
requestorVpc: vpcC,
acceptorVpc: vpcD,
});

const template = Template.fromStack(stackC);
template.hasResourceProperties('AWS::EC2::VPCPeeringConnection', {
VpcId: {
'Fn::GetAtt': ['VpcC211819BA', 'VpcId'],
},
PeerVpcId: {
'Fn::GetAtt': ['VpcD66D5BFD0', 'VpcId'], // Replace ... with actual ID suffix
},
PeerRegion: 'us-west-2',
});
});

test('Creates a cross account VPC peering connection', () => {
Expand Down Expand Up @@ -628,15 +712,34 @@ describe('VPCPeeringConnection', () => {
});

test('CIDR block overlap with primary CIDR block should throw error', () => {
const vpcD = new vpc.VpcV2(stackA, 'VpcD', {
const testVpc = new vpc.VpcV2(stackA, 'TestVpc', {
primaryAddressBlock: vpc.IpAddresses.ipv4('10.0.0.0/16'),
});

expect(() => {
new route.VPCPeeringConnection(stackA, 'TestPeering', {
requestorVpc: vpcA,
acceptorVpc: vpcD,
acceptorVpc: testVpc,
});
}).toThrow(/CIDR block should not overlap with each other for establishing a peering connection/);
});

test('Can create route for VPC peering connection as a target', () => {
const peering = new route.VPCPeeringConnection(stackC, 'TestPeering', {
requestorVpc: vpcC,
acceptorVpc: vpcD,
});

const routeTable = new route.RouteTable(stackC, 'TestRouteTable', {
vpc: vpcD,
});

routeTable.addRoute('TestRoute', '172.16.0.0/16', {
gateway: peering,
});
const template = Template.fromStack(stackC);
template.hasResourceProperties('AWS::EC2::Route', {
VpcPeeringConnectionId: { 'Fn::GetAtt': ['TestPeeringVPCPeeringConnection0E2D1596', 'Id'] },
RouteTableId: { 'Fn::GetAtt': ['TestRouteTableC34C2E1C', 'RouteTableId'] },
});
});
});
58 changes: 58 additions & 0 deletions packages/@aws-cdk/aws-ec2-alpha/test/subnet-v2.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -322,4 +322,62 @@ describe('Subnet V2 with custom IP and routing', () => {

expect(Template.fromStack(stack).hasResource('AWS::EC2::SubnetNetworkAclAssociation', {}));
});

test('Subnet Creation throws error if assignIPv6 set to true with no IPv6 CIDR', () => {
const testVpc = new vpc.VpcV2(stack, 'TestVPC', {
primaryAddressBlock: vpc.IpAddresses.ipv4('10.1.0.0/16'),
secondaryAddressBlocks: [vpc.IpAddresses.amazonProvidedIpv6(
{ cidrBlockName: 'SecondaryAddress' })],
});

const subnetConfig = {
vpcV2: testVpc,
availabilityZone: 'us-east-1a',
cidrBlock: new subnet.IpCidr('10.1.0.0/24'),
subnetType: SubnetType.PUBLIC,
assignIpv6AddressOnCreation: true,
};
expect(() => createTestSubnet(stack, subnetConfig)).toThrow('IPv6 CIDR block is required when assigning IPv6 address on creation');
});

test('Subnet Creation does not throw error if assignIPv6 set to true with no IPv6 CIDR', () => {
const testVpc = new vpc.VpcV2(stack, 'TestVPC', {
primaryAddressBlock: vpc.IpAddresses.ipv4('10.1.0.0/16'),
secondaryAddressBlocks: [vpc.IpAddresses.amazonProvidedIpv6(
{ cidrBlockName: 'SecondaryAddress' })],
});

const subnetConfig = {
vpcV2: testVpc,
availabilityZone: 'us-east-1a',
cidrBlock: new subnet.IpCidr('10.1.0.0/24'),
ipv6Cidr: new subnet.IpCidr('2001:db8:1::/64'),
subnetType: SubnetType.PUBLIC,
assignIpv6AddressOnCreation: true,
};
createTestSubnet(stack, subnetConfig);
Template.fromStack(stack).hasResourceProperties('AWS::EC2::Subnet', {
AssignIpv6AddressOnCreation: true,
Ipv6CidrBlock: '2001:db8:1::/64',
});
});

test('Subnet creates custom route Table if not provided', () => {
const testVpc = new vpc.VpcV2(stack, 'TestVPC', {
primaryAddressBlock: vpc.IpAddresses.ipv4('10.1.0.0/16'),
});
const subnetConfig = {
vpcV2: testVpc,
availabilityZone: 'us-east-1a',
cidrBlock: new subnet.IpCidr('10.1.0.0/24'),
subnetType: SubnetType.PUBLIC,
};
createTestSubnet(stack, subnetConfig);
Template.fromStack(stack).hasResourceProperties('AWS::EC2::SubnetRouteTableAssociation', {
SubnetId: {
Ref: 'TestSubnet2A4BE4CA',
},
RouteTableId: { 'Fn::GetAtt': ['TestSubnetRouteTable5AF4379E', 'RouteTableId'] },
});
});
});
27 changes: 26 additions & 1 deletion packages/@aws-cdk/aws-ec2-alpha/test/util.test.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { CidrBlock, CidrBlockIpv6 } from '../lib/util';
import { CidrBlock, CidrBlockIpv6, NetworkUtils } from '../lib/util';

describe('Tests for the CidrBlock.rangesOverlap method to check if IPv4 ranges overlap', () =>{
test('Should return false for non-overlapping IP ranges', () => {
Expand Down Expand Up @@ -45,4 +45,29 @@ describe('Tests for the CidrBlock.rangesOverlap method to check if IPv4 ranges o
const testCidr = new CidrBlockIpv6('2001:db8::/32');
expect(testCidr.rangesOverlap('2001:db8::1/64', '2001:db8::1/60')).toBe(true);
});

test('valid IP addresses return true', () => {
const validIps = [
'192.168.1.1',
'10.0.0.0',
'172.16.254.1',
'0.0.0.0',
'255.255.255.255',
];

validIps.forEach(ip => {
expect(NetworkUtils.validIp(ip)).toBe(true);
});
});

test('invalid IP addresses return false', () => {
const invalidIps = [
'256.1.2.3', // octet > 255
'1.2.3.256', // octet > 255
'1.2.3.4.5',
];
invalidIps.forEach(ip => {
expect(NetworkUtils.validIp(ip)).toBe(false);
}); // too many octets
});
});
Loading

0 comments on commit aec9218

Please sign in to comment.