@@ -23,6 +23,23 @@ export enum DataStorageUnit {
2323 GIGABYTES = 'GB' ,
2424}
2525
26+ /**
27+ * Minimum data storage size in GB for ServerlessCache
28+ */
29+ const DATA_STORAGE_MIN_GB = 1 ;
30+ /**
31+ * Maximum data storage size in GB for ServerlessCache
32+ */
33+ const DATA_STORAGE_MAX_GB = 5000 ;
34+ /**
35+ * Minimum request rate limit in ECPUs per second for ServerlessCache
36+ */
37+ const REQUEST_RATE_MIN_ECPU = 1000 ;
38+ /**
39+ * Maximum request rate limit in ECPUs per second for ServerlessCache
40+ */
41+ const REQUEST_RATE_MAX_ECPU = 15000000 ;
42+
2643/**
2744 * Usage limits configuration for ServerlessCache
2845 */
@@ -266,27 +283,26 @@ export class ServerlessCache extends ServerlessCacheBase {
266283 let arn : string ;
267284 const stack = Stack . of ( scope ) ;
268285
269- if ( ! attrs . serverlessCacheName ) {
270- if ( ! attrs . serverlessCacheArn ) {
271- throw new ValidationError ( 'One of serverlessCacheName or serverlessCacheArn is required!' , scope ) ;
272- }
286+ if ( attrs . serverlessCacheArn && attrs . serverlessCacheName ) {
287+ throw new ValidationError ( 'Only one of serverlessCacheArn or serverlessCacheName can be provided.' , scope ) ;
288+ }
273289
290+ if ( attrs . serverlessCacheArn ) {
274291 arn = attrs . serverlessCacheArn ;
275- const maybeServerlessCacheName = stack . splitArn ( attrs . serverlessCacheArn , ArnFormat . SLASH_RESOURCE_NAME ) . resourceName ;
276- if ( ! maybeServerlessCacheName ) {
277- throw new ValidationError ( 'Unable to extract serverless cache name from ARN' , scope ) ;
278- }
279- name = maybeServerlessCacheName ;
280- } else {
281- if ( attrs . serverlessCacheArn ) {
282- throw new ValidationError ( 'Only one of serverlessCacheArn or serverlessCacheName can be provided' , scope ) ;
292+ const extractedServerlessCacheName = stack . splitArn ( attrs . serverlessCacheArn , ArnFormat . SLASH_RESOURCE_NAME ) . resourceName ;
293+ if ( ! extractedServerlessCacheName ) {
294+ throw new ValidationError ( 'Unable to extract serverless cache name from ARN.' , scope ) ;
283295 }
296+ name = extractedServerlessCacheName ;
297+ } else if ( attrs . serverlessCacheName ) {
284298 name = attrs . serverlessCacheName ;
285299 arn = stack . formatArn ( {
286300 service : 'elasticache' ,
287301 resource : 'serverlesscache' ,
288302 resourceName : attrs . serverlessCacheName ,
289303 } ) ;
304+ } else {
305+ throw new ValidationError ( 'One of serverlessCacheName or serverlessCacheArn is required.' , scope ) ;
290306 }
291307
292308 class Import extends ServerlessCacheBase {
@@ -308,23 +324,24 @@ export class ServerlessCache extends ServerlessCacheBase {
308324 this . serverlessCacheName = serverlessCacheName ;
309325
310326 if ( this . engine ) {
311- const getDefaultPort = ( engine : CacheEngine ) : ec2 . Port => {
312- switch ( engine ) {
313- case CacheEngine . VALKEY_DEFAULT :
314- case CacheEngine . VALKEY_7 :
315- case CacheEngine . VALKEY_8 :
316- case CacheEngine . REDIS_DEFAULT :
317- return ec2 . Port . tcp ( 6379 ) ;
318- case CacheEngine . MEMCACHED_DEFAULT :
319- return ec2 . Port . tcp ( 11211 ) ;
320- default :
321- throw new ValidationError ( `Unsupported cache engine: ${ engine } ` , scope ) ;
322- }
323- } ;
327+ let defaultPort : ec2 . Port ;
328+ switch ( this . engine ) {
329+ case CacheEngine . VALKEY_DEFAULT :
330+ case CacheEngine . VALKEY_7 :
331+ case CacheEngine . VALKEY_8 :
332+ case CacheEngine . REDIS_DEFAULT :
333+ defaultPort = ec2 . Port . tcp ( 6379 ) ;
334+ break ;
335+ case CacheEngine . MEMCACHED_DEFAULT :
336+ defaultPort = ec2 . Port . tcp ( 11211 ) ;
337+ break ;
338+ default :
339+ throw new ValidationError ( `Unsupported cache engine: ${ this . engine } ` , scope ) ;
340+ }
324341
325342 this . connections = new ec2 . Connections ( {
326343 securityGroups : this . securityGroups ,
327- defaultPort : getDefaultPort ( this . engine ) ,
344+ defaultPort : defaultPort ,
328345 } ) ;
329346 } else {
330347 this . connections = new ec2 . Connections ( {
@@ -404,35 +421,18 @@ export class ServerlessCache extends ServerlessCacheBase {
404421 this . userGroup = props . userGroup ;
405422
406423 this . validateDescription ( props . description ) ;
407- this . validateUsageLimits ( props . cacheUsageLimits ) ;
424+ this . validateDataStorageLimits ( props . cacheUsageLimits ) ;
425+ this . validateRequestRateLimits ( props . cacheUsageLimits ) ;
408426 this . validateBackupSettings ( props . backup ) ;
409427 this . validateUserGroupCompatibility ( this . engine , this . userGroup ) ;
410428
411- let subnetIds : string [ ] | undefined ;
412- let securityGroupIds : string [ ] ;
413-
414- let selectedSubnets ;
415- if ( props . vpcSubnets ) {
416- selectedSubnets = props . vpc . selectSubnets ( props . vpcSubnets ) ;
417- } else {
418- selectedSubnets = props . vpc . selectSubnets ( {
419- subnetType : ec2 . SubnetType . PRIVATE_WITH_EGRESS ,
420- } ) ;
421- }
422- subnetIds = selectedSubnets . subnetIds . length > 0 ? selectedSubnets . subnetIds : undefined ;
423- this . subnets = selectedSubnets . subnets . length > 0 ? selectedSubnets . subnets : undefined ;
429+ const subnetConfig = this . configureSubnets ( props ) ;
430+ const subnetIds = subnetConfig . subnetIds ;
431+ this . subnets = subnetConfig . subnets ;
424432
425- if ( props . securityGroups && props . securityGroups . length > 0 ) {
426- securityGroupIds = props . securityGroups . map ( sg => sg . securityGroupId ) ;
427- this . securityGroups = props . securityGroups ;
428- } else {
429- const newSecurityGroup = new ec2 . SecurityGroup ( this , 'SecurityGroup' , {
430- description : `Security group for ${ this . node . id } cache.` ,
431- vpc : props . vpc ,
432- } ) ;
433- securityGroupIds = [ newSecurityGroup . securityGroupId ] ;
434- this . securityGroups = [ newSecurityGroup ] ;
435- }
433+ const securityGroupConfig = this . configureSecurityGroups ( props ) ;
434+ const securityGroupIds = securityGroupConfig . securityGroupIds ;
435+ this . securityGroups = securityGroupConfig . securityGroups ;
436436
437437 const { engine, version } = this . parseEngine ( this . engine ) ;
438438
@@ -491,34 +491,47 @@ export class ServerlessCache extends ServerlessCacheBase {
491491 }
492492
493493 /**
494- * Validate usage limits are within AWS constraints
494+ * Validate data storage size limits
495495 *
496- * @param limits The usage limits to validate
496+ * @param limits The usage limits containing data storage settings
497497 */
498- private validateUsageLimits ( limits ?: CacheUsageLimitsProperty ) : void {
499- if ( limits ?. dataStorageMinimumSize && ! limits . dataStorageMinimumSize . isUnresolved ( ) &&
500- ( limits . dataStorageMinimumSize . toGibibytes ( ) < 1 || limits . dataStorageMinimumSize . toGibibytes ( ) > 5000 ) ) {
498+ private validateDataStorageLimits ( limits ?: CacheUsageLimitsProperty ) : void {
499+ if ( ! limits ) return ;
500+
501+ if ( limits . dataStorageMinimumSize && ! limits . dataStorageMinimumSize . isUnresolved ( ) &&
502+ ( limits . dataStorageMinimumSize . toGibibytes ( ) < DATA_STORAGE_MIN_GB || limits . dataStorageMinimumSize . toGibibytes ( ) > DATA_STORAGE_MAX_GB ) ) {
501503 throw new ValidationError ( 'Data storage minimum must be between 1 and 5000 GB.' , this ) ;
502504 }
503-
504- if ( limits ?. dataStorageMaximumSize && ! limits . dataStorageMaximumSize . isUnresolved ( ) &&
505- ( limits . dataStorageMaximumSize . toGibibytes ( ) < 1 || limits . dataStorageMaximumSize . toGibibytes ( ) > 5000 ) ) {
505+ if ( limits . dataStorageMaximumSize && ! limits . dataStorageMaximumSize . isUnresolved ( ) &&
506+ ( limits . dataStorageMaximumSize . toGibibytes ( ) < DATA_STORAGE_MIN_GB || limits . dataStorageMaximumSize . toGibibytes ( ) > DATA_STORAGE_MAX_GB ) ) {
506507 throw new ValidationError ( 'Data storage maximum must be between 1 and 5000 GB.' , this ) ;
507508 }
508509
509- if ( limits ? .dataStorageMinimumSize && limits ? .dataStorageMaximumSize &&
510- ! limits . dataStorageMinimumSize . isUnresolved ( ) && ! limits . dataStorageMaximumSize . isUnresolved ( ) &&
511- limits . dataStorageMinimumSize . toGibibytes ( ) > limits . dataStorageMaximumSize . toGibibytes ( ) ) {
510+ if ( limits . dataStorageMinimumSize && limits . dataStorageMaximumSize &&
511+ ! limits . dataStorageMinimumSize . isUnresolved ( ) && ! limits . dataStorageMaximumSize . isUnresolved ( ) &&
512+ limits . dataStorageMinimumSize . toGibibytes ( ) > limits . dataStorageMaximumSize . toGibibytes ( ) ) {
512513 throw new ValidationError ( 'Data storage minimum cannot be greater than maximum' , this ) ;
513514 }
515+ }
516+
517+ /**
518+ * Validate request rate limits
519+ *
520+ * @param limits The usage limits containing request rate settings
521+ */
522+ private validateRequestRateLimits ( limits ?: CacheUsageLimitsProperty ) : void {
523+ if ( ! limits ) return ;
514524
515- if ( limits ?. requestRateLimitMinimum !== undefined && ( limits . requestRateLimitMinimum < 1000 || limits . requestRateLimitMinimum > 15000000 ) ) {
525+ if ( limits . requestRateLimitMinimum !== undefined &&
526+ ( limits . requestRateLimitMinimum < REQUEST_RATE_MIN_ECPU || limits . requestRateLimitMinimum > REQUEST_RATE_MAX_ECPU ) ) {
516527 throw new ValidationError ( 'Request rate minimum must be between 1,000 and 15,000,000 ECPUs per second' , this ) ;
517528 }
518- if ( limits ?. requestRateLimitMaximum !== undefined && ( limits . requestRateLimitMaximum < 1000 || limits . requestRateLimitMaximum > 15000000 ) ) {
529+ if ( limits . requestRateLimitMaximum !== undefined &&
530+ ( limits . requestRateLimitMaximum < REQUEST_RATE_MIN_ECPU || limits . requestRateLimitMaximum > REQUEST_RATE_MAX_ECPU ) ) {
519531 throw new ValidationError ( 'Request rate maximum must be between 1,000 and 15,000,000 ECPUs per second' , this ) ;
520532 }
521- if ( limits ?. requestRateLimitMinimum !== undefined && limits ?. requestRateLimitMaximum !== undefined &&
533+
534+ if ( limits . requestRateLimitMinimum !== undefined && limits . requestRateLimitMaximum !== undefined &&
522535 limits . requestRateLimitMinimum > limits . requestRateLimitMaximum ) {
523536 throw new ValidationError ( 'Request rate minimum cannot be greater than maximum' , this ) ;
524537 }
@@ -567,6 +580,10 @@ export class ServerlessCache extends ServerlessCacheBase {
567580 private validateUserGroupCompatibility ( engine : CacheEngine , userGroup ?: IUserGroup ) : void {
568581 if ( ! userGroup ) return ;
569582
583+ if ( engine === CacheEngine . MEMCACHED_DEFAULT ) {
584+ throw new ValidationError ( 'User groups cannot be used with Memcached engines. Only Redis and Valkey engines support user groups.' , this ) ;
585+ }
586+
570587 if ( engine === CacheEngine . REDIS_DEFAULT && userGroup . engine !== UserEngine . REDIS ) {
571588 throw new ValidationError ( 'Redis cache can only use Redis user groups.' , this ) ;
572589 }
@@ -601,6 +618,52 @@ export class ServerlessCache extends ServerlessCacheBase {
601618 return Object . keys ( cacheUsageLimits ) . length > 0 ? cacheUsageLimits : undefined ;
602619 }
603620
621+ /**
622+ * Configure subnets for the cache
623+ *
624+ * @param props The ServerlessCache properties
625+ * @returns Object containing subnet IDs and subnet objects
626+ */
627+ private configureSubnets ( props : ServerlessCacheProps ) : { subnetIds : string [ ] | undefined ; subnets : ec2 . ISubnet [ ] | undefined } {
628+ let selectedSubnets ;
629+ if ( props . vpcSubnets ) {
630+ selectedSubnets = props . vpc . selectSubnets ( props . vpcSubnets ) ;
631+ } else {
632+ selectedSubnets = props . vpc . selectSubnets ( {
633+ subnetType : ec2 . SubnetType . PRIVATE_WITH_EGRESS ,
634+ } ) ;
635+ }
636+
637+ return {
638+ subnetIds : selectedSubnets . subnetIds . length > 0 ? selectedSubnets . subnetIds : undefined ,
639+ subnets : selectedSubnets . subnets . length > 0 ? selectedSubnets . subnets : undefined ,
640+ } ;
641+ }
642+
643+ /**
644+ * Configure security groups for the cache
645+ *
646+ * @param props The ServerlessCache properties
647+ * @returns Object containing security group IDs and security group objects
648+ */
649+ private configureSecurityGroups ( props : ServerlessCacheProps ) : { securityGroupIds : string [ ] ; securityGroups : ec2 . ISecurityGroup [ ] } {
650+ if ( props . securityGroups && props . securityGroups . length > 0 ) {
651+ return {
652+ securityGroupIds : props . securityGroups . map ( sg => sg . securityGroupId ) ,
653+ securityGroups : props . securityGroups ,
654+ } ;
655+ } else {
656+ const newSecurityGroup = new ec2 . SecurityGroup ( this , 'SecurityGroup' , {
657+ description : `Security group for ${ this . node . id } cache.` ,
658+ vpc : props . vpc ,
659+ } ) ;
660+ return {
661+ securityGroupIds : [ newSecurityGroup . securityGroupId ] ,
662+ securityGroups : [ newSecurityGroup ] ,
663+ } ;
664+ }
665+ }
666+
604667 /**
605668 * Format schedule to HH:MM format for daily backups
606669 *
0 commit comments