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 <[email protected]>
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);

Reply via email to