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

MartijnVisser pushed a commit to branch release-2.3
in repository https://gitbox.apache.org/repos/asf/flink.git


The following commit(s) were added to refs/heads/release-2.3 by this push:
     new ad50e9b0a2a [FLINK-40592][metrics] Fix PushGateway basic 
authentication without JAXB (#29151)
ad50e9b0a2a is described below

commit ad50e9b0a2abdcaafc125300a15f87ccd596e817
Author: weicheng-07 <[email protected]>
AuthorDate: Mon Sep 14 02:44:17 2026 +0800

    [FLINK-40592][metrics] Fix PushGateway basic authentication without JAXB 
(#29151)
    
    Encode the Basic Authorization header with JDK Base64 and UTF-8 while
    preserving the default connection factory and credential configuration.
    Cover PUT and DELETE requests with regression tests that isolate JAXB.
    
    Generated-by: OpenAI Codex 0.153.4
    Co-authored-by: jiweicheng <[email protected]>
    (cherry picked from commit 189b6ca8313ada196596cb53315866e7a531d441)
---
 .../prometheus/PrometheusPushGatewayReporter.java  |  30 ++++-
 ...theusPushGatewayReporterAuthenticationTest.java | 146 +++++++++++++++++++++
 2 files changed, 174 insertions(+), 2 deletions(-)

diff --git 
a/flink-metrics/flink-metrics-prometheus/src/main/java/org/apache/flink/metrics/prometheus/PrometheusPushGatewayReporter.java
 
b/flink-metrics/flink-metrics-prometheus/src/main/java/org/apache/flink/metrics/prometheus/PrometheusPushGatewayReporter.java
index 2ee6d6df431..9de72da28e5 100644
--- 
a/flink-metrics/flink-metrics-prometheus/src/main/java/org/apache/flink/metrics/prometheus/PrometheusPushGatewayReporter.java
+++ 
b/flink-metrics/flink-metrics-prometheus/src/main/java/org/apache/flink/metrics/prometheus/PrometheusPushGatewayReporter.java
@@ -25,13 +25,17 @@ import org.apache.flink.metrics.reporter.MetricReporter;
 import org.apache.flink.metrics.reporter.Scheduled;
 import org.apache.flink.util.Preconditions;
 
-import io.prometheus.client.exporter.BasicAuthHttpConnectionFactory;
+import io.prometheus.client.exporter.DefaultHttpConnectionFactory;
+import io.prometheus.client.exporter.HttpConnectionFactory;
 import io.prometheus.client.exporter.PushGateway;
 
 import javax.annotation.Nullable;
 
 import java.io.IOException;
+import java.net.HttpURLConnection;
 import java.net.URL;
+import java.nio.charset.StandardCharsets;
+import java.util.Base64;
 import java.util.Map;
 
 /**
@@ -58,7 +62,7 @@ public class PrometheusPushGatewayReporter extends 
AbstractPrometheusReporter im
         this.pushGateway = new PushGateway(hostUrl);
         if (username != null && password != null) {
             this.pushGateway.setConnectionFactory(
-                    new BasicAuthHttpConnectionFactory(username, password));
+                    new JdkBasicAuthHttpConnectionFactory(username, password));
             this.basicAuthEnabled = true;
         } else {
             this.basicAuthEnabled = false;
@@ -96,4 +100,26 @@ public class PrometheusPushGatewayReporter extends 
AbstractPrometheusReporter im
         }
         super.close();
     }
+
+    private static final class JdkBasicAuthHttpConnectionFactory implements 
HttpConnectionFactory {
+        private final HttpConnectionFactory connectionFactory = new 
DefaultHttpConnectionFactory();
+        private final String authorizationHeader;
+
+        private JdkBasicAuthHttpConnectionFactory(String username, String 
password) {
+            // Use the JDK encoder because simpleclient's implementation 
requires JAXB.
+            authorizationHeader =
+                    "Basic "
+                            + Base64.getEncoder()
+                                    .encodeToString(
+                                            (username + ":" + password)
+                                                    
.getBytes(StandardCharsets.UTF_8));
+        }
+
+        @Override
+        public HttpURLConnection create(String url) throws IOException {
+            final HttpURLConnection connection = connectionFactory.create(url);
+            connection.setRequestProperty("Authorization", 
authorizationHeader);
+            return connection;
+        }
+    }
 }
diff --git 
a/flink-metrics/flink-metrics-prometheus/src/test/java/org/apache/flink/metrics/prometheus/PrometheusPushGatewayReporterAuthenticationTest.java
 
b/flink-metrics/flink-metrics-prometheus/src/test/java/org/apache/flink/metrics/prometheus/PrometheusPushGatewayReporterAuthenticationTest.java
new file mode 100644
index 00000000000..633f1bfc922
--- /dev/null
+++ 
b/flink-metrics/flink-metrics-prometheus/src/test/java/org/apache/flink/metrics/prometheus/PrometheusPushGatewayReporterAuthenticationTest.java
@@ -0,0 +1,146 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.flink.metrics.prometheus;
+
+import org.apache.flink.metrics.MetricConfig;
+import org.apache.flink.metrics.reporter.MetricReporter;
+import org.apache.flink.metrics.reporter.MetricReporterFactory;
+import org.apache.flink.metrics.reporter.Scheduled;
+
+import com.sun.net.httpserver.HttpServer;
+import io.prometheus.client.Collector;
+import io.prometheus.client.exporter.PushGateway;
+import io.prometheus.client.exporter.common.TextFormat;
+import org.junit.jupiter.params.ParameterizedTest;
+import org.junit.jupiter.params.provider.CsvSource;
+
+import java.net.InetSocketAddress;
+import java.net.URL;
+import java.net.URLClassLoader;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
+import java.util.stream.Stream;
+
+import static 
org.apache.flink.metrics.prometheus.PrometheusPushGatewayReporterOptions.DELETE_ON_SHUTDOWN;
+import static 
org.apache.flink.metrics.prometheus.PrometheusPushGatewayReporterOptions.HOST_URL;
+import static 
org.apache.flink.metrics.prometheus.PrometheusPushGatewayReporterOptions.JOB_NAME;
+import static 
org.apache.flink.metrics.prometheus.PrometheusPushGatewayReporterOptions.PASSWORD;
+import static 
org.apache.flink.metrics.prometheus.PrometheusPushGatewayReporterOptions.RANDOM_JOB_NAME_SUFFIX;
+import static 
org.apache.flink.metrics.prometheus.PrometheusPushGatewayReporterOptions.USERNAME;
+import static org.assertj.core.api.Assertions.assertThat;
+import static org.assertj.core.api.Assertions.assertThatThrownBy;
+
+/** Tests authentication on actual PushGateway requests. */
+class PrometheusPushGatewayReporterAuthenticationTest {
+
+    @ParameterizedTest
+    @CsvSource({
+        "user, password, Basic dXNlcjpwYXNzd29yZA==",
+        "user, päss:word, Basic dXNlcjpww6Rzczp3b3Jk",
+        "'', '', Basic Og==",
+        ", ,",
+        "user, ,",
+        ", password,"
+    })
+    void testAuthenticationOnPushAndDeleteWithoutJaxb(
+            String username, String password, String expectedAuthorization) 
throws Exception {
+        final List<String> methods = Collections.synchronizedList(new 
ArrayList<>());
+        final List<String> authorizations = Collections.synchronizedList(new 
ArrayList<>());
+        final HttpServer server = HttpServer.create(new 
InetSocketAddress("127.0.0.1", 0), 0);
+        server.createContext(
+                "/metrics/job/test-job",
+                exchange -> {
+                    try {
+                        exchange.getRequestBody().readAllBytes();
+                        methods.add(exchange.getRequestMethod());
+                        
authorizations.add(exchange.getRequestHeaders().getFirst("Authorization"));
+                        exchange.sendResponseHeaders(202, -1);
+                    } finally {
+                        exchange.close();
+                    }
+                });
+        server.start();
+        try (URLClassLoader classLoader = createClassLoaderWithoutJaxb()) {
+            final MetricConfig config = new MetricConfig();
+            config.setProperty(HOST_URL.key(), "http://127.0.0.1:"; + 
server.getAddress().getPort());
+            config.setProperty(JOB_NAME.key(), "test-job");
+            config.setProperty(RANDOM_JOB_NAME_SUFFIX.key(), "false");
+            config.setProperty(DELETE_ON_SHUTDOWN.key(), "true");
+            if (username != null) {
+                config.setProperty(USERNAME.key(), username);
+            }
+            if (password != null) {
+                config.setProperty(PASSWORD.key(), password);
+            }
+            final MetricReporterFactory factory =
+                    (MetricReporterFactory)
+                            classLoader
+                                    
.loadClass(PrometheusPushGatewayReporterFactory.class.getName())
+                                    .getDeclaredConstructor()
+                                    .newInstance();
+            final MetricReporter reporter = 
factory.createMetricReporter(config);
+            try {
+                assertThatThrownBy(
+                                () ->
+                                        reporter.getClass()
+                                                .getClassLoader()
+                                                
.loadClass("javax.xml.bind.DatatypeConverter"))
+                        .isInstanceOf(ClassNotFoundException.class);
+                ((Scheduled) reporter).report();
+            } finally {
+                reporter.close();
+            }
+            assertThat(methods).containsExactly("PUT", "DELETE");
+            assertThat(authorizations)
+                    .containsExactly(expectedAuthorization, 
expectedAuthorization);
+        } finally {
+            server.stop(0);
+        }
+    }
+
+    private static URLClassLoader createClassLoaderWithoutJaxb() {
+        final URL[] urls =
+                Stream.of(
+                                PrometheusPushGatewayReporter.class,
+                                Collector.class,
+                                PushGateway.class,
+                                TextFormat.class)
+                        .map(type -> 
type.getProtectionDomain().getCodeSource().getLocation())
+                        .distinct()
+                        .toArray(URL[]::new);
+        // Reload the reporter and its Prometheus dependencies so JAXB on the 
test classpath
+        // cannot hide the missing runtime dependency.
+        final ClassLoader parent =
+                new ClassLoader(
+                        
PrometheusPushGatewayReporterAuthenticationTest.class.getClassLoader()) {
+                    @Override
+                    protected Class<?> loadClass(String name, boolean resolve)
+                            throws ClassNotFoundException {
+                        if (name.startsWith("javax.xml.bind.")
+                                || 
name.startsWith("org.apache.flink.metrics.prometheus.")
+                                || name.startsWith("io.prometheus.")) {
+                            throw new ClassNotFoundException(name);
+                        }
+                        return super.loadClass(name, resolve);
+                    }
+                };
+        return new URLClassLoader(urls, parent);
+    }
+}

Reply via email to