From cef6250e5fcaddacef2b2b81436fefc585740375 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bernardo=20De=20Marco=20Gon=C3=A7alves?= Date: Fri, 25 Apr 2025 15:23:05 -0300 Subject: [PATCH] validate naming of k8s clusters --- .../apache/cloudstack/api/ApiConstants.java | 4 ++ .../cluster/KubernetesClusterManagerImpl.java | 32 ++++++++++-- ...esClusterResourceModifierActionWorker.java | 13 +---- .../cluster/CreateKubernetesClusterCmd.java | 2 +- .../KubernetesClusterManagerImplTest.java | 50 +++++++++++++++++++ 5 files changed, 85 insertions(+), 16 deletions(-) diff --git a/api/src/main/java/org/apache/cloudstack/api/ApiConstants.java b/api/src/main/java/org/apache/cloudstack/api/ApiConstants.java index b2042c116a7f..25201f096774 100644 --- a/api/src/main/java/org/apache/cloudstack/api/ApiConstants.java +++ b/api/src/main/java/org/apache/cloudstack/api/ApiConstants.java @@ -1107,6 +1107,10 @@ public class ApiConstants { public static final String NFS_MOUNT_OPTIONS = "nfsmountopts"; + public static final String PARAMETER_DESCRIPTION_KUBERNETES_CLUSTER_NAME = "Kubernetes cluster's name. It must: " + + "contain at most 43 characters; contain only lowercase alphanumeric characters or '-'; " + + "start with a letter; end with an alphanumeric character."; + /** * This enum specifies IO Drivers, each option controls specific policies on I/O. * Qemu guests support "threads" and "native" options Since 0.8.8 ; "io_uring" is supported Since 6.3.0 (QEMU 5.0). diff --git a/plugins/integrations/kubernetes-service/src/main/java/com/cloud/kubernetes/cluster/KubernetesClusterManagerImpl.java b/plugins/integrations/kubernetes-service/src/main/java/com/cloud/kubernetes/cluster/KubernetesClusterManagerImpl.java index 9c402f83b031..0e565335874a 100644 --- a/plugins/integrations/kubernetes-service/src/main/java/com/cloud/kubernetes/cluster/KubernetesClusterManagerImpl.java +++ b/plugins/integrations/kubernetes-service/src/main/java/com/cloud/kubernetes/cluster/KubernetesClusterManagerImpl.java @@ -746,9 +746,7 @@ private void validateManagedKubernetesClusterCreateParameters(final CreateKubern final Long nodeRootDiskSize = cmd.getNodeRootDiskSize(); final String externalLoadBalancerIpAddress = cmd.getExternalLoadBalancerIpAddress(); - if (name == null || name.isEmpty()) { - throw new InvalidParameterValueException("Invalid name for the Kubernetes cluster name: " + name); - } + validateKubernetesClusterName(name); if (controlNodeCount < 1) { throw new InvalidParameterValueException("Invalid cluster control nodes count: " + controlNodeCount); @@ -838,6 +836,34 @@ private void validateManagedKubernetesClusterCreateParameters(final CreateKubern } } + /** + * Checks whether Kubernetes cluster name complies with the Kubernetes naming convention; throws an {@link InvalidParameterValueException} otherwise. + * @see Kubernetes documentation + * + * @param name Kubernetes cluster name to be validated. + * @throws InvalidParameterValueException When Kubernetes cluster name does not comply with Kubernetes naming convention. + */ + protected void validateKubernetesClusterName(String name) { + String baseErrorString = "Unable to create Kubernetes cluster. Reason: "; + if (!name.equals(name.toLowerCase())) { + String errorString = String.format("%s cluster name [%s] needs to be entirely in lowercase.", baseErrorString, name); + LOGGER.debug(errorString); + throw new InvalidParameterValueException(errorString); + } + if (name.length() > 43) { + String reason = "CloudStack appends the VM type and an 11-character hash to the cluster name to generate VM names, which must not exceed 63 characters. Please ensure the cluster name is 43 characters or fewer."; + String errorString = String.format("%s cluster name [%s] needs to contain at most 43 characters. %s", baseErrorString, name, reason); + LOGGER.debug(errorString); + throw new InvalidParameterValueException(errorString); + } + String pattern = "[a-z]($|[a-z\\d-]*[a-z\\d]$)"; + if (!name.matches(pattern)) { + String errorString = String.format("%s cluster name [%s] needs to start with a letter and end with an alphanumeric character, and can contain only '-' aside from alphanumeric characters.", baseErrorString, name); + LOGGER.debug(errorString); + throw new InvalidParameterValueException(errorString); + } + } + private Network getKubernetesClusterNetworkIfMissing(final String clusterName, final DataCenter zone, final Account owner, final int controlNodesCount, final int nodesCount, final String externalLoadBalancerIpAddress, final Long networkId) throws CloudRuntimeException { Network network = null; diff --git a/plugins/integrations/kubernetes-service/src/main/java/com/cloud/kubernetes/cluster/actionworkers/KubernetesClusterResourceModifierActionWorker.java b/plugins/integrations/kubernetes-service/src/main/java/com/cloud/kubernetes/cluster/actionworkers/KubernetesClusterResourceModifierActionWorker.java index 60cd9a2dff4e..a75a43b7faa2 100644 --- a/plugins/integrations/kubernetes-service/src/main/java/com/cloud/kubernetes/cluster/actionworkers/KubernetesClusterResourceModifierActionWorker.java +++ b/plugins/integrations/kubernetes-service/src/main/java/com/cloud/kubernetes/cluster/actionworkers/KubernetesClusterResourceModifierActionWorker.java @@ -773,18 +773,7 @@ protected void setupKubernetesClusterVpcTierRules(IpAddress publicIp, Network ne } protected String getKubernetesClusterNodeNamePrefix() { - String prefix = kubernetesCluster.getName(); - if (!NetUtils.verifyDomainNameLabel(prefix, true)) { - prefix = prefix.replaceAll("[^a-zA-Z0-9-]", ""); - if (prefix.length() == 0) { - prefix = kubernetesCluster.getUuid(); - } - prefix = "k8s-" + prefix; - } - if (prefix.length() > 40) { - prefix = prefix.substring(0, 40); - } - return prefix; + return kubernetesCluster.getName(); } protected KubernetesClusterVO updateKubernetesClusterEntry(final Long cores, final Long memory, final Long size, diff --git a/plugins/integrations/kubernetes-service/src/main/java/org/apache/cloudstack/api/command/user/kubernetes/cluster/CreateKubernetesClusterCmd.java b/plugins/integrations/kubernetes-service/src/main/java/org/apache/cloudstack/api/command/user/kubernetes/cluster/CreateKubernetesClusterCmd.java index 529088254306..0fcf0df2d227 100644 --- a/plugins/integrations/kubernetes-service/src/main/java/org/apache/cloudstack/api/command/user/kubernetes/cluster/CreateKubernetesClusterCmd.java +++ b/plugins/integrations/kubernetes-service/src/main/java/org/apache/cloudstack/api/command/user/kubernetes/cluster/CreateKubernetesClusterCmd.java @@ -67,7 +67,7 @@ public class CreateKubernetesClusterCmd extends BaseAsyncCreateCmd { //////////////// API parameters ///////////////////// ///////////////////////////////////////////////////// - @Parameter(name = ApiConstants.NAME, type = CommandType.STRING, required = true, description = "name for the Kubernetes cluster") + @Parameter(name = ApiConstants.NAME, type = CommandType.STRING, required = true, description = ApiConstants.PARAMETER_DESCRIPTION_KUBERNETES_CLUSTER_NAME) private String name; @Parameter(name = ApiConstants.DESCRIPTION, type = CommandType.STRING, description = "description for the Kubernetes cluster") diff --git a/plugins/integrations/kubernetes-service/src/test/java/com/cloud/kubernetes/cluster/KubernetesClusterManagerImplTest.java b/plugins/integrations/kubernetes-service/src/test/java/com/cloud/kubernetes/cluster/KubernetesClusterManagerImplTest.java index a6d46ffc9aa1..3d1158e2e722 100644 --- a/plugins/integrations/kubernetes-service/src/test/java/com/cloud/kubernetes/cluster/KubernetesClusterManagerImplTest.java +++ b/plugins/integrations/kubernetes-service/src/test/java/com/cloud/kubernetes/cluster/KubernetesClusterManagerImplTest.java @@ -292,4 +292,54 @@ public void removeVmsFromCluster() { Mockito.when(kubernetesClusterDao.findById(Mockito.anyLong())).thenReturn(cluster); Assert.assertTrue(kubernetesClusterManager.removeVmsFromCluster(cmd).size() > 0); } + + @Test(expected = InvalidParameterValueException.class) + public void validateKubernetesClusterNameTestThrowExceptionWhenClusterNameContainsUpperCaseLetters() { + kubernetesClusterManager.validateKubernetesClusterName("clusterName"); + } + + @Test(expected = InvalidParameterValueException.class) + public void validateKubernetesClusterNameTestThrowExceptionWhenClusterNameExceedsMaxAllowedLength() { + kubernetesClusterManager.validateKubernetesClusterName("c".repeat(44)); + } + + @Test(expected = InvalidParameterValueException.class) + public void validateKubernetesClusterNameTestThrowExceptionWhenClusterNameStartsWithDigit() { + kubernetesClusterManager.validateKubernetesClusterName("1clustername"); + } + + @Test(expected = InvalidParameterValueException.class) + public void validateKubernetesClusterNameTestThrowExceptionWhenClusterNameContainsOneDigit() { + kubernetesClusterManager.validateKubernetesClusterName("1"); + } + + @Test(expected = InvalidParameterValueException.class) + public void validateKubernetesClusterNameTestThrowExceptionWhenClusterNameStartsWithNonAlphanumericCharacter() { + kubernetesClusterManager.validateKubernetesClusterName("-clustername"); + } + + @Test(expected = InvalidParameterValueException.class) + public void validateKubernetesClusterNameTestThrowExceptionWhenClusterNameContainsOneHyphen() { + kubernetesClusterManager.validateKubernetesClusterName("-"); + } + + @Test(expected = InvalidParameterValueException.class) + public void validateKubernetesClusterNameTestThrowExceptionWhenClusterNameEndsWithNonAlphanumericCharacter() { + kubernetesClusterManager.validateKubernetesClusterName("clustername-"); + } + + @Test(expected = InvalidParameterValueException.class) + public void validateKubernetesClusterNameTestThrowExceptionWhenClusterNameContainsNonAlphanumericCharactersOtherThanHyphen() { + kubernetesClusterManager.validateKubernetesClusterName("cluster$name"); + } + + @Test + public void validateKubernetesClusterNameTestValidateClusterNameWhenItCompliesWithTheNamingConvention() { + kubernetesClusterManager.validateKubernetesClusterName("c-" + "c".repeat(41)); + } + + @Test + public void validateKubernetesClusterNameTestValidateClusterNameWithOneCharacter() { + kubernetesClusterManager.validateKubernetesClusterName("k"); + } }