You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@pulsar.apache.org by pe...@apache.org on 2021/02/18 15:57:19 UTC

[pulsar] 25/27: [Issue 9360][pulsar-functions] kubernetes runtime functions create rfc1123 compliant labels (#9556)

This is an automated email from the ASF dual-hosted git repository.

penghui pushed a commit to branch branch-2.7
in repository https://gitbox.apache.org/repos/asf/pulsar.git

commit 919f2d316f46aff9d2e40c876de14d3260fd7fdd
Author: jdbeck <73...@users.noreply.github.com>
AuthorDate: Tue Feb 16 03:40:18 2021 -0500

    [Issue 9360][pulsar-functions] kubernetes runtime functions create rfc1123 compliant labels (#9556)
    
    Fixes #9360
    
    ### Motivation
    
    Currently, it's valid to (via pulsar-admin and the admin rest api) specify names that include the colon ':' character. For example, I can create a namespace via pulsar-admin and the rest api as something like 'my:example:namespace'. We've found this useful for purposes of adding structure to object names that is then easier to programmatically parse.
    
    We've been able to use this format with no problems with the ProcessRuntime for deploying functions.
    
    The KubernetesRuntime, however, is currently not compatible with pulsar object names that are not compliant with RFC1123, because KubernetesRuntime attempts to use these object names verbatim in the labels it attaches to the resource specifications.
    Trying to use the KubernetesRuntime with the name format described above prevents the functions from deploying successfully.
    
    ### Modifications
    
    Changed the KubernetesRuntime to translate the names of the pulsar objects to forms that are RFC1123-compliant for k8s resource labels.
    
    The rules for translating the Pulsar object names are:
    - truncate to 63 chars
    - replace any non alphanumeric character ([a-z0-9A-Z]), dashes (-), underscores(_), dot(.) with "-"
    - replace beginning & end non-alphanumeric character with "0"
    
    This change added tests and can be verified as follows:
      - Added an extra test to KubernetesRuntimeTest.java to make sure labels are created that are RFC1123-compliant AND also make sure the labels are no bigger than the k8s limit of 63 chars
    
    (cherry picked from commit 5763c0e002055dd7773184f886212f346b87d6e4)
---
 .../runtime/kubernetes/KubernetesRuntime.java      | 13 +++++++++---
 .../runtime/kubernetes/KubernetesRuntimeTest.java  | 24 ++++++++++++++++++++++
 2 files changed, 34 insertions(+), 3 deletions(-)

diff --git a/pulsar-functions/runtime/src/main/java/org/apache/pulsar/functions/runtime/kubernetes/KubernetesRuntime.java b/pulsar-functions/runtime/src/main/java/org/apache/pulsar/functions/runtime/kubernetes/KubernetesRuntime.java
index 6741d14..d3ae6ff 100644
--- a/pulsar-functions/runtime/src/main/java/org/apache/pulsar/functions/runtime/kubernetes/KubernetesRuntime.java
+++ b/pulsar-functions/runtime/src/main/java/org/apache/pulsar/functions/runtime/kubernetes/KubernetesRuntime.java
@@ -87,6 +87,7 @@ import java.util.regex.Pattern;
 import static java.net.HttpURLConnection.HTTP_CONFLICT;
 import static java.net.HttpURLConnection.HTTP_NOT_FOUND;
 import static org.apache.commons.lang3.StringUtils.isNotBlank;
+import static org.apache.commons.lang3.StringUtils.left;
 import static org.apache.pulsar.functions.auth.FunctionAuthUtils.getFunctionAuthData;
 import static org.apache.pulsar.functions.utils.FunctionCommon.roundDecimal;
 
@@ -105,6 +106,7 @@ public class KubernetesRuntime implements Runtime {
 
     private static final String ENV_SHARD_ID = "SHARD_ID";
     private static final int maxJobNameSize = 55;
+    private static final int maxLabelSize = 63;
     public static final Pattern VALID_POD_NAME_REGEX =
             Pattern.compile("[a-z0-9]([-a-z0-9]*[a-z0-9])?(\\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*",
                     Pattern.CASE_INSENSITIVE);
@@ -950,10 +952,11 @@ public class KubernetesRuntime implements Runtime {
                 break;
         }
         labels.put("component", component);
-        labels.put("namespace", functionDetails.getNamespace());
-        labels.put("tenant", functionDetails.getTenant());
-        labels.put("name", functionDetails.getName());
+        labels.put("namespace", toValidLabelName(functionDetails.getNamespace()));
+        labels.put("tenant", toValidLabelName(functionDetails.getTenant()));
+        labels.put("name", toValidLabelName(functionDetails.getName()));
         if (customLabels != null && !customLabels.isEmpty()) {
+            customLabels.replaceAll((k, v) -> toValidLabelName(v));
             labels.putAll(customLabels);
         }
         return labels;
@@ -1105,6 +1108,10 @@ public class KubernetesRuntime implements Runtime {
     private static String toValidPodName(String ori) {
         return ori.toLowerCase().replaceAll("[^a-z0-9-\\.]", "-");
     }
+
+    private static String toValidLabelName(String ori) {
+        return left(ori.toLowerCase().replaceAll("[^a-zA-Z0-9-_\\.]", "-").replaceAll("^[^a-zA-Z0-9]", "0").replaceAll("[^a-zA-Z0-9]$", "0"), maxLabelSize);
+    }
     
     private static String createJobName(String jobName, String tenant, String namespace, String functionName) {
     	final String convertedJobName = toValidPodName(jobName);
diff --git a/pulsar-functions/runtime/src/test/java/org/apache/pulsar/functions/runtime/kubernetes/KubernetesRuntimeTest.java b/pulsar-functions/runtime/src/test/java/org/apache/pulsar/functions/runtime/kubernetes/KubernetesRuntimeTest.java
index a360023..0139d39 100644
--- a/pulsar-functions/runtime/src/test/java/org/apache/pulsar/functions/runtime/kubernetes/KubernetesRuntimeTest.java
+++ b/pulsar-functions/runtime/src/test/java/org/apache/pulsar/functions/runtime/kubernetes/KubernetesRuntimeTest.java
@@ -511,6 +511,30 @@ public class KubernetesRuntimeTest {
         verifyCreateJobNameWithNameOverMaxCharLimit();
     }
 
+    @Test
+    public void testCreateFunctionLabels() throws Exception {
+        FunctionDetails.Builder functionDetailsBuilder = FunctionDetails.newBuilder();
+        functionDetailsBuilder.setRuntime(FunctionDetails.Runtime.JAVA);
+        functionDetailsBuilder.setTenant("tenant");
+        // use long namespace to make sure label is truncated to 63 max characters for k8s requirements
+        functionDetailsBuilder.setNamespace(String.format("%-100s", "namespace:$second.part:third@test_0").replace(" ", "0"));
+        functionDetailsBuilder.setName("$function_name!");
+        JsonObject configObj = new JsonObject();
+        configObj.addProperty("jobNamespace", "custom-ns");
+        configObj.addProperty("jobName", "custom-name");
+        functionDetailsBuilder.setCustomRuntimeOptions(configObj.toString());
+        final FunctionDetails functionDetails = functionDetailsBuilder.build();
+
+        InstanceConfig config = createJavaInstanceConfig(FunctionDetails.Runtime.JAVA, false);
+        config.setFunctionDetails(functionDetails);
+        KubernetesRuntime container = factory.createContainer(config, userJarFile, userJarFile, 30l);
+        V1StatefulSet spec = container.createStatefulSet();
+
+        assertEquals(spec.getMetadata().getLabels().get("tenant"), "tenant");
+        assertEquals(spec.getMetadata().getLabels().get("namespace"), String.format("%-63s", "namespace--second.part-third-test_0").replace(" ", "0"));
+        assertEquals(spec.getMetadata().getLabels().get("name"), "0function_name0");
+    }
+
     FunctionDetails createFunctionDetails(final String functionName) {
         FunctionDetails.Builder functionDetailsBuilder = FunctionDetails.newBuilder();
         functionDetailsBuilder.setRuntime(FunctionDetails.Runtime.JAVA);