Skip to content

Commit

Permalink
chore(vpcv2): increasing test coverage for graduation
Browse files Browse the repository at this point in the history
fixing tests
  • Loading branch information
shikha372 committed Jan 25, 2025
1 parent d6e3c61 commit e7125f3
Show file tree
Hide file tree
Showing 13 changed files with 662 additions and 31 deletions.
38 changes: 38 additions & 0 deletions packages/@aws-cdk/aws-ec2-alpha/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,7 @@ new VpcV2(this, 'Vpc', {

`SubnetV2` is a re-write of the [`ec2.Subnet`](https://docs.aws.amazon.com/cdk/api/v2/docs/aws-cdk-lib.aws_ec2.Subnet.html) construct.
This new construct can be used to add subnets to a `VpcV2` instance:
Note: When defining a subnet with `SubnetV2`, CDK automatically creates a new route table, unless a route table is explicitly provided as an input to the construct.

```ts

Expand All @@ -66,6 +67,14 @@ By default `VpcV2` uses `10.0.0.0/16` as the primary CIDR if none is defined.
Additional CIDRs can be adding to the VPC via the `secondaryAddressBlocks` prop.
The following example illustrates the different options of defining the address blocks:

Note: There’s currently an issue with IPAM pool deletion that may affect the `cdk --destroy` command. This is because IPAM takes time to detect when the IP address pool has been deallocated after the VPC is deleted. The current workaround is to wait until the IP address is fully deallocated from the pool before retrying the deletion. Below command can be used to check allocations for a pool using CLI

```shell
aws ec2 get-ipam-pool-allocations --ipam-pool-id <ipam-pool-id>
```

Ref: https://docs.aws.amazon.com/cli/latest/reference/ec2/get-ipam-pool-allocations.html

```ts

const stack = new Stack();
Expand Down Expand Up @@ -527,6 +536,7 @@ For more information, see [Enable VPC internet access using internet gateways](h

You can add an internet gateway to a VPC using `addInternetGateway` method. By default, this method creates a route in all Public Subnets with outbound destination set to `0.0.0.0` for IPv4 and `::0` for IPv6 enabled VPC.
Instead of using the default settings, you can configure a custom destination range by providing an optional input `destination` to the method.
In addition to the custom IP range, you can also choose to filter subnets where default routes should be created.

The code example below shows how to add an internet gateway with a custom outbound destination IP range:

Expand All @@ -546,6 +556,34 @@ myVpc.addInternetGateway({
});
```

The following code examples demonstrates how to add an internet gateway with a custom outbound destination IP range for specific subnets:

```ts
const stack = new Stack();
const myVpc = new VpcV2(this, 'Vpc');

const mySubnet = new SubnetV2(this, 'Subnet', {
vpc: myVpc,
availabilityZone: 'eu-west-2a',
ipv4CidrBlock: new IpCidr('10.0.0.0/24'),
subnetType: SubnetType.PUBLIC });

myVpc.addInternetGateway({
ipv4Destination: '192.168.0.0/16',
subnets: [mySubnet],
});
```

```ts
const stack = new Stack();
const myVpc = new VpcV2(this, 'Vpc');

myVpc.addInternetGateway({
ipv4Destination: '192.168.0.0/16',
subnets: [{subnetType: SubnetType.PRIVATE_WITH_EGRESS}],
});
```

## Importing an existing VPC

You can import an existing VPC and its subnets using the `VpcV2.fromVpcV2Attributes()` method or an individual subnet using `SubnetV2.fromSubnetV2Attributes()` method.
Expand Down
6 changes: 0 additions & 6 deletions packages/@aws-cdk/aws-ec2-alpha/jest.config.js
Original file line number Diff line number Diff line change
@@ -1,10 +1,4 @@
const baseConfig = require('@aws-cdk/cdk-build-tools/config/jest.config');
module.exports = {
...baseConfig,
coverageThreshold: {
global: {
statements: 75,
branches: 63,
},
},
};;
2 changes: 1 addition & 1 deletion packages/@aws-cdk/aws-ec2-alpha/lib/ipam.ts
Original file line number Diff line number Diff line change
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 && (props.operatingRegion.length === 0)) {
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
38 changes: 30 additions & 8 deletions packages/@aws-cdk/aws-ec2-alpha/lib/vpc-v2-base.ts
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,13 @@ export interface InternetGatewayOptions{
* @default - provisioned without a resource name
*/
readonly internetGatewayName?: string;

/**
* List of subnets where route to IGW will be added
*
* @default - route created for all subnets with Type `SubnetType.Public`
*/
readonly subnets?: SubnetSelection[];
}

/**
Expand Down Expand Up @@ -438,9 +445,14 @@ export abstract class VpcV2Base extends Resource implements IVpcV2 {
};

if (options?.subnets) {
// Use Set to ensure unique subnets
const processedSubnets = new Set<string>();
const subnets = flatten(options.subnets.map(s => this.selectSubnets(s).subnets));
subnets.forEach((subnet) => {
this.createEgressRoute(subnet, egw, options.destination);
if (!processedSubnets.has(subnet.node.id)) {
this.createEgressRoute(subnet, egw, options.destination);
processedSubnets.add(subnet.node.id);
}
});
}
}
Expand Down Expand Up @@ -477,9 +489,23 @@ export abstract class VpcV2Base extends Resource implements IVpcV2 {
this._internetConnectivityEstablished.add(igw);
this._internetGatewayId = igw.routerTargetId;

// If there are no public subnets defined, no default route will be added
if (this.publicSubnets) {
this.publicSubnets.forEach( (s) => this.addDefaultInternetRoute(s, igw, options));
// Add routes for subnets defined as an input
if (options?.subnets) {
// Use Set to ensure unique subnets
const processedSubnets = new Set<string>();
const subnets = flatten(options.subnets.map(s => this.selectSubnets(s).subnets));
subnets.forEach((subnet) => {
if (!processedSubnets.has(subnet.node.id)) {
if (!this.publicSubnets.includes(subnet)) {
Annotations.of(this).addWarningV2('InternetGatewayWarning',
`Subnet ${subnet.node.id} is not a public subnet. Internet Gateway should be added only to public subnets.`);
}
this.addDefaultInternetRoute(subnet, igw, options);
processedSubnets.add(subnet.node.id);
};
}); // If there are no input subnets defined, default route will be added to all public subnets
} else if (!options?.subnets && this.publicSubnets) {
this.publicSubnets.forEach((publicSubnets) => this.addDefaultInternetRoute(publicSubnets, igw, options));
}
}

Expand All @@ -489,10 +515,6 @@ export abstract class VpcV2Base extends Resource implements IVpcV2 {
*/
private addDefaultInternetRoute(subnet: ISubnetV2, igw: InternetGateway, options?: InternetGatewayOptions): void {

if (subnet.subnetType !== SubnetType.PUBLIC) {
throw new Error('No public subnets defined to add route for internet gateway');
}

// Add default route to IGW for IPv6
if (subnet.ipv6CidrBlock) {
new Route(this, `${subnet.node.id}-DefaultIPv6Route`, {
Expand Down
105 changes: 105 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,109 @@ describe('IPAM Test', () => {
);
});

test('IPAM throws error if awsService is not provided for IPv6 address', () => {
// 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 provided as an empty array', () => {
const app = new cdk.App();
const stack_new = new cdk.Stack(app, 'TestStack');
expect(() => new Ipam(stack_new, 'TestIpam', {
operatingRegion: [],
})).toThrow('Please provide at least one operating region');
});

test('IPAM infers region from provided operating region correctly', () => {
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).hasResourceProperties(
'AWS::EC2::IPAM', {
OperatingRegions: [
{
RegionName: 'us-west-2',
},
],
},
);
});

test('IPAM infers region from stack if not provided under IPAM class object', () => {
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).hasResourceProperties(
'AWS::EC2::IPAM', {
OperatingRegions: [
{
RegionName: 'us-west-2',
},
],
},
);
});

test('IPAM refers to stack region token', () => {
const app = new cdk.App();
const stack_new = new cdk.Stack(app, 'TestStack');
new Ipam(stack_new, 'TestIpam', {});
Template.fromStack(stack_new).hasResourceProperties(
'AWS::EC2::IPAM', {
OperatingRegions: [
{
RegionName: {
Ref: 'AWS::Region',
},
},
],
},
);
});

test('IPAM throws error if locale(region) of pool is not one of the 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', // Incorrect Region
};
expect(() => ipamRegion.publicScope.addPool('TestPool', poolOptions)).toThrow("The provided locale 'us-west-1' is not in the operating regions.");
});

test('IPAM handles operating regions correctly', () => {
const new_app = new cdk.App();
const testStack = new cdk.Stack(new_app, 'TestStack', {
env: {
region: 'us-west-1',
},
});
new Ipam(testStack, 'TestIpamNew', {});
Template.fromStack(testStack).hasResourceProperties(
'AWS::EC2::IPAM', {
OperatingRegions: [
{
RegionName: 'us-west-1',
},
],
},
);
});
});// End Test
Loading

0 comments on commit e7125f3

Please sign in to comment.