From 69f13383b9bf81008e516e260c6a79355558e359 Mon Sep 17 00:00:00 2001 From: Joe Dixon Date: Thu, 21 Dec 2023 14:12:47 +0000 Subject: [PATCH] [1.x] Implements serverless ElastiCache scaling (#242) * create serverless caches * update list caches for serverless * update allowed values * update show command for serverless * allow serverless scaling * formatting * formatting --- src/Commands/CacheCommand.php | 21 ++++++++--- src/Commands/CacheListCommand.php | 10 +++++- src/Commands/CacheMetricsCommand.php | 21 ++++++++++- src/Commands/CacheScaleCommand.php | 43 +++++++++++++++++++--- src/Commands/CacheShowCommand.php | 54 ++++++++++++++++++++++------ src/ConsoleVaporClient.php | 4 ++- 6 files changed, 130 insertions(+), 23 deletions(-) diff --git a/src/Commands/CacheCommand.php b/src/Commands/CacheCommand.php index 88af10e4..4b0a42af 100644 --- a/src/Commands/CacheCommand.php +++ b/src/Commands/CacheCommand.php @@ -44,7 +44,13 @@ public function handle() } $instanceClass = $this->determineInstanceClass(); - $type = $this->determineCacheType(); + + if ($instanceClass == 'serverless') { + $instanceClass = null; + $type = 'redis7.x-serverless'; + } else { + $type = $this->determineCacheType(); + } $response = $this->vapor->createCache( $networkId, @@ -67,7 +73,7 @@ protected function determineCacheType() { return $this->menu('Which type of cache would you like to create?', [ 'redis6.x-cluster' => 'Redis 6.x Cluster', - 'redis-cluster' => 'Redis 5.x Cluster', + 'redis-cluster' => 'Redis 5.x Cluster', ]); } @@ -79,15 +85,20 @@ protected function determineCacheType() protected function determineInstanceClass() { $type = $this->menu('Which type of cache instance would you like to create?', [ + 'serverless' => 'Serverless', 'general' => 'General Purpose', - 'memory' => 'Memory Optimized', + 'memory' => 'Memory Optimized', ]); + if ($type == 'serverless') { + return 'serverless'; + } + if ($type == 'general') { return $this->determineGeneralInstanceClass(); - } else { - return $this->determineMemoryOptimizedInstanceClass(); } + + return $this->determineMemoryOptimizedInstanceClass(); } /** diff --git a/src/Commands/CacheListCommand.php b/src/Commands/CacheListCommand.php index 6400fc10..451c7a30 100644 --- a/src/Commands/CacheListCommand.php +++ b/src/Commands/CacheListCommand.php @@ -51,6 +51,14 @@ public function handle() */ protected function cacheType($type) { - return $type == 'redis6.x-cluster' ? 'Redis 6.x Cluster' : 'Redis 5.x Cluster'; + if ($type == 'redis7.x-serverless') { + return 'Redis 7.x Serverless'; + } + + if ($type == 'redis6.x-cluster') { + return 'Redis 6.x Cluster'; + } + + return 'Redis 5.x Cluster'; } } diff --git a/src/Commands/CacheMetricsCommand.php b/src/Commands/CacheMetricsCommand.php index 7cc2100f..d4a9c17c 100644 --- a/src/Commands/CacheMetricsCommand.php +++ b/src/Commands/CacheMetricsCommand.php @@ -17,7 +17,7 @@ protected function configure() $this ->setName('cache:metrics') ->addArgument('cache', InputArgument::REQUIRED, 'The cache name / ID') - ->addArgument('period', InputArgument::OPTIONAL, 'The metric period (1m, 5m, 1h, 8h, 1d, 3d, 7d, 1M)', '1d') + ->addArgument('period', InputArgument::OPTIONAL, 'The metric period (5m, 30m, 1h, 8h, 1d, 3d, 7d, 1M)', '1d') ->setDescription('Get usage and performance metrics for a cache'); } @@ -43,6 +43,10 @@ public function handle() $this->argument('period') ); + if (isset($metrics['averageCacheProcessingUnits'])) { + return $this->serverlessMetrics($metrics); + } + $this->table([ 'Node', 'Average CPU Utilization', 'Cache Hits', 'Cache Misses', ], collect(range(0, count($metrics['totalCacheHits']) - 1))->map(function ($node) use ($metrics) { @@ -54,4 +58,19 @@ public function handle() ]; })->all()); } + + /** + * Format the serverless metrics for display. + */ + protected function serverlessMetrics(array $metrics): void + { + $this->table([ + 'Average CPU (ECPU Units)', 'Average Memory Utilization (Bytes)', 'Cache Hits', 'Cache Misses', + ], [[ + number_format($metrics['averageCacheProcessingUnits'][0]), + number_format($metrics['averageCacheBytesUsed'][0]), + $metrics['totalCacheHits'][0], + $metrics['totalCacheMisses'][0], + ]]); + } } diff --git a/src/Commands/CacheScaleCommand.php b/src/Commands/CacheScaleCommand.php index 12e6dbc1..db51b1c8 100644 --- a/src/Commands/CacheScaleCommand.php +++ b/src/Commands/CacheScaleCommand.php @@ -4,6 +4,7 @@ use Laravel\VaporCli\Helpers; use Symfony\Component\Console\Input\InputArgument; +use Symfony\Component\Console\Input\InputOption; class CacheScaleCommand extends Command { @@ -17,7 +18,9 @@ protected function configure() $this ->setName('cache:scale') ->addArgument('cache', InputArgument::REQUIRED, 'The cache name / ID') - ->addArgument('scale', InputArgument::REQUIRED, 'The number of nodes that should be in the cache cluster') + ->addArgument('scale', InputArgument::OPTIONAL, 'The number of nodes that should be in the cache cluster') + ->addOption('memory', null, InputOption::VALUE_OPTIONAL, 'The maximum amount of memory that can be used by the serverless cache') + ->addOption('cpu', null, InputOption::VALUE_OPTIONAL, 'The maximum amount of ECPUs that can be used by the serverless cache') ->setDescription('Modify the number of nodes in a cache cluster'); } @@ -40,13 +43,43 @@ public function handle() $cache = $this->vapor->cache($cacheId); - $this->vapor->scaleCache( - $cacheId, - $this->argument('scale') - ); + if ($cache['type'] === 'redis7.x-serverless') { + $this->scaleServerlessCache($cacheId); + } else { + $this->scaleCacheCluster($cacheId); + } Helpers::info('Cache modification initiated successfully.'); Helpers::line(); Helpers::line('Caches may take several minutes to finish scaling.'); } + + /** + * Scale a serverless cache. + */ + protected function scaleServerlessCache(int $cacheId): void + { + if (is_null($this->option('memory')) || is_null($this->option('cpu'))) { + Helpers::abort('You must specify both the memory and CPU limits. To remove the either limit, set it to 0.'); + } + + $this->vapor->scaleCache( + $cacheId, + null, + $this->option('memory') === '0' ? null : $this->option('memory'), + $this->option('cpu') === '0' ? null : $this->option('cpu') + ); + } + + /** + * Scale a cache cluster. + */ + protected function scaleCacheCluster(int $cacheId): void + { + if (! $scale = $this->argument('scale')) { + Helpers::abort('You must specify the number of nodes to scale the cache to.'); + } + + $this->vapor->scaleCache($cacheId, $scale); + } } diff --git a/src/Commands/CacheShowCommand.php b/src/Commands/CacheShowCommand.php index 78db5859..a44a660d 100644 --- a/src/Commands/CacheShowCommand.php +++ b/src/Commands/CacheShowCommand.php @@ -40,6 +40,50 @@ public function handle() $cache = $this->vapor->cache($cacheId); + if ($cache['type'] === 'redis7.x-serverless') { + $this->showServerlessCache($cache); + } else { + $this->showCacheCluster($cache); + } + + if ($cache['endpoint']) { + Helpers::line(); + + Helpers::line(' Endpoint: '.$cache['endpoint']); + } + + Helpers::line(); + + $this->call('cache:metrics', ['cache' => $this->argument('cache')]); + } + + /** + * Render serverless cache details. + */ + protected function showServerlessCache(array $cache): void + { + $this->table([ + 'ID', 'Provider', 'Name', 'Region', 'Class', 'Snapshot Retention (Days)', 'Memory Limit (Bytes)', 'ECPU Limit', 'Status', + ], collect([$cache])->map(function ($cache) { + return [ + $cache['id'], + $cache['cloud_provider']['name'], + $cache['name'], + $cache['region'], + 'Serverless', + ($snapshotLimit = $cache['snapshot_retention_limit'] ?? null) ? $snapshotLimit : 'N/A', + ($memoryLimit = $cache['memory_limit'] ?? null) ? $memoryLimit : 'Unlimited', + ($cpuLimit = $cache['cpu_limit'] ?? null) ? $cpuLimit : 'Unlimited', + Str::title(str_replace('_', ' ', $cache['status'])), + ]; + })->all()); + } + + /** + * Render cluster cache details. + */ + protected function showCacheCluster(array $cache): void + { $this->table([ 'ID', 'Provider', 'Name', 'Region', 'Class', 'Scale', 'Status', ], collect([$cache])->map(function ($cache) { @@ -53,15 +97,5 @@ public function handle() Str::title(str_replace('_', ' ', $cache['status'])), ]; })->all()); - - if ($cache['endpoint']) { - Helpers::line(); - - Helpers::line(' Endpoint: '.$cache['endpoint']); - } - - Helpers::line(); - - $this->call('cache:metrics', ['cache' => $this->argument('cache')]); } } diff --git a/src/ConsoleVaporClient.php b/src/ConsoleVaporClient.php index 3ac7d025..efcca0f0 100644 --- a/src/ConsoleVaporClient.php +++ b/src/ConsoleVaporClient.php @@ -598,10 +598,12 @@ public function createCache($networkId, $name, $type, $instanceClass) * @param int $scale * @return void */ - public function scaleCache($cacheId, $scale) + public function scaleCache($cacheId, $scale = null, $memoryLimit = null, $cpuLimit = null) { $this->requestWithErrorHandling('put', '/api/caches/'.$cacheId.'/size', [ 'scale' => $scale, + 'memory_limit' => $memoryLimit, + 'cpu_limit' => $cpuLimit, ]); }