From 27b97ab9825de05bfc1333be1d707667b6c0a23c Mon Sep 17 00:00:00 2001 From: Simon Graband Date: Wed, 3 Jul 2024 12:44:15 +0200 Subject: [PATCH] Fix maxInstances error --- .../handler/util/TheiaCloudK8sUtil.java | 12 +++++--- .../cloud/operator/util/JavaResourceUtil.java | 5 ++-- .../eclipse/theia/cloud/service/K8sUtil.java | 29 +++++++++++++++++-- .../theia/cloud/service/RootResource.java | 6 ++++ 4 files changed, 43 insertions(+), 9 deletions(-) diff --git a/java/operator/org.eclipse.theia.cloud.operator/src/main/java/org/eclipse/theia/cloud/operator/handler/util/TheiaCloudK8sUtil.java b/java/operator/org.eclipse.theia.cloud.operator/src/main/java/org/eclipse/theia/cloud/operator/handler/util/TheiaCloudK8sUtil.java index cab90b1c..cd413925 100644 --- a/java/operator/org.eclipse.theia.cloud.operator/src/main/java/org/eclipse/theia/cloud/operator/handler/util/TheiaCloudK8sUtil.java +++ b/java/operator/org.eclipse.theia.cloud.operator/src/main/java/org/eclipse/theia/cloud/operator/handler/util/TheiaCloudK8sUtil.java @@ -20,6 +20,7 @@ import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; +import org.eclipse.theia.cloud.common.k8s.resource.OperatorStatus; import org.eclipse.theia.cloud.common.k8s.resource.appdefinition.AppDefinitionSpec; import org.eclipse.theia.cloud.common.k8s.resource.session.Session; import org.eclipse.theia.cloud.common.k8s.resource.session.SessionSpec; @@ -41,7 +42,7 @@ public static boolean checkIfMaxInstancesReached(NamespacedKubernetesClient clie if (appDefinitionSpec.getMaxInstances() == null || appDefinitionSpec.getMaxInstances() < 0) { LOGGER.debug(formatLogMessage(correlationId, - "App Definition " + appDefinitionSpec.getName() + " allows indefinite sessions.")); + "App Definition " + appDefinitionSpec.getName() + " allows infinite sessions.")); return false; } @@ -56,9 +57,12 @@ public static boolean checkIfMaxInstancesReached(NamespacedKubernetesClient clie .list().getItems().stream()// .filter(w -> { String sessionAppDefinition = w.getSpec().getAppDefinition(); - boolean result = appDefinitionName.equals(sessionAppDefinition); - LOGGER.trace(formatLogMessage(correlationId, "Counting instances of app definition " - + appDefinitionSpec.getName() + ": Is " + w.getSpec() + " of app definition? " + result)); + // Errored resources should not be counted + boolean result = appDefinitionName.equals(sessionAppDefinition) + && !OperatorStatus.ERROR.equals(w.getStatus().getOperatorStatus()); + LOGGER.trace(formatLogMessage(correlationId, + "Counting handled instances of app definition " + appDefinitionSpec.getName() + ": Is " + + w.getSpec() + " of app definition and handled? " + result)); return result; })// .count(); diff --git a/java/operator/org.eclipse.theia.cloud.operator/src/main/java/org/eclipse/theia/cloud/operator/util/JavaResourceUtil.java b/java/operator/org.eclipse.theia.cloud.operator/src/main/java/org/eclipse/theia/cloud/operator/util/JavaResourceUtil.java index 104e7aed..d988cb4a 100644 --- a/java/operator/org.eclipse.theia.cloud.operator/src/main/java/org/eclipse/theia/cloud/operator/util/JavaResourceUtil.java +++ b/java/operator/org.eclipse.theia.cloud.operator/src/main/java/org/eclipse/theia/cloud/operator/util/JavaResourceUtil.java @@ -47,12 +47,13 @@ public static String readResourceAndReplacePlaceholders(String resourceName, Map try (InputStream inputStream = getInputStream(resourceName, correlationId)) { String template = new BufferedReader(new InputStreamReader(inputStream)).lines().parallel() .collect(Collectors.joining("\n")); + LOGGER.trace(formatLogMessage(correlationId, "Replacing placeholders. Starting template:\n" + template)); for (Entry replacement : replacements.entrySet()) { String value = replacement.getValue() != null ? replacement.getValue() : ""; template = template.replace(replacement.getKey(), value); - LOGGER.trace(formatLogMessage(correlationId, - "Replaced " + replacement.getKey() + " with " + value + " :\n" + template)); + LOGGER.trace(formatLogMessage(correlationId, "Replaced " + replacement.getKey() + " with " + value)); } + LOGGER.trace(formatLogMessage(correlationId, "Replacement finished. Result:\n" + template)); return template; } } diff --git a/java/service/org.eclipse.theia.cloud.service/src/main/java/org/eclipse/theia/cloud/service/K8sUtil.java b/java/service/org.eclipse.theia.cloud.service/src/main/java/org/eclipse/theia/cloud/service/K8sUtil.java index 9f93fd87..9f1fdc65 100644 --- a/java/service/org.eclipse.theia.cloud.service/src/main/java/org/eclipse/theia/cloud/service/K8sUtil.java +++ b/java/service/org.eclipse.theia.cloud.service/src/main/java/org/eclipse/theia/cloud/service/K8sUtil.java @@ -25,6 +25,7 @@ import org.eclipse.theia.cloud.common.k8s.client.DefaultTheiaCloudClient; import org.eclipse.theia.cloud.common.k8s.client.TheiaCloudClient; +import org.eclipse.theia.cloud.common.k8s.resource.appdefinition.AppDefinition; import org.eclipse.theia.cloud.common.k8s.resource.session.Session; import org.eclipse.theia.cloud.common.k8s.resource.session.SessionSpec; import org.eclipse.theia.cloud.common.k8s.resource.session.SessionStatus; @@ -33,6 +34,7 @@ import org.eclipse.theia.cloud.common.util.CustomResourceUtil; import org.eclipse.theia.cloud.service.session.SessionPerformance; import org.eclipse.theia.cloud.service.workspace.UserWorkspace; +import org.jboss.logging.Logger; import io.fabric8.kubernetes.api.model.Container; import io.fabric8.kubernetes.api.model.EnvVar; @@ -48,7 +50,9 @@ @ApplicationScoped public final class K8sUtil { private NamespacedKubernetesClient KUBERNETES = CustomResourceUtil.createClient(); - private TheiaCloudClient CLIENT = new DefaultTheiaCloudClient(KUBERNETES); + public TheiaCloudClient CLIENT = new DefaultTheiaCloudClient(KUBERNETES); + + protected final Logger logger = Logger.getLogger(getClass()); public Workspace createWorkspace(String correlationId, UserWorkspace data) { WorkspaceSpec spec = new WorkspaceSpec(data.name, data.label, data.appDefinition, data.user); @@ -178,15 +182,34 @@ private boolean isPodFromSession(Pod pod, Session session) { } Container container = optionalContainer.get(); Optional optionalEnv = container.getEnv().stream() - .filter(env -> env.getName().equals("THEIA_CLOUD_SESSION_NAME")).findFirst(); + .filter(env -> env.getName().equals("THEIACLOUD_SESSION_NAME")).findFirst(); if (optionalEnv.isEmpty()) { return false; } EnvVar env = optionalEnv.get(); - return env.getValue().equals(session.getSpec().getName()) ? true : false; + return env.getValue().equals(session.getSpec().getName()); } public boolean hasAppDefinition(String appDefinition) { return CLIENT.appDefinitions().get(appDefinition).isPresent(); } + + public boolean isMaxInstancesReached(String appDefString) { + Optional optAppDef = CLIENT.appDefinitions().get(appDefString); + if (!optAppDef.isPresent()) { + return true; // appDef does not exist, so we already reached the maximum number of instances + } + AppDefinition appDef = optAppDef.get(); + if (appDef.getSpec().getMaxInstances() == null) { + return false; // max instances is not set, so we do not have a limit + } + long maxInstances = appDef.getSpec().getMaxInstances(); + if (maxInstances < 0) { + return false; // max instances is set to negative, so we can ignore it + } + long podsOfAppDef = CLIENT.sessions().list().stream() // All sessions + .filter(s -> s.getSpec().getAppDefinition().equals(appDefString)) // That are from the appDefinition + .filter(s -> getPodForSession(s).isPresent()).count(); // That already have a pod + return podsOfAppDef >= maxInstances; + } } diff --git a/java/service/org.eclipse.theia.cloud.service/src/main/java/org/eclipse/theia/cloud/service/RootResource.java b/java/service/org.eclipse.theia.cloud.service/src/main/java/org/eclipse/theia/cloud/service/RootResource.java index 5c8193a8..6d95f228 100644 --- a/java/service/org.eclipse.theia.cloud.service/src/main/java/org/eclipse/theia/cloud/service/RootResource.java +++ b/java/service/org.eclipse.theia.cloud.service/src/main/java/org/eclipse/theia/cloud/service/RootResource.java @@ -65,6 +65,12 @@ public String launch(LaunchRequest request) { "Failed to launch session. App Definition '" + request.appDefinition + "' does not exist."); throw new TheiaCloudWebException(TheiaCloudError.INVALID_APP_DEFINITION_NAME); } + + if (k8sUtil.isMaxInstancesReached(request.appDefinition)) { + error(correlationId, "Failed to launch session. App Definition '" + request.appDefinition + + "' has max instances reached."); + throw new TheiaCloudWebException(TheiaCloudError.SESSION_SERVER_LIMIT_REACHED); + } if (request.isEphemeral()) { info(correlationId, "Launching ephemeral session " + request);