From f781f1fd194ba81d40fd4edaa65f1f5217b14e23 Mon Sep 17 00:00:00 2001 From: Antonis Kalipetis Date: Tue, 5 Dec 2023 18:05:41 +0200 Subject: [PATCH] Stop the user in case they add resources that exceed their limit Since the current API does not provide a way to stop an update, this might lead in a state where the environment is not working and even doing forks will initialize new environment in this broken state. This PR tries to calculate if the requested update will exceed the current user limit and if it does, it stops the operation. In case the user wants to proceed, they can use the `--force` argument. --- src/Command/Resources/ResourcesSetCommand.php | 111 ++++++++++++++++++ 1 file changed, 111 insertions(+) diff --git a/src/Command/Resources/ResourcesSetCommand.php b/src/Command/Resources/ResourcesSetCommand.php index 83cf775328..a7cbb550cf 100644 --- a/src/Command/Resources/ResourcesSetCommand.php +++ b/src/Command/Resources/ResourcesSetCommand.php @@ -35,6 +35,7 @@ protected function configure() 'Set the disk size (in MB) of apps or services.' . "\nItems are in the format name:value as above." ) + ->addOption('force', 'f', InputOption::VALUE_NONE, 'Try to run the update, even if it might exceed your limits') ->addOption('dry-run', null, InputOption::VALUE_NONE, 'Show the changes that would be made, without changing anything') ->addProjectOption() ->addEnvironmentOption() @@ -134,11 +135,16 @@ protected function execute(InputInterface $input, OutputInterface $output) && $input->getOption('disk') === []; $updates = []; + $current = []; foreach ($services as $name => $service) { $type = $this->typeName($service); $group = $this->group($service); $properties = $service->getProperties(); + $current[$group][$name]['resources']['profile_size'] = $properties['resources']['profile_size']; + $current[$group][$name]['instance_count'] = $properties['instance_count']; + $current[$group][$name]['disk'] = $properties['disk']; + $current[$group][$name]['sizes'] = $containerProfiles[$properties['container_profile']]; $header = '' . ucfirst($type) . ': ' . $name . ''; $headerShown = false; @@ -259,6 +265,48 @@ protected function execute(InputInterface $input, OutputInterface $output) $this->debug('Raw updates: ' . json_encode($updates, JSON_UNESCAPED_SLASHES)); + $project = $this->getSelectedProject(); + $organization = $this->api()->getClient()->getOrganizationById($project->getProperty('organization')); + $profile = $organization->getProfile(); + if ($input->getOption('force') === false && isset($profile->resources_limit) && $profile->resources_limit) { + $diff = $this->computeMemoryCPUStorageDiff($updates, $current); + $limit = $profile->resources_limit['limit']; + $used = $profile->resources_limit['used']['totals']; + + $this->debug('Raw diff: ' . json_encode($diff, JSON_UNESCAPED_SLASHES)); + $this->debug('Raw limits: ' . json_encode($limit, JSON_UNESCAPED_SLASHES)); + $this->debug('Raw used: ' . json_encode($used, JSON_UNESCAPED_SLASHES)); + + $errored = false; + if ($limit['cpu'] < $used['cpu'] + $diff['cpu']) { + $this->stdErr->writeln(sprintf( + 'The requested resources will exceed your CPU limit, which is: %s.', + $limit['cpu'], + )); + $errored = true; + } + + if ($limit['memory'] < $used['memory'] + ($diff['memory'] / 1024)) { + $this->stdErr->writeln(sprintf( + 'The requested resources will exceed your memory limit, which is: %sGB.', + $limit['memory'], + )); + $errored = true; + } + + if ($limit['storage'] < $used['storage'] + ($diff['disk'] / 1024)) { + $this->stdErr->writeln(sprintf( + 'The requested resources will exceed your disk limit, which is: %sGB.', + $limit['disk'], + )); + $errored = true; + } + + if ($errored) { + return 1; + } + } + if ($input->getOption('dry-run')) { return 0; } @@ -581,4 +629,67 @@ private function formatErrors(array $errors, $optionName) } return $ret; } + + /** + * Compute the total memory/CPU/storage diff that will occur when the given update + * is applied. + * + * @param array $updates + * @param array $current + * + * @return array + */ + private function computeMemoryCPUStorageDiff(array $updates, array $current) + { + $diff = [ + 'memory' => 0, + 'cpu' => 0, + 'disk' => 0, + ]; + foreach ($updates as $group => $groupUpdates) { + foreach ($groupUpdates as $serviceName => $serviceUpdates) { + if ($serviceUpdates['resources']) { + if (isset($current[$group][$serviceName]['instance_count']) === false) { + $current[$group][$serviceName]['instance_count'] = 1; + } + if (isset($current[$group][$serviceName]['disk']) === false) { + $current[$group][$serviceName]['disk'] = 0; + } + + $currentCount = $current[$group][$serviceName]['instance_count']; + $currentSize = $current[$group][$serviceName]['resources']['profile_size']; + $currentStorage = $current[$group][$serviceName]['disk']; + + $newCount = $currentCount; + $newSize = $currentSize; + $newStorage = $currentStorage; + if (isset($serviceUpdates['instance_count'])) { + $newCount = $serviceUpdates['instance_count']; + } + if (isset($serviceUpdates['resources'])) { + $newSize = $serviceUpdates['resources']['profile_size']; + } + if (isset($serviceUpdates['disk'])) { + $newStorage = $serviceUpdates['disk']; + } + + $currentService = $current[$group][$serviceName]; + $currentSize = $currentService['resources']['profile_size']; + $currentProfile = $currentService['sizes'][$currentSize]; + $currentCPU = $currentCount * $currentProfile['cpu']; + $currentRAM = $currentCount * $currentProfile['memory']; + + $newProfile = $currentService['sizes'][$newSize]; + $newCPU = $newCount * $newProfile['cpu']; + $newRAM = $newCount * $newProfile['memory']; + + $diff['memory'] += $newRAM - $currentRAM; + $diff['cpu'] += $newCPU - $currentCPU; + $diff['disk'] += $newStorage - $currentStorage; + } + } + } + + return $diff; + } }