You are viewing a plain text version of this content. The canonical link for it is here.
Posted to dev@tomcat.apache.org by re...@apache.org on 2018/10/08 14:09:16 UTC

svn commit: r1843148 - in /tomcat/trunk: ./ java/org/apache/catalina/tribes/membership/cloud/ res/checkstyle/ webapps/docs/

Author: remm
Date: Mon Oct  8 14:09:16 2018
New Revision: 1843148

URL: http://svn.apache.org/viewvc?rev=1843148&view=rev
Log:
- Experimental Kubernetes aware cloud membership provider, based on code by Maxime Beck.
- Contains code derived from jgroups.
- Documentation is hard and will be a wip.
- Requires openjson to run.
- Kube is usually available so it should provide wide support, but native support for certain other orchestrators like Docker is a possibility.

Added:
    tomcat/trunk/java/org/apache/catalina/tribes/membership/cloud/
    tomcat/trunk/java/org/apache/catalina/tribes/membership/cloud/AbstractStreamProvider.java   (with props)
    tomcat/trunk/java/org/apache/catalina/tribes/membership/cloud/CertificateStreamProvider.java   (with props)
    tomcat/trunk/java/org/apache/catalina/tribes/membership/cloud/CloudMembershipProvider.java   (with props)
    tomcat/trunk/java/org/apache/catalina/tribes/membership/cloud/CloudMembershipService.java   (with props)
    tomcat/trunk/java/org/apache/catalina/tribes/membership/cloud/Constants.java   (with props)
    tomcat/trunk/java/org/apache/catalina/tribes/membership/cloud/InsecureStreamProvider.java   (with props)
    tomcat/trunk/java/org/apache/catalina/tribes/membership/cloud/KubernetesMembershipProvider.java   (with props)
    tomcat/trunk/java/org/apache/catalina/tribes/membership/cloud/LocalStrings.properties   (with props)
    tomcat/trunk/java/org/apache/catalina/tribes/membership/cloud/StreamProvider.java   (with props)
    tomcat/trunk/java/org/apache/catalina/tribes/membership/cloud/TokenStreamProvider.java   (with props)
Modified:
    tomcat/trunk/NOTICE
    tomcat/trunk/build.properties.default
    tomcat/trunk/build.xml
    tomcat/trunk/res/checkstyle/org-import-control.xml
    tomcat/trunk/webapps/docs/changelog.xml

Modified: tomcat/trunk/NOTICE
URL: http://svn.apache.org/viewvc/tomcat/trunk/NOTICE?rev=1843148&r1=1843147&r2=1843148&view=diff
==============================================================================
--- tomcat/trunk/NOTICE (original)
+++ tomcat/trunk/NOTICE Mon Oct  8 14:09:16 2018
@@ -28,6 +28,12 @@ project developed at Twitter
 * Copyright 2014 The Netty Project
 * Copyright 2014 Twitter
 
+For portions of the Tomcat cloud support
+The org.apache.catalina.tribes.membership.cloud package contains derivative
+work originating from the jgroups project.
+https://github.com/jgroups-extras/jgroups-kubernetes
+Copyright 2018 Red Hat Inc.
+
 The original XML Schemas for Java EE Deployment Descriptors:
  - javaee_5.xsd
  - javaee_web_services_1_2.xsd

Modified: tomcat/trunk/build.properties.default
URL: http://svn.apache.org/viewvc/tomcat/trunk/build.properties.default?rev=1843148&r1=1843147&r2=1843148&view=diff
==============================================================================
--- tomcat/trunk/build.properties.default (original)
+++ tomcat/trunk/build.properties.default Mon Oct  8 14:09:16 2018
@@ -283,6 +283,15 @@ saaj-api.home=${base.path}/saaj-api-${sa
 saaj-api.jar=${saaj-api.home}/saaj-api-${saaj-api.version}.jar
 saaj-api.loc=${base-maven.loc}/javax/xml/soap/saaj-api/${saaj-api.version}/saaj-api-${saaj-api.version}.jar
 
+# ----- OpenJSON, version 1.0.10 or later -----
+openjson.version=1.0.10
+openjson.checksum.enabled=true
+openjson.checksum.algorithm=MD5|SHA-1
+openjson.checksum.value=c7c4cb9266cacc0aab5dcbb59456720c|8dcccbcc8bbfa15162cd7ca77bcf2b9daa90e70a
+openjson.home=${base.path}/openjson-${openjson.version}
+openjson.jar=${easymock.home}/openjson-${openjson.version}.jar
+openjson.loc=${base-maven.loc}/com/github/openjson/openjson/${openjson.version}/openjson-${openjson.version}.jar
+
 # ----- bnd & bndlib, version 4.0.0 or later  -----
 # ----- provides OSGI metadata for JARs       -----
 bnd.version=4.0.0

Modified: tomcat/trunk/build.xml
URL: http://svn.apache.org/viewvc/tomcat/trunk/build.xml?rev=1843148&r1=1843147&r2=1843148&view=diff
==============================================================================
--- tomcat/trunk/build.xml (original)
+++ tomcat/trunk/build.xml Mon Oct  8 14:09:16 2018
@@ -213,6 +213,7 @@
   <path id="compile.classpath">
     <pathelement location="${jdt.jar}"/>
     <pathelement location="${saaj-api.jar}"/>
+    <pathelement location="${openjson.jar}"/>
   </path>
 
   <path id="tomcat.classpath">
@@ -2717,7 +2718,17 @@ skip.installer property in build.propert
       <param name="checksum.value" value="${saaj-api.checksum.value}"/>
     </antcall>
 
-  </target>
+    <!-- Download openjson -->
+    <antcall target="downloadfile">
+      <param name="sourcefile" value="${openjson.loc}"/>
+      <param name="destfile" value="${openjson.jar}"/>
+      <param name="destdir" value="${openjson.home}"/>
+      <param name="checksum.enabled" value="${openjson.checksum.enabled}"/>
+      <param name="checksum.algorithm" value="${openjson.checksum.algorithm}"/>
+      <param name="checksum.value" value="${openjson.checksum.value}"/>
+    </antcall>
+
+ </target>
 
   <target name="download-test-compile"
           description="Download additional components for the tests" >

Added: tomcat/trunk/java/org/apache/catalina/tribes/membership/cloud/AbstractStreamProvider.java
URL: http://svn.apache.org/viewvc/tomcat/trunk/java/org/apache/catalina/tribes/membership/cloud/AbstractStreamProvider.java?rev=1843148&view=auto
==============================================================================
--- tomcat/trunk/java/org/apache/catalina/tribes/membership/cloud/AbstractStreamProvider.java (added)
+++ tomcat/trunk/java/org/apache/catalina/tribes/membership/cloud/AbstractStreamProvider.java Mon Oct  8 14:09:16 2018
@@ -0,0 +1,77 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.catalina.tribes.membership.cloud;
+
+import java.io.IOException;
+import java.net.URL;
+import java.net.URLConnection;
+import java.security.cert.CertificateException;
+import java.security.cert.X509Certificate;
+import java.util.Map;
+
+import javax.net.ssl.TrustManager;
+import javax.net.ssl.X509TrustManager;
+
+import org.apache.catalina.tribes.membership.Constants;
+import org.apache.catalina.tribes.util.StringManager;
+import org.apache.juli.logging.Log;
+import org.apache.juli.logging.LogFactory;
+
+public abstract class AbstractStreamProvider implements StreamProvider {
+    private static final Log log = LogFactory.getLog(AbstractStreamProvider.class);
+    protected static final StringManager sm = StringManager.getManager(Constants.Package);
+
+    protected static final TrustManager[] INSECURE_TRUST_MANAGERS = new TrustManager[] {
+            new X509TrustManager() {
+                public void checkClientTrusted(X509Certificate[] chain, String authType) throws CertificateException {}
+                public void checkServerTrusted(X509Certificate[] chain, String authType) throws CertificateException {}
+                public X509Certificate[] getAcceptedIssuers() {
+                    return null;
+                }
+            }
+        };
+
+    /**
+     * Open URL connection to the specified URL.
+     * @param url the url
+     * @param headers the headers map
+     * @param connectTimeout connection timeout in ms
+     * @param readTimeout read timeout in ms
+     * @return the URL connection
+     * @throws IOException when an error occurs
+     */
+    public URLConnection openConnection(String url, Map<String, String> headers, int connectTimeout, int readTimeout) throws IOException {
+        if (log.isDebugEnabled()) {
+            log.debug(String.format("%s opening connection: url [%s], headers [%s], connectTimeout [%s], readTimeout [%s]", getClass().getSimpleName(), url, headers, connectTimeout, readTimeout));
+        }
+        URLConnection connection = new URL(url).openConnection();
+        if (headers != null) {
+            for (Map.Entry<String, String> entry : headers.entrySet()) {
+                connection.addRequestProperty(entry.getKey(), entry.getValue());
+            }
+        }
+        if (connectTimeout < 0 || readTimeout < 0) {
+            throw new IllegalArgumentException(
+                String.format("Neither connectTimeout [%s] nor readTimeout [%s] can be less than 0 for URLConnection.", connectTimeout, readTimeout));
+        }
+        connection.setConnectTimeout(connectTimeout);
+        connection.setReadTimeout(readTimeout);
+        return connection;
+    }
+
+}

Propchange: tomcat/trunk/java/org/apache/catalina/tribes/membership/cloud/AbstractStreamProvider.java
------------------------------------------------------------------------------
    svn:eol-style = native

Added: tomcat/trunk/java/org/apache/catalina/tribes/membership/cloud/CertificateStreamProvider.java
URL: http://svn.apache.org/viewvc/tomcat/trunk/java/org/apache/catalina/tribes/membership/cloud/CertificateStreamProvider.java?rev=1843148&view=auto
==============================================================================
--- tomcat/trunk/java/org/apache/catalina/tribes/membership/cloud/CertificateStreamProvider.java (added)
+++ tomcat/trunk/java/org/apache/catalina/tribes/membership/cloud/CertificateStreamProvider.java Mon Oct  8 14:09:16 2018
@@ -0,0 +1,129 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.catalina.tribes.membership.cloud;
+
+import java.io.FileInputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.net.URLConnection;
+import java.security.KeyStore;
+import java.security.PrivateKey;
+import java.security.cert.Certificate;
+import java.security.cert.CertificateFactory;
+import java.security.cert.X509Certificate;
+import java.util.Map;
+
+import javax.net.ssl.HttpsURLConnection;
+import javax.net.ssl.KeyManager;
+import javax.net.ssl.KeyManagerFactory;
+import javax.net.ssl.SSLContext;
+import javax.net.ssl.SSLSocketFactory;
+import javax.net.ssl.TrustManager;
+import javax.net.ssl.TrustManagerFactory;
+
+import org.apache.juli.logging.Log;
+import org.apache.juli.logging.LogFactory;
+import org.apache.tomcat.util.net.jsse.PEMFile;
+
+public class CertificateStreamProvider extends AbstractStreamProvider {
+
+    private static final Log log = LogFactory.getLog(CertificateStreamProvider.class);
+
+    private final SSLSocketFactory factory;
+
+    CertificateStreamProvider(String clientCertFile, String clientKeyFile, String clientKeyPassword, String clientKeyAlgo, String caCertFile) throws Exception {
+        // defaults - RSA and empty password
+        char[] password = (clientKeyPassword != null) ? clientKeyPassword.toCharArray() : new char[0];
+        String algorithm = (clientKeyAlgo != null) ? clientKeyAlgo : "RSA";
+
+        KeyManager[] keyManagers = configureClientCert(clientCertFile, clientKeyFile, password, algorithm);
+        TrustManager[] trustManagers = configureCaCert(caCertFile);
+        SSLContext context = SSLContext.getInstance("TLS");
+        context.init(keyManagers, trustManagers, null);
+        factory = context.getSocketFactory();
+    }
+
+    @Override
+    public InputStream openStream(String url, Map<String, String> headers, int connectTimeout, int readTimeout) throws IOException {
+        URLConnection connection = openConnection(url, headers, connectTimeout, readTimeout);
+        if (connection instanceof HttpsURLConnection) {
+            HttpsURLConnection httpsConnection = HttpsURLConnection.class.cast(connection);
+            //httpsConnection.setHostnameVerifier(InsecureStreamProvider.INSECURE_HOSTNAME_VERIFIER);
+            httpsConnection.setSSLSocketFactory(factory);
+            if (log.isDebugEnabled()) {
+                log.debug(String.format("Using HttpsURLConnection with SSLSocketFactory [%s] for url [%s].", factory, url));
+            }
+        } else {
+            if (log.isDebugEnabled()) {
+                log.debug(String.format("Using URLConnection for url [%s].", url));
+            }
+        }
+        return connection.getInputStream();
+    }
+
+    private static KeyManager[] configureClientCert(String clientCertFile, String clientKeyFile, char[] clientKeyPassword, String clientKeyAlgo) throws Exception {
+        try (InputStream certInputStream = new FileInputStream(clientCertFile)) {
+            CertificateFactory certFactory = CertificateFactory.getInstance("X509");
+            X509Certificate cert = (X509Certificate)certFactory.generateCertificate(certInputStream);
+
+            PEMFile pemFile = new PEMFile(clientKeyFile, new String(clientKeyPassword));
+            PrivateKey privKey = pemFile.getPrivateKey();
+
+            KeyStore keyStore = KeyStore.getInstance("JKS");
+            keyStore.load(null,  null);
+
+            String alias = cert.getSubjectX500Principal().getName();
+            keyStore.setKeyEntry(alias, privKey, clientKeyPassword, new Certificate[]{cert});
+
+            KeyManagerFactory keyManagerFactory = KeyManagerFactory.getInstance(KeyManagerFactory.getDefaultAlgorithm());
+            keyManagerFactory.init(keyStore, clientKeyPassword);
+
+            return keyManagerFactory.getKeyManagers();
+        } catch (IOException e) {
+            log.error(sm.getString("certificateStream.clientCertError", clientCertFile, clientKeyFile), e);
+            throw e;
+        }
+    }
+
+    private static TrustManager[] configureCaCert(String caCertFile) throws Exception {
+        if (caCertFile != null) {
+            try (InputStream pemInputStream = new FileInputStream(caCertFile)) {
+                CertificateFactory certFactory = CertificateFactory.getInstance("X509");
+                X509Certificate cert = (X509Certificate) certFactory.generateCertificate(pemInputStream);
+
+                KeyStore trustStore = KeyStore.getInstance("JKS");
+                trustStore.load(null);
+
+                String alias = cert.getSubjectX500Principal().getName();
+                trustStore.setCertificateEntry(alias, cert);
+
+                TrustManagerFactory trustManagerFactory = TrustManagerFactory.getInstance(TrustManagerFactory.getDefaultAlgorithm());
+                trustManagerFactory.init(trustStore);
+
+                return trustManagerFactory.getTrustManagers();
+            } catch (Exception e) {
+                log.error(sm.getString("certificateStream.CACertError", caCertFile), e);
+                throw e;
+            }
+        } else {
+            log.warn(sm.getString("certificateStream.CACertUndefined"));
+            return InsecureStreamProvider.INSECURE_TRUST_MANAGERS;
+        }
+    }
+
+}

Propchange: tomcat/trunk/java/org/apache/catalina/tribes/membership/cloud/CertificateStreamProvider.java
------------------------------------------------------------------------------
    svn:eol-style = native

Added: tomcat/trunk/java/org/apache/catalina/tribes/membership/cloud/CloudMembershipProvider.java
URL: http://svn.apache.org/viewvc/tomcat/trunk/java/org/apache/catalina/tribes/membership/cloud/CloudMembershipProvider.java?rev=1843148&view=auto
==============================================================================
--- tomcat/trunk/java/org/apache/catalina/tribes/membership/cloud/CloudMembershipProvider.java (added)
+++ tomcat/trunk/java/org/apache/catalina/tribes/membership/cloud/CloudMembershipProvider.java Mon Oct  8 14:09:16 2018
@@ -0,0 +1,141 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.catalina.tribes.membership.cloud;
+
+import java.io.IOException;
+import java.io.Serializable;
+import java.net.InetAddress;
+import java.security.AccessController;
+import java.security.MessageDigest;
+import java.security.NoSuchAlgorithmException;
+import java.security.PrivilegedAction;
+import java.time.Instant;
+import java.util.HashMap;
+import java.util.Map;
+import java.util.Properties;
+
+import org.apache.catalina.tribes.ChannelListener;
+import org.apache.catalina.tribes.Heartbeat;
+import org.apache.catalina.tribes.Member;
+import org.apache.catalina.tribes.membership.Constants;
+import org.apache.catalina.tribes.membership.Membership;
+import org.apache.catalina.tribes.membership.MembershipProviderBase;
+import org.apache.catalina.tribes.util.StringManager;
+import org.apache.juli.logging.Log;
+import org.apache.juli.logging.LogFactory;
+
+public abstract class CloudMembershipProvider extends MembershipProviderBase implements Heartbeat, ChannelListener {
+    private static final Log log = LogFactory.getLog(KubernetesMembershipProvider.class);
+    protected static final StringManager sm = StringManager.getManager(Constants.Package);
+
+    protected String url;
+    protected StreamProvider streamProvider;
+    protected int connectionTimeout;
+    protected int readTimeout;
+
+    protected Instant startTime;
+    protected MessageDigest md5;
+
+    protected Map<String, String> headers = new HashMap<>();
+
+    protected int port;
+    protected String hostName;
+
+    public CloudMembershipProvider() {
+        try {
+            md5 = MessageDigest.getInstance("md5");
+        } catch (NoSuchAlgorithmException e) {
+            // Ignore
+        }
+    }
+
+    // Get value of environment variable named keys[0]
+    // If keys[0] isn't found, try keys[1], keys[2], ...
+    // If nothing is found, return null
+    protected static String getEnv(String... keys) {
+        String val = null;
+
+        for (String key : keys) {
+            val = AccessController.doPrivileged((PrivilegedAction<String>) () -> System.getenv(key));
+            if (val != null)
+                break;
+        }
+
+        return val;
+    }
+
+    @Override
+    public void init(Properties properties) throws IOException {
+        startTime = Instant.now();
+
+        connectionTimeout = Integer.parseInt(properties.getProperty("connectionTimeout", "1000"));
+        readTimeout = Integer.parseInt(properties.getProperty("readTimeout", "1000"));
+
+        hostName = InetAddress.getLocalHost().getHostName();
+        port = Integer.parseInt(properties.getProperty("tcpListenPort"));
+    }
+
+    @Override
+    public void start(int level) throws Exception {
+        if (membership == null) {
+            membership = new Membership(service.getLocalMember(true));
+        }
+        service.getChannel().addChannelListener(this);
+    }
+
+    @Override
+    public boolean stop(int level) throws Exception {
+        return true;
+    }
+
+    @Override
+    public void heartbeat() {
+        log.debug("Fetching announced members");
+        Member[] announcedMembers = fetchMembers();
+        // Add new members or refresh the members in the membership
+        for (Member member : announcedMembers) {
+            if (membership.memberAlive(member)) {
+                membershipListener.memberAdded(member);
+            }
+        }
+        // Remove non refreshed members from the membership
+        Member[] expired = membership.expire(100); // TODO: is 100ms a good value?
+        for (Member member : expired) {
+            if (log.isDebugEnabled()) {
+                log.debug("Member is dead: " + member);
+            }
+            membershipListener.memberDisappeared(member);
+        }
+    }
+
+    /**
+     * Fetch current cluster members from the cloud orchestration.
+     * @return the member array
+     */
+    protected abstract Member[] fetchMembers();
+
+    @Override
+    public void messageReceived(Serializable msg, Member sender) {
+    }
+
+    @Override
+    public boolean accept(Serializable msg, Member sender) {
+        return false;
+    }
+
+}

Propchange: tomcat/trunk/java/org/apache/catalina/tribes/membership/cloud/CloudMembershipProvider.java
------------------------------------------------------------------------------
    svn:eol-style = native

Added: tomcat/trunk/java/org/apache/catalina/tribes/membership/cloud/CloudMembershipService.java
URL: http://svn.apache.org/viewvc/tomcat/trunk/java/org/apache/catalina/tribes/membership/cloud/CloudMembershipService.java?rev=1843148&view=auto
==============================================================================
--- tomcat/trunk/java/org/apache/catalina/tribes/membership/cloud/CloudMembershipService.java (added)
+++ tomcat/trunk/java/org/apache/catalina/tribes/membership/cloud/CloudMembershipService.java Mon Oct  8 14:09:16 2018
@@ -0,0 +1,215 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.catalina.tribes.membership.cloud;
+
+import java.io.IOException;
+import java.net.InetAddress;
+import java.security.MessageDigest;
+import java.security.NoSuchAlgorithmException;
+
+import javax.management.ObjectName;
+
+import org.apache.catalina.tribes.Member;
+import org.apache.catalina.tribes.MembershipProvider;
+import org.apache.catalina.tribes.MembershipService;
+import org.apache.catalina.tribes.jmx.JmxRegistry;
+import org.apache.catalina.tribes.membership.Constants;
+import org.apache.catalina.tribes.membership.MemberImpl;
+import org.apache.catalina.tribes.membership.MembershipServiceBase;
+import org.apache.catalina.tribes.util.StringManager;
+import org.apache.juli.logging.Log;
+import org.apache.juli.logging.LogFactory;
+
+public class CloudMembershipService extends MembershipServiceBase {
+    private static final Log log = LogFactory.getLog(CloudMembershipService.class);
+    protected static final StringManager sm = StringManager.getManager(Constants.Package);
+
+    public static final String MEMBERSHIP_PROVIDER_CLASS_NAME = "membershipProviderClassName";
+    private static final String KUBE = "kubernetes";
+    private static final String KUBE_PROVIDER_CLASS = "org.apache.catalina.cloud.membership.KubernetesMembershipProvider";
+
+    private MembershipProvider membershipProvider;
+    private MemberImpl localMember;
+
+    private byte[] payload;
+    private byte[] domain;
+
+    private ObjectName oname = null;
+
+    /**
+     * Return a property.
+     * @param name the property name
+     * @return the property value
+     */
+    public Object getProperty(String name) {
+        return properties.getProperty(name);
+    }
+
+    /**
+     * Set a property.
+     * @param name the property name
+     * @param value the property value
+     * @return <code>true</code> if the property was successfully set
+     */
+    public boolean setProperty(String name, String value) {
+        return (properties.setProperty(name, value) == null);
+    }
+
+    /**
+     * Return the membership provider class.
+     * @return the classname
+     */
+    public String getMembershipProviderClassName() {
+        return properties.getProperty(MEMBERSHIP_PROVIDER_CLASS_NAME);
+    }
+
+    /**
+     * Set the membership provider class.
+     * @param membershipProviderClassName the class name
+     */
+    public void setMembershipProviderClassName(String membershipProviderClassName) {
+        properties.setProperty(MEMBERSHIP_PROVIDER_CLASS_NAME, membershipProviderClassName);
+    }
+
+    @Override
+    public void start(int level) throws Exception {
+        if ((level & MembershipService.MBR_RX) == 0) {
+            return;
+        }
+
+        createOrUpdateLocalMember();
+        localMember.setServiceStartTime(System.currentTimeMillis());
+        localMember.setMemberAliveTime(100);
+        localMember.setPayload(payload);
+        localMember.setDomain(domain);
+
+        if (membershipProvider == null) {
+            String provider = getMembershipProviderClassName();
+            if (provider == null || KUBE.equals(provider)) {
+                provider = KUBE_PROVIDER_CLASS;
+            }
+            if (log.isDebugEnabled()) {
+                log.debug("Using membershipProvider: " + provider);
+            }
+            membershipProvider = (MembershipProvider) Class.forName(provider).newInstance();
+            membershipProvider.setMembershipListener(this);
+            membershipProvider.setMembershipService(this);
+            membershipProvider.init(properties);
+        }
+        membershipProvider.start(level);
+
+        JmxRegistry jmxRegistry = JmxRegistry.getRegistry(channel);
+        if (jmxRegistry != null) {
+            oname = jmxRegistry.registerJmx(",component=Membership", this);
+        }
+    }
+
+    @Override
+    public void stop(int level) {
+        try {
+            if (membershipProvider != null && membershipProvider.stop(level)) {
+                if (oname != null) {
+                    JmxRegistry.getRegistry(channel).unregisterJmx(oname);
+                    oname = null;
+                }
+                membershipProvider = null;
+                channel = null;
+            }
+        } catch (Exception e) {
+            log.error(sm.getString("cloudMembershipService.stopFail", Integer.valueOf(level)), e);
+        }
+    }
+
+    @Override
+    public Member getLocalMember(boolean incAliveTime) {
+        if (incAliveTime && localMember != null) {
+            localMember.setMemberAliveTime(System.currentTimeMillis() - localMember.getServiceStartTime());
+        }
+        return localMember;
+    }
+
+    @Override
+    public void setLocalMemberProperties(String listenHost, int listenPort, int securePort, int udpPort) {
+        if (log.isDebugEnabled()) {
+            log.debug(String.format("setLocalMemberProperties(%s, %d, %d, %d)", listenHost, listenPort, securePort, udpPort));
+        }
+        properties.setProperty("tcpListenHost", listenHost);
+        properties.setProperty("tcpListenPort", String.valueOf(listenPort));
+        properties.setProperty("udpListenPort", String.valueOf(udpPort));
+        properties.setProperty("tcpSecurePort", String.valueOf(securePort));
+
+        try {
+            createOrUpdateLocalMember();
+            localMember.setPayload(payload);
+            localMember.setDomain(domain);
+            localMember.getData(true, true);
+        } catch (IOException e) {
+            throw new IllegalArgumentException(e);
+        }
+    }
+
+    private void createOrUpdateLocalMember() throws IOException {
+        String host = properties.getProperty("tcpListenHost");
+        int port = Integer.parseInt(properties.getProperty("tcpListenPort"));
+        int securePort = Integer.parseInt(properties.getProperty("tcpSecurePort"));
+        int udpPort = Integer.parseInt(properties.getProperty("udpListenPort"));
+
+        if (localMember == null) {
+            localMember = new MemberImpl();
+            try {
+                // Set localMember unique ID to md5 hash of hostname
+                localMember.setUniqueId(MessageDigest.getInstance("md5")
+                        .digest(InetAddress.getLocalHost().getHostName().getBytes()));
+            } catch (NoSuchAlgorithmException e) {
+                throw new IOException(e);
+            }
+            localMember.setLocal(true);
+        }
+        localMember.setHostname(host);
+        localMember.setPort(port);
+        localMember.setSecurePort(securePort);
+        localMember.setUdpPort(udpPort);
+        localMember.getData(true, true);
+    }
+
+    @Override
+    public void setPayload(byte[] payload) {
+        this.payload = payload;
+        if (localMember != null) {
+            localMember.setPayload(payload);
+        }
+    }
+
+    @Override
+    public void setDomain(byte[] domain) {
+        this.domain = domain;
+        if (localMember != null) {
+            localMember.setDomain(domain);
+        }
+    }
+
+    @Override
+    public MembershipProvider getMembershipProvider() {
+        return membershipProvider;
+    }
+
+    public void setMembershipProvider(MembershipProvider memberProvider) {
+        this.membershipProvider = memberProvider;
+    }
+
+}

Propchange: tomcat/trunk/java/org/apache/catalina/tribes/membership/cloud/CloudMembershipService.java
------------------------------------------------------------------------------
    svn:eol-style = native

Added: tomcat/trunk/java/org/apache/catalina/tribes/membership/cloud/Constants.java
URL: http://svn.apache.org/viewvc/tomcat/trunk/java/org/apache/catalina/tribes/membership/cloud/Constants.java?rev=1843148&view=auto
==============================================================================
--- tomcat/trunk/java/org/apache/catalina/tribes/membership/cloud/Constants.java (added)
+++ tomcat/trunk/java/org/apache/catalina/tribes/membership/cloud/Constants.java Mon Oct  8 14:09:16 2018
@@ -0,0 +1,22 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.catalina.tribes.membership.cloud;
+
+public class Constants {
+    public static final String Package = "org.apache.catalina.tribes.membership.cloud";
+}
\ No newline at end of file

Propchange: tomcat/trunk/java/org/apache/catalina/tribes/membership/cloud/Constants.java
------------------------------------------------------------------------------
    svn:eol-style = native

Added: tomcat/trunk/java/org/apache/catalina/tribes/membership/cloud/InsecureStreamProvider.java
URL: http://svn.apache.org/viewvc/tomcat/trunk/java/org/apache/catalina/tribes/membership/cloud/InsecureStreamProvider.java?rev=1843148&view=auto
==============================================================================
--- tomcat/trunk/java/org/apache/catalina/tribes/membership/cloud/InsecureStreamProvider.java (added)
+++ tomcat/trunk/java/org/apache/catalina/tribes/membership/cloud/InsecureStreamProvider.java Mon Oct  8 14:09:16 2018
@@ -0,0 +1,69 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.catalina.tribes.membership.cloud;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.net.URLConnection;
+import java.util.Map;
+
+import javax.net.ssl.HostnameVerifier;
+import javax.net.ssl.HttpsURLConnection;
+import javax.net.ssl.SSLContext;
+import javax.net.ssl.SSLSession;
+import javax.net.ssl.SSLSocketFactory;
+
+import org.apache.juli.logging.Log;
+import org.apache.juli.logging.LogFactory;
+
+public class InsecureStreamProvider extends AbstractStreamProvider {
+    private static final Log log = LogFactory.getLog(InsecureStreamProvider.class);
+
+    private static final HostnameVerifier INSECURE_HOSTNAME_VERIFIER = new HostnameVerifier() {
+        public boolean verify(String arg0, SSLSession arg1) {
+            return true;
+        }
+    };
+
+    private final SSLSocketFactory factory;
+
+    InsecureStreamProvider() throws Exception {
+        SSLContext context = SSLContext.getInstance("TLS");
+        context.init(null,  INSECURE_TRUST_MANAGERS, null);
+        factory = context.getSocketFactory();
+    }
+
+    @Override
+    public InputStream openStream(String url, Map<String, String> headers, int connectTimeout, int readTimeout) throws IOException {
+        URLConnection connection = openConnection(url, headers, connectTimeout, readTimeout);
+        if (connection instanceof HttpsURLConnection) {
+            HttpsURLConnection httpsConnection = HttpsURLConnection.class.cast(connection);
+            httpsConnection.setHostnameVerifier(INSECURE_HOSTNAME_VERIFIER);
+            httpsConnection.setSSLSocketFactory(factory);
+            if (log.isDebugEnabled()) {
+                log.debug(String.format("Using HttpsURLConnection with SSLSocketFactory [%s] for url [%s].", factory, url));
+            }
+        } else {
+            if (log.isDebugEnabled()) {
+                log.debug(String.format("Using URLConnection for url [%s].", url));
+            }
+        }
+        return connection.getInputStream();
+    }
+
+}
\ No newline at end of file

Propchange: tomcat/trunk/java/org/apache/catalina/tribes/membership/cloud/InsecureStreamProvider.java
------------------------------------------------------------------------------
    svn:eol-style = native

Added: tomcat/trunk/java/org/apache/catalina/tribes/membership/cloud/KubernetesMembershipProvider.java
URL: http://svn.apache.org/viewvc/tomcat/trunk/java/org/apache/catalina/tribes/membership/cloud/KubernetesMembershipProvider.java?rev=1843148&view=auto
==============================================================================
--- tomcat/trunk/java/org/apache/catalina/tribes/membership/cloud/KubernetesMembershipProvider.java (added)
+++ tomcat/trunk/java/org/apache/catalina/tribes/membership/cloud/KubernetesMembershipProvider.java Mon Oct  8 14:09:16 2018
@@ -0,0 +1,194 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.catalina.tribes.membership.cloud;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.InputStreamReader;
+import java.net.URLEncoder;
+import java.nio.file.FileSystems;
+import java.nio.file.Files;
+import java.time.Duration;
+import java.time.Instant;
+import java.util.ArrayList;
+import java.util.List;
+
+import org.apache.catalina.tribes.Member;
+import org.apache.catalina.tribes.MembershipService;
+import org.apache.catalina.tribes.membership.MemberImpl;
+import org.apache.juli.logging.Log;
+import org.apache.juli.logging.LogFactory;
+import org.apache.tomcat.util.codec.binary.StringUtils;
+
+import com.github.openjson.JSONArray;
+import com.github.openjson.JSONException;
+import com.github.openjson.JSONObject;
+import com.github.openjson.JSONTokener;
+
+
+public class KubernetesMembershipProvider extends CloudMembershipProvider {
+    private static final Log log = LogFactory.getLog(KubernetesMembershipProvider.class);
+
+    private static final String CUSTOM_ENV_PREFIX = "OPENSHIFT_KUBE_PING_";
+
+    @Override
+    public void start(int level) throws Exception {
+        if ((level & MembershipService.MBR_RX) == 0) {
+            return;
+        }
+
+        super.start(level);
+
+        // Set up Kubernetes API parameters
+        String namespace = getEnv("KUBERNETES_NAMESPACE", CUSTOM_ENV_PREFIX + "NAMESPACE");
+        if (namespace == null || namespace.length() == 0)
+            throw new RuntimeException(sm.getString("kubernetesMembershipProvider.noNamespace"));
+
+        if (log.isDebugEnabled()) {
+            log.debug(String.format("Namespace [%s] set; clustering enabled", namespace));
+        }
+
+        String protocol = getEnv("KUBERNETES_MASTER_PROTOCOL", CUSTOM_ENV_PREFIX + "MASTER_PROTOCOL");
+        String masterHost = getEnv("KUBERNETES_SERVICE_HOST", CUSTOM_ENV_PREFIX + "MASTER_HOST");
+        String masterPort = getEnv("KUBERNETES_SERVICE_PORT", CUSTOM_ENV_PREFIX + "MASTER_PORT");
+
+        String clientCertificateFile = getEnv("KUBERNETES_CLIENT_CERTIFICATE_FILE", CUSTOM_ENV_PREFIX + "CLIENT_CERT_FILE");
+        String caCertFile = getEnv("KUBERNETES_CA_CERTIFICATE_FILE", CUSTOM_ENV_PREFIX + "CA_CERT_FILE");
+        if (caCertFile == null) {
+            caCertFile = "/var/run/secrets/kubernetes.io/serviceaccount/ca.crt";
+        }
+
+        if (clientCertificateFile == null) {
+            if (protocol == null) {
+                protocol = "https";
+            }
+            String saTokenFile = getEnv("SA_TOKEN_FILE", CUSTOM_ENV_PREFIX + "SA_TOKEN_FILE");
+            if (saTokenFile == null) {
+                saTokenFile = "/var/run/secrets/kubernetes.io/serviceaccount/token";
+            }
+            byte[] bytes = Files.readAllBytes(FileSystems.getDefault().getPath(saTokenFile));
+            streamProvider = new TokenStreamProvider(StringUtils.newStringUsAscii(bytes), caCertFile);
+        } else {
+            if (protocol == null) {
+                protocol = "http";
+            }
+            String clientKeyFile = getEnv("KUBERNETES_CLIENT_KEY_FILE");
+            String clientKeyPassword = getEnv("KUBERNETES_CLIENT_KEY_PASSWORD");
+            String clientKeyAlgo = getEnv("KUBERNETES_CLIENT_KEY_ALGO");
+            if (clientKeyAlgo == null) {
+                clientKeyAlgo = "RSA";
+            }
+            streamProvider = new CertificateStreamProvider(clientCertificateFile, clientKeyFile, clientKeyPassword, clientKeyAlgo, caCertFile);
+        }
+
+        String ver = getEnv("KUBERNETES_API_VERSION", CUSTOM_ENV_PREFIX + "API_VERSION");
+        if (ver == null)
+            ver = "v1";
+
+        String labels = getEnv("KUBERNETES_LABELS", CUSTOM_ENV_PREFIX + "LABELS");
+
+        namespace = URLEncoder.encode(namespace, "UTF-8");
+        labels = labels == null ? null : URLEncoder.encode(labels, "UTF-8");
+
+        url = String.format("%s://%s:%s/api/%s/namespaces/%s/pods", protocol, masterHost, masterPort, ver, namespace);
+        if (labels != null && labels.length() > 0) {
+            url = url + "?labelSelector=" + labels;
+        }
+
+        // Fetch initial members
+        heartbeat();
+    }
+
+    @Override
+    public boolean stop(int level) throws Exception {
+        try {
+            return super.stop(level);
+        } finally {
+            streamProvider = null;
+        }
+    }
+
+    @Override
+    protected Member[] fetchMembers() {
+        if (streamProvider == null) {
+            return new Member[0];
+        }
+
+        List<MemberImpl> members = new ArrayList<>();
+
+        try (InputStream stream = streamProvider.openStream(url, headers, connectionTimeout, readTimeout)) {
+            JSONObject json = new JSONObject(new JSONTokener(new InputStreamReader(stream, "UTF-8")));
+
+            JSONArray items = json.getJSONArray("items");
+
+            for (int i = 0; i < items.length(); i++) {
+                String phase;
+                String ip;
+                String name;
+                Instant creationTime;
+
+                try {
+                    JSONObject item = items.getJSONObject(i);
+                    JSONObject status = item.getJSONObject("status");
+                    phase = status.getString("phase");
+
+                    // Ignore shutdown pods
+                    if (!phase.equals("Running"))
+                        continue;
+
+                    ip = status.getString("podIP");
+
+                    // Get name & start time
+                    JSONObject metadata = item.getJSONObject("metadata");
+                    name = metadata.getString("name");
+                    String timestamp = metadata.getString("creationTimestamp");
+                    creationTime = Instant.parse(timestamp);
+                } catch (JSONException e) {
+                    log.warn(sm.getString("kubernetesMembershipProvider.jsonError"), e);
+                    continue;
+                }
+
+                // We found ourselves, ignore
+                if (name.equals(hostName))
+                    continue;
+
+                // id = md5(hostname)
+                byte[] id = md5.digest(name.getBytes());
+                long aliveTime = Duration.between(creationTime, startTime).getSeconds() * 1000; // aliveTime is in ms
+
+                MemberImpl member = null;
+                try {
+                    member = new MemberImpl(ip, port, aliveTime);
+                } catch (IOException e) {
+                    // Shouldn't happen:
+                    // an exception is thrown if hostname can't be resolved to IP, but we already provide an IP
+                    log.warn(sm.getString("kubernetesMembershipProvider.memberError"), e);
+                    continue;
+                }
+
+                member.setUniqueId(id);
+                members.add(member);
+            }
+        } catch (IOException e) {
+            log.warn(sm.getString("kubernetesMembershipProvider.streamError"), e);
+        }
+
+        return members.toArray(new Member[0]);
+    }
+
+}

Propchange: tomcat/trunk/java/org/apache/catalina/tribes/membership/cloud/KubernetesMembershipProvider.java
------------------------------------------------------------------------------
    svn:eol-style = native

Added: tomcat/trunk/java/org/apache/catalina/tribes/membership/cloud/LocalStrings.properties
URL: http://svn.apache.org/viewvc/tomcat/trunk/java/org/apache/catalina/tribes/membership/cloud/LocalStrings.properties?rev=1843148&view=auto
==============================================================================
--- tomcat/trunk/java/org/apache/catalina/tribes/membership/cloud/LocalStrings.properties (added)
+++ tomcat/trunk/java/org/apache/catalina/tribes/membership/cloud/LocalStrings.properties Mon Oct  8 14:09:16 2018
@@ -0,0 +1,30 @@
+# Licensed to the Apache Software Foundation (ASF) under one or more
+# contributor license agreements.  See the NOTICE file distributed with
+# this work for additional information regarding copyright ownership.
+# The ASF licenses this file to You under the Apache License, Version 2.0
+# (the "License"); you may not use this file except in compliance with
+# the License.  You may obtain a copy of the License at
+#
+#     http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+certificateStream.clientCertError=Could not create key manager for {0} ({1})
+certificateStream.CACertError=Could not create trust store for {0}
+certificateStream.CACertUndefined=CA cert undefined
+
+tokenStream.failedConnection=Failed connection to {0} with token {1} and CA {2}
+tokenStream.fileNotFound=CA cert file {0} not found
+tokenStream.trustManagerError=Could not create trust manager for {0}
+tokenStream.CACertUndefined=CA cert file undefined
+
+cloudMembershipService.stopFail=Unable to stop the static membership service, level: [{0}]
+
+kubernetesMembershipProvider.noNamespace=Namespace not set
+kubernetesMembershipProvider.jsonError=JSON error
+kubernetesMembershipProvider.memberError=Error creating member
+kubernetesMembershipProvider.streamError=Failed to open stream

Propchange: tomcat/trunk/java/org/apache/catalina/tribes/membership/cloud/LocalStrings.properties
------------------------------------------------------------------------------
    svn:eol-style = native

Added: tomcat/trunk/java/org/apache/catalina/tribes/membership/cloud/StreamProvider.java
URL: http://svn.apache.org/viewvc/tomcat/trunk/java/org/apache/catalina/tribes/membership/cloud/StreamProvider.java?rev=1843148&view=auto
==============================================================================
--- tomcat/trunk/java/org/apache/catalina/tribes/membership/cloud/StreamProvider.java (added)
+++ tomcat/trunk/java/org/apache/catalina/tribes/membership/cloud/StreamProvider.java Mon Oct  8 14:09:16 2018
@@ -0,0 +1,35 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.catalina.tribes.membership.cloud;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.util.Map;
+
+public interface StreamProvider {
+    /**
+     * Open stream to the specified URL.
+     * @param url the url
+     * @param headers the headers map
+     * @param connectTimeout connection timeout in ms
+     * @param readTimeout read timeout in ms
+     * @return the stream
+     * @throws IOException when an error occurs
+     */
+    public InputStream openStream(String url, Map<String, String> headers, int connectTimeout, int readTimeout) throws IOException;
+}

Propchange: tomcat/trunk/java/org/apache/catalina/tribes/membership/cloud/StreamProvider.java
------------------------------------------------------------------------------
    svn:eol-style = native

Added: tomcat/trunk/java/org/apache/catalina/tribes/membership/cloud/TokenStreamProvider.java
URL: http://svn.apache.org/viewvc/tomcat/trunk/java/org/apache/catalina/tribes/membership/cloud/TokenStreamProvider.java?rev=1843148&view=auto
==============================================================================
--- tomcat/trunk/java/org/apache/catalina/tribes/membership/cloud/TokenStreamProvider.java (added)
+++ tomcat/trunk/java/org/apache/catalina/tribes/membership/cloud/TokenStreamProvider.java Mon Oct  8 14:09:16 2018
@@ -0,0 +1,136 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.catalina.tribes.membership.cloud;
+
+import java.io.BufferedInputStream;
+import java.io.FileInputStream;
+import java.io.FileNotFoundException;
+import java.io.IOException;
+import java.io.InputStream;
+import java.net.URLConnection;
+import java.security.KeyStore;
+import java.security.cert.CertificateFactory;
+import java.security.cert.X509Certificate;
+import java.util.Map;
+
+import javax.net.ssl.HttpsURLConnection;
+import javax.net.ssl.SSLContext;
+import javax.net.ssl.SSLSocketFactory;
+import javax.net.ssl.TrustManager;
+import javax.net.ssl.TrustManagerFactory;
+
+import org.apache.juli.logging.Log;
+import org.apache.juli.logging.LogFactory;
+
+public class TokenStreamProvider extends AbstractStreamProvider {
+
+    private static final Log log = LogFactory.getLog(TokenStreamProvider.class);
+
+    private String token;
+    private String caCertFile;
+    private SSLSocketFactory factory;
+
+    TokenStreamProvider(String token, String caCertFile) {
+        this.token = token;
+        this.caCertFile = caCertFile;
+    }
+
+    @Override
+    public InputStream openStream(String url, Map<String, String> headers, int connectTimeout, int readTimeout)
+            throws IOException {
+        // Set token header
+        if (token != null) {
+            headers.put("Authorization", "Bearer " + token);
+        }
+
+        // Open HTTP connection
+        URLConnection connection = openConnection(url, headers, connectTimeout, readTimeout);
+
+        if (connection instanceof HttpsURLConnection) {
+            HttpsURLConnection httpsConnection = HttpsURLConnection.class.cast(connection);
+            //httpsConnection.setHostnameVerifier(InsecureStreamProvider.INSECURE_HOSTNAME_VERIFIER);
+            httpsConnection.setSSLSocketFactory(getSSLSocketFactory());
+            if (log.isDebugEnabled()) {
+                log.debug(String.format("Using HttpsURLConnection with SSLSocketFactory [%s] for url [%s].", factory, url));
+            }
+        } else {
+            if (log.isDebugEnabled()) {
+                log.debug(String.format("Using URLConnection for url [%s].", url));
+            }
+        }
+
+        try {
+            return connection.getInputStream();
+        } catch (IOException e) {
+            throw new IOException(sm.getString("tokenStream.failedConnection", url, token, caCertFile), e);
+        }
+    }
+
+    private TrustManager[] configureCaCert(String caCertFile) throws Exception {
+        if (caCertFile != null) {
+            try {
+                InputStream pemInputStream = new BufferedInputStream(new FileInputStream(caCertFile));
+                try {
+                    CertificateFactory certFactory = CertificateFactory.getInstance("X509");
+                    X509Certificate cert = (X509Certificate)certFactory.generateCertificate(pemInputStream);
+
+                    KeyStore trustStore = KeyStore.getInstance("JKS");
+                    trustStore.load(null);
+
+                    String alias = cert.getSubjectX500Principal().getName();
+                    trustStore.setCertificateEntry(alias, cert);
+
+                    TrustManagerFactory trustManagerFactory = TrustManagerFactory.getInstance(TrustManagerFactory.getDefaultAlgorithm());
+                    trustManagerFactory.init(trustStore);
+
+                    return trustManagerFactory.getTrustManagers();
+                } finally {
+                    pemInputStream.close();
+                }
+            } catch (FileNotFoundException fnfe) {
+                log.error(sm.getString("tokenStream.fileNotFound", caCertFile));
+                throw fnfe;
+            } catch (Exception e) {
+                log.error(sm.getString("tokenStream.trustManagerError", caCertFile), e);
+                throw e;
+            }
+        } else {
+            log.warn(sm.getString("tokenStream.CACertUndefined"));
+            return InsecureStreamProvider.INSECURE_TRUST_MANAGERS;
+        }
+    }
+
+    private SSLSocketFactory getSSLSocketFactory() throws IOException {
+        if(this.factory == null) {
+            synchronized(this) {
+                if(this.factory == null) {
+                    try {
+                        TrustManager[] trustManagers = configureCaCert(this.caCertFile);
+                        SSLContext context = SSLContext.getInstance("TLS");
+                        context.init(null, trustManagers, null);
+                        this.factory = context.getSocketFactory();
+                    } catch(Exception e) {
+                        throw new IOException(e);
+                    }
+                }
+            }
+        }
+        return this.factory;
+    }
+
+}
\ No newline at end of file

Propchange: tomcat/trunk/java/org/apache/catalina/tribes/membership/cloud/TokenStreamProvider.java
------------------------------------------------------------------------------
    svn:eol-style = native

Modified: tomcat/trunk/res/checkstyle/org-import-control.xml
URL: http://svn.apache.org/viewvc/tomcat/trunk/res/checkstyle/org-import-control.xml?rev=1843148&r1=1843147&r2=1843148&view=diff
==============================================================================
--- tomcat/trunk/res/checkstyle/org-import-control.xml (original)
+++ tomcat/trunk/res/checkstyle/org-import-control.xml Mon Oct  8 14:09:16 2018
@@ -81,6 +81,13 @@
       <disallow pkg="org.apache.naming"/>
       <disallow pkg="org.apache.tomcat"/>
       <allow pkg="org.apache.catalina.tribes"/>
+      <subpackage name="membership">
+        <subpackage name="cloud">
+          <allow class="org.apache.tomcat.util.codec.binary.StringUtils"/>
+          <allow class="org.apache.tomcat.util.net.jsse.PEMFile"/>
+          <allow pkg="com.github.openjson"/>
+        </subpackage>
+      </subpackage>
     </subpackage>
   </subpackage>
   <subpackage name="coyote">

Modified: tomcat/trunk/webapps/docs/changelog.xml
URL: http://svn.apache.org/viewvc/tomcat/trunk/webapps/docs/changelog.xml?rev=1843148&r1=1843147&r2=1843148&view=diff
==============================================================================
--- tomcat/trunk/webapps/docs/changelog.xml (original)
+++ tomcat/trunk/webapps/docs/changelog.xml Mon Oct  8 14:09:16 2018
@@ -181,6 +181,10 @@
         Add <code>setMembershipService</code> method to the
         <code>MembershipProvider</code>. (kfujino)
       </add>
+      <add>
+        Experimental Kubernetes aware cloud membership provider, based on code
+        by Maxime Beck. Contains code derived from jgroups. (remm/kfujino)
+      </add>
     </changelog>
   </subsection>
   <subsection name="Other">



---------------------------------------------------------------------
To unsubscribe, e-mail: dev-unsubscribe@tomcat.apache.org
For additional commands, e-mail: dev-help@tomcat.apache.org