From 48c262f552a2884b6f1130469768879779c541bf Mon Sep 17 00:00:00 2001 From: Hsing-Hui Hsu Date: Thu, 28 Feb 2019 15:39:50 -0800 Subject: [PATCH] Add different Instance Types --- .../lib/alias-target-instance.ts | 67 ++++ .../lib/cname-instance.ts | 65 ++++ .../aws-servicediscovery/lib/index.ts | 4 + .../aws-servicediscovery/lib/instance.ts | 192 +--------- .../aws-servicediscovery/lib/ip-instance.ts | 108 ++++++ .../lib/non-ip-instance.ts | 47 +++ .../aws-servicediscovery/lib/service.ts | 87 ++++- .../aws-servicediscovery/package.json | 7 +- .../aws-servicediscovery/test/cdk.json | 10 + ....service-with-http-namespace.expected.json | 26 ++ ...e-with-private-dns-namespace.expected.json | 294 +++++++++++++++ ...nteg.service-with-private-dns-namespace.ts | 15 +- .../test/test.instance.ts | 356 +++++++++++------- .../aws-servicediscovery/test/test.service.ts | 125 +++++- 14 files changed, 1076 insertions(+), 327 deletions(-) create mode 100644 packages/@aws-cdk/aws-servicediscovery/lib/alias-target-instance.ts create mode 100644 packages/@aws-cdk/aws-servicediscovery/lib/cname-instance.ts create mode 100644 packages/@aws-cdk/aws-servicediscovery/lib/ip-instance.ts create mode 100644 packages/@aws-cdk/aws-servicediscovery/lib/non-ip-instance.ts create mode 100644 packages/@aws-cdk/aws-servicediscovery/test/cdk.json create mode 100644 packages/@aws-cdk/aws-servicediscovery/test/integ.service-with-http-namespace.expected.json create mode 100644 packages/@aws-cdk/aws-servicediscovery/test/integ.service-with-private-dns-namespace.expected.json diff --git a/packages/@aws-cdk/aws-servicediscovery/lib/alias-target-instance.ts b/packages/@aws-cdk/aws-servicediscovery/lib/alias-target-instance.ts new file mode 100644 index 0000000000000..dca8335522ed6 --- /dev/null +++ b/packages/@aws-cdk/aws-servicediscovery/lib/alias-target-instance.ts @@ -0,0 +1,67 @@ +import cdk = require('@aws-cdk/cdk'); +import { BaseInstanceProps, InstanceBase } from './instance'; +import { NamespaceType } from './namespace'; +import { DnsRecordType, IService, RoutingPolicy } from './service'; +import { CfnInstance } from './servicediscovery.generated'; + +export interface AliasTargetInstanceProps extends BaseInstanceProps { + /** + * DNS name of the target + */ + dnsName: string; + + /** + * The Cloudmap service this resource is registered to. + */ + service: IService; +} + +export class AliasTargetInstance extends InstanceBase { + /** + * The Id of the instance + */ + public readonly instanceId: string; + + /** + * The Cloudmap service to which the instance is registered. + */ + public readonly service: IService; + + /** + * The Route53 DNS name of the alias target + */ + public readonly dnsName: string; + + constructor(scope: cdk.Construct, id: string, props: AliasTargetInstanceProps) { + super(scope, id); + + if (props.service.namespace.type === NamespaceType.Http) { + throw new Error('Namespace associated with Service must be a DNS Namespace.'); + } + + // Should already be enforced when creating service, but validates if service is not instantiated with #createService + const dnsRecordType = props.service.dnsRecordType; + if (dnsRecordType !== DnsRecordType.A + && dnsRecordType !== DnsRecordType.AAAA + && dnsRecordType !== DnsRecordType.A_AAAA) { + throw new Error('Service must use `A` or `AAAA` records to register an AliasRecordTarget.'); + } + + if (props.service.routingPolicy !== RoutingPolicy.Weighted) { + throw new Error('Service must use `WEIGHTED` routing policy.'); + } + + const resource = new CfnInstance(this, 'Resource', { + instanceAttributes: { + AWS_ALIAS_DNS_NAME: props.dnsName, + ...props.customAttributes + }, + instanceId: props.instanceId, + serviceId: props.service.serviceId + }); + + this.service = props.service; + this.instanceId = resource.instanceId; + this.dnsName = props.dnsName; + } +} diff --git a/packages/@aws-cdk/aws-servicediscovery/lib/cname-instance.ts b/packages/@aws-cdk/aws-servicediscovery/lib/cname-instance.ts new file mode 100644 index 0000000000000..a10d30b10dcea --- /dev/null +++ b/packages/@aws-cdk/aws-servicediscovery/lib/cname-instance.ts @@ -0,0 +1,65 @@ +import cdk = require('@aws-cdk/cdk'); +import { BaseInstanceProps, InstanceBase } from './instance'; +import { NamespaceType } from './namespace'; +import { DnsRecordType, IService } from './service'; +import { CfnInstance } from './servicediscovery.generated'; + +export interface CnameInstanceBaseProps extends BaseInstanceProps { + /** + * If the service configuration includes a CNAME record, the domain name that you want Route 53 to return in + * response to DNS queries, for example, example.com. This value is required if the service specified by ServiceId + * includes settings for an CNAME record. + */ + instanceCname: string; +} + +export interface CnameInstanceProps extends CnameInstanceBaseProps { + /** + * The Cloudmap service this resource is registered to. + */ + service: IService; +} + +export class CnameInstance extends InstanceBase { + /** + * The Id of the instance + */ + public readonly instanceId: string; + + /** + * The Cloudmap service to which the instance is registered. + */ + public readonly service: IService; + + /** + * The domain name returned by DNS queries for the instance + */ + public readonly instanceCname: string; + + constructor(scope: cdk.Construct, id: string, props: CnameInstanceProps) { + super(scope, id); + + if (props.service.namespace.type === NamespaceType.Http) { + throw new Error('Namespace associated with Service must be a DNS Namespace.'); + } + + if (props.service.dnsRecordType === DnsRecordType.Cname) { + if (!props.instanceCname) { + throw new Error('A `instanceCname` must be specified for a service using a `CNAME` record.'); + } + } + + const resource = new CfnInstance(this, 'Resource', { + instanceId: props.instanceId, + serviceId: props.service.serviceId, + instanceAttributes: { + AWS_INSTANCE_CNAME: props.instanceCname, + ...props.customAttributes + } + }); + + this.service = props.service; + this.instanceId = resource.instanceId; + this.instanceCname = props.instanceCname; + } +} diff --git a/packages/@aws-cdk/aws-servicediscovery/lib/index.ts b/packages/@aws-cdk/aws-servicediscovery/lib/index.ts index d584777e43d23..17cb4a95e5363 100644 --- a/packages/@aws-cdk/aws-servicediscovery/lib/index.ts +++ b/packages/@aws-cdk/aws-servicediscovery/lib/index.ts @@ -1,4 +1,8 @@ export * from './instance'; +export * from './alias-target-instance'; +export * from './cname-instance'; +export * from './ip-instance'; +export * from './non-ip-instance'; export * from './namespace'; export * from './http-namespace'; export * from './private-dns-namespace'; diff --git a/packages/@aws-cdk/aws-servicediscovery/lib/instance.ts b/packages/@aws-cdk/aws-servicediscovery/lib/instance.ts index 73a721cac1623..28bb8b21305b5 100644 --- a/packages/@aws-cdk/aws-servicediscovery/lib/instance.ts +++ b/packages/@aws-cdk/aws-servicediscovery/lib/instance.ts @@ -1,14 +1,7 @@ import cdk = require('@aws-cdk/cdk'); -import { NamespaceType } from './namespace'; -import { DnsRecordType, IService } from './service'; -import { CfnInstance } from './servicediscovery.generated'; +import { IService } from './service'; export interface IInstance extends cdk.IConstruct { - /** - * The instance attributes. - */ - readonly instanceAttributes: InstanceAttributes; - /** * The id of the instance resource */ @@ -21,191 +14,42 @@ export interface IInstance extends cdk.IConstruct { } /** - * Properties to define ServiceDiscovery Instance + * Used when the resource that's associated with the service instance is accessible using values other than an IP + * address or a domain name (CNAME), i.e. for non-ip-instances */ -export interface InstanceProps { - /** - * The instance attributes. - */ - instanceAttributes: InstanceAttributes; - +export interface BaseInstanceProps { /** * The id of the instance resource */ instanceId: string; /** - * The Cloudmap service this resource is registered to. + * Custom attributes of the instance. + * + * @default none */ - service: IService; + customAttributes?: object; + } /** - * Define a Service Discovery Instance + * Properties to define ServiceDiscovery Instance */ -export class Instance extends cdk.Construct implements IInstance { - /** - * The Id of the instance - */ - public readonly instanceId: string; +export interface InstanceProps extends BaseInstanceProps { /** - * The Cloudmap service to which the instance is registered. - */ - public readonly service: IService; - - /** - * The instance attributes. - * FIXME break out into different instance types + * The Cloudmap service this resource is registered to. */ - public readonly instanceAttributes: InstanceAttributes; - - constructor(scope: cdk.Construct, id: string, props: InstanceProps) { - super(scope, id); - - const resource = new CfnInstance(this, 'Resource', { - instanceAttributes: renderInstanceAttributes(props), - instanceId: props.instanceId, - serviceId: props.service.serviceId - }); - - this.service = props.service; - this.instanceId = resource.instanceId; - } + service: IService; } -// NOTE: These are the 5 that seem to be supported in cloudformation, but the API docs indicate that you can also -// specify custom attributes. Not sure if CFN would support these? In the generated L1s instance attributes appears to -// just be an object. -export interface InstanceAttributes { +export abstract class InstanceBase extends cdk.Construct implements IInstance { /** - * If you want AWS Cloud Map to create an Amazon Route 53 alias record that routes traffic to an Elastic Load - * Balancing load balancer, specify the DNS name that is associated with the load balancer. - * - * @default none - */ - aliasDnsName?: string; - - /** - * If the service configuration includes a CNAME record, the domain name that you want Route 53 to return in - * response to DNS queries, for example, example.com. This value is required if the service specified by ServiceId - * includes settings for an CNAME record. - * - * @default none - */ - instanceCname?: string; - - /** - * The port on the endpoint that you want AWS Cloud Map to perform health checks on. This value is also used for - * the port value in an SRV record if the service that you specify includes an SRV record. You can also specify a - * default port that is applied to all instances in the Service configuration. - * - * @default none - */ - port?: string; - - /** - * If the service that you specify contains a template for an A record, the IPv4 address that you want AWS Cloud - * Map to use for the value of the A record. - * - * @default none - */ - ipv4?: string; - - /** - * If the service that you specify contains a template for an AAAA record, the IPv6 address that you want AWS Cloud - * Map to use for the value of the AAAA record. - * - * @default none + * The Id of the instance */ - ipv6?: string; + public abstract readonly instanceId: string; /** - * Custom attributes of the instance. - * - * @default none + * The Cloudmap service to which the instance is registered. */ - customAttributes?: object; -} - -/** - * Validates instance attributes and returns standard attributes based on the namespace/service type. - * - * @param props instance props - * @throws if the instance attributes are invalid - */ -function renderInstanceAttributes(props: InstanceProps): object { - - const customAttributes = props.instanceAttributes.customAttributes || {}; - - if (props.instanceAttributes.aliasDnsName && props.instanceAttributes.instanceCname) { - throw new Error('Cannot specify both `aliasDnsName` and `instanceCname`.'); - } - - if (props.service.namespace.type === NamespaceType.Http) { - if (props.instanceAttributes.aliasDnsName || props.instanceAttributes.instanceCname) { - throw new Error('Cannot specify `aliasDnsName` or `instanceCname` for an HTTP namespace.'); - } - - return { - AWS_INSTANCE_IPV4: props.instanceAttributes.ipv4, - AWS_INSTANCE_IPV6: props.instanceAttributes.ipv6, - AWS_INSTANCE_PORT: props.instanceAttributes.port, - ...customAttributes - }; - } - - if (props.service.dnsRecordType === DnsRecordType.Cname) { - if (!props.instanceAttributes.instanceCname) { - throw new Error('A `instanceCname` must be specified for a service using a `CNAME` record.'); - } - - return { - AWS_INSTANCE_CNAME: props.instanceAttributes.instanceCname, - ...customAttributes - }; - } - - if (props.service.dnsRecordType === DnsRecordType.Srv) { - if (!props.instanceAttributes.port) { - throw new Error('A `port` must be specified for a service using a `SRV` record.'); - } - - if (!props.instanceAttributes.ipv4 && !props.instanceAttributes.ipv6) { - throw new Error('At least `ipv4` or `ipv6` must be specified for a service using a `SRV` record.'); - } - - return { - AWS_INSTANCE_IPV4: props.instanceAttributes.ipv4, - AWS_INSTANCE_IPV6: props.instanceAttributes.ipv6, - AWS_INSTANCE_PORT: props.instanceAttributes.port, - ...customAttributes - }; - } - - if (props.instanceAttributes.aliasDnsName) { - if (props.instanceAttributes.ipv4 || props.instanceAttributes.ipv6 || props.instanceAttributes.port) { - throw new Error('Cannot specify `ipv4`, `ipv6` or `port` when specifying `aliasDnsName`.'); - } - - return { - AWS_ALIAS_DNS_NAME: props.instanceAttributes.aliasDnsName, - ...customAttributes - }; - } - - if (!props.instanceAttributes.ipv4 && (props.service.dnsRecordType === DnsRecordType.A || props.service.dnsRecordType === DnsRecordType.A_AAAA)) { - throw new Error('An `ipv4` must be specified for a service using a `A` record.'); - } - - if (!props.instanceAttributes.ipv6 && - (props.service.dnsRecordType === DnsRecordType.AAAA || props.service.dnsRecordType === DnsRecordType.A_AAAA)) { - throw new Error('An `ipv6` must be specified for a service using a `AAAA` record.'); - } - - return { - AWS_INSTANCE_IPV4: props.instanceAttributes.ipv4, - AWS_INSTANCE_IPV6: props.instanceAttributes.ipv6, - AWS_INSTANCE_PORT: props.instanceAttributes.port, - ...customAttributes - }; + public abstract readonly service: IService; } diff --git a/packages/@aws-cdk/aws-servicediscovery/lib/ip-instance.ts b/packages/@aws-cdk/aws-servicediscovery/lib/ip-instance.ts new file mode 100644 index 0000000000000..c27f1742665e1 --- /dev/null +++ b/packages/@aws-cdk/aws-servicediscovery/lib/ip-instance.ts @@ -0,0 +1,108 @@ +import cdk = require('@aws-cdk/cdk'); +import { BaseInstanceProps, InstanceBase } from './instance'; +import { DnsRecordType, IService } from './service'; +import { CfnInstance } from './servicediscovery.generated'; + +export interface IpInstanceBaseProps extends BaseInstanceProps { + /** + * The port on the endpoint that you want AWS Cloud Map to perform health checks on. This value is also used for + * the port value in an SRV record if the service that you specify includes an SRV record. You can also specify a + * default port that is applied to all instances in the Service configuration. + * + * @default 80 + */ + port?: number; + + /** + * If the service that you specify contains a template for an A record, the IPv4 address that you want AWS Cloud + * Map to use for the value of the A record. + * + * @default none + */ + ipv4?: string; + + /** + * If the service that you specify contains a template for an AAAA record, the IPv6 address that you want AWS Cloud + * Map to use for the value of the AAAA record. + * + * @default none + */ + ipv6?: string; +} + +export interface IpInstanceProps extends IpInstanceBaseProps { + /** + * The Cloudmap service this resource is registered to. + */ + service: IService; +} + +export class IpInstance extends InstanceBase { + /** + * The Id of the instance + */ + public readonly instanceId: string; + + /** + * The Cloudmap service to which the instance is registered. + */ + public readonly service: IService; + + /** + * The Ipv4 address of the instance + */ + public readonly ipv4: string; + + /** + * The Ipv6 address of the instance + */ + public readonly ipv6: string; + + /** + * The exposed port of the instance + */ + public readonly port: number; + + constructor(scope: cdk.Construct, id: string, props: IpInstanceProps) { + super(scope, id); + const dnsRecordType = props.service.dnsRecordType; + + if (dnsRecordType === DnsRecordType.Srv) { + if (!props.port) { + throw new Error('A `port` must be specified for a service using a `SRV` record.'); + } + + if (!props.ipv4 && !props.ipv6) { + throw new Error('At least `ipv4` or `ipv6` must be specified for a service using a `SRV` record.'); + } + } + + if (!props.ipv4 && (dnsRecordType === DnsRecordType.A || dnsRecordType === DnsRecordType.A_AAAA)) { + throw new Error('An `ipv4` must be specified for a service using a `A` record.'); + } + + if (!props.ipv6 && + (dnsRecordType === DnsRecordType.AAAA || dnsRecordType === DnsRecordType.A_AAAA)) { + throw new Error('An `ipv6` must be specified for a service using a `AAAA` record.'); + } + + const port = props.port !== undefined ? props.port : 80; + + const resource = new CfnInstance(this, 'Resource', { + instanceAttributes: { + AWS_INSTANCE_IPV4: props.ipv4, + AWS_INSTANCE_IPV6: props.ipv6, + AWS_INSTANCE_PORT: port.toString(), + ...props.customAttributes + }, + instanceId: props.instanceId, + serviceId: props.service.serviceId + }); + + this.service = props.service; + this.instanceId = resource.instanceId; + this.ipv4 = props.ipv4 || ''; + this.ipv6 = props.ipv6 || ''; + this.port = port; + } +} diff --git a/packages/@aws-cdk/aws-servicediscovery/lib/non-ip-instance.ts b/packages/@aws-cdk/aws-servicediscovery/lib/non-ip-instance.ts new file mode 100644 index 0000000000000..7f5c3b55becfe --- /dev/null +++ b/packages/@aws-cdk/aws-servicediscovery/lib/non-ip-instance.ts @@ -0,0 +1,47 @@ +import cdk = require('@aws-cdk/cdk'); +import { BaseInstanceProps, InstanceBase } from './instance'; +import { NamespaceType } from './namespace'; +import { IService } from './service'; +import { CfnInstance } from './servicediscovery.generated'; + +// tslint:disable-next-line:no-empty-interface +export interface NonIpInstanceBaseProps extends BaseInstanceProps { +} + +export interface NonIpInstanceProps extends NonIpInstanceBaseProps { + /** + * The Cloudmap service this resource is registered to. + */ + service: IService; +} + +export class NonIpInstance extends InstanceBase { + /** + * The Id of the instance + */ + public readonly instanceId: string; + + /** + * The Cloudmap service to which the instance is registered. + */ + public readonly service: IService; + + constructor(scope: cdk.Construct, id: string, props: NonIpInstanceProps) { + super(scope, id); + + if (props.service.namespace.type !== NamespaceType.Http) { + throw new Error('This type of instance can only be registered for HTTP namespaces.'); + } + + const resource = new CfnInstance(this, 'Resource', { + instanceId: props.instanceId, + serviceId: props.service.serviceId, + instanceAttributes: { + ...props.customAttributes + } + }); + + this.service = props.service; + this.instanceId = resource.instanceId; + } +} diff --git a/packages/@aws-cdk/aws-servicediscovery/lib/service.ts b/packages/@aws-cdk/aws-servicediscovery/lib/service.ts index 312206a5395b7..f67cb89966438 100644 --- a/packages/@aws-cdk/aws-servicediscovery/lib/service.ts +++ b/packages/@aws-cdk/aws-servicediscovery/lib/service.ts @@ -1,6 +1,12 @@ +import route53 = require('@aws-cdk/aws-route53'); import cdk = require('@aws-cdk/cdk'); +import { AliasTargetInstance } from './alias-target-instance'; +import { CnameInstance, CnameInstanceBaseProps } from './cname-instance'; +import { IInstance } from './instance'; +import { IpInstance, IpInstanceBaseProps } from './ip-instance'; import { INamespace, NamespaceType } from './namespace'; -import { CfnService} from './servicediscovery.generated'; +import { NonIpInstance, NonIpInstanceBaseProps } from './non-ip-instance'; +import { CfnService } from './servicediscovery.generated'; export interface IService extends cdk.IConstruct { /** @@ -27,6 +33,13 @@ export interface IService extends cdk.IConstruct { * The DnsRecordType used by the service */ readonly dnsRecordType: DnsRecordType; + + /** + * The Routing Policy used by the service + */ + readonly routingPolicy: RoutingPolicy; + + // Possibly add boolean loadbalancer field? } /** @@ -95,6 +108,14 @@ export interface DnsServiceProps extends BaseServiceProps { * @default WEIGHTED for CNAME records, MULTIVALUE otherwise */ routingPolicy?: RoutingPolicy; + + /** + * Whether or not this service will have an Elastic LoadBalancer registered to it as an AliasTargetInstance + * + * @default false + */ + + loadBalancer?: boolean; } export interface ServiceProps extends DnsServiceProps { @@ -133,6 +154,11 @@ export class Service extends cdk.Construct implements IService { */ public readonly dnsRecordType: DnsRecordType; + /** + * The Routing Policy used by the service + */ + public readonly routingPolicy: RoutingPolicy; + // FIXME make this only called through #createService on namespace classes? constructor(scope: cdk.Construct, id: string, props: ServiceProps) { super(scope, id); @@ -157,6 +183,11 @@ export class Service extends cdk.Construct implements IService { throw new Error('Cannot use `CNAME` record when routing policy is `Multivalue`.'); } + if (props.routingPolicy === RoutingPolicy.Multivalue + && props.loadBalancer) { + throw new Error('Cannot register loadbalancers when routing policy is `Multivalue`.'); + } + if (props.healthCheckConfig && props.healthCheckConfig.type === HealthCheckType.Tcp && props.healthCheckConfig.resourcePath) { @@ -164,11 +195,19 @@ export class Service extends cdk.Construct implements IService { } // Set defaults where necessary - const routingPolicy = props.dnsRecordType === DnsRecordType.Cname + const routingPolicy = (props.dnsRecordType === DnsRecordType.Cname) || props.loadBalancer ? RoutingPolicy.Weighted : RoutingPolicy.Multivalue; const dnsRecordType = props.dnsRecordType !== undefined ? props.dnsRecordType : DnsRecordType.A; + + if (props.loadBalancer + && (!(dnsRecordType === DnsRecordType.A + || dnsRecordType === DnsRecordType.AAAA + || dnsRecordType === DnsRecordType.A_AAAA))) { + throw new Error('Must support `A` or `AAAA` records to register loadbalancers.'); + } + const dnsConfig = props.namespace.type === NamespaceType.Http ? undefined : { @@ -208,6 +247,50 @@ export class Service extends cdk.Construct implements IService { this.serviceId = service.serviceId; this.namespace = props.namespace; this.dnsRecordType = dnsRecordType; + this.routingPolicy = routingPolicy; + } + + /** + * Registers an ELB as a new instance with unique name instanceId in this service. + * + * FIXME don't pass in ID? not sure if id can be used as instance ID here (instanceId is arbitrary but required for + * cloudmap#registerInstance) + */ + public registerLoadBalancer(id: string, loadBalancer: route53.IAliasRecordTarget, customAttributes?: object): IInstance { + return new AliasTargetInstance(this, id, { + service: this, + instanceId: id, + dnsName: loadBalancer.asAliasRecordTarget().dnsName, + customAttributes + }); + } + + public registerNonIpInstance(props: NonIpInstanceBaseProps): IInstance { + return new NonIpInstance(this, props.instanceId, { + service: this, + instanceId: props.instanceId, + customAttributes: props.customAttributes + }); + } + + public registerIpInstance(props: IpInstanceBaseProps): IInstance { + return new IpInstance(this, props.instanceId, { + service: this, + instanceId: props.instanceId, + ipv4: props.ipv4, + ipv6: props.ipv6, + port: props.port, + customAttributes: props.customAttributes + }); + } + + public registerCnameInstance(props: CnameInstanceBaseProps): IInstance { + return new CnameInstance(this, props.instanceId, { + service: this, + instanceId: props.instanceId, + instanceCname: props.instanceCname, + customAttributes: props.customAttributes + }); } } diff --git a/packages/@aws-cdk/aws-servicediscovery/package.json b/packages/@aws-cdk/aws-servicediscovery/package.json index a3c7e47e01f0a..a62a53ba76d5b 100644 --- a/packages/@aws-cdk/aws-servicediscovery/package.json +++ b/packages/@aws-cdk/aws-servicediscovery/package.json @@ -56,19 +56,22 @@ "devDependencies": { "@aws-cdk/assert": "^0.25.1", "cdk-build-tools": "^0.25.1", + "cdk-integ-tools": "^0.25.1", "cfn2ts": "^0.25.1", "pkglint": "^0.25.1" }, "dependencies": { "@aws-cdk/cdk": "^0.25.1", "@aws-cdk/aws-ec2": "^0.25.1", - "@aws-cdk/aws-elasticloadbalancingv2": "^0.25.1" + "@aws-cdk/aws-elasticloadbalancingv2": "^0.25.1", + "@aws-cdk/aws-route53": "^0.25.1" }, "homepage": "https://github.com/awslabs/aws-cdk", "peerDependencies": { "@aws-cdk/cdk": "^0.25.1", "@aws-cdk/aws-ec2": "^0.25.1", - "@aws-cdk/aws-elasticloadbalancingv2": "^0.25.1" + "@aws-cdk/aws-elasticloadbalancingv2": "^0.25.1", + "@aws-cdk/aws-route53": "^0.25.1" }, "engines": { "node": ">= 8.10.0" diff --git a/packages/@aws-cdk/aws-servicediscovery/test/cdk.json b/packages/@aws-cdk/aws-servicediscovery/test/cdk.json new file mode 100644 index 0000000000000..d7ad87e006b89 --- /dev/null +++ b/packages/@aws-cdk/aws-servicediscovery/test/cdk.json @@ -0,0 +1,10 @@ +{ + "context": { + "availability-zones:account=794715269151:region=us-west-2": [ + "us-west-2a", + "us-west-2b", + "us-west-2c", + "us-west-2d" + ] + } +} diff --git a/packages/@aws-cdk/aws-servicediscovery/test/integ.service-with-http-namespace.expected.json b/packages/@aws-cdk/aws-servicediscovery/test/integ.service-with-http-namespace.expected.json new file mode 100644 index 0000000000000..b613b0adbca3c --- /dev/null +++ b/packages/@aws-cdk/aws-servicediscovery/test/integ.service-with-http-namespace.expected.json @@ -0,0 +1,26 @@ +{ + "Resources": { + "MyNamespaceD0BB8558": { + "Type": "AWS::ServiceDiscovery::HttpNamespace", + "Properties": { + "Name": "covfefe" + } + }, + "ServiceDBC79909": { + "Type": "AWS::ServiceDiscovery::Service", + "Properties": { + "Description": "service description", + "HealthCheckCustomConfig": { + "FailureThreshold": 3 + }, + "Name": "service", + "NamespaceId": { + "Fn::GetAtt": [ + "MyNamespaceD0BB8558", + "Id" + ] + } + } + } + } +} \ No newline at end of file diff --git a/packages/@aws-cdk/aws-servicediscovery/test/integ.service-with-private-dns-namespace.expected.json b/packages/@aws-cdk/aws-servicediscovery/test/integ.service-with-private-dns-namespace.expected.json new file mode 100644 index 0000000000000..22dbd2aa8974e --- /dev/null +++ b/packages/@aws-cdk/aws-servicediscovery/test/integ.service-with-private-dns-namespace.expected.json @@ -0,0 +1,294 @@ +Resources: + Vpc8378EB38: + Type: AWS::EC2::VPC + Properties: + CidrBlock: 10.0.0.0/16 + EnableDnsHostnames: true + EnableDnsSupport: true + InstanceTenancy: default + Tags: + - Key: Name + Value: aws-servicediscovery-integ/Vpc + Metadata: + aws:cdk:path: aws-servicediscovery-integ/Vpc/Resource + VpcPublicSubnet1Subnet5C2D37C4: + Type: AWS::EC2::Subnet + Properties: + CidrBlock: 10.0.0.0/18 + VpcId: + Ref: Vpc8378EB38 + AvailabilityZone: us-west-2a + MapPublicIpOnLaunch: true + Tags: + - Key: Name + Value: aws-servicediscovery-integ/Vpc/PublicSubnet1 + - Key: aws-cdk:subnet-name + Value: Public + - Key: aws-cdk:subnet-type + Value: Public + Metadata: + aws:cdk:path: aws-servicediscovery-integ/Vpc/PublicSubnet1/Subnet + VpcPublicSubnet1RouteTable6C95E38E: + Type: AWS::EC2::RouteTable + Properties: + VpcId: + Ref: Vpc8378EB38 + Tags: + - Key: Name + Value: aws-servicediscovery-integ/Vpc/PublicSubnet1 + Metadata: + aws:cdk:path: aws-servicediscovery-integ/Vpc/PublicSubnet1/RouteTable + VpcPublicSubnet1RouteTableAssociation97140677: + Type: AWS::EC2::SubnetRouteTableAssociation + Properties: + RouteTableId: + Ref: VpcPublicSubnet1RouteTable6C95E38E + SubnetId: + Ref: VpcPublicSubnet1Subnet5C2D37C4 + Metadata: + aws:cdk:path: aws-servicediscovery-integ/Vpc/PublicSubnet1/RouteTableAssociation + VpcPublicSubnet1DefaultRoute3DA9E72A: + Type: AWS::EC2::Route + Properties: + RouteTableId: + Ref: VpcPublicSubnet1RouteTable6C95E38E + DestinationCidrBlock: 0.0.0.0/0 + GatewayId: + Ref: VpcIGWD7BA715C + DependsOn: + - VpcVPCGWBF912B6E + Metadata: + aws:cdk:path: aws-servicediscovery-integ/Vpc/PublicSubnet1/DefaultRoute + VpcPublicSubnet1EIPD7E02669: + Type: AWS::EC2::EIP + Properties: + Domain: vpc + Metadata: + aws:cdk:path: aws-servicediscovery-integ/Vpc/PublicSubnet1/EIP + VpcPublicSubnet1NATGateway4D7517AA: + Type: AWS::EC2::NatGateway + Properties: + AllocationId: + Fn::GetAtt: + - VpcPublicSubnet1EIPD7E02669 + - AllocationId + SubnetId: + Ref: VpcPublicSubnet1Subnet5C2D37C4 + Tags: + - Key: Name + Value: aws-servicediscovery-integ/Vpc/PublicSubnet1 + Metadata: + aws:cdk:path: aws-servicediscovery-integ/Vpc/PublicSubnet1/NATGateway + VpcPublicSubnet2Subnet691E08A3: + Type: AWS::EC2::Subnet + Properties: + CidrBlock: 10.0.64.0/18 + VpcId: + Ref: Vpc8378EB38 + AvailabilityZone: us-west-2b + MapPublicIpOnLaunch: true + Tags: + - Key: Name + Value: aws-servicediscovery-integ/Vpc/PublicSubnet2 + - Key: aws-cdk:subnet-name + Value: Public + - Key: aws-cdk:subnet-type + Value: Public + Metadata: + aws:cdk:path: aws-servicediscovery-integ/Vpc/PublicSubnet2/Subnet + VpcPublicSubnet2RouteTable94F7E489: + Type: AWS::EC2::RouteTable + Properties: + VpcId: + Ref: Vpc8378EB38 + Tags: + - Key: Name + Value: aws-servicediscovery-integ/Vpc/PublicSubnet2 + Metadata: + aws:cdk:path: aws-servicediscovery-integ/Vpc/PublicSubnet2/RouteTable + VpcPublicSubnet2RouteTableAssociationDD5762D8: + Type: AWS::EC2::SubnetRouteTableAssociation + Properties: + RouteTableId: + Ref: VpcPublicSubnet2RouteTable94F7E489 + SubnetId: + Ref: VpcPublicSubnet2Subnet691E08A3 + Metadata: + aws:cdk:path: aws-servicediscovery-integ/Vpc/PublicSubnet2/RouteTableAssociation + VpcPublicSubnet2DefaultRoute97F91067: + Type: AWS::EC2::Route + Properties: + RouteTableId: + Ref: VpcPublicSubnet2RouteTable94F7E489 + DestinationCidrBlock: 0.0.0.0/0 + GatewayId: + Ref: VpcIGWD7BA715C + DependsOn: + - VpcVPCGWBF912B6E + Metadata: + aws:cdk:path: aws-servicediscovery-integ/Vpc/PublicSubnet2/DefaultRoute + VpcPublicSubnet2EIP3C605A87: + Type: AWS::EC2::EIP + Properties: + Domain: vpc + Metadata: + aws:cdk:path: aws-servicediscovery-integ/Vpc/PublicSubnet2/EIP + VpcPublicSubnet2NATGateway9182C01D: + Type: AWS::EC2::NatGateway + Properties: + AllocationId: + Fn::GetAtt: + - VpcPublicSubnet2EIP3C605A87 + - AllocationId + SubnetId: + Ref: VpcPublicSubnet2Subnet691E08A3 + Tags: + - Key: Name + Value: aws-servicediscovery-integ/Vpc/PublicSubnet2 + Metadata: + aws:cdk:path: aws-servicediscovery-integ/Vpc/PublicSubnet2/NATGateway + VpcPrivateSubnet1Subnet536B997A: + Type: AWS::EC2::Subnet + Properties: + CidrBlock: 10.0.128.0/18 + VpcId: + Ref: Vpc8378EB38 + AvailabilityZone: us-west-2a + MapPublicIpOnLaunch: false + Tags: + - Key: Name + Value: aws-servicediscovery-integ/Vpc/PrivateSubnet1 + - Key: aws-cdk:subnet-name + Value: Private + - Key: aws-cdk:subnet-type + Value: Private + Metadata: + aws:cdk:path: aws-servicediscovery-integ/Vpc/PrivateSubnet1/Subnet + VpcPrivateSubnet1RouteTableB2C5B500: + Type: AWS::EC2::RouteTable + Properties: + VpcId: + Ref: Vpc8378EB38 + Tags: + - Key: Name + Value: aws-servicediscovery-integ/Vpc/PrivateSubnet1 + Metadata: + aws:cdk:path: aws-servicediscovery-integ/Vpc/PrivateSubnet1/RouteTable + VpcPrivateSubnet1RouteTableAssociation70C59FA6: + Type: AWS::EC2::SubnetRouteTableAssociation + Properties: + RouteTableId: + Ref: VpcPrivateSubnet1RouteTableB2C5B500 + SubnetId: + Ref: VpcPrivateSubnet1Subnet536B997A + Metadata: + aws:cdk:path: aws-servicediscovery-integ/Vpc/PrivateSubnet1/RouteTableAssociation + VpcPrivateSubnet1DefaultRouteBE02A9ED: + Type: AWS::EC2::Route + Properties: + RouteTableId: + Ref: VpcPrivateSubnet1RouteTableB2C5B500 + DestinationCidrBlock: 0.0.0.0/0 + NatGatewayId: + Ref: VpcPublicSubnet1NATGateway4D7517AA + Metadata: + aws:cdk:path: aws-servicediscovery-integ/Vpc/PrivateSubnet1/DefaultRoute + VpcPrivateSubnet2Subnet3788AAA1: + Type: AWS::EC2::Subnet + Properties: + CidrBlock: 10.0.192.0/18 + VpcId: + Ref: Vpc8378EB38 + AvailabilityZone: us-west-2b + MapPublicIpOnLaunch: false + Tags: + - Key: Name + Value: aws-servicediscovery-integ/Vpc/PrivateSubnet2 + - Key: aws-cdk:subnet-name + Value: Private + - Key: aws-cdk:subnet-type + Value: Private + Metadata: + aws:cdk:path: aws-servicediscovery-integ/Vpc/PrivateSubnet2/Subnet + VpcPrivateSubnet2RouteTableA678073B: + Type: AWS::EC2::RouteTable + Properties: + VpcId: + Ref: Vpc8378EB38 + Tags: + - Key: Name + Value: aws-servicediscovery-integ/Vpc/PrivateSubnet2 + Metadata: + aws:cdk:path: aws-servicediscovery-integ/Vpc/PrivateSubnet2/RouteTable + VpcPrivateSubnet2RouteTableAssociationA89CAD56: + Type: AWS::EC2::SubnetRouteTableAssociation + Properties: + RouteTableId: + Ref: VpcPrivateSubnet2RouteTableA678073B + SubnetId: + Ref: VpcPrivateSubnet2Subnet3788AAA1 + Metadata: + aws:cdk:path: aws-servicediscovery-integ/Vpc/PrivateSubnet2/RouteTableAssociation + VpcPrivateSubnet2DefaultRoute060D2087: + Type: AWS::EC2::Route + Properties: + RouteTableId: + Ref: VpcPrivateSubnet2RouteTableA678073B + DestinationCidrBlock: 0.0.0.0/0 + NatGatewayId: + Ref: VpcPublicSubnet2NATGateway9182C01D + Metadata: + aws:cdk:path: aws-servicediscovery-integ/Vpc/PrivateSubnet2/DefaultRoute + VpcIGWD7BA715C: + Type: AWS::EC2::InternetGateway + Properties: + Tags: + - Key: Name + Value: aws-servicediscovery-integ/Vpc + Metadata: + aws:cdk:path: aws-servicediscovery-integ/Vpc/IGW + VpcVPCGWBF912B6E: + Type: AWS::EC2::VPCGatewayAttachment + Properties: + VpcId: + Ref: Vpc8378EB38 + InternetGatewayId: + Ref: VpcIGWD7BA715C + Metadata: + aws:cdk:path: aws-servicediscovery-integ/Vpc/VPCGW + Namespace9B63B8C8: + Type: AWS::ServiceDiscovery::PrivateDnsNamespace + Properties: + Name: foobar.com + Vpc: + Ref: Vpc8378EB38 + Metadata: + aws:cdk:path: aws-servicediscovery-integ/Namespace/Resource + NamespaceServiceCABDF534: + Type: AWS::ServiceDiscovery::Service + Properties: + DnsConfig: + DnsRecords: + - TTL: "30" + Type: A + - TTL: "30" + Type: AAAA + NamespaceId: + Fn::GetAtt: + - Namespace9B63B8C8 + - Id + RoutingPolicy: MULTIVALUE + HealthCheckCustomConfig: + FailureThreshold: 2 + Name: frontend + NamespaceId: + Fn::GetAtt: + - Namespace9B63B8C8 + - Id + Metadata: + aws:cdk:path: aws-servicediscovery-integ/Namespace/Service/Resource + CDKMetadata: + Type: AWS::CDK::Metadata + Properties: + Modules: aws-cdk=0.22.0,@aws-cdk/aws-ec2=0.24.1,@aws-cdk/aws-servicediscovery=0.24.1,@aws-cdk/cdk=0.24.1,@aws-cdk/cx-api=0.24.1,jsii-runtime=node.js/v8.11.4 + diff --git a/packages/@aws-cdk/aws-servicediscovery/test/integ.service-with-private-dns-namespace.ts b/packages/@aws-cdk/aws-servicediscovery/test/integ.service-with-private-dns-namespace.ts index 16fbe947117ef..ac17225ba5926 100644 --- a/packages/@aws-cdk/aws-servicediscovery/test/integ.service-with-private-dns-namespace.ts +++ b/packages/@aws-cdk/aws-servicediscovery/test/integ.service-with-private-dns-namespace.ts @@ -1,4 +1,5 @@ import ec2 = require('@aws-cdk/aws-ec2'); +import elbv2 = require('@aws-cdk/aws-elasticloadbalancingv2'); import cdk = require('@aws-cdk/cdk'); import servicediscovery = require('../lib'); @@ -8,15 +9,23 @@ const stack = new cdk.Stack(app, 'aws-servicediscovery-integ'); const vpc = new ec2.VpcNetwork(stack, 'Vpc', { maxAZs: 2 }); const namespace = new servicediscovery.PrivateDnsNamespace(stack, 'Namespace', { - name: "woobar.com", + name: "boobar.com", vpc, }); -namespace.createService('Service', { +const service = namespace.createService('Service', { name: "bloop", dnsRecordType: servicediscovery.DnsRecordType.A_AAAA, dnsTtlSec: 30, - healthCheckCustomConfig: { failureThreshold: 2 } + healthCheckCustomConfig: { failureThreshold: 2 }, + loadBalancer: true }); +const loadbalancer = new elbv2.ApplicationLoadBalancer(stack, 'LB', { vpc, internetFacing: true }); + +service.registerLoadBalancer('LoadBalancer', loadbalancer); + // AWS::ServiceDiscovery::Instance | Namespace/Service/LoadBalancer (NamespaceServiceLoadBalancer0AF75CE8) +// ALIAS instance can be registered only in services with WEIGHTED routing policy (Service: AWSServiceDiscovery; Status +// Code: 400; Error Code: InvalidInput; Request ID: dbab53cb-393c-11e9-88fe-295c6aa86971) + app.run(); diff --git a/packages/@aws-cdk/aws-servicediscovery/test/test.instance.ts b/packages/@aws-cdk/aws-servicediscovery/test/test.instance.ts index 9bed0177a1cd3..ac2396dbe71e3 100644 --- a/packages/@aws-cdk/aws-servicediscovery/test/test.instance.ts +++ b/packages/@aws-cdk/aws-servicediscovery/test/test.instance.ts @@ -6,7 +6,7 @@ import { Test } from 'nodeunit'; import servicediscovery = require('../lib'); export = { - 'Instance for service in HTTP namespace'(test: Test) { + 'IpInstance for service in HTTP namespace'(test: Test) { // GIVEN const stack = new cdk.Stack(); @@ -14,18 +14,15 @@ export = { name: 'http', }); - const service = new servicediscovery.Service(stack, 'MyService', { - namespace, + const service = namespace.createService('MyService', { + name: 'service', }); - new servicediscovery.Instance(stack, 'MyInstance', { - service, - instanceId: "41332780-d796-feed-face-02252334a661", - instanceAttributes: { - ipv4: '10.0.0.0', - ipv6: '0:0:0:0:0:ffff:a00:0', - port: '443' - } + service.registerIpInstance({ + instanceId: "myInstance", + ipv4: '10.0.0.0', + ipv6: '0:0:0:0:0:ffff:a00:0', + port: 443 }); // THEN @@ -37,180 +34,168 @@ export = { }, ServiceId: { "Fn::GetAtt": [ - "MyServiceA1F951EB", + "MyNamespaceMyService365E2470", "Id" ] }, - InstanceId: "41332780-d796-feed-face-02252334a661" + InstanceId: "myInstance" })); test.done(); }, - 'Instance for a load balancer'(test: Test) { + 'IpInstance for service in PublicDnsNamespace'(test: Test) { // GIVEN const stack = new cdk.Stack(); - const vpc = new ec2.VpcNetwork(stack, 'MyVPC'); - const alb = new elbv2.ApplicationLoadBalancer(stack, 'MyALB', { vpc }); - - const namespace = new servicediscovery.PrivateDnsNamespace(stack, 'MyNamespace', { - name: 'dns', - vpc + const namespace = new servicediscovery.PublicDnsNamespace(stack, 'MyNamespace', { + name: 'public', }); - const service = new servicediscovery.Service(stack, 'MyService', { - namespace, + const service = namespace.createService('MyService', { + name: 'service', + dnsRecordType: servicediscovery.DnsRecordType.A_AAAA }); - new servicediscovery.Instance(stack, 'MyInstance', { - service, - instanceId: 'id', - instanceAttributes: { - aliasDnsName: alb.asAliasRecordTarget().dnsName - } + service.registerIpInstance({ + instanceId: "myInstance", + ipv4: '10.0.0.0', + ipv6: '0:0:0:0:0:ffff:a00:0', + port: 443 }); // THEN expect(stack).to(haveResource('AWS::ServiceDiscovery::Instance', { InstanceAttributes: { - AWS_ALIAS_DNS_NAME: { - "Fn::GetAtt": [ - "MyALB911A8556", - "DNSName" - ] - } + AWS_INSTANCE_IPV4: "10.0.0.0", + AWS_INSTANCE_IPV6: "0:0:0:0:0:ffff:a00:0", + AWS_INSTANCE_PORT: "443" }, ServiceId: { "Fn::GetAtt": [ - "MyServiceA1F951EB", + "MyNamespaceMyService365E2470", "Id" ] }, - InstanceId: "id" + InstanceId: "myInstance" })); test.done(); }, - 'Instance with domain name'(test: Test) { + 'IpInstance for service in PrivateDnsNamespace'(test: Test) { // GIVEN const stack = new cdk.Stack(); + const vpc = new ec2.VpcNetwork(stack, 'MyVpc'); - const namespace = new servicediscovery.PublicDnsNamespace(stack, 'MyNamespace', { - name: 'dns', + const namespace = new servicediscovery.PrivateDnsNamespace(stack, 'MyNamespace', { + name: 'public', + vpc }); - const service = new servicediscovery.Service(stack, 'MyService', { - namespace, - dnsRecordType: servicediscovery.DnsRecordType.Cname + const service = namespace.createService('MyService', { + name: 'service', + dnsRecordType: servicediscovery.DnsRecordType.A_AAAA }); - new servicediscovery.Instance(stack, 'MyInstance', { - instanceId: 'id', - service, - instanceAttributes: { - instanceCname: 'foo.com' - } + service.registerIpInstance({ + instanceId: "myInstance", + ipv4: '10.0.0.0', + ipv6: '0:0:0:0:0:ffff:a00:0', + port: 443 }); // THEN expect(stack).to(haveResource('AWS::ServiceDiscovery::Instance', { InstanceAttributes: { - AWS_INSTANCE_CNAME: "foo.com", + AWS_INSTANCE_IPV4: "10.0.0.0", + AWS_INSTANCE_IPV6: "0:0:0:0:0:ffff:a00:0", + AWS_INSTANCE_PORT: "443" }, ServiceId: { "Fn::GetAtt": [ - "MyServiceA1F951EB", + "MyNamespaceMyService365E2470", "Id" ] }, - InstanceId: "id" + InstanceId: "myInstance" })); test.done(); }, - 'Throws when specifying both aliasDnsName and instanceCname'(test: Test) { + 'Registering IpInstance throws when omitting port for a service using SRV'(test: Test) { // GIVEN const stack = new cdk.Stack(); - const namespace = new servicediscovery.HttpNamespace(stack, 'MyNamespace', { - name: 'http', + const namespace = new servicediscovery.PublicDnsNamespace(stack, 'MyNamespace', { + name: 'public', }); - const service = new servicediscovery.Service(stack, 'MyService', { - namespace, + const service = namespace.createService('MyService', { + name: 'service', + dnsRecordType: servicediscovery.DnsRecordType.Srv }); // THEN test.throws(() => { - new servicediscovery.Instance(stack, 'MyInstance', { - service, + service.registerIpInstance({ instanceId: 'id', - instanceAttributes: { - aliasDnsName: 'alb.foo.com', - instanceCname: 'domain' - } }); - }, /Cannot specify both `aliasDnsName` and `instanceCname`/); + }, /A `port` must be specified for a service using a `SRV` record./); test.done(); }, - 'Throws when specifying aliasDnsName for an HTTP only namespace'(test: Test) { + 'Registering IpInstance throws when omitting ipv4 and ipv6 for a service using SRV'(test: Test) { // GIVEN const stack = new cdk.Stack(); - const namespace = new servicediscovery.HttpNamespace(stack, 'MyNamespace', { - name: 'http', + const namespace = new servicediscovery.PublicDnsNamespace(stack, 'MyNamespace', { + name: 'dns', }); - const service = new servicediscovery.Service(stack, 'MyService', { - namespace, + const service = namespace.createService('MyService', { + name: 'service', + dnsRecordType: servicediscovery.DnsRecordType.Srv }); // THEN test.throws(() => { - new servicediscovery.Instance(stack, 'MyInstance', { - service, + service.registerIpInstance({ instanceId: 'id', - instanceAttributes: { - aliasDnsName: "foo.com", - } + port: 3306 }); - }, /Cannot specify `aliasDnsName` or `instanceCname` for an HTTP namespace./); + }, /At least `ipv4` or `ipv6` must be specified for a service using a `SRV` record./); test.done(); }, - 'Throws when specifying instanceCname for an HTTP namespace'(test: Test) { + 'Registering IpInstance throws when omitting ipv4 for a service using A records'(test: Test) { // GIVEN const stack = new cdk.Stack(); - const namespace = new servicediscovery.HttpNamespace(stack, 'MyNamespace', { - name: 'http', + const namespace = new servicediscovery.PublicDnsNamespace(stack, 'MyNamespace', { + name: 'dns', }); - const service = new servicediscovery.Service(stack, 'MyService', { - namespace, + const service = namespace.createService('MyService', { + name: 'service', + dnsRecordType: servicediscovery.DnsRecordType.A }); // THEN test.throws(() => { - new servicediscovery.Instance(stack, 'MyInstance', { - service, + service.registerIpInstance({ instanceId: 'id', - instanceAttributes: { - instanceCname: "domain", - } + port: 3306 }); - }, /Cannot specify `aliasDnsName` or `instanceCname` for an HTTP namespace./); + }, /An `ipv4` must be specified for a service using a `A` record./); test.done(); }, - 'Throws when omitting instanceCname for a service using CNAME'(test: Test) { + 'Registering IpInstance throws when omitting ipv6 for a service using AAAA records'(test: Test) { // GIVEN const stack = new cdk.Stack(); @@ -218,77 +203,113 @@ export = { name: 'dns', }); - const service = new servicediscovery.Service(stack, 'MyService', { - namespace, - dnsRecordType: servicediscovery.DnsRecordType.Cname, - dnsTtlSec: 300 + const service = namespace.createService('MyService', { + name: 'service', + dnsRecordType: servicediscovery.DnsRecordType.AAAA }); // THEN test.throws(() => { - new servicediscovery.Instance(stack, 'MyInstance', { - service, + service.registerIpInstance({ instanceId: 'id', - instanceAttributes: {} + port: 3306 }); - }, /A `instanceCname` must be specified for a service using a `CNAME` record./); + }, /An `ipv6` must be specified for a service using a `AAAA` record./); test.done(); }, - 'Throws when omitting port for a service using SRV'(test: Test) { + 'Registering AliasTargetInstance'(test: Test) { // GIVEN const stack = new cdk.Stack(); - const namespace = new servicediscovery.PublicDnsNamespace(stack, 'MyNamespace', { + const vpc = new ec2.VpcNetwork(stack, 'MyVPC'); + const alb = new elbv2.ApplicationLoadBalancer(stack, 'MyALB', { vpc }); + + const namespace = new servicediscovery.PrivateDnsNamespace(stack, 'MyNamespace', { name: 'dns', + vpc + }); + + const service = namespace.createService('MyService', { + name: 'service', + loadBalancer: true, + }); + const customAttributes = { foo: 'bar' }; + + service.registerLoadBalancer('MyLoadBalancer', alb, customAttributes ); + + // THEN + expect(stack).to(haveResource('AWS::ServiceDiscovery::Instance', { + InstanceAttributes: { + AWS_ALIAS_DNS_NAME: { + "Fn::GetAtt": [ + "MyALB911A8556", + "DNSName" + ] + }, + foo: "bar" + }, + ServiceId: { + "Fn::GetAtt": [ + "MyNamespaceMyService365E2470", + "Id" + ] + }, + InstanceId: "MyLoadBalancer" + })); + + test.done(); + }, + + 'Throws when registering AliasTargetInstance with Http Namespace'(test: Test) { + // GIVEN + const stack = new cdk.Stack(); + + const namespace = new servicediscovery.HttpNamespace(stack, 'MyNamespace', { + name: 'http', }); const service = new servicediscovery.Service(stack, 'MyService', { namespace, - dnsRecordType: servicediscovery.DnsRecordType.Srv }); + const vpc = new ec2.VpcNetwork(stack, 'MyVPC'); + const alb = new elbv2.ApplicationLoadBalancer(stack, 'MyALB', { vpc }); + // THEN test.throws(() => { - new servicediscovery.Instance(stack, 'MyInstance', { - service, - instanceId: 'id', - instanceAttributes: {} - }); - }, /A `port` must be specified for a service using a `SRV` record./); + service.registerLoadBalancer('MyLoadBalancer', alb); + }, /Namespace associated with Service must be a DNS Namespace./); test.done(); }, - 'Throws when omitting ipv4 and ipv6 for a service using SRV'(test: Test) { + // TODO shouldn't be allowed to do this if loadbalancer on ServiceProps is not set to true. + 'Throws when registering AliasTargetInstance with wrong Routing Policy'(test: Test) { // GIVEN const stack = new cdk.Stack(); const namespace = new servicediscovery.PublicDnsNamespace(stack, 'MyNamespace', { - name: 'dns', + name: 'http', }); - const service = new servicediscovery.Service(stack, 'MyService', { - namespace, - dnsRecordType: servicediscovery.DnsRecordType.Srv + const service = namespace.createService('MyService', { + routingPolicy: servicediscovery.RoutingPolicy.Multivalue }); + const vpc = new ec2.VpcNetwork(stack, 'MyVPC'); + const alb = new elbv2.ApplicationLoadBalancer(stack, 'MyALB', { vpc }); + // THEN test.throws(() => { - new servicediscovery.Instance(stack, 'MyInstance', { - service, - instanceId: 'id', - instanceAttributes: { - port: '3306' - } - }); - }, /At least `ipv4` or `ipv6` must be specified for a service using a `SRV` record./); + service.registerLoadBalancer('MyLoadBalancer', alb); + }, /Service must use `WEIGHTED` routing policy./); test.done(); }, - 'Throws when omitting ipv4 for a service using A'(test: Test) { + 'Register CnameInstance'(test: Test) { // GIVEN const stack = new cdk.Stack(); @@ -296,49 +317,106 @@ export = { name: 'dns', }); + const service = namespace.createService('MyService', { + dnsRecordType: servicediscovery.DnsRecordType.Cname + }); + + service.registerCnameInstance({ + instanceId: "cnameInstance", + instanceCname: 'foo.com', + customAttributes: { "all dogs": "good dogs" } + }); + + // THEN + expect(stack).to(haveResource('AWS::ServiceDiscovery::Instance', { + InstanceAttributes: { + "AWS_INSTANCE_CNAME": "foo.com", + "all dogs": "good dogs" + }, + ServiceId: { + "Fn::GetAtt": [ + "MyNamespaceMyService365E2470", + "Id" + ] + }, + InstanceId: "cnameInstance" + })); + + test.done(); + }, + + 'Throws when specifying instanceCname for an HTTP namespace'(test: Test) { + // GIVEN + const stack = new cdk.Stack(); + + const namespace = new servicediscovery.HttpNamespace(stack, 'MyNamespace', { + name: 'http', + }); + const service = new servicediscovery.Service(stack, 'MyService', { namespace, - dnsRecordType: servicediscovery.DnsRecordType.A }); // THEN test.throws(() => { - new servicediscovery.Instance(stack, 'MyInstance', { - service, - instanceId: 'id', - instanceAttributes: { - port: '3306' - } + service.registerCnameInstance({ + instanceId: "cnameInstance", + instanceCname: 'foo.com', }); - }, /An `ipv4` must be specified for a service using a `A` record./); + }, /Namespace associated with Service must be a DNS Namespace/); test.done(); }, - 'Throws when omitting ipv6 for a service using AAAA'(test: Test) { + 'Register NonIpInstance'(test: Test) { // GIVEN const stack = new cdk.Stack(); - const namespace = new servicediscovery.PublicDnsNamespace(stack, 'MyNamespace', { - name: 'dns', + const namespace = new servicediscovery.HttpNamespace(stack, 'MyNamespace', { + name: 'http', }); - const service = new servicediscovery.Service(stack, 'MyService', { - namespace, - dnsRecordType: servicediscovery.DnsRecordType.AAAA + const service = namespace.createService('MyService'); + + service.registerNonIpInstance({ + instanceId: "nonIp", + customAttributes: { "all dogs": "good dogs" } }); + // THEN + expect(stack).to(haveResource('AWS::ServiceDiscovery::Instance', { + InstanceAttributes: { + "all dogs": "good dogs" + }, + ServiceId: { + "Fn::GetAtt": [ + "MyNamespaceMyService365E2470", + "Id" + ] + }, + InstanceId: "nonIp" + })); + + test.done(); + }, + + 'Throws when specifying instanceNonIp for an HTTP namespace'(test: Test) { + // GIVEN + const stack = new cdk.Stack(); + + const namespace = new servicediscovery.PublicDnsNamespace(stack, 'MyNamespace', { + name: 'http', + }); + + const service = namespace.createService('MyService'); + // THEN test.throws(() => { - new servicediscovery.Instance(stack, 'MyInstance', { - service, - instanceId: 'id', - instanceAttributes: { - port: '3306' - } + service.registerNonIpInstance({ + instanceId: "nonIp", }); - }, /An `ipv6` must be specified for a service using a `AAAA` record./); + }, /This type of instance can only be registered for HTTP namespaces./); test.done(); - } + }, }; diff --git a/packages/@aws-cdk/aws-servicediscovery/test/test.service.ts b/packages/@aws-cdk/aws-servicediscovery/test/test.service.ts index 0306a4f26104c..61be1e0447595 100644 --- a/packages/@aws-cdk/aws-servicediscovery/test/test.service.ts +++ b/packages/@aws-cdk/aws-servicediscovery/test/test.service.ts @@ -1,4 +1,5 @@ -import { expect } from '@aws-cdk/assert'; +import { expect, haveResource } from '@aws-cdk/assert'; +import ec2 = require('@aws-cdk/aws-ec2'); import cdk = require('@aws-cdk/cdk'); import { Test } from 'nodeunit'; import servicediscovery = require('../lib'); @@ -51,7 +52,7 @@ export = { test.done(); }, - 'Service for DNS namespace'(test: Test) { + 'Service for Public DNS namespace'(test: Test) { // GIVEN const stack = new cdk.Stack(); @@ -113,7 +114,7 @@ export = { test.done(); }, - 'Service for DNS namespace with A and AAAA records'(test: Test) { + 'Service for Public DNS namespace with A and AAAA records'(test: Test) { // GIVEN const stack = new cdk.Stack(); @@ -170,7 +171,7 @@ export = { test.done(); }, - 'Defaults to weighted for CNAME'(test: Test) { + 'Defaults to WEIGHTED routing policy for CNAME'(test: Test) { // GIVEN const stack = new cdk.Stack(); @@ -223,7 +224,7 @@ export = { test.done(); }, - 'Throws when specifying both healthCheckConfig and healthCheckCustomCOnfig'(test: Test) { + 'Throws when specifying both healthCheckConfig and healthCheckCustomConfig on PublicDnsNamespace'(test: Test) { const stack = new cdk.Stack(); const namespace = new servicediscovery.PublicDnsNamespace(stack, 'MyNamespace', { @@ -246,6 +247,31 @@ export = { test.done(); }, + 'Throws when specifying healthCheckConfig on PrivateDnsNamespace'(test: Test) { + const stack = new cdk.Stack(); + const vpc = new ec2.VpcNetwork(stack, 'MyVpc'); + + const namespace = new servicediscovery.PrivateDnsNamespace(stack, 'MyNamespace', { + name: 'name', + vpc + }); + + // THEN + test.throws(() => { + namespace.createService('MyService', { + name: 'service', + healthCheckConfig: { + resourcePath: '/' + }, + healthCheckCustomConfig: { + failureThreshold: 1 + } + }); + }, /`healthCheckConfig`.+`healthCheckCustomConfig`/); + + test.done(); + }, + 'Throws when using CNAME and Multivalue'(test: Test) { // GIVEN const stack = new cdk.Stack(); @@ -286,7 +312,92 @@ export = { }, /`resourcePath`.+`TCP`/); test.done(); - } + }, + + 'Throws when specifying loadbalancer with wrong DnsRecordType'(test: Test) { + const stack = new cdk.Stack(); + + const namespace = new servicediscovery.PublicDnsNamespace(stack, 'MyNamespace', { + name: 'name', + }); + + // THEN + test.throws(() => { + namespace.createService('MyService', { + name: 'service', + dnsRecordType: servicediscovery.DnsRecordType.Cname, + loadBalancer: true + }); + }, /Must support `A` or `AAAA` records to register loadbalancers/); + + test.done(); + }, + + 'Throws when specifying loadbalancer with wrong Routing Policy'(test: Test) { + // GIVEN + const stack = new cdk.Stack(); + + const namespace = new servicediscovery.PublicDnsNamespace(stack, 'MyNamespace', { + name: 'http', + }); + + // THEN + test.throws(() => { + namespace.createService('MyService', { + loadBalancer: true, + routingPolicy: servicediscovery.RoutingPolicy.Multivalue + }); + }, /Cannot register loadbalancers when routing policy is `Multivalue`./); + + test.done(); + }, - // TODO add tests for Private DNS namespace + 'Service for Private DNS namespace'(test: Test) { + // GIVEN + const stack = new cdk.Stack(); + const vpc = new ec2.VpcNetwork(stack, 'MyVpc'); + + const namespace = new servicediscovery.PrivateDnsNamespace(stack, 'MyNamespace', { + name: 'private', + vpc + }); + + namespace.createService('MyService', { + name: 'service', + description: 'service description', + }); + + // THEN + expect(stack).to(haveResource('AWS::ServiceDiscovery::PrivateDnsNamespace', { + Name: "private" + })); + + expect(stack).to(haveResource('AWS::ServiceDiscovery::Service', { + Description: "service description", + DnsConfig: { + DnsRecords: [ + { + TTL: "60", + Type: "A" + } + ], + NamespaceId: { + "Fn::GetAtt": [ + "MyNamespaceD0BB8558", + "Id" + ] + }, + RoutingPolicy: "MULTIVALUE" + }, + Name: "service", + NamespaceId: { + "Fn::GetAtt": [ + "MyNamespaceD0BB8558", + "Id" + ] + } + })); + + test.done(); + }, };