diff --git a/application/org.openjdk.jmc.feature.core/feature.xml b/application/org.openjdk.jmc.feature.core/feature.xml index a9f76add84..98d06c0868 100644 --- a/application/org.openjdk.jmc.feature.core/feature.xml +++ b/application/org.openjdk.jmc.feature.core/feature.xml @@ -244,6 +244,13 @@ version="0.0.0" unpack="false"/> + + new TreeSet(Arrays.asList(ServerDetector.FALLBACK))); + logHandleer -> new TreeSet(Arrays.asList(ServerDetector.FALLBACK))); return serviceManager.start(); } diff --git a/application/org.openjdk.jmc.kubernetes/META-INF/MANIFEST.MF b/application/org.openjdk.jmc.kubernetes/META-INF/MANIFEST.MF new file mode 100644 index 0000000000..12a30d821d --- /dev/null +++ b/application/org.openjdk.jmc.kubernetes/META-INF/MANIFEST.MF @@ -0,0 +1,19 @@ +Manifest-Version: 1.0 +Bundle-ManifestVersion: 2 +Bundle-Name: %Bundle-Name +Bundle-SymbolicName: org.openjdk.jmc.kubernetes;singleton:=true +Bundle-Version: 10.0.0.qualifier +Automatic-Module-Name: org.openjdk.jmc.kubernetes +Bundle-RequiredExecutionEnvironment: JavaSE-17 +Require-Bundle: org.openjdk.jmc.jolokia, + org.eclipse.core.runtime, + org.eclipse.ui, + org.openjdk.jmc.ui, + org.eclipse.swt, + org.jolokia.client-kubernetes.standalone;bundle-version="2.2.9", + org.openjdk.jmc.rjmx +Export-Package: org.openjdk.jmc.kubernetes, + org.openjdk.jmc.kubernetes.preferences +Import-Package: org.apache.commons.logging;version="1.2.0" +Bundle-Activator: org.openjdk.jmc.kubernetes.JmcKubernetesPlugin +Bundle-ActivationPolicy: lazy diff --git a/application/org.openjdk.jmc.kubernetes/OSGI-INF/l10n/bundle.properties b/application/org.openjdk.jmc.kubernetes/OSGI-INF/l10n/bundle.properties new file mode 100644 index 0000000000..83ca6439dd --- /dev/null +++ b/application/org.openjdk.jmc.kubernetes/OSGI-INF/l10n/bundle.properties @@ -0,0 +1,3 @@ +#Properties file for org.openjdk.jmc.kubernetes +page.name = Kubernetes +Bundle-Name = Kubernetes JMC extensions \ No newline at end of file diff --git a/application/org.openjdk.jmc.kubernetes/build.properties b/application/org.openjdk.jmc.kubernetes/build.properties new file mode 100644 index 0000000000..25bd92d046 --- /dev/null +++ b/application/org.openjdk.jmc.kubernetes/build.properties @@ -0,0 +1,7 @@ +source.. = src/main/java/ +output.. = target/classes/ +bin.includes = META-INF/,\ + OSGI-INF/,\ + .,\ + OSGI-INF/l10n/bundle.properties,\ + plugin.xml diff --git a/application/org.openjdk.jmc.kubernetes/plugin.xml b/application/org.openjdk.jmc.kubernetes/plugin.xml new file mode 100644 index 0000000000..84b4b4f478 --- /dev/null +++ b/application/org.openjdk.jmc.kubernetes/plugin.xml @@ -0,0 +1,64 @@ + + + + + + + + + + + + + + + + + + + + + diff --git a/application/org.openjdk.jmc.kubernetes/pom.xml b/application/org.openjdk.jmc.kubernetes/pom.xml new file mode 100644 index 0000000000..f993def1b0 --- /dev/null +++ b/application/org.openjdk.jmc.kubernetes/pom.xml @@ -0,0 +1,49 @@ + + + + 4.0.0 + + org.openjdk.jmc + missioncontrol.application + ${revision}${changelist} + + org.openjdk.jmc.kubernetes + eclipse-plugin + + ${project.basedir}/../../configuration + + diff --git a/application/org.openjdk.jmc.kubernetes/src/main/java/org/openjdk/jmc/kubernetes/JmcKubernetesJmxConnection.java b/application/org.openjdk.jmc.kubernetes/src/main/java/org/openjdk/jmc/kubernetes/JmcKubernetesJmxConnection.java new file mode 100644 index 0000000000..0eb6ad9f26 --- /dev/null +++ b/application/org.openjdk.jmc.kubernetes/src/main/java/org/openjdk/jmc/kubernetes/JmcKubernetesJmxConnection.java @@ -0,0 +1,83 @@ +/* + * Copyright (c) 2025, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2025, Kantega AS. All rights reserved. + * + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * The contents of this file are subject to the terms of either the Universal Permissive License + * v 1.0 as shown at http://oss.oracle.com/licenses/upl + * + * or the following license: + * + * Redistribution and use in source and binary forms, with or without modification, are permitted + * provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, this list of conditions + * and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright notice, this list of + * conditions and the following disclaimer in the documentation and/or other materials provided with + * the distribution. + * + * 3. Neither the name of the copyright holder nor the names of its contributors may be used to + * endorse or promote products derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR + * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND + * FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, + * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY + * WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package org.openjdk.jmc.kubernetes; + +import java.io.IOException; +import java.util.Arrays; +import java.util.Collection; +import java.util.regex.Pattern; + +import javax.management.InstanceNotFoundException; + +import org.jolokia.client.J4pClient; +import org.jolokia.client.exception.J4pException; +import org.jolokia.client.exception.J4pRemoteException; +import org.jolokia.client.request.J4pResponse; +import org.openjdk.jmc.jolokia.JmcJolokiaJmxConnection; +import org.openjdk.jmc.rjmx.common.ConnectionException; + +/** + * Jolokia based MBeanServerConnector tailored for JMC needs + */ +public class JmcKubernetesJmxConnection extends JmcJolokiaJmxConnection { + + static final Collection DISCONNECT_SIGNS = Arrays.asList(Pattern.compile("Error: pods \".+\" not found")); //$NON-NLS-1$ + + public JmcKubernetesJmxConnection(J4pClient client) throws IOException { + super(client); + } + + @SuppressWarnings("rawtypes") + @Override + protected J4pResponse unwrapException(J4pException e) throws IOException, InstanceNotFoundException { + // recognize signs of disconnect and signal to the application for better + // handling + if (isKnownDisconnectException(e)) { + throw new ConnectionException(e.getMessage()); + } else { + return super.unwrapException(e); + } + } + + private boolean isKnownDisconnectException(J4pException e) { + if (!(e instanceof J4pRemoteException)) { + return false; + } + if (!"io.fabric8.kubernetes.client.KubernetesClientException".equals(((J4pRemoteException) e).getErrorType())) { //$NON-NLS-1$ + return false; + } + return DISCONNECT_SIGNS.stream().anyMatch(pattern -> pattern.matcher(e.getMessage()).matches()); + } + +} diff --git a/application/org.openjdk.jmc.kubernetes/src/main/java/org/openjdk/jmc/kubernetes/JmcKubernetesJmxConnectionProvider.java b/application/org.openjdk.jmc.kubernetes/src/main/java/org/openjdk/jmc/kubernetes/JmcKubernetesJmxConnectionProvider.java new file mode 100644 index 0000000000..f26eed4b9e --- /dev/null +++ b/application/org.openjdk.jmc.kubernetes/src/main/java/org/openjdk/jmc/kubernetes/JmcKubernetesJmxConnectionProvider.java @@ -0,0 +1,55 @@ +/* + * Copyright (c) 2025, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2025, Kantega AS. All rights reserved. + * + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * The contents of this file are subject to the terms of either the Universal Permissive License + * v 1.0 as shown at http://oss.oracle.com/licenses/upl + * + * or the following license: + * + * Redistribution and use in source and binary forms, with or without modification, are permitted + * provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, this list of conditions + * and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright notice, this list of + * conditions and the following disclaimer in the documentation and/or other materials provided with + * the distribution. + * + * 3. Neither the name of the copyright holder nor the names of its contributors may be used to + * endorse or promote products derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR + * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND + * FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, + * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY + * WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package org.openjdk.jmc.kubernetes; + +import java.io.IOException; +import java.net.MalformedURLException; +import java.util.Map; + +import javax.management.remote.JMXConnector; +import javax.management.remote.JMXConnectorProvider; +import javax.management.remote.JMXServiceURL; + +/** + * This {@code JMXConnectionProvider} handles the "kubernetes" protocol. + */ +public class JmcKubernetesJmxConnectionProvider implements JMXConnectorProvider { + @Override + public JMXConnector newJMXConnector(JMXServiceURL serviceURL, Map environment) throws IOException { + if (!"kubernetes".equals(serviceURL.getProtocol())) { //$NON-NLS-1$ + throw new MalformedURLException("I only serve Kubernetes connections"); //$NON-NLS-1$ + } + return new JmcKubernetesJmxConnector(serviceURL, environment); + } +} diff --git a/application/org.openjdk.jmc.kubernetes/src/main/java/org/openjdk/jmc/kubernetes/JmcKubernetesJmxConnector.java b/application/org.openjdk.jmc.kubernetes/src/main/java/org/openjdk/jmc/kubernetes/JmcKubernetesJmxConnector.java new file mode 100644 index 0000000000..c1d3a12833 --- /dev/null +++ b/application/org.openjdk.jmc.kubernetes/src/main/java/org/openjdk/jmc/kubernetes/JmcKubernetesJmxConnector.java @@ -0,0 +1,55 @@ +/* + * Copyright (c) 2025, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2025, Kantega AS. All rights reserved. + * + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * The contents of this file are subject to the terms of either the Universal Permissive License + * v 1.0 as shown at http://oss.oracle.com/licenses/upl + * + * or the following license: + * + * Redistribution and use in source and binary forms, with or without modification, are permitted + * provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, this list of conditions + * and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright notice, this list of + * conditions and the following disclaimer in the documentation and/or other materials provided with + * the distribution. + * + * 3. Neither the name of the copyright holder nor the names of its contributors may be used to + * endorse or promote products derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR + * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND + * FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, + * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY + * WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package org.openjdk.jmc.kubernetes; + +import java.io.IOException; +import java.util.Map; + +import javax.management.remote.JMXServiceURL; + +import org.jolokia.client.J4pClient; +import org.jolokia.client.jmxadapter.RemoteJmxAdapter; +import org.jolokia.kubernetes.client.KubernetesJmxConnector; + +public class JmcKubernetesJmxConnector extends KubernetesJmxConnector { + + public JmcKubernetesJmxConnector(JMXServiceURL serviceURL, Map environment) { + super(serviceURL, environment); + } + + @Override + protected RemoteJmxAdapter createAdapter(J4pClient client) throws IOException { + return new JmcKubernetesJmxConnection(client); + } +} diff --git a/application/org.openjdk.jmc.kubernetes/src/main/java/org/openjdk/jmc/kubernetes/JmcKubernetesPlugin.java b/application/org.openjdk.jmc.kubernetes/src/main/java/org/openjdk/jmc/kubernetes/JmcKubernetesPlugin.java new file mode 100644 index 0000000000..7bfdfce3b2 --- /dev/null +++ b/application/org.openjdk.jmc.kubernetes/src/main/java/org/openjdk/jmc/kubernetes/JmcKubernetesPlugin.java @@ -0,0 +1,162 @@ +/* + * Copyright (c) 2025, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2025, Kantega AS. All rights reserved. + * + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * The contents of this file are subject to the terms of either the Universal Permissive License + * v 1.0 as shown at http://oss.oracle.com/licenses/upl + * + * or the following license: + * + * Redistribution and use in source and binary forms, with or without modification, are permitted + * provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, this list of conditions + * and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright notice, this list of + * conditions and the following disclaimer in the documentation and/or other materials provided with + * the distribution. + * + * 3. Neither the name of the copyright holder nor the names of its contributors may be used to + * endorse or promote products derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR + * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND + * FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, + * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY + * WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package org.openjdk.jmc.kubernetes; + +import org.eclipse.core.runtime.Platform; +import org.openjdk.jmc.kubernetes.preferences.KubernetesScanningParameters; +import org.openjdk.jmc.kubernetes.preferences.PreferenceConstants; +import org.openjdk.jmc.ui.MCAbstractUIPlugin; +import org.openjdk.jmc.common.security.ICredentials; +import org.openjdk.jmc.common.security.PersistentCredentials; +import org.openjdk.jmc.common.security.SecurityException; +import org.openjdk.jmc.common.security.SecurityManagerFactory; +import org.openjdk.jmc.ui.misc.DisplayToolkit; +import org.osgi.framework.BundleContext; +import org.osgi.framework.FrameworkUtil; + +public class JmcKubernetesPlugin extends MCAbstractUIPlugin + implements KubernetesScanningParameters, PreferenceConstants { + + public final static String PLUGIN_ID = "org.openjdk.jmc.kubernetes"; //$NON-NLS-1$ + + // The shared instance. + private static JmcKubernetesPlugin plugin; + + /** + * The constructor. + */ + public JmcKubernetesPlugin() { + super(PLUGIN_ID); + plugin = this; + } + + /** + * @return the shared instance. + */ + public static JmcKubernetesPlugin getDefault() { + return plugin; + } + + private void ensureNeededCredentialsAreUnlocked() { + if (getScanningCredentials() != null && SecurityManagerFactory.getSecurityManager().isLocked()) { + DisplayToolkit.safeAsyncExec(() -> { + try { + SecurityManagerFactory.getSecurityManager().unlock(); + } catch (SecurityException e) { + logError("Error unlocking credentials needed for kubernetes scanning", e);//$NON-NLS-1$ + } + }); + } + } + + @Override + public boolean scanForInstances() { + // If credentials are locked and credentials are required, the scanner thread + // will get hung + // therefore await credentials store to be unlocked before proceeding to scan + return getPreferenceStore().getBoolean(P_SCAN_FOR_INSTANCES) + && (getScanningCredentials() == null || !SecurityManagerFactory.getSecurityManager().isLocked()); + + } + + @Override + public boolean scanAllContexts() { + return getPreferenceStore().getBoolean(P_SCAN_ALL_CONTEXTS); + } + + @Override + public String jolokiaPort() { + return getPreferenceStore().getString(P_JOLOKIA_PORT); + } + + private PersistentCredentials getScanningCredentials() { + String key = getPreferenceStore().getString(P_CREDENTIALS_KEY); + return key == null || key.length() == 0 ? null : new PersistentCredentials(key); + } + + public ICredentials storeCredentials(String username, String password) throws SecurityException { + PersistentCredentials credentials = new PersistentCredentials(username, password, "kubernetes");//$NON-NLS-1$ + getPreferenceStore().setValue(P_CREDENTIALS_KEY, credentials.getExportedId()); + return credentials; + } + + @Override + public String username() throws SecurityException { + final PersistentCredentials cred = getScanningCredentials(); + if (cred == null) { + return "";//$NON-NLS-1$ + } else { + return cred.getUsername(); + } + } + + @Override + public String password() throws SecurityException { + final PersistentCredentials cred = getScanningCredentials(); + if (cred == null) { + return "";//$NON-NLS-1$ + } else { + return cred.getPassword(); + } + } + + @Override + public String jolokiaPath() { + return getPreferenceStore().getString(P_JOLOKIA_PATH); + } + + @Override + public String requireLabel() { + return getPreferenceStore().getString(P_REQUIRE_LABEL); + } + + @Override + public String jolokiaProtocol() { + return getPreferenceStore().getString(P_JOLOKIA_PROTOCOL); + } + + @Override + public void logError(String message, Throwable error) { + if (getPreferenceStore().getBoolean(P_LOG_ERRORS)) { + Platform.getLog(FrameworkUtil.getBundle(getClass())).error(message, error); + } + } + + @Override + public void start(BundleContext context) throws Exception { + super.start(context); + this.ensureNeededCredentialsAreUnlocked(); + } + +} diff --git a/application/org.openjdk.jmc.kubernetes/src/main/java/org/openjdk/jmc/kubernetes/KubernetesDiscoveryListener.java b/application/org.openjdk.jmc.kubernetes/src/main/java/org/openjdk/jmc/kubernetes/KubernetesDiscoveryListener.java new file mode 100644 index 0000000000..9d5572f803 --- /dev/null +++ b/application/org.openjdk.jmc.kubernetes/src/main/java/org/openjdk/jmc/kubernetes/KubernetesDiscoveryListener.java @@ -0,0 +1,334 @@ +/* + * Copyright (c) 2025, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2025, Kantega AS. All rights reserved. + * + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * The contents of this file are subject to the terms of either the Universal Permissive License + * v 1.0 as shown at http://oss.oracle.com/licenses/upl + * + * or the following license: + * + * Redistribution and use in source and binary forms, with or without modification, are permitted + * provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, this list of conditions + * and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright notice, this list of + * conditions and the following disclaimer in the documentation and/or other materials provided with + * the distribution. + * + * 3. Neither the name of the copyright holder nor the names of its contributors may be used to + * endorse or promote products derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR + * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND + * FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, + * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY + * WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package org.openjdk.jmc.kubernetes; + +import java.io.ByteArrayInputStream; +import java.io.File; +import java.io.IOException; +import java.util.Arrays; +import java.util.HashMap; +import java.util.HashSet; +import java.util.List; +import java.util.Map; +import java.util.Map.Entry; +import java.util.NoSuchElementException; +import java.util.Properties; +import java.util.Set; +import java.util.regex.Matcher; +import java.util.regex.Pattern; +import java.util.stream.Collectors; + +import javax.management.remote.JMXConnector; +import javax.management.remote.JMXServiceURL; + +import org.apache.commons.codec.binary.Base64; +import org.jolokia.client.J4pClient; +import org.jolokia.kubernetes.client.KubernetesJmxConnector; +import org.jolokia.server.core.http.security.AuthorizationHeaderParser; +import org.jolokia.server.core.util.Base64Util; +import org.openjdk.jmc.common.security.SecurityException; +import org.openjdk.jmc.jolokia.AbstractCachedDescriptorProvider; +import org.openjdk.jmc.jolokia.ServerConnectionDescriptor; +import org.openjdk.jmc.kubernetes.preferences.KubernetesScanningParameters; + +import io.fabric8.kubernetes.api.model.NamedContext; +import io.fabric8.kubernetes.api.model.ObjectMeta; +import io.fabric8.kubernetes.api.model.Pod; +import io.fabric8.kubernetes.api.model.PodList; +import io.fabric8.kubernetes.api.model.Secret; +import io.fabric8.kubernetes.client.Config; +import io.fabric8.kubernetes.client.KubernetesClient; +import io.fabric8.kubernetes.client.dsl.AnyNamespaceOperation; +import io.fabric8.kubernetes.client.dsl.PodResource; +import io.fabric8.kubernetes.client.internal.KubeConfigUtils; +import io.fabric8.kubernetes.client.utils.Utils; + +/** + * This class attempts to connect to JVMs in pods running in kubernetes in a background thread. + * Enablement and parameters for the scanning is given by user preferences. + */ +public class KubernetesDiscoveryListener extends AbstractCachedDescriptorProvider { + + private final static Pattern SECRET_PATTERN = Pattern + .compile("\\$\\{kubernetes/secret/(?[^/]+)/(?[^\\}]+)}"); //$NON-NLS-1$ + private final static Pattern ATTRIBUTE_PATTERN = Pattern + .compile("\\$\\{kubernetes/annotation/(?[^/]+)}"); //$NON-NLS-1$ + private final static Set VALID_JOLOKIA_PROTOCOLS = new HashSet<>(Arrays.asList("http", "https")); //$NON-NLS-1$ //$NON-NLS-2$ + + KubernetesScanningParameters settings; + + public KubernetesDiscoveryListener() { + this(JmcKubernetesPlugin.getDefault()); + } + + //Public constructor in order for test plugin to be able to rig tests in an easier manner + public KubernetesDiscoveryListener(KubernetesScanningParameters parameters) { + this.settings = parameters; + } + + public final String getDescription() { + return Messages.KubernetesDiscoveryListener_Description; + } + + @Override + public String getName() { + return "kubernetes"; //$NON-NLS-1$ + } + + boolean notEmpty(String value) { + return value != null && value.length() > 0; + } + + private List contexts; + private long contextsCached = 0L; + + private List allContexts() throws IOException { + final String path = Utils.getSystemPropertyOrEnvVar(Config.KUBERNETES_KUBECONFIG_FILE, + new File(System.getProperty("user.home"), ".kube" + File.separator + "config").toString()); //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$ + File configPath = new File(path); + if (contexts != null && contextsCached > configPath.lastModified()) {// the YAML parsing is soo incredibly + // sloow, hence cache context names for + // later + // runs + return contexts; + } + // reload config if kubeconfig has been modified since we cached the config + io.fabric8.kubernetes.api.model.Config config = KubeConfigUtils.parseConfig(configPath); + this.contextsCached = System.currentTimeMillis(); + KubernetesJmxConnector.resetKubernetesConfig(); + return contexts = config.getContexts().stream().map(NamedContext::getName).collect(Collectors.toList()); + } + + @Override + protected Map discoverJvms() { + return scanForJvms(); + } + + Map scanForJvms() { + Map found = new HashMap<>(); + if (!isEnabled()) { + return found; + } + boolean hasScanned = false; + + if (settings.scanAllContexts()) { + try { + for (final String context : allContexts()) { + hasScanned = true; + scanContext(found, settings, context); + } + } catch (IOException e) { + settings.logError(Messages.KubernetesDiscoveryListener_UnableToFindContexts, e); + } + } + if (!hasScanned) {// scan default context + return scanContext(found, settings, null); + } + return found; + } + + private Map scanContext( + Map found, KubernetesScanningParameters parameters, String context) { + try { + scanContextUnsafe(found, parameters, context); + } catch (Exception e) { + parameters.logError(Messages.KubernetesDiscoveryListener_UnableToScan + context, e); + } + return found; + } + + private Map scanContextUnsafe( + Map found, KubernetesScanningParameters parameters, String context) { + String pathLabel = parameters.requireLabel(); + KubernetesClient client = KubernetesJmxConnector.getApiClient(context); + + AnyNamespaceOperation query = client.pods().inAnyNamespace(); + List podList; + boolean hasPathLabel = notEmpty(pathLabel); + if (hasPathLabel) { + podList = query.withLabel(pathLabel).list().getItems(); + } else { + podList = query.list().getItems(); + } + // can consider parallelization for big contexts, however since it is the + // background await the situation a bit + podList.stream().forEach(pod -> scanPod(found, parameters, context, client, pod)); + return found; + } + + private void scanPod( + Map found, KubernetesScanningParameters parameters, String context, + KubernetesClient client, Pod pod) { + + final ObjectMeta metadata = pod.getMetadata(); + HashMap headers = new HashMap<>(); + Map env = new HashMap<>(); + try { + if (notEmpty(parameters.username())) { + if (!notEmpty(parameters.password())) { + throw new IllegalArgumentException(Messages.KubernetesDiscoveryListener_MustProvidePassword); + } + authorize(headers, client, parameters.username(), parameters.password(), metadata.getNamespace(), env); + } + } catch (SecurityException e) { + // skipping authorization if anything fails + } + final StringBuilder url = new StringBuilder("/api/").append(pod.getApiVersion()).append("/namespaces/") + .append(metadata.getNamespace()).append("/pods/"); + // JMX url must be reverse constructed, so that we can connect from the + // resulting node in the JVM browser + final StringBuilder jmxUrl = new StringBuilder("service:jmx:kubernetes:///").append(metadata.getNamespace()) //$NON-NLS-1$ + .append('/'); + + final String protocol = getValueOrAttribute(parameters.jolokiaProtocol(), metadata); + final String podName = metadata.getName(); + if (notEmpty(protocol)) { + if (!VALID_JOLOKIA_PROTOCOLS.contains(protocol)) { + throw new IllegalArgumentException(Messages.KubernetesDiscoveryListener_JolokiaProtocol + protocol + + Messages.KubernetesDiscoveryListener_HttpOrHttps); + } + // a bit clumsy, need to inject protocol _before_ podname in selflink + url.append(protocol).append(':'); + jmxUrl.append(protocol).append(':'); + } + + jmxUrl.append(podName); + url.append(podName); + + final String port = getValueOrAttribute(parameters.jolokiaPort(), metadata); + if (port != null) { + url.append(":").append(port); //$NON-NLS-1$ + jmxUrl.append(':').append(port); + } + + url.append("/proxy"); //$NON-NLS-1$ + + final String path = getValueOrAttribute(parameters.jolokiaPath(), metadata); + + if (!path.startsWith("/")) { //$NON-NLS-1$ + url.append('/'); + jmxUrl.append('/'); + } + url.append(path); + jmxUrl.append(path); + + if (context != null) { + env.put(KubernetesJmxConnector.KUBERNETES_CLIENT_CONTEXT, context); + } + J4pClient jvmClient = KubernetesJmxConnector.probeProxyPath(env, client, url, headers); + if (jvmClient != null) { + try { + JMXServiceURL jmxServiceURL = new JMXServiceURL(jmxUrl.toString()); + KubernetesJvmDescriptor descriptor = new KubernetesJvmDescriptor(metadata, jmxServiceURL, env); + found.put(descriptor.getGUID(), descriptor); + } catch (IOException e) { + parameters.logError(Messages.KubernetesDiscoveryListener_ErrConnectingToJvm, e); + + } + } + } + + private String getValueOrAttribute(String configValue, ObjectMeta metadata) { + if (notEmpty(configValue)) { + Matcher pattern = ATTRIBUTE_PATTERN.matcher(configValue); + if (pattern.find()) { + return metadata.getAnnotations().get(pattern.group("annotationName")); //$NON-NLS-1$ + } else { + return configValue;// the default is to use config value as is + } + } + return null; + } + + private void authorize( + HashMap headers, KubernetesClient client, String username, String password, String namespace, + Map jmxEnv) { + + final Matcher userNameMatcher = SECRET_PATTERN.matcher(username); + String secretName = null; + Map secretValues = null; + if (userNameMatcher.find()) { + secretName = userNameMatcher.group("secretName"); //$NON-NLS-1$ + secretValues = findSecret(client, namespace, secretName); + username = secretValues.get(userNameMatcher.group("itemName")); //$NON-NLS-1$ + } + + final Matcher passwordMatcher = SECRET_PATTERN.matcher(password); + if (passwordMatcher.find()) { + if (!secretName.equals(passwordMatcher.group("secretName"))) { //$NON-NLS-1$ + secretValues = findSecret(client, namespace, passwordMatcher.group("secretName")); //$NON-NLS-1$ + } + password = secretValues.get(passwordMatcher.group("itemName")); //$NON-NLS-1$ + } + + headers.put(AuthorizationHeaderParser.JOLOKIA_ALTERNATE_AUTHORIZATION_HEADER, + "Basic " + Base64Util.encode((username + ":" + password).getBytes())); //$NON-NLS-1$ //$NON-NLS-2$ + jmxEnv.put(JMXConnector.CREDENTIALS, new String[] {username, password}); + + } + + @SuppressWarnings({"unchecked", "rawtypes"}) + private Map findSecret(KubernetesClient client, String namespace, String secretName) { + + for (Secret secret : client.secrets().inNamespace(namespace).list().getItems()) { + if (secret.getMetadata().getName().equals(secretName)) { + if ("kubernetes.io/basic-auth".equals(secret.getType())) { //$NON-NLS-1$ + Map data = secret.getData(); + data.replaceAll((key, value) -> new String(Base64.decodeBase64(value))); + return data; + } else if ("Opaque".equals(secret.getType())) { //$NON-NLS-1$ + for (Entry entry : secret.getData().entrySet()) { + if (entry.getKey().endsWith(".properties")) { //$NON-NLS-1$ + try { + Properties properties = new Properties(); + properties.load(new ByteArrayInputStream(Base64.decodeBase64(entry.getValue()))); + return (Map) properties; + } catch (IOException ignore) { + } + } + } + } + } + + } + throw new NoSuchElementException(Messages.KubernetesDiscoveryListener_CouldNotFindSecret + secretName + + Messages.KubernetesDiscoveryListener_InNamespace + namespace); + + } + + @Override + protected boolean isEnabled() { + return this.settings.scanForInstances(); + } +} diff --git a/application/org.openjdk.jmc.kubernetes/src/main/java/org/openjdk/jmc/kubernetes/KubernetesJvmDescriptor.java b/application/org.openjdk.jmc.kubernetes/src/main/java/org/openjdk/jmc/kubernetes/KubernetesJvmDescriptor.java new file mode 100644 index 0000000000..f092cdcb9f --- /dev/null +++ b/application/org.openjdk.jmc.kubernetes/src/main/java/org/openjdk/jmc/kubernetes/KubernetesJvmDescriptor.java @@ -0,0 +1,98 @@ +/* + * Copyright (c) 2025, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2025, Kantega AS. All rights reserved. + * + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * The contents of this file are subject to the terms of either the Universal Permissive License + * v 1.0 as shown at http://oss.oracle.com/licenses/upl + * + * or the following license: + * + * Redistribution and use in source and binary forms, with or without modification, are permitted + * provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, this list of conditions + * and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright notice, this list of + * conditions and the following disclaimer in the documentation and/or other materials provided with + * the distribution. + * + * 3. Neither the name of the copyright holder nor the names of its contributors may be used to + * endorse or promote products derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR + * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND + * FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, + * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY + * WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package org.openjdk.jmc.kubernetes; + +import java.io.IOException; +import java.util.Map; + +import javax.management.remote.JMXServiceURL; + +import org.jolokia.kubernetes.client.KubernetesJmxConnector; +import org.openjdk.jmc.jolokia.ServerConnectionDescriptor; +import org.openjdk.jmc.common.jvm.JVMDescriptor; + +import io.fabric8.kubernetes.api.model.ObjectMeta; + +public class KubernetesJvmDescriptor implements ServerConnectionDescriptor { + + private final ObjectMeta metadata; + private final Map env; + private final JMXServiceURL connectUrl; + + public KubernetesJvmDescriptor(ObjectMeta metadata, JMXServiceURL connectUrl, Map env) { + this.metadata = metadata; + this.env = env; + this.connectUrl = connectUrl; + } + + @Override + public String getGUID() { + return this.metadata.getName(); + } + + @Override + public String getDisplayName() { + return this.metadata.getName(); + } + + @Override + public JVMDescriptor getJvmInfo() { + return null; + } + + public String getPath() { + String namespace = metadata.getNamespace(); + final Object context = this.env.get(KubernetesJmxConnector.KUBERNETES_CLIENT_CONTEXT); + if (context != null) { + return context + "/" + namespace; //$NON-NLS-1$ + } + return namespace; + } + + @Override + public JMXServiceURL createJMXServiceURL() throws IOException { + return this.connectUrl; + } + + @Override + public Map getEnvironment() { + return this.env; + } + + @Override + public JMXServiceURL serviceUrl() { + return this.connectUrl; + } + +} diff --git a/application/org.openjdk.jmc.kubernetes/src/main/java/org/openjdk/jmc/kubernetes/Messages.java b/application/org.openjdk.jmc.kubernetes/src/main/java/org/openjdk/jmc/kubernetes/Messages.java new file mode 100644 index 0000000000..a47e8a9ee3 --- /dev/null +++ b/application/org.openjdk.jmc.kubernetes/src/main/java/org/openjdk/jmc/kubernetes/Messages.java @@ -0,0 +1,56 @@ +/* + * Copyright (c) 2025, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2025, Kantega AS. All rights reserved. + * + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * The contents of this file are subject to the terms of either the Universal Permissive License + * v 1.0 as shown at http://oss.oracle.com/licenses/upl + * + * or the following license: + * + * Redistribution and use in source and binary forms, with or without modification, are permitted + * provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, this list of conditions + * and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright notice, this list of + * conditions and the following disclaimer in the documentation and/or other materials provided with + * the distribution. + * + * 3. Neither the name of the copyright holder nor the names of its contributors may be used to + * endorse or promote products derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR + * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND + * FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, + * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY + * WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package org.openjdk.jmc.kubernetes; + +import org.eclipse.osgi.util.NLS; + +public class Messages extends NLS { + private static final String BUNDLE_NAME = "org.openjdk.jmc.kubernetes.messages"; //$NON-NLS-1$ + public static String KubernetesDiscoveryListener_CouldNotFindSecret; + public static String KubernetesDiscoveryListener_Description; + public static String KubernetesDiscoveryListener_ErrConnectingToJvm; + public static String KubernetesDiscoveryListener_HttpOrHttps; + public static String KubernetesDiscoveryListener_InNamespace; + public static String KubernetesDiscoveryListener_JolokiaProtocol; + public static String KubernetesDiscoveryListener_MustProvidePassword; + public static String KubernetesDiscoveryListener_UnableToFindContexts; + public static String KubernetesDiscoveryListener_UnableToScan; + static { + // initialize resource bundle + NLS.initializeMessages(BUNDLE_NAME, Messages.class); + } + + private Messages() { + } +} diff --git a/application/org.openjdk.jmc.kubernetes/src/main/java/org/openjdk/jmc/kubernetes/messages.properties b/application/org.openjdk.jmc.kubernetes/src/main/java/org/openjdk/jmc/kubernetes/messages.properties new file mode 100644 index 0000000000..ea0166360a --- /dev/null +++ b/application/org.openjdk.jmc.kubernetes/src/main/java/org/openjdk/jmc/kubernetes/messages.properties @@ -0,0 +1,9 @@ +KubernetesDiscoveryListener_CouldNotFindSecret=Could not find secret named +KubernetesDiscoveryListener_Description=List JVM in kubernetes cluster +KubernetesDiscoveryListener_ErrConnectingToJvm=Error connecting to JVM in pod +KubernetesDiscoveryListener_HttpOrHttps=' must be either 'http' or 'https' +KubernetesDiscoveryListener_InNamespace=\ in namespace +KubernetesDiscoveryListener_JolokiaProtocol=Jolokia protocol ' +KubernetesDiscoveryListener_MustProvidePassword=Password must be specified when username is specified +KubernetesDiscoveryListener_UnableToFindContexts=Unable to find all kubernetes contexts +KubernetesDiscoveryListener_UnableToScan=Unable to scan kubernetes context diff --git a/application/org.openjdk.jmc.kubernetes/src/main/java/org/openjdk/jmc/kubernetes/preferences/JmcKubernetesPreferenceForm.java b/application/org.openjdk.jmc.kubernetes/src/main/java/org/openjdk/jmc/kubernetes/preferences/JmcKubernetesPreferenceForm.java new file mode 100644 index 0000000000..1efb921d61 --- /dev/null +++ b/application/org.openjdk.jmc.kubernetes/src/main/java/org/openjdk/jmc/kubernetes/preferences/JmcKubernetesPreferenceForm.java @@ -0,0 +1,192 @@ +/* + * Copyright (c) 2025, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2025, Kantega AS. All rights reserved. + * + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * The contents of this file are subject to the terms of either the Universal Permissive License + * v 1.0 as shown at http://oss.oracle.com/licenses/upl + * + * or the following license: + * + * Redistribution and use in source and binary forms, with or without modification, are permitted + * provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, this list of conditions + * and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright notice, this list of + * conditions and the following disclaimer in the documentation and/or other materials provided with + * the distribution. + * + * 3. Neither the name of the copyright holder nor the names of its contributors may be used to + * endorse or promote products derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR + * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND + * FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, + * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY + * WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package org.openjdk.jmc.kubernetes.preferences; + +import java.util.Map; +import java.util.WeakHashMap; +import java.util.logging.Level; + +import org.eclipse.jface.preference.BooleanFieldEditor; +import org.eclipse.jface.preference.FieldEditor; +import org.eclipse.jface.preference.FieldEditorPreferencePage; +import org.eclipse.jface.preference.StringFieldEditor; +import org.eclipse.swt.SWT; +import org.eclipse.swt.events.ModifyListener; +import org.eclipse.swt.layout.GridData; +import org.eclipse.swt.widgets.Control; +import org.eclipse.swt.widgets.Label; +import org.eclipse.swt.widgets.Text; +import org.eclipse.ui.IWorkbench; +import org.eclipse.ui.IWorkbenchPreferencePage; +import org.openjdk.jmc.common.security.CredentialsNotAvailableException; +import org.openjdk.jmc.common.security.SecurityException; +import org.openjdk.jmc.kubernetes.JmcKubernetesPlugin; + +/** + * This class represents a preference page that is contributed to the Preferences dialog. By + * subclassing FieldEditorPreferencePage, we can use the field support built into JFace + * that allows us to create a page that is small and knows how to save, restore and apply itself. + *

+ * This page is used to modify preferences only. They are stored in the preference store that + * belongs to the main plug-in class. That way, preferences can be accessed directly via the + * preference store. + */ +public class JmcKubernetesPreferenceForm extends FieldEditorPreferencePage + implements IWorkbenchPreferencePage, PreferenceConstants { + + private Map dependantControls = new WeakHashMap<>(); + private Text userField; + private Text passwordField; + private boolean credentialsDirty; + + public JmcKubernetesPreferenceForm() { + super(GRID); + setPreferenceStore(JmcKubernetesPlugin.getDefault().getPreferenceStore()); + setDescription(Messages.JmcKubernetesPreferenceForm_FormDescription); + } + + /** + * Creates the field editors. Field editors are abstractions of the common GUI blocks needed to + * manipulate various types of preferences. Each field editor knows how to save and restore + * itself. + */ + public void createFieldEditors() { + BooleanFieldEditor mainEnabler = new BooleanFieldEditor(P_SCAN_FOR_INSTANCES, + Messages.JmcKubernetesPreferenceForm_ScanForPods, getFieldEditorParent()) { + @Override + protected void valueChanged(boolean oldValue, boolean newValue) { + super.valueChanged(oldValue, newValue); + enableDependantFields(newValue); + } + }; + addField(mainEnabler); + + final BooleanFieldEditor scanContextsEditor = new BooleanFieldEditor(P_SCAN_ALL_CONTEXTS, + Messages.JmcKubernetesPreferenceForm_AllContexts, getFieldEditorParent()); + this.addDependantField(scanContextsEditor, scanContextsEditor.getDescriptionControl(getFieldEditorParent())); + final BooleanFieldEditor logErrors = new BooleanFieldEditor(P_LOG_ERRORS, + Messages.JmcKubernetesPreferenceForm_LogErrorsLabel, getFieldEditorParent()); + this.addDependantField(logErrors, logErrors.getDescriptionControl(getFieldEditorParent())); + this.addTextField(new StringFieldEditor(P_REQUIRE_LABEL, Messages.JmcKubernetesPreferenceForm_RequireLabel, + getFieldEditorParent()), Messages.JmcKubernetesPreferenceForm_LabelToolTip); + this.addTextField(new StringFieldEditor(P_JOLOKIA_PATH, Messages.JmcKubernetesPreferenceForm_PathLabel, + getFieldEditorParent()), Messages.JmcKubernetesPreferenceForm_PathTooltip); + this.addTextField(new StringFieldEditor(P_JOLOKIA_PORT, Messages.JmcKubernetesPreferenceForm_PortLabel, + getFieldEditorParent()), Messages.JmcKubernetesPreferenceForm_PortTooltip); + this.addTextField(new StringFieldEditor(P_JOLOKIA_PROTOCOL, Messages.JmcKubernetesPreferenceForm_ProtocolLabel, + getFieldEditorParent()), Messages.JmcKubernetesPreferenceForm_ProtocolTooltip); + createCredentialFields(); + // set initial enablement + enableDependantFields(JmcKubernetesPlugin.getDefault().scanForInstances()); + + } + + private void createCredentialFields() { + Label userLabel = new Label(getFieldEditorParent(), SWT.NONE); + userLabel.setText(Messages.JmcKubernetesPreferenceForm_UsernameLabel); + userLabel.setLayoutData(new GridData()); + this.userField = new Text(getFieldEditorParent(), SWT.SINGLE | SWT.BORDER); + userField.setLayoutData(new GridData(GridData.FILL_HORIZONTAL)); + userField.setToolTipText(Messages.JmcKubernetesPreferenceForm_UsernameTooltip); + this.dependantControls.put(userField, null); + + Label passLabel = new Label(getFieldEditorParent(), SWT.NONE); + passLabel.setText(Messages.JmcKubernetesPreferenceForm_PasswordLabel); + passLabel.setLayoutData(new GridData()); + this.passwordField = new Text(getFieldEditorParent(), SWT.PASSWORD | SWT.SINGLE | SWT.BORDER); + passwordField.setLayoutData(new GridData(GridData.FILL_HORIZONTAL)); + this.dependantControls.put(passwordField, null); + + JmcKubernetesPlugin plugin = JmcKubernetesPlugin.getDefault(); + + try { + userField.setText(plugin.username()); + passwordField.setText(plugin.password()); + } catch (CredentialsNotAvailableException ignore) { + // ignore if credentials are not stored + } catch (SecurityException e) { + plugin.getLogger().log(Level.WARNING, "Could not load kubernetes credentials", e); //$NON-NLS-1$ + } + + ModifyListener markCredentials = e -> credentialsDirty = true; + this.userField.addModifyListener(markCredentials); + this.passwordField.addModifyListener(markCredentials); + } + + private void addTextField(StringFieldEditor field, String tooltip) { + Text textControl = field.getTextControl(getFieldEditorParent()); + this.addDependantField(field, textControl); + textControl.setToolTipText(tooltip); + field.getLabelControl(getFieldEditorParent()).setToolTipText(tooltip); + + } + + private void addDependantField(FieldEditor field, Control control) { + this.dependantControls.put(control, null); + addField(field); + } + + private void enableDependantFields(boolean enabled) { + for (Control field : this.dependantControls.keySet()) { + field.setEnabled(enabled); + } + } + + /* + * (non-Javadoc) + * + * @see org.eclipse.ui.IWorkbenchPreferencePage#init(org.eclipse.ui.IWorkbench) + */ + public void init(IWorkbench workbench) { + } + + @Override + public boolean performOk() { + updateCredentialsIfApplicable(); + return super.performOk(); + } + + private void updateCredentialsIfApplicable() { + if (this.credentialsDirty) { + try { + JmcKubernetesPlugin.getDefault().storeCredentials(userField.getText(), passwordField.getText()); + this.credentialsDirty = false; + } catch (SecurityException ex) { + JmcKubernetesPlugin.getDefault().getLogger().log(Level.WARNING, + "Could not store kubernetes credentials", ex); //$NON-NLS-1$ + } + } + } + +} diff --git a/application/org.openjdk.jmc.kubernetes/src/main/java/org/openjdk/jmc/kubernetes/preferences/KubernetesScanningParameters.java b/application/org.openjdk.jmc.kubernetes/src/main/java/org/openjdk/jmc/kubernetes/preferences/KubernetesScanningParameters.java new file mode 100644 index 0000000000..110f4ca881 --- /dev/null +++ b/application/org.openjdk.jmc.kubernetes/src/main/java/org/openjdk/jmc/kubernetes/preferences/KubernetesScanningParameters.java @@ -0,0 +1,59 @@ +/* + * Copyright (c) 2025, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2025, Kantega AS. All rights reserved. + * + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * The contents of this file are subject to the terms of either the Universal Permissive License + * v 1.0 as shown at http://oss.oracle.com/licenses/upl + * + * or the following license: + * + * Redistribution and use in source and binary forms, with or without modification, are permitted + * provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, this list of conditions + * and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright notice, this list of + * conditions and the following disclaimer in the documentation and/or other materials provided with + * the distribution. + * + * 3. Neither the name of the copyright holder nor the names of its contributors may be used to + * endorse or promote products derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR + * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND + * FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, + * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY + * WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package org.openjdk.jmc.kubernetes.preferences; + +import org.openjdk.jmc.common.security.ICredentials; +import org.openjdk.jmc.common.security.SecurityException; + +public interface KubernetesScanningParameters { + boolean scanForInstances(); + + boolean scanAllContexts(); + + String jolokiaPort(); + + String username() throws SecurityException; + + String password() throws SecurityException; + + String jolokiaPath(); + + String jolokiaProtocol(); + + String requireLabel(); + + ICredentials storeCredentials(String username, String password) throws SecurityException; + + void logError(String message, Throwable error); +} diff --git a/application/org.openjdk.jmc.kubernetes/src/main/java/org/openjdk/jmc/kubernetes/preferences/Messages.java b/application/org.openjdk.jmc.kubernetes/src/main/java/org/openjdk/jmc/kubernetes/preferences/Messages.java new file mode 100644 index 0000000000..78234e0af0 --- /dev/null +++ b/application/org.openjdk.jmc.kubernetes/src/main/java/org/openjdk/jmc/kubernetes/preferences/Messages.java @@ -0,0 +1,63 @@ +/* + * Copyright (c) 2025, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2025, Kantega AS. All rights reserved. + * + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * The contents of this file are subject to the terms of either the Universal Permissive License + * v 1.0 as shown at http://oss.oracle.com/licenses/upl + * + * or the following license: + * + * Redistribution and use in source and binary forms, with or without modification, are permitted + * provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, this list of conditions + * and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright notice, this list of + * conditions and the following disclaimer in the documentation and/or other materials provided with + * the distribution. + * + * 3. Neither the name of the copyright holder nor the names of its contributors may be used to + * endorse or promote products derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR + * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND + * FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, + * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY + * WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package org.openjdk.jmc.kubernetes.preferences; + +import org.eclipse.osgi.util.NLS; + +public class Messages extends NLS { + private static final String BUNDLE_NAME = "org.openjdk.jmc.kubernetes.preferences.messages"; //$NON-NLS-1$ + public static String JmcKubernetesPreferenceForm_AllContexts; + public static String JmcKubernetesPreferenceForm_FormDescription; + public static String JmcKubernetesPreferenceForm_LabelToolTip; + public static String JmcKubernetesPreferenceForm_LogErrorsLabel; + public static String JmcKubernetesPreferenceForm_PasswordLabel; + public static String JmcKubernetesPreferenceForm_PasswordTooltip; + public static String JmcKubernetesPreferenceForm_PathLabel; + public static String JmcKubernetesPreferenceForm_PathTooltip; + public static String JmcKubernetesPreferenceForm_PortLabel; + public static String JmcKubernetesPreferenceForm_PortTooltip; + public static String JmcKubernetesPreferenceForm_ProtocolLabel; + public static String JmcKubernetesPreferenceForm_ProtocolTooltip; + public static String JmcKubernetesPreferenceForm_RequireLabel; + public static String JmcKubernetesPreferenceForm_ScanForPods; + public static String JmcKubernetesPreferenceForm_UsernameTooltip; + public static String JmcKubernetesPreferenceForm_UsernameLabel; + static { + // initialize resource bundle + NLS.initializeMessages(BUNDLE_NAME, Messages.class); + } + + private Messages() { + } +} diff --git a/application/org.openjdk.jmc.kubernetes/src/main/java/org/openjdk/jmc/kubernetes/preferences/PreferenceConstants.java b/application/org.openjdk.jmc.kubernetes/src/main/java/org/openjdk/jmc/kubernetes/preferences/PreferenceConstants.java new file mode 100644 index 0000000000..9485253abc --- /dev/null +++ b/application/org.openjdk.jmc.kubernetes/src/main/java/org/openjdk/jmc/kubernetes/preferences/PreferenceConstants.java @@ -0,0 +1,49 @@ +/* + * Copyright (c) 2025, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2025, Kantega AS. All rights reserved. + * + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * The contents of this file are subject to the terms of either the Universal Permissive License + * v 1.0 as shown at http://oss.oracle.com/licenses/upl + * + * or the following license: + * + * Redistribution and use in source and binary forms, with or without modification, are permitted + * provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, this list of conditions + * and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright notice, this list of + * conditions and the following disclaimer in the documentation and/or other materials provided with + * the distribution. + * + * 3. Neither the name of the copyright holder nor the names of its contributors may be used to + * endorse or promote products derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR + * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND + * FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, + * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY + * WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package org.openjdk.jmc.kubernetes.preferences; + +/** + * Constant definitions for plug-in preferences + */ +public interface PreferenceConstants { + + public static final String P_SCAN_FOR_INSTANCES = "scanForInstances"; //$NON-NLS-1$ + public static final String P_SCAN_ALL_CONTEXTS = "scanAllContexts"; //$NON-NLS-1$ + public static final String P_LOG_ERRORS = "logErrors"; //$NON-NLS-1$ + public static final String P_REQUIRE_LABEL = "requireLabel"; //$NON-NLS-1$ + public static final String P_JOLOKIA_PATH = "jolokiaPath"; //$NON-NLS-1$ + public static final String P_JOLOKIA_PORT = "jolokiaPort"; //$NON-NLS-1$ + public static final String P_JOLOKIA_PROTOCOL = "jolokiaProtocol"; //$NON-NLS-1$ + public static final String P_CREDENTIALS_KEY = "kubernetes.scanning.credentials"; //$NON-NLS-1$ +} diff --git a/application/org.openjdk.jmc.kubernetes/src/main/java/org/openjdk/jmc/kubernetes/preferences/PreferenceInitializer.java b/application/org.openjdk.jmc.kubernetes/src/main/java/org/openjdk/jmc/kubernetes/preferences/PreferenceInitializer.java new file mode 100644 index 0000000000..b1f653366e --- /dev/null +++ b/application/org.openjdk.jmc.kubernetes/src/main/java/org/openjdk/jmc/kubernetes/preferences/PreferenceInitializer.java @@ -0,0 +1,60 @@ +/* + * Copyright (c) 2025, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2025, Kantega AS. All rights reserved. + * + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * The contents of this file are subject to the terms of either the Universal Permissive License + * v 1.0 as shown at http://oss.oracle.com/licenses/upl + * + * or the following license: + * + * Redistribution and use in source and binary forms, with or without modification, are permitted + * provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, this list of conditions + * and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright notice, this list of + * conditions and the following disclaimer in the documentation and/or other materials provided with + * the distribution. + * + * 3. Neither the name of the copyright holder nor the names of its contributors may be used to + * endorse or promote products derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR + * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND + * FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, + * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY + * WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package org.openjdk.jmc.kubernetes.preferences; + +import org.eclipse.core.runtime.preferences.AbstractPreferenceInitializer; +import org.eclipse.jface.preference.IPreferenceStore; +import org.openjdk.jmc.kubernetes.JmcKubernetesPlugin; + +/** + * Class used to initialize default preference values. + */ +public class PreferenceInitializer extends AbstractPreferenceInitializer implements PreferenceConstants { + + /* + * (non-Javadoc) + * + * @see org.eclipse.core.runtime.preferences.AbstractPreferenceInitializer# + * initializeDefaultPreferences() + */ + public void initializeDefaultPreferences() { + IPreferenceStore store = JmcKubernetesPlugin.getDefault().getPreferenceStore(); + store.setDefault(P_SCAN_FOR_INSTANCES, false); + store.setDefault(P_SCAN_ALL_CONTEXTS, false); + store.setDefault(P_REQUIRE_LABEL, "jolokia"); //$NON-NLS-1$ + store.setDefault(P_JOLOKIA_PATH, "/jolokia/"); //$NON-NLS-1$ + store.setDefault(P_JOLOKIA_PORT, "8778"); //$NON-NLS-1$ + } + +} diff --git a/application/org.openjdk.jmc.kubernetes/src/main/java/org/openjdk/jmc/kubernetes/preferences/messages.properties b/application/org.openjdk.jmc.kubernetes/src/main/java/org/openjdk/jmc/kubernetes/preferences/messages.properties new file mode 100644 index 0000000000..cb743096d3 --- /dev/null +++ b/application/org.openjdk.jmc.kubernetes/src/main/java/org/openjdk/jmc/kubernetes/preferences/messages.properties @@ -0,0 +1,18 @@ +JmcKubernetesPreferenceForm_AllContexts=Scan pods from all locally configured &contexts, if false: only scan the current contexts +JmcKubernetesPreferenceForm_FormDescription=Options that allows you to scan kubernetes for JVMs running Jolokia\ +\n\nThe Kubernetes Proxy API will be used to attempt to access Jolokia like this (parenthesis denote optional components): \ +\n\n$kubernetesBaseUrl/api/v1/namespaces/podNamespace/pods/(jolokiaProtocol:)podname(:jolokiaPort)/proxy/jolokiaPath\n\n +JmcKubernetesPreferenceForm_LabelToolTip=Only attempt to connect to pods with this label set, leave empty to try to scan all pods +JmcKubernetesPreferenceForm_LogErrorsLabel=Log errors during scanning. Aids in troubleshooting but may generate some background noise. +JmcKubernetesPreferenceForm_PasswordLabel=Require pass&word +JmcKubernetesPreferenceForm_PasswordTooltip=Password , alternatively use ${kubernetes/secret/secretName/secretItem} where the secret is in the same namespace as the pod and the type is either kubernetes.io/basic-auth or Opaque with java.util.Properties compatible values +JmcKubernetesPreferenceForm_PathLabel=Jolokia &path in pods +JmcKubernetesPreferenceForm_PathTooltip=Use this path for jolokia, or specify ${kubernetes/annotation/annotationName} to be able to to get port name from pod metadata annotation of your choice +JmcKubernetesPreferenceForm_PortLabel=Jolokia p&ort in pods +JmcKubernetesPreferenceForm_PortTooltip=Port to use, leave empty to use default port of Kubernetes proxy, hardcode a port value, or alternatively ${kubernetes/annotation/annotationName} to get port name from pod metadata annotation of your choice +JmcKubernetesPreferenceForm_ProtocolLabel=Jolokia pro&tocol in pods +JmcKubernetesPreferenceForm_ProtocolTooltip=Protocol to use (optional), will infer http if not set, or hardcode to https, or alternatively ${kubernetes/annotation/annotationName} to get port name from pod metadata annotation of your choice +JmcKubernetesPreferenceForm_RequireLabel=Require &label to scan pod +JmcKubernetesPreferenceForm_ScanForPods=&Scan for kubernetes pods with Jolokia support +JmcKubernetesPreferenceForm_UsernameTooltip=Username , alternatively use ${kubernetes/secret/secretName/secretItem} where the secret is in the same namespace as the pod and the type is either kubernetes.io/basic-auth or Opaque with java.util.Properties compatible values +JmcKubernetesPreferenceForm_UsernameLabel=Require &username diff --git a/application/pom.xml b/application/pom.xml index fbd9cc8b2d..13f0c99be8 100644 --- a/application/pom.xml +++ b/application/pom.xml @@ -112,6 +112,7 @@ org.openjdk.jmc.updatesite.ide org.openjdk.jmc.updatesite.rcp org.openjdk.jmc.jolokia + org.openjdk.jmc.kubernetes l10n tests diff --git a/application/tests/org.openjdk.jmc.jolokia.test/pom.xml b/application/tests/org.openjdk.jmc.jolokia.test/pom.xml index bedcfa1fb1..96a395cf93 100644 --- a/application/tests/org.openjdk.jmc.jolokia.test/pom.xml +++ b/application/tests/org.openjdk.jmc.jolokia.test/pom.xml @@ -46,7 +46,7 @@ ${project.basedir}/../../../configuration - 2.0.2 + 2.2.2 diff --git a/application/tests/org.openjdk.jmc.jolokia.test/src/test/java/org/openjdk/jmc/jolokia/JolokiaTest.java b/application/tests/org.openjdk.jmc.jolokia.test/src/test/java/org/openjdk/jmc/jolokia/JolokiaTest.java index bd1f2ceba2..c5d50622aa 100644 --- a/application/tests/org.openjdk.jmc.jolokia.test/src/test/java/org/openjdk/jmc/jolokia/JolokiaTest.java +++ b/application/tests/org.openjdk.jmc.jolokia.test/src/test/java/org/openjdk/jmc/jolokia/JolokiaTest.java @@ -209,7 +209,7 @@ public JolokiaContext getJolokiaContext() { StaticConfiguration configuration = new StaticConfiguration(ConfigKey.AGENT_ID, "jolokiatest"); JolokiaServiceManager serviceManager = JolokiaServiceManagerFactory.createJolokiaServiceManager(configuration, new StdoutLogHandler(true), new AllowAllRestrictor(), - () -> new TreeSet(Arrays.asList(ServerDetector.FALLBACK))); + log -> new TreeSet<>(Arrays.asList(ServerDetector.FALLBACK))); return serviceManager.start(); } diff --git a/application/uitests/org.openjdk.jmc.kubernetes.test/META-INF/MANIFEST.MF b/application/uitests/org.openjdk.jmc.kubernetes.test/META-INF/MANIFEST.MF new file mode 100644 index 0000000000..d028d9df45 --- /dev/null +++ b/application/uitests/org.openjdk.jmc.kubernetes.test/META-INF/MANIFEST.MF @@ -0,0 +1,20 @@ +Manifest-Version: 1.0 +Bundle-ManifestVersion: 2 +Bundle-Name: RJMX Test +Bundle-SymbolicName: org.openjdk.jmc.kubernetes.test;singleton:=true +Bundle-Version: 10.0.0.qualifier +Import-Package: io.fabric8.kubernetes.api.model +Bundle-Vendor: Oracle Corporation +Bundle-RequiredExecutionEnvironment: JavaSE-17 +Bundle-ActivationPolicy: lazy +Require-Bundle: org.junit, + org.openjdk.jmc.jolokia, + org.eclipse.osgi;bundle-version="3.16.200", + org.eclipse.ui.workbench, + org.openjdk.jmc.rjmx;bundle-version="9.0.0", + org.eclipse.ui, + org.hamcrest;bundle-version="2.1", + org.openjdk.jmc.kubernetes;bundle-version="9.0.0", + com.github.tomakehurst.wiremock-standalone;bundle-version="2.27.2", + org.awaitility;bundle-version="4.0.0" +Automatic-Module-Name: org.openjdk.jmc.jolokia.test diff --git a/application/uitests/org.openjdk.jmc.kubernetes.test/build.properties b/application/uitests/org.openjdk.jmc.kubernetes.test/build.properties new file mode 100644 index 0000000000..3be66a2718 --- /dev/null +++ b/application/uitests/org.openjdk.jmc.kubernetes.test/build.properties @@ -0,0 +1,36 @@ +# +# Copyright (c) 2025, Oracle and/or its affiliates. All rights reserved. +# Copyright (c) 2025, Kantega AS. All rights reserved. +# +# DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. +# +# The contents of this file are subject to the terms of either the Universal Permissive License +# v 1.0 as shown at http://oss.oracle.com/licenses/upl +# +# or the following license: +# +# Redistribution and use in source and binary forms, with or without modification, are permitted +# provided that the following conditions are met: +# +# 1. Redistributions of source code must retain the above copyright notice, this list of conditions +# and the following disclaimer. +# +# 2. Redistributions in binary form must reproduce the above copyright notice, this list of +# conditions and the following disclaimer in the documentation and/or other materials provided with +# the distribution. +# +# 3. Neither the name of the copyright holder nor the names of its contributors may be used to +# endorse or promote products derived from this software without specific prior written permission. +# +# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR +# IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND +# FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR +# CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL +# DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +# DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, +# WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY +# WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +source.. = src/test/java/,\ + src/test/resources/ +output.. = target/test-classes/ +bin.includes = META-INF/,. diff --git a/application/uitests/org.openjdk.jmc.kubernetes.test/pom.xml b/application/uitests/org.openjdk.jmc.kubernetes.test/pom.xml new file mode 100644 index 0000000000..61fe1bf30b --- /dev/null +++ b/application/uitests/org.openjdk.jmc.kubernetes.test/pom.xml @@ -0,0 +1,58 @@ + + + + 4.0.0 + + org.openjdk.jmc + missioncontrol.application.uitests + ${revision}${changelist} + + org.openjdk.jmc.kubernetes.test + eclipse-test-plugin + + + ${project.basedir}/../../../configuration + + + + + org.awaitility + awaitility + 4.0.0 + + + diff --git a/application/uitests/org.openjdk.jmc.kubernetes.test/src/test/java/org/openjdk/jmc/kubernetes/JmcKubernetesTest.java b/application/uitests/org.openjdk.jmc.kubernetes.test/src/test/java/org/openjdk/jmc/kubernetes/JmcKubernetesTest.java new file mode 100644 index 0000000000..832483331d --- /dev/null +++ b/application/uitests/org.openjdk.jmc.kubernetes.test/src/test/java/org/openjdk/jmc/kubernetes/JmcKubernetesTest.java @@ -0,0 +1,313 @@ +/* + * Copyright (c) 2025, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2025, Kantega AS. All rights reserved. + * + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * The contents of this file are subject to the terms of either the Universal Permissive License + * v 1.0 as shown at http://oss.oracle.com/licenses/upl + * + * or the following license: + * + * Redistribution and use in source and binary forms, with or without modification, are permitted + * provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, this list of conditions + * and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright notice, this list of + * conditions and the following disclaimer in the documentation and/or other materials provided with + * the distribution. + * + * 3. Neither the name of the copyright holder nor the names of its contributors may be used to + * endorse or promote products derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR + * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND + * FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, + * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY + * WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package org.openjdk.jmc.kubernetes; + +import java.io.File; +import java.io.FileOutputStream; +import java.io.IOException; +import java.net.MalformedURLException; +import java.time.Duration; +import java.util.Base64; +import java.util.Collections; +import java.util.HashMap; +import java.util.Map; + +import javax.management.AttributeNotFoundException; +import javax.management.InstanceNotFoundException; +import javax.management.InvalidAttributeValueException; +import javax.management.MBeanException; +import javax.management.MBeanServerConnection; +import javax.management.MalformedObjectNameException; +import javax.management.ObjectName; +import javax.management.ReflectionException; +import javax.management.remote.JMXConnector; +import javax.management.remote.JMXServiceURL; + +import org.apache.http.client.methods.CloseableHttpResponse; +import org.apache.http.client.methods.HttpGet; +import org.apache.http.impl.client.HttpClients; +import org.awaitility.Awaitility; +import org.junit.Assert; +import org.junit.Before; +import org.junit.BeforeClass; +import org.junit.ClassRule; +import org.junit.Test; +import org.openjdk.jmc.common.IDescribable; +import org.openjdk.jmc.common.jvm.JVMType; +import org.openjdk.jmc.common.security.ICredentials; +import org.openjdk.jmc.common.security.InMemoryCredentials; +import org.openjdk.jmc.common.security.SecurityException; +import org.openjdk.jmc.kubernetes.preferences.KubernetesScanningParameters; +import org.openjdk.jmc.rjmx.common.IConnectionDescriptor; +import org.openjdk.jmc.rjmx.common.IServerDescriptor; +import org.openjdk.jmc.rjmx.descriptorprovider.IDescriptorListener; + +import com.github.tomakehurst.wiremock.client.WireMock; +import com.github.tomakehurst.wiremock.core.WireMockConfiguration; +import com.github.tomakehurst.wiremock.extension.responsetemplating.ResponseTemplateTransformer; +import com.github.tomakehurst.wiremock.junit.WireMockRule; + +/** + * Test that JMX connections done with JmcKubernetesJmxConnectionProvider are functional. In order + * to be able to test this in a contained environment, the kubernetes API is mocked with wiremock. + */ +@SuppressWarnings("restriction") +public class JmcKubernetesTest { + + static class TestParameters implements KubernetesScanningParameters { + public boolean scanForInstances, scanAllContexts; + public String jolokiaPort, jolokiaPath = "/jolokia/", jolokiaProtocol, requireLabel; + public InMemoryCredentials credentials; + + @Override + public boolean scanForInstances() { + return this.scanForInstances; + } + + @Override + public boolean scanAllContexts() { + return this.scanAllContexts; + } + + @Override + public String jolokiaPort() { + return this.jolokiaPort; + } + + @Override + public String username() throws SecurityException { + return this.credentials == null ? null : this.credentials.getUsername(); + } + + @Override + public String password() throws SecurityException { + return this.credentials == null ? null : this.credentials.getPassword(); + } + + @Override + public String jolokiaPath() { + return this.jolokiaPath; + } + + @Override + public String jolokiaProtocol() { + return this.jolokiaProtocol; + } + + @Override + public String requireLabel() { + return this.requireLabel; + } + + @Override + public ICredentials storeCredentials(String username, String password) throws SecurityException { + return this.credentials = new InMemoryCredentials(username, password); + } + + @Override + public void logError(String message, Throwable error) { + System.out.println(message); + error.printStackTrace(System.out); + } + } + + @ClassRule + public static WireMockRule wiremock = new WireMockRule( + WireMockConfiguration.options().extensions(new ResponseTemplateTransformer(false)).port(0)); + + static final String jolokiaUrl = "service:jmx:kubernetes:///ns1/pod-abcdef/jolokia"; + + private static MBeanServerConnection jolokiaConnection; + + @BeforeClass + public static void connect() throws Exception { + CloseableHttpResponse configResponse = HttpClients.createDefault() + .execute(new HttpGet(wiremock.baseUrl() + "/mock-kube-config.yml")); + Assert.assertEquals(configResponse.getStatusLine().getStatusCode(), 200); + File configFile = File.createTempFile("mock-kube-config", ".yml"); + configResponse.getEntity().writeTo(new FileOutputStream(configFile)); + // we set this so the KubernetesDiscoveryListener will work + //Setting taken from: https://github.com/fabric8io/kubernetes-client/blob/77a65f7d40f31a5dc37492cd9de3c317c2702fb4/kubernetes-client-api/src/main/java/io/fabric8/kubernetes/client/Config.java#L120, unlikely to change + System.setProperty("kubeconfig", configFile.getAbsolutePath()); + jolokiaConnection = getKubernetesMBeanConnector(); + } + + @Test + public void testExecuteOperation() throws InstanceNotFoundException, MalformedObjectNameException, MBeanException, + ReflectionException, MalformedURLException, IOException { + jolokiaConnection.invoke(new ObjectName("java.lang:type=Memory"), "gc", new Object[0], new String[0]); + } + + @Test + public void testReadAttribute() + throws InstanceNotFoundException, AttributeNotFoundException, InvalidAttributeValueException, + MalformedObjectNameException, MBeanException, ReflectionException, MalformedURLException, IOException { + MBeanServerConnection jmxConnection = jolokiaConnection; + assertOneSingleAttribute(jmxConnection); + + } + + private void assertOneSingleAttribute(MBeanServerConnection jmxConnection) throws MalformedObjectNameException, + MBeanException, AttributeNotFoundException, InstanceNotFoundException, ReflectionException, IOException { + ObjectName objectName = new ObjectName("java.lang:type=Memory"); + String attribute = "Verbose"; + Assert.assertEquals(false, jmxConnection.getAttribute(objectName, attribute)); + } + + @Before + public void reset() { + wiremock.resetAll(); + wiremock.resetRequests(); + } + + private static MBeanServerConnection getKubernetesMBeanConnector() throws IOException, MalformedURLException { + JMXConnector connector = new JmcKubernetesJmxConnectionProvider().newJMXConnector(new JMXServiceURL(jolokiaUrl), + Collections.emptyMap()); + connector.connect(); + MBeanServerConnection connection = connector.getMBeanServerConnection(); + return connection; + } + + @Test + public void testDiscoverWithMostlyDefaultSettings() throws Exception { + + TestParameters parameters = new TestParameters(); + // Set config so that scanning takes place + parameters.scanForInstances = true; + testThatJvmIsFound(parameters); + } + + @Test + public void testDiscoverWithPathFromAnnotation() throws Exception { + TestParameters parameters = new TestParameters(); + parameters.scanForInstances = true; + parameters.jolokiaPath = "${kubernetes/annotation/jolokiaPath}"; + testThatJvmIsFound(parameters); + } + + @Test + public void testDiscoverWithPortFromAnnotation() throws Exception { + TestParameters parameters = new TestParameters(); + parameters.scanForInstances = true; + parameters.jolokiaPort = "${kubernetes/annotation/jolokiaPort}"; + + testThatJvmIsFound(parameters); + } + + @Test + public void testDiscoverWithBasicAuthFromSecret() throws Exception { + TestParameters parameters = new TestParameters(); + parameters.scanForInstances = true; + parameters.credentials = new InMemoryCredentials("${kubernetes/secret/jolokia-auth/username}", + "${kubernetes/secret/jolokia-auth/password}"); + + testThatJvmIsFound(parameters); + // Verify that the expected authorization was picked up + WireMock.verify(WireMock + .postRequestedFor(WireMock.urlPathMatching("/api/v1/namespaces/ns1/pods/pod-abcdef.*/proxy/jolokia.*")) + .withHeader("X-jolokia-authorization", + WireMock.equalTo("Basic " + Base64.getEncoder().encodeToString("admin:admin".getBytes())))); + } + + @Test + public void testDiscoverWithAuthFromProperties() throws Exception { + + TestParameters parameters = new TestParameters(); + parameters.scanForInstances = true; + parameters.credentials = new InMemoryCredentials("${kubernetes/secret/jolokia-properties/user}", + "${kubernetes/secret/jolokia-properties/password}"); + + testThatJvmIsFound(parameters); + // Verify that the expected authorization was picked up + WireMock.verify(WireMock + .postRequestedFor(WireMock.urlPathMatching("/api/v1/namespaces/ns1/pods/pod-abcdef.*/proxy/jolokia.*")) + .withHeader("X-jolokia-authorization", + WireMock.equalTo("Basic " + Base64.getEncoder().encodeToString("admin:secret".getBytes())))); + } + + @Test + public void testDiscoverWithAuthDirectlyFromSettings() throws Exception { + + TestParameters parameters = new TestParameters(); + parameters.scanForInstances = true; + parameters.credentials = new InMemoryCredentials("user", "***"); + testThatJvmIsFound(parameters); + // Verify that the expected authorization was picked up + WireMock.verify(WireMock + .postRequestedFor(WireMock.urlPathMatching("/api/v1/namespaces/ns1/pods/pod-abcdef.*/proxy/jolokia.*")) + .withHeader("X-jolokia-authorization", + WireMock.equalTo("Basic " + Base64.getEncoder().encodeToString("user:***".getBytes())))); + } + + private void testThatJvmIsFound(TestParameters parameters) throws Exception { + + final KubernetesDiscoveryListener scanner = new KubernetesDiscoveryListener(parameters); + final Map foundVms = new HashMap<>(); + IDescriptorListener descriptorListener = new IDescriptorListener() { + public void onDescriptorDetected( + IServerDescriptor serverDescriptor, String path, JMXServiceURL url, + IConnectionDescriptor connectionDescriptor, IDescribable provider) { + foundVms.put(serverDescriptor.getGUID(), serverDescriptor); + } + + public void onDescriptorRemoved(String descriptorId) { + foundVms.remove(descriptorId); + } + }; + scanner.addDescriptorListener(descriptorListener); + + try { + // Test that at least one VM (the one running the test was discovered) + + Awaitility.await().atMost(Duration.ofSeconds(5)).until(() -> !foundVms.isEmpty()); + IServerDescriptor descriptor = foundVms.get("pod-abcdef"); + Assert.assertNotNull(descriptor); + Assert.assertEquals( + "[JVMDescriptor] Java command: /Users/marska/Downloads/hawtio-app-2.9.1.jar --port 9090 PID: 88774", + descriptor.getJvmInfo().toString()); + Assert.assertEquals(JVMType.HOTSPOT, descriptor.getJvmInfo().getJvmType()); + Assert.assertEquals("18.0.1", descriptor.getJvmInfo().getJavaVersion()); + Assert.assertTrue(descriptor instanceof IConnectionDescriptor); + IConnectionDescriptor connectDescriptor = (IConnectionDescriptor) descriptor; + JMXConnector connector = new JmcKubernetesJmxConnectionProvider() + .newJMXConnector(connectDescriptor.createJMXServiceURL(), connectDescriptor.getEnvironment()); + connector.connect(); + assertOneSingleAttribute(connector.getMBeanServerConnection()); + + } finally { + // Tell scanner thread to exit + scanner.shutdown(); + } + } +} diff --git a/application/uitests/org.openjdk.jmc.kubernetes.test/src/test/resources/__files/jolokia-attribute.json b/application/uitests/org.openjdk.jmc.kubernetes.test/src/test/resources/__files/jolokia-attribute.json new file mode 100644 index 0000000000..4230121a7e --- /dev/null +++ b/application/uitests/org.openjdk.jmc.kubernetes.test/src/test/resources/__files/jolokia-attribute.json @@ -0,0 +1,10 @@ +{ + "request" : { + "attribute" : "Verbose", + "mbean" : "java.lang:type=Memory", + "type" : "read" + }, + "status" : 200, + "timestamp" : 1658563869, + "value" : false +} diff --git a/application/uitests/org.openjdk.jmc.kubernetes.test/src/test/resources/__files/jolokia-basic-auth-secret.json b/application/uitests/org.openjdk.jmc.kubernetes.test/src/test/resources/__files/jolokia-basic-auth-secret.json new file mode 100644 index 0000000000..b5bb351b7e --- /dev/null +++ b/application/uitests/org.openjdk.jmc.kubernetes.test/src/test/resources/__files/jolokia-basic-auth-secret.json @@ -0,0 +1,19 @@ +{ + "apiVersion": "v1", + "data": { + "password": "YWRtaW4=", + "username": "YWRtaW4=" + }, + "kind": "Secret", + "metadata": { + "annotations": { + "kubectl.kubernetes.io/last-applied-configuration": "{\"apiVersion\":\"v1\",\"kind\":\"Secret\",\"metadata\":{\"annotations\":{},\"name\":\"jolokia-auth\",\"namespace\":\"jfr\"},\"stringData\":{\"password\":\"admin\",\"username\":\"admin\"},\"type\":\"kubernetes.io/basic-auth\"}\n" + }, + "creationTimestamp": "2022-05-20T13:59:12Z", + "name": "jolokia-auth", + "namespace": "jfr", + "resourceVersion": "130", + "uid": "e1563217-ef08-481d-b2e3-233fa3040b56" + }, + "type": "kubernetes.io/basic-auth" +} diff --git a/application/uitests/org.openjdk.jmc.kubernetes.test/src/test/resources/__files/jolokia-exec.json b/application/uitests/org.openjdk.jmc.kubernetes.test/src/test/resources/__files/jolokia-exec.json new file mode 100644 index 0000000000..5bd35a69f2 --- /dev/null +++ b/application/uitests/org.openjdk.jmc.kubernetes.test/src/test/resources/__files/jolokia-exec.json @@ -0,0 +1,10 @@ +{ + "request" : { + "mbean" : "java.lang:type=Memory", + "operation" : "gc()", + "type" : "exec" + }, + "status" : 200, + "timestamp" : 1658567003, + "value" : null +} diff --git a/application/uitests/org.openjdk.jmc.kubernetes.test/src/test/resources/__files/jolokia-properties-secret.json b/application/uitests/org.openjdk.jmc.kubernetes.test/src/test/resources/__files/jolokia-properties-secret.json new file mode 100644 index 0000000000..e12bbd16c1 --- /dev/null +++ b/application/uitests/org.openjdk.jmc.kubernetes.test/src/test/resources/__files/jolokia-properties-secret.json @@ -0,0 +1,15 @@ +{ + "apiVersion": "v1", + "data": { + "jolokia.properties": "aG9zdD0wLjAuMC4wCnBhc3N3b3JkPXNlY3JldAp1c2VyPWFkbWluCnBvcnQ9ODc3OApkaXNjb3ZlcnlFbmFibGVkPXRydWUKZGlzY292ZXJ5QWdlbnRVcmw9aHR0cDovLyR7aG9zdH06ODc3OC9qb2xva2lhLwpwb2xpY3lMb2NhdGlvbj1jbGFzc3BhdGg6L0JPT1QtSU5GL2NsYXNzZXMvam9sb2tpYS1hY2Nlc3MueG1s" + }, + "kind": "Secret", + "metadata": { + "creationTimestamp": "2022-05-20T13:59:12Z", + "name": "jolokia-properties", + "namespace": "jfr", + "resourceVersion": "147", + "uid": "025bf17a-cff5-46e6-8499-dd9d6de43fa9" + }, + "type": "Opaque" +} diff --git a/application/uitests/org.openjdk.jmc.kubernetes.test/src/test/resources/__files/jolokia-version.json b/application/uitests/org.openjdk.jmc.kubernetes.test/src/test/resources/__files/jolokia-version.json new file mode 100644 index 0000000000..6299d0407a --- /dev/null +++ b/application/uitests/org.openjdk.jmc.kubernetes.test/src/test/resources/__files/jolokia-version.json @@ -0,0 +1,41 @@ +{ + "request": { + "type": "version" + }, + "value": { + "agent": "1.7.1", + "protocol": "7.2", + "config": { + "listenForHttpService": "true", + "authIgnoreCerts": "false", + "agentId": "192.168.1.104-28660-5bd82fed-servlet", + "debug": "false", + "agentType": "servlet", + "policyLocation": "classpath:\/jolokia-access.xml", + "agentContext": "\/jolokia", + "serializeException": "false", + "mimeType": "text\/plain", + "dispatcherClasses": "org.jolokia.http.Jsr160ProxyNotEnabledByDefaultAnymoreDispatcher", + "authMode": "basic", + "authMatch": "any", + "streaming": "true", + "canonicalNaming": "true", + "historyMaxEntries": "10", + "allowErrorDetails": "false", + "allowDnsReverseLookup": "true", + "realm": "jolokia", + "includeStackTrace": "false", + "restrictorClass": "io.hawt.system.RBACRestrictor", + "mbeanQualifier": "qualifier=hawtio", + "useRestrictorService": "false", + "debugMaxEntries": "100" + }, + "info": { + "product": "jetty", + "vendor": "Eclipse", + "version": "9.4.z-SNAPSHOT" + } + }, + "timestamp": 1658556959, + "status": 200 +} \ No newline at end of file diff --git a/application/uitests/org.openjdk.jmc.kubernetes.test/src/test/resources/__files/memory-list.json b/application/uitests/org.openjdk.jmc.kubernetes.test/src/test/resources/__files/memory-list.json new file mode 100644 index 0000000000..46d117f2e4 --- /dev/null +++ b/application/uitests/org.openjdk.jmc.kubernetes.test/src/test/resources/__files/memory-list.json @@ -0,0 +1,46 @@ +{ + "request" : { + "path" : "java.lang/type=Memory", + "type" : "list" + }, + "status" : 200, + "timestamp" : 1658566168, + "value" : { + "attr" : { + "HeapMemoryUsage" : { + "desc" : "HeapMemoryUsage", + "rw" : false, + "type" : "javax.management.openmbean.CompositeData" + }, + "NonHeapMemoryUsage" : { + "desc" : "NonHeapMemoryUsage", + "rw" : false, + "type" : "javax.management.openmbean.CompositeData" + }, + "ObjectName" : { + "desc" : "ObjectName", + "rw" : false, + "type" : "javax.management.ObjectName" + }, + "ObjectPendingFinalizationCount" : { + "desc" : "ObjectPendingFinalizationCount", + "rw" : false, + "type" : "int" + }, + "Verbose" : { + "desc" : "Verbose", + "rw" : true, + "type" : "boolean" + } + }, + "class" : "sun.management.MemoryImpl", + "desc" : "Information on the management interface of the MBean", + "op" : { + "gc" : { + "args" : [], + "desc" : "gc", + "ret" : "void" + } + } + } +} diff --git a/application/uitests/org.openjdk.jmc.kubernetes.test/src/test/resources/__files/mock-kube-config.yml b/application/uitests/org.openjdk.jmc.kubernetes.test/src/test/resources/__files/mock-kube-config.yml new file mode 100644 index 0000000000..f222f471a7 --- /dev/null +++ b/application/uitests/org.openjdk.jmc.kubernetes.test/src/test/resources/__files/mock-kube-config.yml @@ -0,0 +1,17 @@ +apiVersion: v1 +clusters: +- cluster: + server: {{request.baseUrl}}/ + name: test +contexts: +- context: + cluster: test + user: test + name: test +current-context: test +kind: Config +preferences: {} +users: +- name: test + user: + token: foobar diff --git a/application/uitests/org.openjdk.jmc.kubernetes.test/src/test/resources/__files/pod.json b/application/uitests/org.openjdk.jmc.kubernetes.test/src/test/resources/__files/pod.json new file mode 100644 index 0000000000..77a61a3e32 --- /dev/null +++ b/application/uitests/org.openjdk.jmc.kubernetes.test/src/test/resources/__files/pod.json @@ -0,0 +1,11 @@ +{ + "metadata": { + "clusterName": "test", + "labels": { + "jolokia": true + }, + "name": "pod-abcdef", + "namespace": "ns1", + "selfLink": "/api/v1/namespaces/ns1/pods/pod-abcdef" + } +} \ No newline at end of file diff --git a/application/uitests/org.openjdk.jmc.kubernetes.test/src/test/resources/__files/pods.json b/application/uitests/org.openjdk.jmc.kubernetes.test/src/test/resources/__files/pods.json new file mode 100644 index 0000000000..114edd3640 --- /dev/null +++ b/application/uitests/org.openjdk.jmc.kubernetes.test/src/test/resources/__files/pods.json @@ -0,0 +1,19 @@ +{ + "items": [ + { + "metadata": { + "clusterName": "test", + "labels": { + "jolokia": true + }, + "annotations": { + "jolokiaPort": 8778, + "jolokiaPath": "jolokia" + }, + "name": "pod-abcdef", + "namespace": "ns1", + "selfLink": "/api/v1/namespaces/ns1/pods/pod-abcdef" + } + } + ] +} \ No newline at end of file diff --git a/application/uitests/org.openjdk.jmc.kubernetes.test/src/test/resources/__files/runtime-list.json b/application/uitests/org.openjdk.jmc.kubernetes.test/src/test/resources/__files/runtime-list.json new file mode 100644 index 0000000000..c096d78c52 --- /dev/null +++ b/application/uitests/org.openjdk.jmc.kubernetes.test/src/test/resources/__files/runtime-list.json @@ -0,0 +1,104 @@ +{ + "request" : { + "path" : "java.lang/type=Runtime", + "type" : "list" + }, + "status" : 200, + "timestamp" : 1658564737, + "value" : { + "attr" : { + "BootClassPath" : { + "desc" : "BootClassPath", + "rw" : false, + "type" : "java.lang.String" + }, + "BootClassPathSupported" : { + "desc" : "BootClassPathSupported", + "rw" : false, + "type" : "boolean" + }, + "ClassPath" : { + "desc" : "ClassPath", + "rw" : false, + "type" : "java.lang.String" + }, + "InputArguments" : { + "desc" : "InputArguments", + "rw" : false, + "type" : "[Ljava.lang.String;" + }, + "LibraryPath" : { + "desc" : "LibraryPath", + "rw" : false, + "type" : "java.lang.String" + }, + "ManagementSpecVersion" : { + "desc" : "ManagementSpecVersion", + "rw" : false, + "type" : "java.lang.String" + }, + "Name" : { + "desc" : "Name", + "rw" : false, + "type" : "java.lang.String" + }, + "ObjectName" : { + "desc" : "ObjectName", + "rw" : false, + "type" : "javax.management.ObjectName" + }, + "Pid" : { + "desc" : "Pid", + "rw" : false, + "type" : "long" + }, + "SpecName" : { + "desc" : "SpecName", + "rw" : false, + "type" : "java.lang.String" + }, + "SpecVendor" : { + "desc" : "SpecVendor", + "rw" : false, + "type" : "java.lang.String" + }, + "SpecVersion" : { + "desc" : "SpecVersion", + "rw" : false, + "type" : "java.lang.String" + }, + "StartTime" : { + "desc" : "StartTime", + "rw" : false, + "type" : "long" + }, + "SystemProperties" : { + "desc" : "SystemProperties", + "rw" : false, + "type" : "javax.management.openmbean.TabularData" + }, + "Uptime" : { + "desc" : "Uptime", + "rw" : false, + "type" : "long" + }, + "VmName" : { + "desc" : "VmName", + "rw" : false, + "type" : "java.lang.String" + }, + "VmVendor" : { + "desc" : "VmVendor", + "rw" : false, + "type" : "java.lang.String" + }, + "VmVersion" : { + "desc" : "VmVersion", + "rw" : false, + "type" : "java.lang.String" + } + }, + "class" : "sun.management.RuntimeImpl", + "desc" : "Information on the management interface of the MBean" + } +} diff --git a/application/uitests/org.openjdk.jmc.kubernetes.test/src/test/resources/__files/secrets.json b/application/uitests/org.openjdk.jmc.kubernetes.test/src/test/resources/__files/secrets.json new file mode 100644 index 0000000000..ac68f94b51 --- /dev/null +++ b/application/uitests/org.openjdk.jmc.kubernetes.test/src/test/resources/__files/secrets.json @@ -0,0 +1,43 @@ +{ + "apiVersion": "v1", + "items": [ + { + "apiVersion": "v1", + "data": { + "password": "YWRtaW4=", + "username": "YWRtaW4=" + }, + "kind": "Secret", + "metadata": { + "annotations": { + "kubectl.kubernetes.io/last-applied-configuration": "{\"apiVersion\":\"v1\",\"kind\":\"Secret\",\"metadata\":{\"annotations\":{},\"name\":\"jolokia-auth\",\"namespace\":\"ns1\"},\"stringData\":{\"password\":\"admin\",\"username\":\"admin\"},\"type\":\"kubernetes.io/basic-auth\"}\n" + }, + "creationTimestamp": "2022-05-20T13:59:12Z", + "name": "jolokia-auth", + "namespace": "ns1", + "resourceVersion": "130", + "uid": "e1563217-ef08-481d-b2e3-233fa3040b56" + }, + "type": "kubernetes.io/basic-auth" + }, + { + "apiVersion": "v1", + "data": { + "jolokia.properties": "aG9zdD0wLjAuMC4wCnBhc3N3b3JkPXNlY3JldAp1c2VyPWFkbWluCnBvcnQ9ODc3OApkaXNjb3ZlcnlFbmFibGVkPXRydWUKZGlzY292ZXJ5QWdlbnRVcmw9aHR0cDovLyR7aG9zdH06ODc3OC9qb2xva2lhLwpwb2xpY3lMb2NhdGlvbj1jbGFzc3BhdGg6L0JPT1QtSU5GL2NsYXNzZXMvam9sb2tpYS1hY2Nlc3MueG1s" + }, + "kind": "Secret", + "metadata": { + "creationTimestamp": "2022-05-20T13:59:12Z", + "name": "jolokia-properties", + "namespace": "ns1", + "resourceVersion": "147", + "uid": "025bf17a-cff5-46e6-8499-dd9d6de43fa9" + }, + "type": "Opaque" + } + ], + "kind": "List", + "metadata": { + "resourceVersion": "" + } +} diff --git a/application/uitests/org.openjdk.jmc.kubernetes.test/src/test/resources/__files/system-attributes.json b/application/uitests/org.openjdk.jmc.kubernetes.test/src/test/resources/__files/system-attributes.json new file mode 100644 index 0000000000..dc2ff55433 --- /dev/null +++ b/application/uitests/org.openjdk.jmc.kubernetes.test/src/test/resources/__files/system-attributes.json @@ -0,0 +1,101 @@ +[ + { + "request" : { + "attribute" : "Pid", + "mbean" : "java.lang:type=Runtime", + "type" : "read" + }, + "status" : 200, + "timestamp" : 1658586915, + "value" : 88774 + }, + { + "request" : { + "attribute" : "Name", + "mbean" : "java.lang:type=Runtime", + "type" : "read" + }, + "status" : 200, + "timestamp" : 1658586915, + "value" : "88774@Martins-MacBook-Pro-2.local" + }, + { + "request" : { + "attribute" : "InputArguments", + "mbean" : "java.lang:type=Runtime", + "type" : "read" + }, + "status" : 200, + "timestamp" : 1658586915, + "value" : [] + }, + { + "request" : { + "attribute" : "SystemProperties", + "mbean" : "java.lang:type=Runtime", + "type" : "read" + }, + "status" : 200, + "timestamp" : 1658586915, + "value" : { + "apple.awt.application.name" : "App", + "file.encoding" : "UTF-8", + "file.separator" : "/", + "ftp.nonProxyHosts" : "local|*.local|169.254/16|*.169.254/16", + "hawtio.authenticationEnabled" : "false", + "hawtio.redirect.scheme" : "http", + "hawtio.url" : "http://localhost:9090/hawtio", + "http.nonProxyHosts" : "local|*.local|169.254/16|*.169.254/16", + "java.class.path" : "/Users/marska/Downloads/hawtio-app-2.9.1.jar", + "java.class.version" : "62.0", + "java.home" : "/Library/Java/JavaVirtualMachines/temurin-18.jdk/Contents/Home", + "java.io.tmpdir" : "/var/folders/1f/cdm0073x1mj1swnhtw181_4m0000gn/T/", + "java.library.path" : "/Users/marska/Library/Java/Extensions:/Library/Java/Extensions:/Network/Library/Java/Extensions:/System/Library/Java/Extensions:/usr/lib/java:.", + "java.runtime.name" : "OpenJDK Runtime Environment", + "java.runtime.version" : "18.0.1+10", + "java.specification.name" : "Java Platform API Specification", + "java.specification.vendor" : "Oracle Corporation", + "java.specification.version" : "18", + "java.vendor" : "Eclipse Adoptium", + "java.vendor.url" : "https://adoptium.net/", + "java.vendor.url.bug" : "https://github.com/adoptium/adoptium-support/issues", + "java.vendor.version" : "Temurin-18.0.1+10", + "java.version" : "18.0.1", + "java.version.date" : "2022-04-19", + "java.vm.compressedOopsMode" : "Zero based", + "java.vm.info" : "mixed mode, sharing", + "java.vm.name" : "OpenJDK 64-Bit Server VM", + "java.vm.specification.name" : "Java Virtual Machine Specification", + "java.vm.specification.vendor" : "Oracle Corporation", + "java.vm.specification.version" : "18", + "java.vm.vendor" : "Eclipse Adoptium", + "java.vm.version" : "18.0.1+10", + "jdk.debug" : "release", + "line.separator" : "\n", + "native.encoding" : "UTF-8", + "org.eclipse.jetty.util.log.class" : "org.eclipse.jetty.util.log.Slf4jLog", + "os.arch" : "x86_64", + "os.name" : "Mac OS X", + "os.version" : "11.6.6", + "path.separator" : ":", + "socksNonProxyHosts" : "local|*.local|169.254/16|*.169.254/16", + "sun.arch.data.model" : "64", + "sun.awt.enableExtraMouseButtons" : "true", + "sun.boot.library.path" : "/Library/Java/JavaVirtualMachines/temurin-18.jdk/Contents/Home/lib", + "sun.cpu.endian" : "little", + "sun.io.unicode.encoding" : "UnicodeBig", + "sun.java.command" : "/Users/marska/Downloads/hawtio-app-2.9.1.jar --port 9090", + "sun.java.launcher" : "SUN_STANDARD", + "sun.jnu.encoding" : "UTF-8", + "sun.management.compiler" : "HotSpot 64-Bit Tiered Compilers", + "sun.stderr.encoding" : "UTF-8", + "sun.stdout.encoding" : "UTF-8", + "user.country" : "NO", + "user.dir" : "/Users/marska/Documents/dev/jmc/jmc8/releng/third-party", + "user.home" : "/Users/marska", + "user.language" : "nb", + "user.name" : "marska", + "user.timezone" : "Asia/Taipei" + } + } +] diff --git a/application/uitests/org.openjdk.jmc.kubernetes.test/src/test/resources/mappings/jolokia-exec.json b/application/uitests/org.openjdk.jmc.kubernetes.test/src/test/resources/mappings/jolokia-exec.json new file mode 100644 index 0000000000..6bb03ececf --- /dev/null +++ b/application/uitests/org.openjdk.jmc.kubernetes.test/src/test/resources/mappings/jolokia-exec.json @@ -0,0 +1,15 @@ +{ + "request": { + "method": "POST", + "urlPath": "/api/v1/namespaces/ns1/pods/pod-abcdef/proxy/jolokia", + "bodyPatterns": [ + { + "equalToJson": "{\"operation\":\"gc()\",\"mbean\":\"java.lang:type=Memory\",\"type\":\"EXEC\"}" + } + ] + }, + "response": { + "status": 200, + "bodyFileName": "jolokia-exec.json" + } +} \ No newline at end of file diff --git a/application/uitests/org.openjdk.jmc.kubernetes.test/src/test/resources/mappings/jolokia-list-memory.json b/application/uitests/org.openjdk.jmc.kubernetes.test/src/test/resources/mappings/jolokia-list-memory.json new file mode 100644 index 0000000000..e4170948cb --- /dev/null +++ b/application/uitests/org.openjdk.jmc.kubernetes.test/src/test/resources/mappings/jolokia-list-memory.json @@ -0,0 +1,15 @@ +{ + "request": { + "method": "POST", + "urlPathPattern": "/api/v1/namespaces/ns1/pods/pod-abcdef.*/proxy/jolokia.*", + "bodyPatterns": [ + { + "equalToJson": "{\"path\":\"java.lang/type=Memory\",\"type\":\"LIST\"}" + } + ] + }, + "response": { + "status": 200, + "bodyFileName": "memory-list.json" + } +} \ No newline at end of file diff --git a/application/uitests/org.openjdk.jmc.kubernetes.test/src/test/resources/mappings/jolokia-list-runtime.json b/application/uitests/org.openjdk.jmc.kubernetes.test/src/test/resources/mappings/jolokia-list-runtime.json new file mode 100644 index 0000000000..f936e7eb0f --- /dev/null +++ b/application/uitests/org.openjdk.jmc.kubernetes.test/src/test/resources/mappings/jolokia-list-runtime.json @@ -0,0 +1,15 @@ +{ + "request": { + "method": "POST", + "urlPathPattern": "/api/v1/namespaces/ns1/pods/pod-abcdef.*/proxy/jolokia.*", + "bodyPatterns": [ + { + "equalToJson": "{\"path\":\"java.lang/type=Runtime\",\"type\":\"LIST\"}" + } + ] + }, + "response": { + "status": 200, + "bodyFileName": "runtime-list.json" + } +} \ No newline at end of file diff --git a/application/uitests/org.openjdk.jmc.kubernetes.test/src/test/resources/mappings/jolokia-probe.json b/application/uitests/org.openjdk.jmc.kubernetes.test/src/test/resources/mappings/jolokia-probe.json new file mode 100644 index 0000000000..5b26da423d --- /dev/null +++ b/application/uitests/org.openjdk.jmc.kubernetes.test/src/test/resources/mappings/jolokia-probe.json @@ -0,0 +1,15 @@ +{ + "request": { + "method": "POST", + "urlPathPattern": "/api/v1/namespaces/ns1/pods/pod-abcdef.*/proxy/jolokia.*", + "bodyPatterns": [ + { + "equalToJson": "{\"type\":\"version\"}" + } + ] + }, + "response": { + "status": 200, + "bodyFileName": "jolokia-version.json" + } +} \ No newline at end of file diff --git a/application/uitests/org.openjdk.jmc.kubernetes.test/src/test/resources/mappings/jolokia-probe2.json b/application/uitests/org.openjdk.jmc.kubernetes.test/src/test/resources/mappings/jolokia-probe2.json new file mode 100644 index 0000000000..98670016fd --- /dev/null +++ b/application/uitests/org.openjdk.jmc.kubernetes.test/src/test/resources/mappings/jolokia-probe2.json @@ -0,0 +1,15 @@ +{ + "request": { + "method": "POST", + "urlPathPattern": "/api/v1/namespaces/ns1/pods/pod-abcdef.*/proxy/jolokia.*", + "bodyPatterns": [ + { + "equalToJson": "{\"type\":\"VERSION\"}" + } + ] + }, + "response": { + "status": 200, + "bodyFileName": "jolokia-version.json" + } +} \ No newline at end of file diff --git a/application/uitests/org.openjdk.jmc.kubernetes.test/src/test/resources/mappings/jolokia-read-attribute.json b/application/uitests/org.openjdk.jmc.kubernetes.test/src/test/resources/mappings/jolokia-read-attribute.json new file mode 100644 index 0000000000..1584e3b4a3 --- /dev/null +++ b/application/uitests/org.openjdk.jmc.kubernetes.test/src/test/resources/mappings/jolokia-read-attribute.json @@ -0,0 +1,15 @@ +{ + "request": { + "method": "POST", + "urlPathPattern": "/api/v1/namespaces/ns1/pods/pod-abcdef.*/proxy/jolokia.*", + "bodyPatterns": [ + { + "equalToJson": "{\"attribute\":\"Verbose\",\"mbean\":\"java.lang:type=Memory\",\"type\":\"READ\"}" + } + ] + }, + "response": { + "status": 200, + "bodyFileName": "jolokia-attribute.json" + } +} \ No newline at end of file diff --git a/application/uitests/org.openjdk.jmc.kubernetes.test/src/test/resources/mappings/jvm-properties.json b/application/uitests/org.openjdk.jmc.kubernetes.test/src/test/resources/mappings/jvm-properties.json new file mode 100644 index 0000000000..5b26da423d --- /dev/null +++ b/application/uitests/org.openjdk.jmc.kubernetes.test/src/test/resources/mappings/jvm-properties.json @@ -0,0 +1,15 @@ +{ + "request": { + "method": "POST", + "urlPathPattern": "/api/v1/namespaces/ns1/pods/pod-abcdef.*/proxy/jolokia.*", + "bodyPatterns": [ + { + "equalToJson": "{\"type\":\"version\"}" + } + ] + }, + "response": { + "status": 200, + "bodyFileName": "jolokia-version.json" + } +} \ No newline at end of file diff --git a/application/uitests/org.openjdk.jmc.kubernetes.test/src/test/resources/mappings/kube-config.json b/application/uitests/org.openjdk.jmc.kubernetes.test/src/test/resources/mappings/kube-config.json new file mode 100644 index 0000000000..418a887637 --- /dev/null +++ b/application/uitests/org.openjdk.jmc.kubernetes.test/src/test/resources/mappings/kube-config.json @@ -0,0 +1,13 @@ +{ + "request": { + "method": "GET", + "url": "/mock-kube-config.yml" + }, + "response": { + "status": 200, + "bodyFileName": "mock-kube-config.yml", + "transformers": [ + "response-template" + ] + } +} \ No newline at end of file diff --git a/application/uitests/org.openjdk.jmc.kubernetes.test/src/test/resources/mappings/pod.json b/application/uitests/org.openjdk.jmc.kubernetes.test/src/test/resources/mappings/pod.json new file mode 100644 index 0000000000..ebcbd7c5e3 --- /dev/null +++ b/application/uitests/org.openjdk.jmc.kubernetes.test/src/test/resources/mappings/pod.json @@ -0,0 +1,10 @@ +{ + "request": { + "method": "GET", + "url": "/api/v1/namespaces/ns1/pods/pod-abcdef" + }, + "response": { + "status": 200, + "bodyFileName": "pod.json" + } +} \ No newline at end of file diff --git a/application/uitests/org.openjdk.jmc.kubernetes.test/src/test/resources/mappings/pods.json b/application/uitests/org.openjdk.jmc.kubernetes.test/src/test/resources/mappings/pods.json new file mode 100644 index 0000000000..061fb0f53b --- /dev/null +++ b/application/uitests/org.openjdk.jmc.kubernetes.test/src/test/resources/mappings/pods.json @@ -0,0 +1,10 @@ +{ + "request": { + "method": "GET", + "urlPath": "/api/v1/pods" + }, + "response": { + "status": 200, + "bodyFileName": "pods.json" + } +} \ No newline at end of file diff --git a/application/uitests/org.openjdk.jmc.kubernetes.test/src/test/resources/mappings/secrets.json b/application/uitests/org.openjdk.jmc.kubernetes.test/src/test/resources/mappings/secrets.json new file mode 100644 index 0000000000..113bb91e1f --- /dev/null +++ b/application/uitests/org.openjdk.jmc.kubernetes.test/src/test/resources/mappings/secrets.json @@ -0,0 +1,10 @@ +{ + "request": { + "method": "GET", + "url": "/api/v1/namespaces/ns1/secrets" + }, + "response": { + "status": 200, + "bodyFileName": "secrets.json" + } +} \ No newline at end of file diff --git a/application/uitests/org.openjdk.jmc.kubernetes.test/src/test/resources/mappings/system-attributes.json b/application/uitests/org.openjdk.jmc.kubernetes.test/src/test/resources/mappings/system-attributes.json new file mode 100644 index 0000000000..e3d1147b2f --- /dev/null +++ b/application/uitests/org.openjdk.jmc.kubernetes.test/src/test/resources/mappings/system-attributes.json @@ -0,0 +1,15 @@ +{ + "request": { + "method": "POST", + "urlPathPattern": "/api/v1/namespaces/ns1/pods/pod-abcdef.*/proxy/jolokia.*", + "bodyPatterns": [ + { + "equalToJson": "[{\"attribute\":\"Pid\",\"mbean\":\"java.lang:type=Runtime\",\"type\":\"READ\"},{\"attribute\":\"Name\",\"mbean\":\"java.lang:type=Runtime\",\"type\":\"READ\"},{\"attribute\":\"InputArguments\",\"mbean\":\"java.lang:type=Runtime\",\"type\":\"READ\"},{\"attribute\":\"SystemProperties\",\"mbean\":\"java.lang:type=Runtime\",\"type\":\"READ\"}]" + } + ] + }, + "response": { + "status": 200, + "bodyFileName": "system-attributes.json" + } +} \ No newline at end of file diff --git a/application/uitests/pom.xml b/application/uitests/pom.xml index b0015a87ac..d49179b6d1 100644 --- a/application/uitests/pom.xml +++ b/application/uitests/pom.xml @@ -58,6 +58,7 @@ org.openjdk.jmc.flightrecorder.uitest org.openjdk.jmc.rcp.application.uitest org.openjdk.jmc.test.jemmy + org.openjdk.jmc.kubernetes.test diff --git a/releng/platform-definitions/platform-definition-2024-06/platform-definition-2024-06.target b/releng/platform-definitions/platform-definition-2024-06/platform-definition-2024-06.target index 727a31350e..a32e9b73ef 100644 --- a/releng/platform-definitions/platform-definition-2024-06/platform-definition-2024-06.target +++ b/releng/platform-definitions/platform-definition-2024-06/platform-definition-2024-06.target @@ -45,9 +45,10 @@ - - - + + + + diff --git a/releng/platform-definitions/platform-definition-2024-09/platform-definition-2024-09.target b/releng/platform-definitions/platform-definition-2024-09/platform-definition-2024-09.target index 09b3673dad..75708de824 100644 --- a/releng/platform-definitions/platform-definition-2024-09/platform-definition-2024-09.target +++ b/releng/platform-definitions/platform-definition-2024-09/platform-definition-2024-09.target @@ -45,9 +45,10 @@ - - - + + + + diff --git a/releng/platform-definitions/platform-definition-2024-12/platform-definition-2024-12.target b/releng/platform-definitions/platform-definition-2024-12/platform-definition-2024-12.target index 892bf86362..98d50bf518 100644 --- a/releng/platform-definitions/platform-definition-2024-12/platform-definition-2024-12.target +++ b/releng/platform-definitions/platform-definition-2024-12/platform-definition-2024-12.target @@ -45,9 +45,10 @@ - - - + + + + diff --git a/releng/platform-definitions/platform-definition-2025-03/platform-definition-2025-03.target b/releng/platform-definitions/platform-definition-2025-03/platform-definition-2025-03.target index 6cb9735a7a..4959312ec1 100644 --- a/releng/platform-definitions/platform-definition-2025-03/platform-definition-2025-03.target +++ b/releng/platform-definitions/platform-definition-2025-03/platform-definition-2025-03.target @@ -45,9 +45,10 @@ - - - + + + + diff --git a/releng/platform-definitions/platform-definition-2025-06/platform-definition-2025-06.target b/releng/platform-definitions/platform-definition-2025-06/platform-definition-2025-06.target index c0b1bd6ab4..ecbcae5797 100644 --- a/releng/platform-definitions/platform-definition-2025-06/platform-definition-2025-06.target +++ b/releng/platform-definitions/platform-definition-2025-06/platform-definition-2025-06.target @@ -45,9 +45,10 @@ - - - + + + + diff --git a/releng/third-party/pom.xml b/releng/third-party/pom.xml index a5357f08cc..51b24b210d 100644 --- a/releng/third-party/pom.xml +++ b/releng/third-party/pom.xml @@ -61,7 +61,7 @@ 2.0.0 12.0.21 1.3.7 - 2.0.2 + 2.2.9 2.27.2 0.0.1-rc.9 7.5.0 @@ -177,6 +177,13 @@ org.osgi:org.osgi.service.servlet:2.0.0 + + org.jolokia:jolokia-client-kubernetes:jar:standalone:${jolokia.version} + false + + + org.awaitility:awaitility:4.0.0 + com.github.tomakehurst:wiremock-standalone:${wiremock.version} false