All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.openshift.activemq.discoveryagent.kube.KubePeerAddressResolver Maven / Gradle / Ivy

Go to download

Provides a discovery agent that integrates with OpenShift capabilities for resolving other brokers in mesh by OpenShift service name.

The newest version!
/**
 *  Copyright 2015 Red Hat, Inc.
 *
 *  Red Hat licenses this file to you under the Apache License, version
 *  2.0 (the "License"); you may not use this file except in compliance
 *  with the License.  You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 *  Unless required by applicable law or agreed to in writing, software
 *  distributed under the License is distributed on an "AS IS" BASIS,
 *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
 *  implied.  See the License for the specific language governing
 *  permissions and limitations under the License.
 */
package org.openshift.activemq.discoveryagent.kube;

import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.io.UnsupportedEncodingException;
import java.net.URLEncoder;
import java.nio.file.FileSystems;
import java.nio.file.Files;
import java.nio.file.Path;
import java.security.AccessController;
import java.security.PrivilegedAction;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.Map;
import java.util.concurrent.Callable;

import org.jboss.dmr.ModelNode;
import org.openshift.activemq.discoveryagent.PeerAddressResolver;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

/**
 * KubePeerAddressResolver
 */
public class KubePeerAddressResolver implements PeerAddressResolver {

    private static final Logger LOGGER = LoggerFactory.getLogger(KubePeerAddressResolver.class);

    private static final String ENV_AMQ_MESH_SERVICE_NAMESPACE = "AMQ_MESH_SERVICE_NAMESPACE";
    private static final String ENV_KUBERNETES_SERVICE_HOST = "KUBERNETES_SERVICE_HOST";
    private static final String ENV_KUBERNETES_SERVICE_PORT = "KUBERNETES_SERVICE_PORT";

    private static final String SERVICE_ACCOUNT_TOKEN_PATH = "/var/run/secrets/kubernetes.io/serviceaccount/token";

    private static final String DEFAULT_KUBERNETES_SERVICE_HOST = "kubernetes.default.svc";
    private static final int DEFAULT_KUBERNETES_SERVICE_PORT = 443;
    private static final String DEFAULT_KUBERNETES_VERSION = "v1";
    private static final String DEFAULT_KUBERNETES_PROTOCOL = "https";

    private static final int DEFAULT_CONNECT_TIMEOUT = 5000;
    private static final int DEFAULT_READ_TIMEOUT = 30000;
    private static final int DEFAULT_OPERATION_ATTEMPTS = 3;
    private static final int DEFAULT_OPERATION_SLEEP = 1000;

    private final InsecureStreamProvider streamProvider;
    private final String url;
    private final Map headers;
    private final int connectTimeout = DEFAULT_CONNECT_TIMEOUT;
    private final int readTimeout = DEFAULT_READ_TIMEOUT;
    private final int operationAttempts = DEFAULT_OPERATION_ATTEMPTS;
    private final long operationSleep = DEFAULT_OPERATION_SLEEP;

    private final String serviceNamespace;
    private final String serviceName;

    private int servicePort = -1;
    private boolean portChecked;

    /**
     * Create a new KubePeerAddressResolver.
     * 
     * @param serviceName the service name
     * @param servicePort the service port
     * 
     * @throws Exception if something goes awry
     */
    public KubePeerAddressResolver(String serviceName, int servicePort) throws Exception {
        this.serviceName = serviceName;
        this.servicePort = servicePort;

        this.serviceNamespace = getSystemEnv(ENV_AMQ_MESH_SERVICE_NAMESPACE, null, true);

        final String masterHost = getSystemEnv(ENV_KUBERNETES_SERVICE_HOST, DEFAULT_KUBERNETES_SERVICE_HOST, true);
        final int masterPort = getSystemEnvInt(ENV_KUBERNETES_SERVICE_PORT, DEFAULT_KUBERNETES_SERVICE_PORT);
        final String masterApiVersion = DEFAULT_KUBERNETES_VERSION;
        final String masterProtocol = DEFAULT_KUBERNETES_PROTOCOL;
        final String saTokenFile = SERVICE_ACCOUNT_TOKEN_PATH;

        this.url = String.format("%s://%s:%s/api/%s/namespaces/%s/endpoints/%s", masterProtocol, masterHost,
                masterPort, masterApiVersion, urlencode(serviceNamespace), urlencode(serviceName));

        final String saToken = readFileToString(new File(saTokenFile));
        if (saToken == null) {
            this.headers = Collections.emptyMap();
        } else {
            this.headers = Collections.singletonMap("Authorization", "Bearer " + saToken);
        }

        this.streamProvider = new InsecureStreamProvider();
    }

    @Override
    public String getServiceName() {
        return serviceName;
    }

    @Override
    public synchronized String[] getPeerIPs() {
        List ips = new ArrayList();
        try {
            ModelNode rootNode = getEndpointsNode();
            if (rootNode.hasDefined("subsets")) {
                List subsetsNode = rootNode.get("subsets").asList();
                for (ModelNode subsetNode : subsetsNode) {
                    if (subsetNode.hasDefined("addresses")) {
                        List addressesNode = subsetNode.get("addresses").asList();
                        for (ModelNode addressNode : addressesNode) {
                            String ip = addressNode.get("ip").asString();
                            if (ip != null && !ip.isEmpty()) {
                                ips.add(ip);
                            }
                        }
                    }
                    if (subsetNode.hasDefined("ports")) {
                        initServicePort(subsetNode.get("ports").asList());
                    }
                }
            }
        } catch (Exception e) {
            LOGGER.error("Error retrieving service endpoints from Kubernetes", e);
        }
        if (LOGGER.isDebugEnabled()) {
            LOGGER.debug(String.format("getEndpoints(%s, %s) = %s", serviceNamespace, serviceName, ips));
        }
        return ips.toArray(new String[ips.size()]);
    }

    @Override
    public synchronized int getServicePort() {
        if (!portChecked) {
            getPeerIPs();
        }
        return servicePort;
    }

    private void initServicePort(List portsNode) {
        if (!portChecked) {
            for (ModelNode portNode : portsNode) {
                int port = portNode.get("port").asInt();
                if (servicePort <= 0) {
                    servicePort = port;
                } else if (servicePort != port) {
                    LOGGER.warn(String
                            .format("Service port mismatch: using (%d), but detected (%d)", servicePort, port));
                }
                portChecked = true;
                break;
            }
        }
    }

    private ModelNode getEndpointsNode() throws Exception {
        try (InputStream stream = openStream(url, headers, connectTimeout, readTimeout, operationAttempts,
                operationSleep, streamProvider)) {
            return ModelNode.fromJSONStream(stream);
        }
    }

    private String urlencode(String s) {
        try {
            return s != null ? URLEncoder.encode(s, "UTF-8") : null;
        } catch (UnsupportedEncodingException uee) {
            throw new RuntimeException(
                    String.format("Could not encode String [%s] as UTF-8 (which should always be supported)."), uee);
        }
    }

    private InputStream openStream(final String url, final Map headers, final int connectTimeout,
            final int readTimeout, final int attempts, final long sleep, final InsecureStreamProvider streamProvider)
            throws Exception {
        return execute(new Callable() {
            public InputStream call() throws Exception {
                return streamProvider.openStream(url, headers, connectTimeout, readTimeout);
            }
        }, attempts, sleep, true);
    }

    private  V execute(Callable callable, int attempts, long sleep, boolean throwOnFail) throws Exception {
        V value = null;
        int tries = attempts;
        Throwable lastFail = null;
        while (tries > 0) {
            tries--;
            try {
                value = callable.call();
                if (value != null) {
                    lastFail = null;
                    break;
                }
            } catch (Throwable fail) {
                lastFail = fail;
            }
            try {
                Thread.sleep(sleep);
            } catch (InterruptedException e) {
                Thread.currentThread().interrupt();
                throw new RuntimeException(e);
            }
        }
        if (lastFail != null && (throwOnFail || LOGGER.isInfoEnabled())) {
            String emsg = String.format(
                    "%s attempt(s) with a %sms sleep to execute [%s] failed. Last failure was [%s: %s]", attempts,
                    sleep, callable.getClass().getSimpleName(), lastFail.getClass().getName(), lastFail.getMessage());
            if (throwOnFail) {
                throw new Exception(emsg, lastFail);
            } else {
                LOGGER.info(emsg);
            }
        }
        return value;
    }

    private String getSystemEnv(final String key, final String def, final boolean trimToNull) {
        if (key != null) {
            String val = AccessController.doPrivileged(new PrivilegedAction() {
                public String run() {
                    return System.getenv(key);
                }
            });
            if (trimToNull) {
                val = trimToNull(val);
            }
            if (val != null) {
                return val;
            }
        }
        return def;
    }

    private int getSystemEnvInt(final String key, final int def) {
        String val = getSystemEnv(key, null, true);
        if (val != null) {
            try {
                return Integer.parseInt(val);
            } catch (NumberFormatException nfe) {
                LOGGER.warn(String.format("system environment variable [%s] with value [%s] is not an integer: %s",
                        key, val, nfe.getMessage()));
            }
        }
        return def;
    }

    private String readFileToString(File file) throws IOException {
        if (file != null && file.canRead()) {
            Path path = FileSystems.getDefault().getPath(file.getCanonicalPath());
            byte[] bytes = Files.readAllBytes(path);
            return new String(bytes, "UTF-8");
        }
        return null;
    }

    private String trimToNull(String s) {
        if (s != null) {
            s = s.trim();
            if (s.length() == 0) {
                s = null;
            }
        }
        return s;
    }
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy