Skip to content

Commit

Permalink
Simplify URL scheme, remove support for connecting to services
Browse files Browse the repository at this point in the history
  • Loading branch information
skarsaune committed Jul 11, 2020
1 parent 2c1af94 commit 25b31a4
Show file tree
Hide file tree
Showing 3 changed files with 56 additions and 128 deletions.
Expand Up @@ -6,14 +6,13 @@
import javax.management.remote.JMXConnector;
import javax.management.remote.JMXConnectorProvider;
import javax.management.remote.JMXServiceURL;
import org.jolokia.client.jmxadapter.JolokiaJmxConnector;

/**
* This provides support for handling JMX urls over the Jolokia protocol to JVMs running in kubernetes pods
* Syntax examples
* <ul>
* <li>service:jmx:kubernetes:///api/v1/namespaces/mynamespace/pods/mypodname-.+/actuator/jolokia/</li>
* <li>service:jmx:kubernetes:///api/v1/namespaces/mynamespace/services/myservice-.+/actuator/jolokia/</li>
* <li>service:jmx:kubernetes:///namespaces/mynamespace/pods/mypodname-abcd-efgh/actuator/jolokia/</li>
* <li>service:jmx:kubernetes:///namespaces/mynamespace/pods/mypodname-.+/actuator/jolokia/</li>
* </ul>
*
* Regular expressions in service url is supported so you can have working URLs across deploys.
Expand All @@ -27,7 +26,7 @@
* Example:
* //NB: include trailing slash to jolokia endpoint
* JMXConnector connector = JMXConnectorFactory
* .connect(new JMXServiceURL("service:jmx:kubernetes:///api/v1/namespaces/mynamespace/pods/mypodname-.+/actuator/jolokia/")));
* .connect(new JMXServiceURL("service:jmx:kubernetes:///namespaces/mynamespace/pods/mypodname-.+/actuator/jolokia/")));
* connector.connect();
* connector.getMBeanServerConnection();
*
Expand Down
Expand Up @@ -7,9 +7,7 @@
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;
Expand All @@ -23,16 +21,13 @@
import javax.management.remote.JMXConnector;
import javax.management.remote.JMXServiceURL;
import org.jolokia.client.J4pClient;
import org.jolokia.client.J4pClientBuilderFactory;
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/(.+)");
.compile("/namespaces/([^/]+)/pods/([^/]+)/(.+)");
private static ApiClient apiClient;

public KubernetesJmxConnector(JMXServiceURL serviceURL,
Expand All @@ -43,7 +38,9 @@ public KubernetesJmxConnector(JMXServiceURL serviceURL,
@Override
public void connect(Map<String, ?> env) throws IOException {
if (!"kubernetes".equals(this.serviceUrl.getProtocol())) {
throw new MalformedURLException(String.format("Invalid URL %s : Only protocol \"kubernetes\" is supported (not %s)", serviceUrl, serviceUrl.getProtocol()));
throw new MalformedURLException(String
.format("Invalid URL %s : Only protocol \"kubernetes\" is supported (not %s)", serviceUrl,
serviceUrl.getProtocol()));
}
final Map<String, Object> mergedEnvironment = this.mergedEnvironment(env);
ApiClient client = getApiClient(mergedEnvironment);
Expand All @@ -57,7 +54,7 @@ protected RemoteJmxAdapter createAdapter(J4pClient client) throws IOException {
}

public static ApiClient getApiClient(Map<String, ?> env) throws IOException {
if(apiClient != null){
if (apiClient != null) {
return apiClient;
}
return buildApiClient(env);
Expand All @@ -80,102 +77,45 @@ public static ApiClient buildApiClient(Map<String, ?> env) throws IOException {
protected J4pClient expandAndProbeUrl(ApiClient client,
Map<String, Object> env) throws MalformedURLException {
Configuration.setDefaultApiClient(client);

CoreV1Api api = new CoreV1Api();
String proxyPath = this.serviceUrl.getURLPath();
J4pClient connection;
final HashMap<String, String> headersForProbe = createHeadersForProbe(env);
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,
headersForProbe);
response.body().close();
if (response.isSuccessful()) {
return new J4pClient(
proxyPath, new MinimalHttpClientAdapter(client, proxyPath, env));
}
} 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 namespace = matcher.group(1);
String podPattern = matcher.group(2);
String actualNamespace = null;
String actualPodName = 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();
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,
headersForProbe);
response.body().close();
if (response.isSuccessful()) {
return new J4pClient(
proxyPath, new MinimalHttpClientAdapter(client, proxyPath, env));
String path = matcher.group(3);
//check if podname pans out directly
if ((connection = probeProxyPath(env, client, namespace, podPattern, path,
headersForProbe)) != null) {
return connection;
} else { //scan through pods in namespace if podname is a pattern
for (final V1Pod pod : api
.listNamespacedPod(namespace,
false,
null,
null,
null,
null,
null,
null,
10,
null)
.getItems()) {
if (pod.getMetadata().getNamespace().matches(namespace) && pod.getMetadata()
.getName().matches(podPattern)) {
if ((connection = probeProxyPath(env, client, namespace,
pod.getMetadata().getName(), path, headersForProbe)) != null) {
return connection;
}
}
}

} catch (IOException ignore) {
}
}

}

} catch (ApiException ignore) {
}
throw new MalformedURLException("Unable to connect to proxypath " + proxyPath);
Expand All @@ -184,46 +124,35 @@ protected J4pClient expandAndProbeUrl(ApiClient client,
private HashMap<String, String> createHeadersForProbe(
Map<String, Object> env) {
final HashMap<String, String> headers = new HashMap<String, String>();
String[] credentials= (String[]) env.get(JMXConnector.CREDENTIALS);
if(credentials != null) {
String[] credentials = (String[]) env.get(JMXConnector.CREDENTIALS);
if (credentials != null) {
headers.put("Authorization", Credentials.basic(credentials[0], credentials[1]));
}
return headers;
}

/**
* Find a pod of a service, fail if none
* Probe whether we find Jolokia in given namespace, pod and path
*/
private String findPodPathIfAnyForService(String actualName, String actualNamespace, String path,
CoreV1Api api) throws MalformedURLException {
public static J4pClient probeProxyPath(Map<String, Object> env, ApiClient client,
String namespace, String podName, String path,
HashMap<String, String> headers)
throws ApiException {

final String proxyPath = String
.format("/api/v1/namespaces/%s/pods/%s/proxy/%s", namespace, podName, path);
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);
}
}
Response response = MinimalHttpClientAdapter
.performRequest(client, proxyPath, Collections.singletonMap("type", "version"),
Collections.<Pair>emptyList(), "POST", headers);

response.body().close();
if (response.isSuccessful()) {
return new J4pClient(
proxyPath, new MinimalHttpClientAdapter(client, proxyPath, env));
}
} catch (ApiException ignore) {
} catch (IOException 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,
HashMap<String, String> headers)
throws IOException, ApiException {
return MinimalHttpClientAdapter
.performRequest(client, proxyPath, Collections.singletonMap("type", "version"),
Collections.<Pair>emptyList(), "POST", headers);
return null;
}
}
Expand Up @@ -11,7 +11,7 @@ public class ManualTestConnection {
@Test(groups = "manual")
public void testConnect() throws IOException {
final JMXServiceURL jmxServiceURL = new JMXServiceURL(
"service:jmx:kubernetes:///api/v1/namespaces/che/pods/workspace.+tools-.+-.+/proxy/manage/jolokia/"
"service:jmx:kubernetes:///namespaces/che/pods/workspace.+tools-.+-.+/manage/jolokia/"
);
jmxServiceURL.getProtocol();

Expand Down

0 comments on commit 25b31a4

Please sign in to comment.