diff --git a/src/main/java/net/geant/nmaas/nmservice/deployment/containerorchestrators/kubernetes/KServiceLifecycleManager.java b/src/main/java/net/geant/nmaas/nmservice/deployment/containerorchestrators/kubernetes/KServiceLifecycleManager.java index 8a521c7ebcf5d8d38530da81cc97bd68dd9fcb51..87cc2328bf2e9b0ecc5208687ee2f5af8274cffc 100644 --- a/src/main/java/net/geant/nmaas/nmservice/deployment/containerorchestrators/kubernetes/KServiceLifecycleManager.java +++ b/src/main/java/net/geant/nmaas/nmservice/deployment/containerorchestrators/kubernetes/KServiceLifecycleManager.java @@ -2,6 +2,7 @@ package net.geant.nmaas.nmservice.deployment.containerorchestrators.kubernetes; import net.geant.nmaas.nmservice.deployment.containerorchestrators.kubernetes.entities.KubernetesTemplate; import net.geant.nmaas.orchestration.Identifier; +import net.geant.nmaas.orchestration.entities.AppDeployment; public interface KServiceLifecycleManager { @@ -15,4 +16,6 @@ public interface KServiceLifecycleManager { void updateHelmRepo(); + void scaleDeployment(AppDeployment deployment, int replicas); + } diff --git a/src/main/java/net/geant/nmaas/nmservice/deployment/containerorchestrators/kubernetes/components/helm/HelmCommandExecutor.java b/src/main/java/net/geant/nmaas/nmservice/deployment/containerorchestrators/kubernetes/components/helm/HelmCommandExecutor.java index 01a3fd9b4307cc5eea008c719b76d0442d1d6b4c..2ecfd4a0e9f62cf7beb7c845edd6eeca4757aff7 100644 --- a/src/main/java/net/geant/nmaas/nmservice/deployment/containerorchestrators/kubernetes/components/helm/HelmCommandExecutor.java +++ b/src/main/java/net/geant/nmaas/nmservice/deployment/containerorchestrators/kubernetes/components/helm/HelmCommandExecutor.java @@ -169,5 +169,20 @@ public class HelmCommandExecutor { throw new CommandExecutionException("Failed to execute helm repository add command -> " + e.getMessage()); } } + public void executeScaleCommand(String namespace, String releaseName, int replicas) { + try { + HelmUpgradeCommand command = HelmUpgradeCommand.commandWithSetValues( + helmVersion, + namespace, + releaseName, + List.of("replicaCount=" + replicas), + enableTls + ); + + commandExecutor.execute(command); + } catch (CommandExecutionException e) { + throw new CommandExecutionException("Failed to execute helm scale command -> " + e.getMessage()); + } + } } diff --git a/src/main/java/net/geant/nmaas/nmservice/deployment/containerorchestrators/kubernetes/components/helm/HelmKServiceManager.java b/src/main/java/net/geant/nmaas/nmservice/deployment/containerorchestrators/kubernetes/components/helm/HelmKServiceManager.java index 753891c45e3e7d4b994bb74ab1a777580518e248..c89387d25d3d432e5eaa2ea0aa083e3ddf8d1982 100644 --- a/src/main/java/net/geant/nmaas/nmservice/deployment/containerorchestrators/kubernetes/components/helm/HelmKServiceManager.java +++ b/src/main/java/net/geant/nmaas/nmservice/deployment/containerorchestrators/kubernetes/components/helm/HelmKServiceManager.java @@ -15,10 +15,11 @@ import net.geant.nmaas.nmservice.deployment.containerorchestrators.kubernetes.en import net.geant.nmaas.nmservice.deployment.containerorchestrators.kubernetes.entities.ServiceStorageVolume; import net.geant.nmaas.nmservice.deployment.containerorchestrators.kubernetes.exceptions.KServiceManipulationException; import net.geant.nmaas.orchestration.Identifier; +import net.geant.nmaas.orchestration.entities.AppDeployment; import net.geant.nmaas.orchestration.repositories.DomainTechDetailsRepository; +import net.geant.nmaas.utils.bash.CommandExecutionException; import net.geant.nmaas.utils.logging.LogLevel; import net.geant.nmaas.utils.logging.Loggable; -import net.geant.nmaas.utils.bash.CommandExecutionException; import org.springframework.beans.factory.annotation.Value; import org.springframework.stereotype.Component; @@ -114,12 +115,12 @@ public class HelmKServiceManager implements KServiceLifecycleManager { static Map<String, String> removeRedundantParameters(Map<String, String> additionalParameters) { return additionalParameters.entrySet().stream().filter(entry -> - !entry.getKey().contains(RANDOM_ARGUMENT_EXPRESSION_PREFIX) - && !entry.getKey().contains(PUBLIC_ACCESS_SELECTOR_ARGUMENT_EXPRESSION_PREFIX) - ).collect(Collectors.toMap(Map.Entry::getKey, Map.Entry::getValue)); + !entry.getKey().contains(RANDOM_ARGUMENT_EXPRESSION_PREFIX) + && !entry.getKey().contains(PUBLIC_ACCESS_SELECTOR_ARGUMENT_EXPRESSION_PREFIX) + ).collect(Collectors.toMap(Map.Entry::getKey, Map.Entry::getValue)); } - private Map<String, String> getIngressVariables(IngressResourceConfigOption ingressResourceConfigOption, Set<ServiceAccessMethod> externalAccessMethods, String domain){ + private Map<String, String> getIngressVariables(IngressResourceConfigOption ingressResourceConfigOption, Set<ServiceAccessMethod> externalAccessMethods, String domain) { return HelmChartVariables.ingressVariablesMap( DEPLOY_FROM_CHART.equals(ingressResourceConfigOption), externalAccessMethods, @@ -131,7 +132,7 @@ public class HelmKServiceManager implements KServiceLifecycleManager { ); } - private String getIngressClass(String domain){ + private String getIngressClass(String domain) { if (Boolean.TRUE.equals(ingressManager.getIngressPerDomain())) { return domainTechDetailsRepository.findByDomainCodename(domain).orElseThrow(() -> new IllegalArgumentException("DomainTechDetails cannot be found for domain " + domain)).getKubernetesIngressClass(); } @@ -191,4 +192,12 @@ public class HelmKServiceManager implements KServiceLifecycleManager { } } + @Override + public void scaleDeployment(AppDeployment deployment, int replicas) { + KubernetesNmServiceInfo serviceInfo = repositoryManager.loadService(deployment.getDeploymentId()); + String namespace = namespaceService.namespace(serviceInfo.getDomain()); + String releaseName = deployment.getDeploymentId().value(); + + helmCommandExecutor.executeScaleCommand(namespace, releaseName, replicas); + } } \ No newline at end of file diff --git a/src/main/java/net/geant/nmaas/nmservice/deployment/containerorchestrators/kubernetes/components/helm/commands/HelmUpgradeCommand.java b/src/main/java/net/geant/nmaas/nmservice/deployment/containerorchestrators/kubernetes/components/helm/commands/HelmUpgradeCommand.java index 0b0cbb3cd17e36c8f839afb7757d3b511af55b36..1f64a1a04974d9068eca9e28bc701e78cbea15a7 100644 --- a/src/main/java/net/geant/nmaas/nmservice/deployment/containerorchestrators/kubernetes/components/helm/commands/HelmUpgradeCommand.java +++ b/src/main/java/net/geant/nmaas/nmservice/deployment/containerorchestrators/kubernetes/components/helm/commands/HelmUpgradeCommand.java @@ -3,6 +3,7 @@ package net.geant.nmaas.nmservice.deployment.containerorchestrators.kubernetes.c import com.google.common.base.Strings; import net.geant.nmaas.nmservice.deployment.containerorchestrators.kubernetes.components.helm.HelmCommand; +import java.util.List; import java.util.function.Predicate; public class HelmUpgradeCommand extends HelmCommand { @@ -78,4 +79,28 @@ public class HelmUpgradeCommand extends HelmCommand { return o -> !o.startsWith("Error"); } + public static HelmUpgradeCommand commandWithSetValues(String helmVersion, String namespace, String releaseName, List<String> setValues, boolean enableTls) { + if (!HELM_VERSION_3.equals(helmVersion)) { + throw new IllegalArgumentException("Upgrades are not supported for Helm v2"); + } + if (releaseName == null || releaseName.isEmpty()) { + throw new IllegalArgumentException("Name of the release can't be null or empty"); + } + + StringBuilder sb = new StringBuilder(); + sb.append(HELM) + .append(SPACE).append(UPGRADE) + .append(SPACE).append(OPTION_NAMESPACE).append(SPACE).append(namespace) + .append(SPACE).append(releaseName) + .append(SPACE).append(releaseName); + + for (String setValue : setValues) { + sb.append(SPACE).append("--set").append(SPACE).append(setValue); + } + + addTlsOptionIfRequired(helmVersion, enableTls, sb); + return new HelmUpgradeCommand(sb.toString()); + } + + } diff --git a/src/main/java/net/geant/nmaas/orchestration/AppLifecycleManager.java b/src/main/java/net/geant/nmaas/orchestration/AppLifecycleManager.java index 94dbad6d4c431158915338caf4d0cd809f778e56..f4410a9345f74aece30c910208f21347b62a371a 100644 --- a/src/main/java/net/geant/nmaas/orchestration/AppLifecycleManager.java +++ b/src/main/java/net/geant/nmaas/orchestration/AppLifecycleManager.java @@ -90,4 +90,7 @@ public interface AppLifecycleManager { * @param deploymentId unique identifier of the deployed user application */ void updateApplicationStatus(Identifier deploymentId); + + void scaleDown(Identifier deploymentId); + void scaleUp(Identifier deploymentId); } diff --git a/src/main/java/net/geant/nmaas/orchestration/DefaultAppLifecycleManager.java b/src/main/java/net/geant/nmaas/orchestration/DefaultAppLifecycleManager.java index 922afaeb1fb79a51e11648bcf95aefbdfa92a075..dc78257a67fb4fe3eefe4485e4cb0809d3cd0967 100644 --- a/src/main/java/net/geant/nmaas/orchestration/DefaultAppLifecycleManager.java +++ b/src/main/java/net/geant/nmaas/orchestration/DefaultAppLifecycleManager.java @@ -7,6 +7,7 @@ import lombok.extern.slf4j.Slf4j; import net.geant.nmaas.nmservice.NmServiceDeploymentStateChangeEvent; import net.geant.nmaas.nmservice.configuration.exceptions.UserConfigHandlingException; import net.geant.nmaas.nmservice.deployment.NmServiceRepositoryManager; +import net.geant.nmaas.nmservice.deployment.containerorchestrators.kubernetes.KServiceLifecycleManager; import net.geant.nmaas.nmservice.deployment.containerorchestrators.kubernetes.components.janitor.JanitorService; import net.geant.nmaas.nmservice.deployment.entities.NmServiceDeploymentState; import net.geant.nmaas.orchestration.api.model.AppConfigurationView; @@ -54,6 +55,9 @@ public class DefaultAppLifecycleManager implements AppLifecycleManager { private final ApplicationEventPublisher eventPublisher; private final NmServiceRepositoryManager serviceRepositoryManager; private final JanitorService janitorService; + private final KServiceLifecycleManager kServiceLifecycleManager; + + private final AppTermsAcceptanceService appTermsAcceptanceService; private final ConfigurationManager configurationManager; @@ -277,5 +281,32 @@ public class DefaultAppLifecycleManager implements AppLifecycleManager { public void updateApplicationStatus(Identifier deploymentId) { eventPublisher.publishEvent(new AppVerifyServiceActionEvent(this, deploymentId)); } + @Override + @Loggable(LogLevel.INFO) + @Transactional(propagation = Propagation.REQUIRES_NEW) + public void scaleDown(Identifier deploymentId) { + AppDeployment appDeployment = deploymentRepositoryManager.load(deploymentId); + + kServiceLifecycleManager.scaleDeployment(appDeployment, 0); + + appDeployment.setState(AppDeploymentState.SCALED_DOWN); + deploymentRepositoryManager.update(appDeployment); + + log.info("Scaled down deployment [{}]", deploymentId.value()); + } + @Override + @Loggable(LogLevel.INFO) + @Transactional(propagation = Propagation.REQUIRES_NEW) + public void scaleUp(Identifier deploymentId) { + AppDeployment appDeployment = deploymentRepositoryManager.load(deploymentId); + + int replicas = 1; + + kServiceLifecycleManager.scaleDeployment(appDeployment, replicas); + + deploymentRepositoryManager.update(appDeployment); + + log.info("Scaled up deployment [{}]", deploymentId.value()); + } } diff --git a/src/main/java/net/geant/nmaas/orchestration/api/AppLifecycleManagerRestController.java b/src/main/java/net/geant/nmaas/orchestration/api/AppLifecycleManagerRestController.java index 315e0c1578bc90fc42aee76873dfa3480566d33a..e8caf8514bc81a3e900f0fdc6a4e7755634b0a83 100644 --- a/src/main/java/net/geant/nmaas/orchestration/api/AppLifecycleManagerRestController.java +++ b/src/main/java/net/geant/nmaas/orchestration/api/AppLifecycleManagerRestController.java @@ -11,16 +11,9 @@ import net.geant.nmaas.orchestration.exceptions.InvalidDeploymentIdException; import net.geant.nmaas.portal.persistent.entity.Application; import net.geant.nmaas.portal.persistent.repositories.ApplicationRepository; import org.springframework.http.HttpStatus; +import org.springframework.http.ResponseEntity; import org.springframework.security.access.prepost.PreAuthorize; -import org.springframework.web.bind.annotation.DeleteMapping; -import org.springframework.web.bind.annotation.ExceptionHandler; -import org.springframework.web.bind.annotation.PathVariable; -import org.springframework.web.bind.annotation.PostMapping; -import org.springframework.web.bind.annotation.RequestBody; -import org.springframework.web.bind.annotation.RequestMapping; -import org.springframework.web.bind.annotation.RequestParam; -import org.springframework.web.bind.annotation.ResponseStatus; -import org.springframework.web.bind.annotation.RestController; +import org.springframework.web.bind.annotation.*; import java.security.Principal; @@ -116,4 +109,17 @@ public class AppLifecycleManagerRestController { return ex.getMessage(); } + + @PutMapping("/{deploymentId}/scale-down") + public ResponseEntity<Void> scaleDownApp(@PathVariable String deploymentId) { + lifecycleManager.scaleDown(new Identifier(deploymentId)); + return ResponseEntity.ok().build(); + } + + @PutMapping("/{deploymentId}/scale-up") + public ResponseEntity<Void> scaleUpApp(@PathVariable String deploymentId) { + lifecycleManager.scaleUp(new Identifier(deploymentId)); + return ResponseEntity.ok().build(); + } + } diff --git a/src/main/java/net/geant/nmaas/orchestration/entities/AppDeploymentState.java b/src/main/java/net/geant/nmaas/orchestration/entities/AppDeploymentState.java index b35e07d6321a576a04040fb6928e7bd8cd4c0c96..21d54f0d3d8fc59602c02764c1a251e290e3036b 100644 --- a/src/main/java/net/geant/nmaas/orchestration/entities/AppDeploymentState.java +++ b/src/main/java/net/geant/nmaas/orchestration/entities/AppDeploymentState.java @@ -541,6 +541,12 @@ public enum AppDeploymentState { @Override public boolean isInFailedState() { return true; } + }, + SCALED_DOWN { + @Override + public AppLifecycleState lifecycleState() { + return null; + } }; public abstract AppLifecycleState lifecycleState();