This is an automated email from the ASF dual-hosted git repository. anatole pushed a commit to branch master in repository https://gitbox.apache.org/repos/asf/incubator-tamaya-sandbox.git
commit 1732596ccd39702a2858059e14a2f688d88f9855 Author: Anatole Tresch <[email protected]> AuthorDate: Thu Feb 21 19:11:34 2019 +0100 Added kube integration project. --- kubernetes/bnd.bnd | 32 ++ kubernetes/pom.xml | 71 +++ .../tamaya/k8s/AbstractEtcdPropertySource.java | 273 +++++++++++ .../java/org/apache/tamaya/k8s/EtcdAccessor.java | 520 +++++++++++++++++++++ .../org/apache/tamaya/k8s/EtcdBackendConfig.java | 95 ++++ .../org/apache/tamaya/k8s/EtcdPropertySource.java | 52 +++ .../java/org/apache/tamaya/k8s/K8SAccessor.java | 44 ++ .../org/apache/tamaya/k8s/EtcdAccessorTest.java | 116 +++++ .../apache/tamaya/k8s/EtcdPropertySourceTest.java | 75 +++ 9 files changed, 1278 insertions(+) diff --git a/kubernetes/bnd.bnd b/kubernetes/bnd.bnd new file mode 100644 index 0000000..f5d0908 --- /dev/null +++ b/kubernetes/bnd.bnd @@ -0,0 +1,32 @@ +-buildpath: \ + osgi.annotation; version=6.0.0,\ + osgi.core; version=6.0,\ + osgi.cmpn; version=6.0 + +-testpath: \ + ${junit} + +javac.source: 1.8 +javac.target: 1.8 + +Automatic-Module-Name: org.apache.tamaya.k8s +Bundle-Version: ${version}.${tstamp} +Bundle-Name: Apache Tamaya - Kubernetes Config +Bundle-SymbolicName: org.apache.tamaya.etcd +Bundle-Description: Apacha Tamaya Config - Kubernetes PropertySources +Bundle-Category: Implementation +Bundle-Copyright: (C) Apache Foundation +Bundle-License: Apache Licence version 2 +Bundle-Vendor: Apache Software Foundation +Bundle-ContactAddress: [email protected] +Bundle-DocURL: https://tamaya.apache.org +Export-Package: \ + org.apache.tamaya.k8s +Import-Package: \ + org.apache.tamaya,\ + org.apache.tamaya.spi,\ + org.apache.tamaya.mutableconfig\ + org.apache.tamaya.mutableconfig.spi +Export-Service: \ + org.apache.tamaya.spi.PropertySource + diff --git a/kubernetes/pom.xml b/kubernetes/pom.xml new file mode 100644 index 0000000..6f11b18 --- /dev/null +++ b/kubernetes/pom.xml @@ -0,0 +1,71 @@ +<!-- +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 current 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. +--> +<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> + <modelVersion>4.0.0</modelVersion> + + <parent> + <groupId>org.apache.tamaya.ext</groupId> + <artifactId>tamaya-extensions</artifactId> + <version>0.4-incubating-SNAPSHOT</version> + </parent> + + <artifactId>tamaya-k8s</artifactId> + <name>Apache Tamaya Modules - Kubernetes PropertySources</name> + <packaging>jar</packaging> + + <dependencies> + <dependency> + <groupId>junit</groupId> + <artifactId>junit</artifactId> + </dependency> + <dependency> + <groupId>org.hamcrest</groupId> + <artifactId>java-hamcrest</artifactId> + </dependency> + <dependency> + <groupId>org.apache.tamaya</groupId> + <artifactId>tamaya-core</artifactId> + <version>${project.parent.version}</version> + <scope>test</scope> + </dependency> + <dependency> + <groupId>org.apache.tamaya</groupId> + <artifactId>tamaya-api</artifactId> + <version>${project.parent.version}</version> + </dependency> + <dependency> + <groupId>org.apache.tamaya.ext</groupId> + <artifactId>tamaya-functions</artifactId> + <version>${project.parent.version}</version> + </dependency> + <dependency> + <groupId>org.apache.tamaya.ext</groupId> + <artifactId>tamaya-mutable-config</artifactId> + <version>${project.parent.version}</version> + <scope>provided</scope> + <optional>true</optional> + </dependency> + <dependency> + <groupId>io.kubernetes</groupId> + <artifactId>client-java</artifactId> + <version>3.0.0</version> + </dependency> + </dependencies> + +</project> diff --git a/kubernetes/src/main/java/org/apache/tamaya/k8s/AbstractEtcdPropertySource.java b/kubernetes/src/main/java/org/apache/tamaya/k8s/AbstractEtcdPropertySource.java new file mode 100644 index 0000000..d425b72 --- /dev/null +++ b/kubernetes/src/main/java/org/apache/tamaya/k8s/AbstractEtcdPropertySource.java @@ -0,0 +1,273 @@ +///* +// * 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.tamaya.etcd; +// +//import org.apache.tamaya.mutableconfig.ConfigChangeRequest; +//import org.apache.tamaya.mutableconfig.spi.MutablePropertySource; +//import org.apache.tamaya.spi.ChangeSupport; +//import org.apache.tamaya.spi.PropertyValue; +//import org.apache.tamaya.spisupport.propertysource.BasePropertySource; +// +//import java.util.*; +//import java.util.concurrent.TimeUnit; +//import java.util.concurrent.atomic.AtomicLong; +//import java.util.logging.Level; +//import java.util.logging.Logger; +// +///** +// * Propertysource that is reading configuration from a configured etcd endpoint. Setting +// * {@code etcd.prefix} as system property maps the etcd based configuration +// * to this prefix namespace. Etcd servers are configured as {@code etcd.server.urls} system or environment property. +// * Etcd can be disabled by setting {@code tamaya.etcdprops.disable} either as environment or system property. +// */ +//public abstract class AbstractEtcdPropertySource extends BasePropertySource +// implements MutablePropertySource{ +// +// private static final Logger LOG = Logger.getLogger(AbstractEtcdPropertySource.class.getName()); +// +// private String directory =""; +// +// private List<String> servers = new ArrayList<>(); +// +// private List<EtcdAccessor> etcdBackends = new ArrayList<>(); +// +// private Map<String,String> metaData = new HashMap<>(); +// +// private AtomicLong timeoutDuration = new AtomicLong(TimeUnit.MILLISECONDS.convert(5, TimeUnit.MINUTES)); +// +// private AtomicLong timeout = new AtomicLong(); +// +// /** The Hazelcast config map used. */ +// private Map<String, PropertyValue> configMap = new HashMap<>(); +// +// public AbstractEtcdPropertySource(){ +// this("etcd"); +// } +// +// public AbstractEtcdPropertySource(String name){ +// super(name); +// metaData.put("source", "etcd"); +// } +// +// /** +// * Get the current timeout, when a reload will be triggered on access. +// * @return the current timeout, or 0 if no data has been loaded at all. +// */ +// public long getValidUntil(){ +// return timeout.get(); +// } +// +// /** +// * Get the current cache timeout. +// * @return the timeout duration after which data will be reloaded. +// */ +// public long getCachePeriod(){ +// return timeoutDuration.get(); +// } +// +// /** +// * Set the duration after which the data cache will be reloaded. +// * @param millis the millis +// */ +// public void setCacheTimeout(long millis){ +// this.timeoutDuration.set(millis); +// } +// +// /** +// * Get the etc directora accessed. +// * @return the etc director, not null. +// */ +// public String getDirectory() { +// return directory; +// } +// +// /** +// * Sets the etcd directory to read from. +// * @param directory the directory, not null. +// */ +// public void setDirectory(String directory) { +// if(!Objects.equals(this.directory, directory)) { +// this.directory = Objects.requireNonNull(directory); +// refresh(); +// } +// } +// +// public void setServer(List<String> servers) { +// if(!Objects.equals(this.servers, servers)) { +// List<EtcdAccessor> etcdBackends = new ArrayList<>(); +// for (String s : servers) { +// etcdBackends.add(new EtcdAccessor(s)); +// } +// this.servers = Collections.unmodifiableList(servers); +// this.etcdBackends = etcdBackends; +// metaData.put("backends", servers.toString()); +// refresh(); +// } +// } +// +// /** +// * Get the underlying servers this instance will try to connect to. +// * @return the server list, not null. +// */ +// public List<String> getServer(){ +// return servers; +// } +// +// /** +// * Checks for a cache timeout and optionally reloads the data. +// */ +// public void checkRefresh(){ +// if(this.timeout.get() < System.currentTimeMillis()){ +// refresh(); +// } +// } +// +// /** +// * Reloads the data and updated the cache timeouts. +// */ +// public void refresh() { +// for(EtcdAccessor accessor: this.etcdBackends){ +// try{ +// Map<String, String> props = accessor.getProperties(directory); +// if(!props.containsKey("_ERROR")) { +// this.configMap = mapPrefix(props); +// this.timeout.set(System.currentTimeMillis() + timeoutDuration.get()); +// } else{ +// LOG.log(Level.FINE, "etcd error on " + accessor.getUrl() + ": " + props.get("_ERROR")); +// } +// } catch(Exception e){ +// LOG.log(Level.FINE, "etcd access failed on " + accessor.getUrl() + ", trying next...", e); +// } +// } +// } +// +// @Override +// public int getOrdinal() { +// PropertyValue configuredOrdinal = get(TAMAYA_ORDINAL); +// if(configuredOrdinal!=null){ +// try{ +// return Integer.parseInt(configuredOrdinal.getValue()); +// } catch(Exception e){ +// Logger.getLogger(getClass().getName()).log(Level.WARNING, +// "Configured ordinal is not an int number: " + configuredOrdinal, e); +// } +// } +// return getDefaultOrdinal(); +// } +// +// @Override +// public PropertyValue get(String key) { +// checkRefresh(); +// return configMap.get(key); +// } +// +// @Override +// public Map<String, PropertyValue> getProperties() { +// checkRefresh(); +// return configMap; +// } +// +// @Override +// public ChangeSupport getChangeSupport(){ +// return ChangeSupport.SUPPORTED; +// } +// +// private Map<String, PropertyValue> mapPrefix(Map<String, String> props) { +// +// Map<String, PropertyValue> values = new HashMap<>(); +// // Evaluate keys +// for(Map.Entry<String,String> entry:props.entrySet()) { +// if (!entry.getKey().startsWith("_")) { +// PropertyValue val = values.get(entry.getKey()); +// if (val == null) { +// val = PropertyValue.createValue(entry.getKey(), "").setMeta("source", getName()).setMeta(metaData); +// values.put(entry.getKey(), val); +// } +// } +// } +// // add getMeta entries +// for(Map.Entry<String,String> entry:props.entrySet()) { +// if (entry.getKey().startsWith("_")) { +// String key = entry.getKey().substring(1); +// for(String field:new String[]{".createdIndex", ".modifiedIndex", ".ttl", +// ".expiration", ".source"}) { +// if (key.endsWith(field)) { +// key = key.substring(0, key.length() - field.length()); +// PropertyValue val = values.get(key); +// if (val != null) { +// val.setMeta(field, entry.getValue()); +// } +// } +// } +// } +// } +// // Map to createValue map. +//// Map<String, PropertyValue> values = new HashMap<>(); +// for(Map.Entry<String,PropertyValue> en:values.entrySet()) { +// values.put(en.getKey(), en.getValue()); +// } +// return values; +// } +// +// @Override +// public void applyChange(ConfigChangeRequest configChange) { +// for(EtcdAccessor accessor: etcdBackends){ +// try{ +// for(String k: configChange.getRemovedProperties()){ +// Map<String,String> res = accessor.delete(k); +// if(res.get("_ERROR")!=null){ +// LOG.info("Failed to remove key from etcd: " + k); +// } +// } +// for(Map.Entry<String,String> en:configChange.getAddedProperties().entrySet()){ +// String key = en.getKey(); +// Integer ttl = null; +// int index = en.getKey().indexOf('?'); +// if(index>0){ +// key = en.getKey().substring(0, index); +// String rawQuery = en.getKey().substring(index+1); +// String[] queries = rawQuery.split("&"); +// for(String query:queries){ +// if(query.contains("ttl")){ +// int qIdx = query.indexOf('='); +// ttl = qIdx>0?Integer.parseInt(query.substring(qIdx+1).trim()):null; +// } +// } +// } +// Map<String,String> res = accessor.set(key, en.getValue(), ttl); +// if(res.get("_ERROR")!=null){ +// LOG.info("Failed to add key to etcd: " + en.getKey() + "=" + en.getValue()); +// } +// } +// // success, stop here +// break; +// } catch(Exception e){ +// LOG.log(Level.FINE, "etcd access failed on " + accessor.getUrl() + ", trying next...", e); +// } +// } +// } +// +// +// @Override +// protected String toStringValues() { +// return super.toStringValues() + +// " directory=" + directory + '\n' + +// " servers=" + this.servers + '\n'; +// } +//} diff --git a/kubernetes/src/main/java/org/apache/tamaya/k8s/EtcdAccessor.java b/kubernetes/src/main/java/org/apache/tamaya/k8s/EtcdAccessor.java new file mode 100644 index 0000000..1e66590 --- /dev/null +++ b/kubernetes/src/main/java/org/apache/tamaya/k8s/EtcdAccessor.java @@ -0,0 +1,520 @@ +///* +// * 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.tamaya.etcd; +// +//import java.io.StringReader; +//import java.util.ArrayList; +//import java.util.HashMap; +//import java.util.List; +//import java.util.Map; +//import java.util.logging.Level; +//import java.util.logging.Logger; +// +//import javax.json.Json; +//import javax.json.JsonArray; +//import javax.json.JsonObject; +//import javax.json.JsonReader; +//import javax.json.JsonReaderFactory; +// +//import org.apache.http.HttpEntity; +//import org.apache.http.HttpStatus; +//import org.apache.http.NameValuePair; +//import org.apache.http.client.config.RequestConfig; +//import org.apache.http.client.entity.UrlEncodedFormEntity; +//import org.apache.http.client.methods.CloseableHttpResponse; +//import org.apache.http.client.methods.HttpDelete; +//import org.apache.http.client.methods.HttpGet; +//import org.apache.http.client.methods.HttpPut; +//import org.apache.http.impl.client.CloseableHttpClient; +//import org.apache.http.impl.client.HttpClients; +//import org.apache.http.message.BasicNameValuePair; +//import org.apache.http.util.EntityUtils; +// +///** +// * Accessor for reading to or writing from an etcd endpoint. +// */ +//class EtcdAccessor { +// +// private static final Logger LOG = Logger.getLogger(EtcdAccessor.class.getName()); +// +// /** +// * Timeout in seconds. +// */ +// private int timeout = 2; +// /** +// * Timeout in seconds. +// */ +// private final int socketTimeout = 1000; +// /** +// * Timeout in seconds. +// */ +// private final int connectTimeout = 1000; +// +// /** +// * Property that makes Johnzon accept comments. +// */ +// public static final String JOHNZON_SUPPORTS_COMMENTS_PROP = "org.apache.johnzon.supports-comments"; +// /** +// * The JSON reader factory used. +// */ +// private final JsonReaderFactory readerFactory = initReaderFactory(); +// +// +// /** +// * Initializes the factory to be used for creating readers. +// */ +// private JsonReaderFactory initReaderFactory() { +// final Map<String, Object> config = new HashMap<>(); +// config.put(JOHNZON_SUPPORTS_COMMENTS_PROP, true); +// return Json.createReaderFactory(config); +// } +// +// /** +// * The base server url. +// */ +// private final String serverURL; +// /** +// * The http client. +// */ +// private final CloseableHttpClient httpclient = HttpClients.createDefault(); +// +// /** +// * Creates a new instance with the basic access url. +// * +// * @param server server url, e.g. {@code http://127.0.0.1:4001}, not null. +// */ +// public EtcdAccessor(String server) { +// this(server, 2); +// } +// +// public EtcdAccessor(String server, int timeout) { +// this.timeout = timeout; +// if (server.endsWith("/")) { +// serverURL = server.substring(0, server.length() - 1); +// } else { +// serverURL = server; +// } +// } +// +// /** +// * Get the etcd server version. +// * +// * @return the etcd server version, never null. +// */ +// public String getVersion() { +// String version = "<ERROR>"; +// try { +// final CloseableHttpClient httpclient = HttpClients.createDefault(); +// final HttpGet httpGet = new HttpGet(serverURL + "/version"); +// httpGet.setConfig(RequestConfig.copy(RequestConfig.DEFAULT).setSocketTimeout(socketTimeout) +// .setConnectTimeout(timeout).build()); +// try (CloseableHttpResponse response = httpclient.execute(httpGet)) { +// if (response.getStatusLine().getStatusCode() == HttpStatus.SC_OK) { +// final HttpEntity entity = response.getEntity(); +// // and ensure it is fully consumed +// version = EntityUtils.toString(entity); +// EntityUtils.consume(entity); +// } +// } +// return version; +// } catch (final Exception e) { +// LOG.log(Level.INFO, "Error getting etcd version from: " + serverURL, e); +// } +// return version; +// } +// +// /** +// * Ask etcd for a single key, createValue pair. Hereby the response returned from +// * etcd: +// * +// * <pre> +// * { +// * "action": "current", +// * "value": { +// * "createdIndex": 2, +// * "key": "/message", +// * "modifiedIndex": 2, +// * "value": "Hello world" +// * } +// * } +// * </pre> +// * +// * is mapped to: +// * +// * <pre> +// * key=value +// * _key.source=[etcd]http://127.0.0.1:4001 +// * _key.createdIndex=12 +// * _key.modifiedIndex=34 +// * _key.ttl=300 +// * _key.expiration=... +// * </pre> +// * +// * @param key the requested key +// * @return the mapped result, including getMeta-entries. +// */ +// public Map<String, String> get(String key) { +// final Map<String, String> result = new HashMap<>(); +// try { +// final HttpGet httpGet = new HttpGet(serverURL + "/v2/keys/" + key); +// httpGet.setConfig(RequestConfig.copy(RequestConfig.DEFAULT).setSocketTimeout(socketTimeout) +// .setConnectionRequestTimeout(timeout).setConnectTimeout(connectTimeout).build()); +// try (CloseableHttpResponse response = httpclient.execute(httpGet)) { +// if (response.getStatusLine().getStatusCode() == HttpStatus.SC_OK) { +// final HttpEntity entity = response.getEntity(); +// final JsonReader reader = readerFactory +// .createReader(new StringReader(EntityUtils.toString(entity))); +// final JsonObject o = reader.readObject(); +// final JsonObject node = o.getJsonObject("value"); +// if (node.containsKey("key")) { +// result.put(key, node.getString("key")); +// result.put("_" + key + ".source", "[etcd]" + serverURL); +// } +// if (node.containsKey("createdIndex")) { +// result.put("_" + key + ".createdIndex", String.valueOf(node.getInt("createdIndex"))); +// } +// if (node.containsKey("modifiedIndex")) { +// result.put("_" + key + ".modifiedIndex", String.valueOf(node.getInt("modifiedIndex"))); +// } +// if (node.containsKey("expiration")) { +// result.put("_" + key + ".expiration", String.valueOf(node.getString("expiration"))); +// } +// if (node.containsKey("ttl")) { +// result.put("_" + key + ".ttl", String.valueOf(node.getInt("ttl"))); +// } +// EntityUtils.consume(entity); +// } else { +// result.put("_" + key + ".NOT_FOUND.target", "[etcd]" + serverURL); +// } +// } +// } catch (final Exception e) { +// LOG.log(Level.INFO, "Error reading key '" + key + "' from etcd: " + serverURL, e); +// result.put("_ERROR", "Error reading key '" + key + "' from etcd: " + serverURL + ": " + e.toString()); +// } +// return result; +// } +// +// /** +// * Creates/updates an entry in etcd without any ttl setCurrent. +// * +// * @param key the property key, not null +// * @param value the createValue to be setCurrent +// * @return the result map as described above. +// * @see #set(String, String, Integer) +// */ +// public Map<String, String> set(String key, String value) { +// return set(key, value, null); +// } +// +// /** +// * Creates/updates an entry in etcd. The response as follows: +// * +// * <pre> +// * { +// * "action": "setCurrent", +// * "getValue": { +// * "createdIndex": 3, +// * "key": "/message", +// * "modifiedIndex": 3, +// * "createValue": "Hello etcd" +// * }, +// * "prevNode": { +// * "createdIndex": 2, +// * "key": "/message", +// * "createValue": "Hello world", +// * "modifiedIndex": 2 +// * } +// * } +// * </pre> +// * +// * is mapped to: +// * +// * <pre> +// * key=createValue +// * _key.source=[etcd]http://127.0.0.1:4001 +// * _key.createdIndex=12 +// * _key.modifiedIndex=34 +// * _key.ttl=300 +// * _key.expiry=... +// * // optional +// * _key.prevNode.createdIndex=12 +// * _key.prevNode.modifiedIndex=34 +// * _key.prevNode.ttl=300 +// * _key.prevNode.expiration=... +// * </pre> +// * +// * @param key the property key, not null +// * @param value the createValue to be setCurrent +// * @param ttlSeconds the ttl in seconds (optional) +// * @return the result map as described above. +// */ +// public Map<String, String> set(String key, String value, Integer ttlSeconds) { +// final Map<String, String> result = new HashMap<>(); +// try { +// final HttpPut put = new HttpPut(serverURL + "/v2/keys/" + key); +// put.setConfig(RequestConfig.copy(RequestConfig.DEFAULT).setSocketTimeout(socketTimeout) +// .setConnectionRequestTimeout(timeout).setConnectTimeout(connectTimeout).build()); +// final List<NameValuePair> nvps = new ArrayList<>(); +// nvps.add(new BasicNameValuePair("value", value)); +// if (ttlSeconds != null) { +// nvps.add(new BasicNameValuePair("ttl", ttlSeconds.toString())); +// } +// put.setEntity(new UrlEncodedFormEntity(nvps)); +// try (CloseableHttpResponse response = httpclient.execute(put)) { +// if (response.getStatusLine().getStatusCode() == HttpStatus.SC_CREATED +// || response.getStatusLine().getStatusCode() == HttpStatus.SC_OK) { +// final HttpEntity entity = response.getEntity(); +// final JsonReader reader = readerFactory +// .createReader(new StringReader(EntityUtils.toString(entity))); +// final JsonObject o = reader.readObject(); +// final JsonObject node = o.getJsonObject("value"); +// if (node.containsKey("createdIndex")) { +// result.put("_" + key + ".createdIndex", String.valueOf(node.getInt("createdIndex"))); +// } +// if (node.containsKey("modifiedIndex")) { +// result.put("_" + key + ".modifiedIndex", String.valueOf(node.getInt("modifiedIndex"))); +// } +// if (node.containsKey("expiration")) { +// result.put("_" + key + ".expiration", String.valueOf(node.getString("expiration"))); +// } +// if (node.containsKey("ttl")) { +// result.put("_" + key + ".ttl", String.valueOf(node.getInt("ttl"))); +// } +// result.put(key, node.getString("key")); +// result.put("_" + key + ".source", "[etcd]" + serverURL); +// parsePrevNode(key, result, node); +// EntityUtils.consume(entity); +// } +// } +// } catch (final Exception e) { +// LOG.log(Level.INFO, "Error writing to etcd: " + serverURL, e); +// result.put("_ERROR", "Error writing '" + key + "' to etcd: " + serverURL + ": " + e.toString()); +// } +// return result; +// } +// +// /** +// * Deletes a given key. The response is as follows: +// * +// * <pre> +// * _key.source=[etcd]http://127.0.0.1:4001 +// * _key.createdIndex=12 +// * _key.modifiedIndex=34 +// * _key.ttl=300 +// * _key.expiry=... +// * // optional +// * _key.prevNode.createdIndex=12 +// * _key.prevNode.modifiedIndex=34 +// * _key.prevNode.ttl=300 +// * _key.prevNode.expiration=... +// * _key.prevNode.value=... +// * </pre> +// * +// * @param key the key to be deleted. +// * @return the response maps as described above. +// */ +// public Map<String, String> delete(String key) { +// final Map<String, String> result = new HashMap<>(); +// try { +// final HttpDelete delete = new HttpDelete(serverURL + "/v2/keys/" + key); +// delete.setConfig(RequestConfig.copy(RequestConfig.DEFAULT).setSocketTimeout(socketTimeout) +// .setConnectionRequestTimeout(timeout).setConnectTimeout(connectTimeout).build()); +// try (CloseableHttpResponse response = httpclient.execute(delete)) { +// if (response.getStatusLine().getStatusCode() == HttpStatus.SC_OK) { +// final HttpEntity entity = response.getEntity(); +// final JsonReader reader = readerFactory +// .createReader(new StringReader(EntityUtils.toString(entity))); +// final JsonObject o = reader.readObject(); +// final JsonObject node = o.getJsonObject("value"); +// if (node.containsKey("createdIndex")) { +// result.put("_" + key + ".createdIndex", String.valueOf(node.getInt("createdIndex"))); +// } +// if (node.containsKey("modifiedIndex")) { +// result.put("_" + key + ".modifiedIndex", String.valueOf(node.getInt("modifiedIndex"))); +// } +// if (node.containsKey("expiration")) { +// result.put("_" + key + ".expiration", String.valueOf(node.getString("expiration"))); +// } +// if (node.containsKey("ttl")) { +// result.put("_" + key + ".ttl", String.valueOf(node.getInt("ttl"))); +// } +// parsePrevNode(key, result, o); +// EntityUtils.consume(entity); +// } +// } +// } catch (final Exception e) { +// LOG.log(Level.INFO, "Error deleting key '" + key + "' from etcd: " + serverURL, e); +// result.put("_ERROR", "Error deleting '" + key + "' from etcd: " + serverURL + ": " + e.toString()); +// } +// return result; +// } +// +// private static void parsePrevNode(String key, Map<String, String> result, JsonObject o) { +// if (o.containsKey("prevNode")) { +// final JsonObject prevNode = o.getJsonObject("prevNode"); +// if (prevNode.containsKey("createdIndex")) { +// result.put("_" + key + ".prevNode.createdIndex", +// String.valueOf(prevNode.getInt("createdIndex"))); +// } +// if (prevNode.containsKey("modifiedIndex")) { +// result.put("_" + key + ".prevNode.modifiedIndex", +// String.valueOf(prevNode.getInt("modifiedIndex"))); +// } +// if (prevNode.containsKey("expiration")) { +// result.put("_" + key + ".prevNode.expiration", +// String.valueOf(prevNode.getString("expiration"))); +// } +// if (prevNode.containsKey("ttl")) { +// result.put("_" + key + ".prevNode.ttl", String.valueOf(prevNode.getInt("ttl"))); +// } +// result.put("_" + key + ".prevNode.value", prevNode.getString("value")); +// } +// } +// +// /** +// * Get all properties for the given directory key recursively. +// * +// * @param directory the directory entry +// * @return the properties and its metadata +// * @see #getProperties(String, boolean) +// */ +// public Map<String, String> getProperties(String directory) { +// return getProperties(directory, true); +// } +// +// /** +// * Access all properties. The response of: +// * +// * <pre> +// * { +// * "action": "current", +// * "getValue": { +// * "key": "/", +// * "dir": true, +// * "getValues": [ +// * { +// * "key": "/foo_dir", +// * "dir": true, +// * "modifiedIndex": 2, +// * "createdIndex": 2 +// * }, +// * { +// * "key": "/foo", +// * "createValue": "two", +// * "modifiedIndex": 1, +// * "createdIndex": 1 +// * } +// * ] +// * } +// * } +// * </pre> +// * +// * is mapped to a regular Tamaya properties map as follows: +// * +// * <pre> +// * key1=myvalue +// * _key1.source=[etcd]http://127.0.0.1:4001 +// * _key1.createdIndex=12 +// * _key1.modifiedIndex=34 +// * _key1.ttl=300 +// * _key1.expiration=... +// * +// * key2=myvaluexxx +// * _key2.source=[etcd]http://127.0.0.1:4001 +// * _key2.createdIndex=12 +// * +// * key3=val3 +// * _key3.source=[etcd]http://127.0.0.1:4001 +// * _key3.createdIndex=12 +// * _key3.modifiedIndex=2 +// * </pre> +// * +// * @param directory remote directory to query. +// * @param recursive allows to setCurrent if querying is performed recursively +// * @return all properties read from the remote server. +// */ +// public Map<String, String> getProperties(String directory, boolean recursive) { +// final Map<String, String> result = new HashMap<>(); +// try { +// final HttpGet get = new HttpGet(serverURL + "/v2/keys/" + directory + "?recursive=" + recursive); +// get.setConfig(RequestConfig.copy(RequestConfig.DEFAULT).setSocketTimeout(socketTimeout) +// .setConnectionRequestTimeout(timeout).setConnectTimeout(connectTimeout).build()); +// try (CloseableHttpResponse response = httpclient.execute(get)) { +// +// if (response.getStatusLine().getStatusCode() == HttpStatus.SC_OK) { +// final HttpEntity entity = response.getEntity(); +// final JsonReader reader = readerFactory.createReader(new StringReader(EntityUtils.toString(entity))); +// final JsonObject o = reader.readObject(); +// final JsonObject node = o.getJsonObject("value"); +// if (node != null) { +// addNodes(result, node); +// } +// EntityUtils.consume(entity); +// } +// } +// } catch (final Exception e) { +// LOG.log(Level.INFO, "Error reading properties for '" + directory + "' from etcd: " + serverURL, e); +// result.put("_ERROR", +// "Error reading properties for '" + directory + "' from etcd: " + serverURL + ": " + e.toString()); +// } +// return result; +// } +// +// /** +// * Recursively read out all key/values from this etcd JSON array. +// * +// * @param result map with key, values and metadata. +// * @param node the getValue to parse. +// */ +// private void addNodes(Map<String, String> result, JsonObject node) { +// if (!node.containsKey("dir") || "false".equals(node.get("dir").toString())) { +// final String key = node.getString("key").substring(1); +// result.put(key, node.getString("value")); +// if (node.containsKey("createdIndex")) { +// result.put("_" + key + ".createdIndex", String.valueOf(node.getInt("createdIndex"))); +// } +// if (node.containsKey("modifiedIndex")) { +// result.put("_" + key + ".modifiedIndex", String.valueOf(node.getInt("modifiedIndex"))); +// } +// if (node.containsKey("expiration")) { +// result.put("_" + key + ".expiration", String.valueOf(node.getString("expiration"))); +// } +// if (node.containsKey("ttl")) { +// result.put("_" + key + ".ttl", String.valueOf(node.getInt("ttl"))); +// } +// result.put("_" + key + ".source", "[etcd]" + serverURL); +// } else { +// final JsonArray nodes = node.getJsonArray("values"); +// if (nodes != null) { +// for (int i = 0; i < nodes.size(); i++) { +// addNodes(result, nodes.getJsonObject(i)); +// } +// } +// } +// } +// +// /** +// * Access the server root URL used by this accessor. +// * +// * @return the server root URL. +// */ +// public String getUrl() { +// return serverURL; +// } +//} diff --git a/kubernetes/src/main/java/org/apache/tamaya/k8s/EtcdBackendConfig.java b/kubernetes/src/main/java/org/apache/tamaya/k8s/EtcdBackendConfig.java new file mode 100644 index 0000000..0eeb925 --- /dev/null +++ b/kubernetes/src/main/java/org/apache/tamaya/k8s/EtcdBackendConfig.java @@ -0,0 +1,95 @@ +///* +// * 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.tamaya.etcd; +// +//import java.util.ArrayList; +//import java.util.List; +//import java.util.concurrent.TimeUnit; +//import java.util.logging.Level; +//import java.util.logging.Logger; +// +///** +// * Singleton that reads the current etcd setup, especially the possible URLs to be used. +// */ +//final class EtcdBackendConfig { +// +// private static final Logger LOG = Logger.getLogger(EtcdBackendConfig.class.getName()); +// private static final String TAMAYA_ETCD_SERVER_URLS = "tamaya.etcd.server"; +// private static final String TAMAYA_ETCD_TIMEOUT = "tamaya.etcd.timeout"; +// private static final String TAMAYA_ETCD_DIRECTORY = "tamaya.etcd.directory"; +// +// +// private EtcdBackendConfig(){} +// +// /** +// * Get the default etcd directory selector, default {@code ""}. +// * @return the default etcd directory selector, never null. +// */ +// public static String getEtcdDirectory(){ +// String val = System.getProperty(TAMAYA_ETCD_DIRECTORY); +// if(val == null){ +// val = System.getenv(TAMAYA_ETCD_DIRECTORY); +// } +// if(val!=null){ +// return val; +// } +// return ""; +// } +// +// /** +// * Get the etcd connection timeout from system/enfironment property {@code tamaya.etcd.timeout (=seconds)} +// * (default 2 seconds). +// * @return the etcd connection timeout. +// */ +// public static long getEtcdTimeout(){ +// String val = System.getProperty(TAMAYA_ETCD_TIMEOUT); +// if(val == null){ +// val = System.getenv(TAMAYA_ETCD_TIMEOUT); +// } +// if(val!=null){ +// return TimeUnit.MILLISECONDS.convert(Integer.parseInt(val), TimeUnit.SECONDS); +// } +// return 2000L; +// } +// +// /** +// * Evaluate the etcd target servers fomr system/environment property {@code tamaya.etcd.server}. +// * @return the servers configured, or {@code http://127.0.0.1:4001} (default). +// */ +// public static List<String> getServers(){ +// String serverURLs = System.getProperty(TAMAYA_ETCD_SERVER_URLS); +// if(serverURLs==null){ +// serverURLs = System.getenv(TAMAYA_ETCD_SERVER_URLS); +// } +// if(serverURLs==null){ +// serverURLs = "http://127.0.0.1:4001"; +// } +// List<String> servers = new ArrayList<>(); +// for(String url:serverURLs.split("\\,")) { +// try{ +// servers.add(url.trim()); +// LOG.info("Using etcd endoint: " + url); +// } catch(Exception e){ +// LOG.log(Level.SEVERE, "Error initializing etcd accessor for URL: " + url, e); +// } +// } +// return servers; +// } +// +//} diff --git a/kubernetes/src/main/java/org/apache/tamaya/k8s/EtcdPropertySource.java b/kubernetes/src/main/java/org/apache/tamaya/k8s/EtcdPropertySource.java new file mode 100644 index 0000000..d4981ba --- /dev/null +++ b/kubernetes/src/main/java/org/apache/tamaya/k8s/EtcdPropertySource.java @@ -0,0 +1,52 @@ +///* +// * 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.tamaya.etcd; +// +//import org.apache.tamaya.spi.PropertyValue; +// +//import java.util.*; +//import java.util.logging.Logger; +// +///** +// * Propertysource that is reading configuration from a configured etcd endpoint. Setting +// * {@code etcd.prefix} as system property maps the etcd based configuration +// * to this prefix namespace. Etcd servers are configured as {@code etcd.server.urls} system or environment property. +// * Etcd can be disabled by setting {@code tamaya.etcdprops.disable} either as environment or system property. +// */ +//public class EtcdPropertySource extends AbstractEtcdPropertySource{ +// +// private static final Logger LOG = Logger.getLogger(EtcdPropertySource.class.getName()); +// +// public EtcdPropertySource(List<String> server){ +// this(); +// setServer(server); +// } +// +// public EtcdPropertySource(String... server){ +// this(); +// setServer(Arrays.asList(server)); +// } +// +// public EtcdPropertySource(){ +// setDefaultOrdinal(1000); +// setDirectory(EtcdBackendConfig.getEtcdDirectory()); +// setServer(EtcdBackendConfig.getServers()); +// } +// +//} diff --git a/kubernetes/src/main/java/org/apache/tamaya/k8s/K8SAccessor.java b/kubernetes/src/main/java/org/apache/tamaya/k8s/K8SAccessor.java new file mode 100644 index 0000000..e84d5fa --- /dev/null +++ b/kubernetes/src/main/java/org/apache/tamaya/k8s/K8SAccessor.java @@ -0,0 +1,44 @@ +/* + * 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.tamaya.k8s; + +import io.kubernetes.client.ApiClient; +import io.kubernetes.client.Configuration; +import io.kubernetes.client.models.V1APIService; +import io.kubernetes.client.models.V1ConfigMap; +import io.kubernetes.client.proto.V1; +import io.kubernetes.client.util.Config; + +import java.util.logging.Logger; + + +/** + * Accessor for reading to or writing from an etcd endpoint. + */ +class K8SAccessor { + + private static final Logger LOG = Logger.getLogger(K8SAccessor.class.getName()); + + public void test(){ + ApiClient client = Config.defaultClient(); + Configuration.setDefaultApiClient(client); + client. + V1ConfigMap configMap = new V1ConfigMap(). } + +} diff --git a/kubernetes/src/test/java/org/apache/tamaya/k8s/EtcdAccessorTest.java b/kubernetes/src/test/java/org/apache/tamaya/k8s/EtcdAccessorTest.java new file mode 100644 index 0000000..f7fdc3a --- /dev/null +++ b/kubernetes/src/test/java/org/apache/tamaya/k8s/EtcdAccessorTest.java @@ -0,0 +1,116 @@ +///* +// * 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.tamaya.etcd; +// +//import org.junit.BeforeClass; +//import org.junit.Test; +// +//import java.net.MalformedURLException; +//import java.util.Map; +//import java.util.UUID; +// +//import static org.junit.Assert.*; +// +///** +// * Tests for the etcd backend integration. You must have setCurrent a system property so, theses tests are executed, e.g. +// * {@code -Detcd.url=http://127.0.0.1:4001}. +// */ +//public class EtcdAccessorTest { +// +// private static EtcdAccessor accessor; +// static boolean execute = false; +// +// @BeforeClass +// public static void setup() throws MalformedURLException { +// accessor = new EtcdAccessor("http://192.168.99.105:4001"); +// if(!accessor.getVersion().contains("etcd")){ +// System.out.println("Disabling etcd tests, etcd not accessible at: " + System.getProperty("etcd.server.urls")); +// System.out.println("Configure etcd with -Detcd.server.urls=http://<IP>:<PORT>"); +// } +// else{ +// execute = true; +// } +// } +// +// @Test +// public void testGetVersion() throws Exception { +// if(!execute)return; +// assertEquals(accessor.getVersion(), "etcd 0.4.9"); +// } +// +// @Test +// public void testGet() throws Exception { +// if(!execute)return; +// Map<String,String> result = accessor.get("test1"); +// assertNotNull(result); +// } +// +// @Test +// public void testSetNormal() throws Exception { +// if(!execute)return; +// String value = UUID.randomUUID().toString(); +// Map<String,String> result = accessor.set("testSetNormal", value); +// assertNull(result.get("_testSetNormal.ttl")); +// assertEquals(value, accessor.get("testSetNormal").get("testSetNormal")); +// } +// +// @Test +// public void testSetNormal2() throws Exception { +// if(!execute)return; +// String value = UUID.randomUUID().toString(); +// Map<String,String> result = accessor.set("testSetNormal2", value, null); +// assertNull(result.get("_testSetNormal2.ttl")); +// assertEquals(value, accessor.get("testSetNormal2").get("testSetNormal2")); +// } +// +// @Test +// public void testSetWithTTL() throws Exception { +// if(!execute)return; +// String value = UUID.randomUUID().toString(); +// Map<String,String> result = accessor.set("testSetWithTTL", value, 1); +// assertNotNull(result.get("_testSetWithTTL.ttl")); +// assertEquals(value, accessor.get("testSetWithTTL").get("testSetWithTTL")); +// Thread.sleep(2000L); +// result = accessor.get("testSetWithTTL"); +// assertNull(result.get("testSetWithTTL")); +// } +// +// @Test +// public void testDelete() throws Exception { +// if(!execute)return; +// String value = UUID.randomUUID().toString(); +// Map<String,String> result = accessor.set("testDelete", value, null); +// assertEquals(value, accessor.get("testDelete").get("testDelete")); +// assertNotNull(result.get("_testDelete.createdIndex")); +// result = accessor.delete("testDelete"); +// assertEquals(value, result.get("_testDelete.prevNode.createValue")); +// assertNull(accessor.get("testDelete").get("testDelete")); +// } +// +// @Test +// public void testGetProperties() throws Exception { +// if(!execute)return; +// String value = UUID.randomUUID().toString(); +// accessor.set("testGetProperties1", value); +// Map<String,String> result = accessor.getProperties(""); +// assertNotNull(result); +// assertEquals(value, result.get("testGetProperties1")); +// assertNotNull(result.get("_testGetProperties1.createdIndex")); +// } +//} \ No newline at end of file diff --git a/kubernetes/src/test/java/org/apache/tamaya/k8s/EtcdPropertySourceTest.java b/kubernetes/src/test/java/org/apache/tamaya/k8s/EtcdPropertySourceTest.java new file mode 100644 index 0000000..f7ee47a --- /dev/null +++ b/kubernetes/src/test/java/org/apache/tamaya/k8s/EtcdPropertySourceTest.java @@ -0,0 +1,75 @@ +///* +// * 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.tamaya.etcd; +// +//import org.apache.tamaya.spi.PropertyValue; +//import org.junit.BeforeClass; +//import org.junit.Test; +// +//import java.util.Map; +// +//import static org.junit.Assert.*; +//import static org.junit.Assert.assertEquals; +// +///** +// * Created by atsticks on 07.01.16. +// */ +//public class EtcdPropertySourceTest { +// +// private final EtcdPropertySource propertySource = new EtcdPropertySource(); +// +// @BeforeClass +// public static void setup(){ +// System.setProperty("etcd.server.urls", "http://8.8.8.8:4001,http://192.168.99.105:4001"); +// } +// +// @Test +// public void testGetOrdinal() throws Exception { +// assertEquals(1000, propertySource.getOrdinal()); +// } +// +// @Test +// public void testGetDefaultOrdinal() throws Exception { +// assertEquals(1000, propertySource.getDefaultOrdinal()); +// } +// +// @Test +// public void testGetName() throws Exception { +// assertEquals("etcd", propertySource.getName()); +// } +// +// @Test +// public void testGet() throws Exception { +// Map<String,PropertyValue> props = propertySource.getProperties(); +// for(Map.Entry<String,PropertyValue> en:props.entrySet()){ +// assertNotNull("Key not found: " + en.getKey(), propertySource.get(en.getKey())); +// } +// } +// +// @Test +// public void testGetProperties() throws Exception { +// Map<String,PropertyValue> props = propertySource.getProperties(); +// assertNotNull(props); +// } +// +// @Test +// public void testIsScannable() throws Exception { +// assertTrue(propertySource.isScannable()); +// } +//} \ No newline at end of file
