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-unsubscr...@tomcat.apache.org For additional commands, e-mail: dev-h...@tomcat.apache.org