-
Notifications
You must be signed in to change notification settings - Fork 217
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
Showing
9 changed files
with
600 additions
and
2 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,86 @@ | ||
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" | ||
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> | ||
<modelVersion>4.0.0</modelVersion> | ||
|
||
<artifactId>jolokia-kubernetes</artifactId> | ||
<packaging>bundle</packaging> | ||
<parent> | ||
<groupId>org.jolokia</groupId> | ||
<artifactId>jolokia-client-parent</artifactId> | ||
<version>1.6.3-SNAPSHOT</version> | ||
<relativePath>../pom.xml</relativePath> | ||
</parent> | ||
|
||
<name>jolokia-kubernetes</name> | ||
|
||
<dependencies> | ||
|
||
|
||
<dependency> | ||
<groupId>org.jolokia</groupId> | ||
<artifactId>jolokia-jmx-adapter</artifactId> | ||
<version>${project.version}</version> | ||
<!-- as jolokia-jmx-adapter has its own bundle, keep them separate --> | ||
<scope>provided</scope> | ||
</dependency> | ||
<dependency> | ||
<groupId>io.kubernetes</groupId> | ||
<artifactId>client-java</artifactId> | ||
<version>5.0.0</version> | ||
<scope>compile</scope> | ||
</dependency> | ||
|
||
<dependency> | ||
<groupId>org.testng</groupId> | ||
<artifactId>testng</artifactId> | ||
<scope>test</scope> | ||
</dependency> | ||
|
||
</dependencies> | ||
|
||
<build> | ||
<plugins> | ||
<plugin> | ||
<groupId>org.apache.felix</groupId> | ||
<artifactId>maven-bundle-plugin</artifactId> | ||
<extensions>true</extensions> | ||
<executions> | ||
<execution> | ||
<id>bundle-manifest</id> | ||
<phase>process-classes</phase> | ||
<goals> | ||
<goal>manifest</goal> | ||
</goals> | ||
</execution> | ||
</executions> | ||
<configuration> | ||
<instructions> | ||
<Export-Package> | ||
org.jolokia.kubernetes.client.* | ||
</Export-Package> | ||
<Bundle-SymbolicName>org.jolokia</Bundle-SymbolicName> | ||
<Bundle-Description>Jolokia JMX adapter</Bundle-Description> | ||
</instructions> | ||
</configuration> | ||
</plugin> | ||
<plugin> | ||
<artifactId>maven-assembly-plugin</artifactId> | ||
<configuration> | ||
<descriptors> | ||
<descriptor>src/main/assembly/standalone.xml</descriptor> | ||
</descriptors> | ||
</configuration> | ||
<executions> | ||
<execution> | ||
<id>make-assembly</id> | ||
<phase>package</phase> | ||
<goals> | ||
<goal>single</goal> | ||
</goals> | ||
</execution> | ||
</executions> | ||
</plugin> | ||
</plugins> | ||
</build> | ||
|
||
</project> |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,32 @@ | ||
<?xml version="1.0" encoding="utf-8"?> | ||
<!-- | ||
~ Copyright 2009-2020 Roland Huss | ||
~ | ||
~ Licensed under the Apache License, Version 2.0 (the "License"); | ||
~ you may not use this file except in compliance with the License. | ||
~ You may obtain a copy of the License at | ||
~ | ||
~ http://www.apache.org/licenses/LICENSE-2.0 | ||
~ | ||
~ Unless required by applicable law or agreed to in writing, software | ||
~ distributed under the License is distributed on an "AS IS" BASIS, | ||
~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||
~ See the License for the specific language governing permissions and | ||
~ limitations under the License. | ||
--> | ||
|
||
<assembly> | ||
<id>standalone</id> | ||
<formats> | ||
<format>jar</format> | ||
</formats> | ||
<includeBaseDirectory>false</includeBaseDirectory> | ||
<dependencySets> | ||
<dependencySet> | ||
<outputFileNameMapping>${artifact.artifactId}.${artifact.extension}</outputFileNameMapping> | ||
<unpack>true</unpack> | ||
<scope>runtime</scope> | ||
<useProjectArtifact>true</useProjectArtifact> | ||
</dependencySet> | ||
</dependencySets> | ||
</assembly> |
39 changes: 39 additions & 0 deletions
39
...bernetes/src/main/java/org/jolokia/kubernetes/client/KubernetesJmxConnectionProvider.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,39 @@ | ||
package org.jolokia.kubernetes.client; | ||
|
||
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; | ||
import org.jolokia.client.jmxadapter.JolokiaJmxConnector; | ||
|
||
/** | ||
* I provide support for handling JMX urls for the Jolokia protocol | ||
* Syntax service:jmx:jolokia://host:port/path/to/jolokia/with/slash/suffix/ | ||
* My Jar contains a service loader, so that Jolokia JMX protocol is supported | ||
* as long as my jar (jmx-adapter-version-standalone.jar) is on the classpath | ||
* | ||
* <code> | ||
* Example: | ||
* //NB: include trailing slash | ||
* https will be used if port number fits the pattern *443 or connect env map contains "jmx.remote.x.check.stub"->"true" | ||
* JMXConnector connector = JMXConnectorFactory | ||
* .connect(new JMXServiceURL("service:jmx:kubernetes://host:port/jolokia/"), Collections.singletonMap(JMXConnector.CREDENTIALS, Arrays | ||
* .asList("user", "password"))); | ||
* connector.connect(); | ||
* connector.getMBeanServerConnection(); | ||
* | ||
* </code> | ||
*/ | ||
public class KubernetesJmxConnectionProvider implements JMXConnectorProvider { | ||
@Override | ||
public JMXConnector newJMXConnector(JMXServiceURL serviceURL, Map<String, ?> environment) throws IOException { | ||
//the exception will be handled by JMXConnectorFactory so that other handlers are allowed to handle | ||
//other protocols | ||
if(!"kubernetes".equals(serviceURL.getProtocol())) { | ||
throw new MalformedURLException("I only serve Kubernetes connections"); | ||
} | ||
return new KubernetesJmxConnector(serviceURL, environment); | ||
} | ||
} |
219 changes: 219 additions & 0 deletions
219
client/kubernetes/src/main/java/org/jolokia/kubernetes/client/KubernetesJmxConnector.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,219 @@ | ||
package org.jolokia.kubernetes.client; | ||
|
||
import com.squareup.okhttp.Response; | ||
import io.kubernetes.client.ApiClient; | ||
import io.kubernetes.client.ApiException; | ||
import io.kubernetes.client.Configuration; | ||
import io.kubernetes.client.Pair; | ||
import io.kubernetes.client.apis.CoreV1Api; | ||
import io.kubernetes.client.models.V1OwnerReference; | ||
import io.kubernetes.client.models.V1Pod; | ||
import io.kubernetes.client.models.V1Service; | ||
import io.kubernetes.client.util.ClientBuilder; | ||
import io.kubernetes.client.util.KubeConfig; | ||
import java.io.FileReader; | ||
import java.io.IOException; | ||
import java.net.MalformedURLException; | ||
import java.util.Collections; | ||
import java.util.HashMap; | ||
import java.util.Map; | ||
import java.util.regex.Matcher; | ||
import java.util.regex.Pattern; | ||
import javax.management.remote.JMXServiceURL; | ||
import org.jolokia.client.J4pClient; | ||
import org.jolokia.client.jmxadapter.JolokiaJmxConnector; | ||
import org.jolokia.client.jmxadapter.RemoteJmxAdapter; | ||
|
||
public class KubernetesJmxConnector extends JolokiaJmxConnector { | ||
|
||
private static Pattern POD_PATTERN = Pattern | ||
.compile("/api/v1/namespaces/([^/]+)/pods/([^/]+)/proxy/(.+)"); | ||
private static Pattern SERVICE_PATTERN = Pattern | ||
.compile("/api/v1/namespaces/([^/]+)/services/([^/]+)/proxy/(.+)"); | ||
|
||
public KubernetesJmxConnector(JMXServiceURL serviceURL, | ||
Map<String, ?> environment) { | ||
super(serviceURL, environment); | ||
} | ||
|
||
@Override | ||
public void connect(Map<String, ?> env) throws IOException { | ||
if (!"kubernetes".equals(this.serviceUrl.getProtocol())) { | ||
throw new MalformedURLException("Only Kubernetes urls are supported"); | ||
} | ||
ApiClient client = getApiClient(env); | ||
|
||
this.adapter = createAdapter(client); | ||
this.postCreateAdapter(); | ||
} | ||
|
||
protected RemoteJmxAdapter createAdapter(ApiClient client) throws IOException { | ||
return new RemoteJmxAdapter(expandAndProbeUrl(client)); | ||
} | ||
|
||
protected ApiClient getApiClient(Map<String, ?> env) throws IOException { | ||
// file path to your KubeConfig | ||
final Object configPath = env != null ? env.get("kube.config.path") : null; | ||
String kubeConfigPath = configPath != null ? configPath.toString() | ||
: String.format("%s/.kube/config", System.getProperty("user.home")); | ||
|
||
// loading the out-of-cluster config, a kubeconfig from file-system | ||
return ClientBuilder | ||
.kubeconfig(KubeConfig.loadKubeConfig(new FileReader(kubeConfigPath))).build(); | ||
} | ||
|
||
/** | ||
* @return a connection if successful | ||
*/ | ||
protected J4pClient expandAndProbeUrl(ApiClient client) throws MalformedURLException { | ||
Configuration.setDefaultApiClient(client); | ||
|
||
CoreV1Api api = new CoreV1Api(); | ||
String proxyPath = this.serviceUrl.getURLPath(); | ||
try { | ||
if (SERVICE_PATTERN.matcher(proxyPath).matches()) { | ||
final Matcher matcher = SERVICE_PATTERN.matcher(proxyPath); | ||
if (matcher.find()) { | ||
String namespacePattern = matcher.group(1); | ||
String servicePattern = matcher.group(); | ||
String actualNamespace = null; | ||
String actualName = null; | ||
for (final V1Service service : api | ||
.listServiceForAllNamespaces(null, null, false, null, null, null, null, 5, null) | ||
.getItems()) { | ||
|
||
if (service.getMetadata().getNamespace().matches(namespacePattern) && service | ||
.getMetadata().getName().matches(servicePattern)) { | ||
actualNamespace = service.getMetadata().getNamespace(); | ||
actualName = service.getMetadata().getName(); | ||
} | ||
} | ||
if (actualName == null || actualNamespace == null) { | ||
throw new MalformedURLException( | ||
"Coult not find service in cluster for pattern " + proxyPath); | ||
} | ||
if (!actualNamespace.equals(namespacePattern)) { | ||
proxyPath = proxyPath.replace(namespacePattern, actualNamespace); | ||
} | ||
if (!actualName.equals(servicePattern)) { | ||
proxyPath = proxyPath.replace(servicePattern, actualName); | ||
} | ||
//probe a request to this URL via proxy | ||
try { | ||
final Response response = probeProxyPath(client, proxyPath); | ||
if (response.isSuccessful()) { | ||
return new J4pClient( | ||
proxyPath, new MinimalHttpClientAdapter(client, proxyPath)); | ||
} | ||
} catch (IOException ignore) { | ||
} | ||
//try to fall back to finding a pod to see if we are more successful connecting to it, will be picked up from next if block if successful | ||
|
||
String path = matcher.group(3); | ||
proxyPath = findPodPathIfAnyForService(actualName, actualNamespace, path, api); | ||
|
||
|
||
} | ||
} | ||
if (POD_PATTERN.matcher(proxyPath).matches()) { | ||
final Matcher matcher = POD_PATTERN.matcher(proxyPath); | ||
if (matcher.find()) { | ||
String namespacePattern = matcher.group(1); | ||
|
||
String podPattern = matcher.group(2); | ||
String actualNamespace = null; | ||
String actualPodName = null; | ||
V1Pod actualPod = null; | ||
for (final V1Pod pod : api | ||
.listPodForAllNamespaces(null, null, false, null, null, null, null, 5, null) | ||
.getItems()) { | ||
|
||
if (pod.getMetadata().getNamespace().matches(namespacePattern) && pod.getMetadata() | ||
.getName().matches(podPattern)) { | ||
actualNamespace = pod.getMetadata().getNamespace(); | ||
actualPodName = pod.getMetadata().getName(); | ||
actualPod = pod; | ||
break; | ||
} | ||
} | ||
if (actualPodName == null || actualNamespace == null) { | ||
throw new MalformedURLException( | ||
"Could not find pod in cluster for pattern " + proxyPath); | ||
} | ||
if (!actualNamespace.equals(namespacePattern)) { | ||
proxyPath = proxyPath.replace(namespacePattern, actualNamespace); | ||
} | ||
if (!actualPodName.equals(podPattern)) { | ||
proxyPath = proxyPath.replace(podPattern, actualPodName); | ||
} | ||
//probe a request to this URL via proxy | ||
try { | ||
final Response response = probeProxyPath(client, proxyPath); | ||
if (response.isSuccessful()) { | ||
return new J4pClient( | ||
proxyPath, new MinimalHttpClientAdapter(client, proxyPath)); | ||
} | ||
/* port forward stragegy | ||
else if (response.code() == 403 ) {//could be proxy is not allowed try a port forward workaround instead | ||
String path = matcher.group(3); | ||
final Integer port = actualPod.getSpec().getContainers().get(0).getPorts() | ||
.get(0).getContainerPort(); | ||
final String scheme = "http"; | ||
final Response forwardResponse = api | ||
.connectGetNamespacedPodPortforwardCall(actualPodName, actualNamespace, | ||
port, null, null).execute(); | ||
if (forwardResponse.isSuccessful()) { | ||
//test both http and https | ||
return new RemoteJmxAdapter( | ||
new J4pClient(String.format("%s://localhost:%d/%s", scheme, port, path))); | ||
} | ||
}*/ | ||
} catch (IOException ignore) { | ||
} | ||
} | ||
|
||
} | ||
|
||
} catch (ApiException ignore) { | ||
} | ||
throw new | ||
|
||
MalformedURLException("Unable to connect to proxypath " + proxyPath); | ||
|
||
} | ||
|
||
/** | ||
* Find a pod of a service, fail if none | ||
*/ | ||
private String findPodPathIfAnyForService(String actualName, String actualNamespace, String path, | ||
CoreV1Api api) throws MalformedURLException { | ||
try { | ||
for (V1Pod pod : api | ||
.listNamespacedPod(actualNamespace, false, null, null, null, null, null, null, 5, false) | ||
.getItems()) { | ||
for (V1OwnerReference ref : pod.getMetadata().getOwnerReferences()) { | ||
//pod that references the service | ||
if (ref.getName().equals(actualName)) { | ||
return String | ||
.format("/api/v1/namespaces/%s/pods/%s/proxy/%s", actualNamespace, actualName, | ||
path); | ||
} | ||
} | ||
} | ||
} catch (ApiException ignore) { | ||
} | ||
|
||
throw new MalformedURLException(String | ||
.format("Could not find any pod of service %s in namespace %s with path %s", actualName, | ||
actualNamespace, path)); | ||
} | ||
|
||
/** | ||
*/ | ||
public static Response probeProxyPath(ApiClient client, String proxyPath) | ||
throws IOException, ApiException { | ||
return MinimalHttpClientAdapter | ||
.performRequest(client, proxyPath, Collections.singletonMap("type", "version"), | ||
Collections.<Pair>emptyList(), "POST", new HashMap<String, String>()); | ||
} | ||
} |
Oops, something went wrong.