Please wait. This can take some minutes ...
Many resources are needed to download a project. Please understand that we have to compensate our server costs. Thank you in advance.
Project price only 1 $
You can buy this project and download/modify it how often you want.
org.jacpfx.vxms.k8s.client.KubeDiscovery Maven / Gradle / Ivy
/*
* Copyright [2018] [Andy Moncsek]
*
* Licensed 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.jacpfx.vxms.k8s.client;
import io.fabric8.annotations.PortName;
import io.fabric8.annotations.ServiceName;
import io.fabric8.annotations.WithLabel;
import io.fabric8.annotations.WithLabels;
import io.fabric8.kubernetes.api.model.DoneableService;
import io.fabric8.kubernetes.api.model.Service;
import io.fabric8.kubernetes.api.model.ServiceList;
import io.fabric8.kubernetes.api.model.ServicePort;
import io.fabric8.kubernetes.client.Config;
import io.fabric8.kubernetes.client.DefaultKubernetesClient;
import io.fabric8.kubernetes.client.KubernetesClient;
import io.fabric8.kubernetes.client.KubernetesClientException;
import io.fabric8.kubernetes.client.Watch;
import io.fabric8.kubernetes.client.Watcher;
import io.fabric8.kubernetes.client.dsl.FilterWatchListDeletable;
import io.fabric8.kubernetes.client.dsl.MixedOperation;
import io.fabric8.kubernetes.client.dsl.Resource;
import io.vertx.core.AbstractVerticle;
import io.vertx.core.json.JsonObject;
import java.lang.reflect.Field;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Objects;
import java.util.Optional;
import java.util.stream.Collectors;
import java.util.stream.Stream;
import org.jacpfx.vxms.common.util.ConfigurationUtil;
import org.jacpfx.vxms.k8s.util.FieldUtil;
import org.jacpfx.vxms.k8s.util.StringUtil;
/** The Kubernetes service discovery implementation */
public class KubeDiscovery {
private static final String SEPERATOR = ":";
/**
* Resolves discovery annotation in AbstractVerticles
*
* @param service the service where to resolve the annotations
* @param kubeConfig the kubernetes config
*/
public static void resolveBeanAnnotations(AbstractVerticle service, Config kubeConfig) {
final JsonObject env = service.config();
final List serviceNameFields = findServiceFields(service);
if (!env.getBoolean("kube.offline", false)) { // online
final DefaultKubernetesClient client =
new DefaultKubernetesClient(kubeConfig); // TODO be aware of OpenShiftClient
if (!serviceNameFields.isEmpty()) {
findServiceEntryAndSetValue(service, serviceNameFields, env, client);
} else {
// TODO check and handle Endpoints & Pods
}
} else {
// resolve properties offline
if (!serviceNameFields.isEmpty()) {
resolveServicesOffline(service, serviceNameFields, env);
} else {
// TODO check and handle Endpoints & Pods
}
}
}
private static void resolveServicesOffline(
Object bean, List serverNameFields, JsonObject env) throws KubernetesClientException {
serverNameFields.forEach(
serviceNameField -> {
final ServiceName serviceNameAnnotation =
serviceNameField.getAnnotation(ServiceName.class);
final String serviceName = serviceNameAnnotation.value();
final boolean withLabel = serviceNameField.isAnnotationPresent(WithLabel.class);
final boolean withLabels = serviceNameField.isAnnotationPresent(WithLabels.class);
if (isServiceNameOnly(withLabel, withLabels)) {
resolveOfflineByServiceName(bean, env, serviceNameField, serviceName);
} else {
resolveOfflineServiceByLabelOnly(bean, env, serviceNameField, withLabel, withLabels);
}
});
}
private static void findServiceEntryAndSetValue(
Object bean, List serverNameFields, JsonObject env, KubernetesClient client)
throws KubernetesClientException {
Objects.requireNonNull(client, "no client available");
serverNameFields.forEach(
serviceNameField -> {
final ServiceName serviceNameAnnotation =
serviceNameField.getAnnotation(ServiceName.class);
final String serviceName = serviceNameAnnotation.value();
final boolean withLabel = serviceNameField.isAnnotationPresent(WithLabel.class);
final boolean withLabels = serviceNameField.isAnnotationPresent(WithLabels.class);
if (isServiceNameOnly(withLabel, withLabels)) {
resolveByServiceName(bean, env, client, serviceNameField, serviceName);
} else {
resolveServiceByLabelOnly(bean, env, client, serviceNameField, withLabel, withLabels);
}
});
}
private static boolean isServiceNameOnly(boolean withLabel, boolean withLabels) {
return !withLabel && !withLabels;
}
private static void resolveOfflineByServiceName(
Object bean, JsonObject env, Field serviceNameField, String serviceName) {
final Optional serviceEntryOptional = findOfflineServiceEntry(env, serviceName);
serviceEntryOptional.ifPresent(
serviceEntry -> resolveOfflibneHostAndSetValue(bean, env, serviceNameField, serviceEntry));
}
private static void resolveOfflineServiceByLabelOnly(
Object bean, JsonObject env, Field serviceNameField, boolean withLabel, boolean withLabels) {
final Map labelsFromAnnotation =
getLabelsFromAnnotation(env, serviceNameField, withLabel, withLabels);
final Optional localAccessKey =
labelsFromAnnotation
.entrySet()
.stream()
.map(entry -> entry.getKey().concat(".").concat(entry.getValue()))
.reduce((a, b) -> StringUtil.isNullOrEmpty(b) ? a : a.concat(".").concat(b));
localAccessKey.ifPresent(
accessKey -> {
final String serviceValue =
ConfigurationUtil.getStringConfiguration(env, accessKey, accessKey);
final String hostString = getOfflineHostString(serviceValue, env, serviceNameField);
FieldUtil.setFieldValue(bean, serviceNameField, hostString);
});
}
private static void resolveByServiceName(
Object bean,
JsonObject env,
KubernetesClient client,
Field serviceNameField,
String serviceName) {
final Optional serviceEntryOptional = findServiceEntry(env, client, serviceName);
serviceEntryOptional.ifPresent(
serviceEntry -> resolveHostAndSetValue(bean, env, serviceNameField, serviceEntry));
}
private static void resolveHostAndSetValue(
Object bean, JsonObject env, Field serviceNameField, Service serviceEntry) {
final String hostString = getHostString(serviceEntry, env, serviceNameField);
FieldUtil.setFieldValue(bean, serviceNameField, hostString);
}
private static void resolveOfflibneHostAndSetValue(
Object bean, JsonObject env, Field serviceNameField, String serviceEntry) {
final String hostString = getOfflineHostString(serviceEntry, env, serviceNameField);
FieldUtil.setFieldValue(bean, serviceNameField, hostString);
}
private static String getHostString(
Service serviceEntry, JsonObject env, Field serviceNameField) {
final String clusterIP = serviceEntry.getSpec().getClusterIP();
final List ports = serviceEntry.getSpec().getPorts();
return serviceNameField.isAnnotationPresent(PortName.class)
? resolveServiceWithPortName(env, serviceNameField, clusterIP, ports)
: resolveService("", clusterIP, ports);
}
private static String getOfflineHostString(
String serviceEntry, JsonObject env, Field serviceNameField) {
return serviceNameField.isAnnotationPresent(PortName.class)
? resolveOfflineServiceWithPortName(env, serviceNameField, serviceEntry)
: serviceEntry;
}
private static String resolveService(
String hostString, String clusterIP, List ports) {
if (ports.size() >= 1) {
final ServicePort servicePort = ports.get(0);
hostString = buildServiceHostString(clusterIP, servicePort, null);
}
return hostString;
}
private static String resolveServiceWithPortName(
JsonObject env, Field serviceNameField, String clusterIP, List ports) {
String hostString = null;
final PortName portNameAnnotation = serviceNameField.getAnnotation(PortName.class);
final String portName = resolveProperty(env, portNameAnnotation.value());
final Optional portMatch = findPortByName(ports, portName);
if (portMatch.isPresent()) {
final ServicePort port = portMatch.get();
final String protocol = port.getProtocol();
// TODO check out how http can get resolved
hostString = buildServiceHostString(clusterIP, port, null);
}
return hostString;
}
private static String resolveOfflineServiceWithPortName(
JsonObject env, Field serviceNameField, String serviceEntry) {
final PortName portNameAnnotation = serviceNameField.getAnnotation(PortName.class);
final String portName = resolveProperty(env, portNameAnnotation.value());
final String portValue = ConfigurationUtil.getStringConfiguration(env, portName, null);
return buildOfflineServiceHostString(serviceEntry, portValue, null);
}
private static Optional findPortByName(List ports, String portName) {
return ports.stream().filter(port -> port.getName().equalsIgnoreCase(portName)).findFirst();
}
private static String buildOfflineServiceHostString(
String clusterIP, String port, String protocol) {
String hostString;
if (StringUtil.isNullOrEmpty(protocol)) {
hostString = clusterIP + SEPERATOR + port;
} else {
hostString = protocol + "://" + clusterIP + SEPERATOR + port;
}
return hostString;
}
private static String buildServiceHostString(
String clusterIP, ServicePort port, String protocol) {
String hostString;
if (StringUtil.isNullOrEmpty(protocol)) {
hostString = clusterIP + SEPERATOR + port.getPort();
} else {
hostString = protocol + "://" + clusterIP + SEPERATOR + port.getPort();
}
return hostString;
}
private static void resolveServiceByLabelOnly(
Object bean,
JsonObject env,
KubernetesClient client,
Field serviceNameField,
boolean withLabel,
boolean withLabels)
throws KubernetesClientException {
final Map labels =
getLabelsFromAnnotation(env, serviceNameField, withLabel, withLabels);
final ServiceList serviceListResult = getServicesByLabel(labels, client);
Optional.ofNullable(serviceListResult)
.ifPresent(
list -> {
if (!list.getItems().isEmpty() && list.getItems().size() == 1) {
final Service serviceEntry = list.getItems().get(0);
resolveHostAndSetValue(bean, env, serviceNameField, serviceEntry);
} else if (!list.getItems().isEmpty() && list.getItems().size() > 1) {
handleNonUniqueLabelsError(labels);
}
});
}
private static void handleNonUniqueLabelsError(Map labels) {
final String entries =
labels
.entrySet()
.stream()
.map(entry -> entry.getKey() + ":" + entry.getValue() + " ")
.reduce((a, b) -> a + b)
.get();
throw new KubernetesClientException("labels " + entries + " returns a non unique result");
}
private static Map getLabelsFromAnnotation(
JsonObject env, Field serviceNameField, boolean withLabel, boolean withLabels) {
final Map labels = new HashMap<>();
if (withLabel) {
final WithLabel wl = serviceNameField.getAnnotation(WithLabel.class);
labels.put(resolveProperty(env, wl.name()), resolveProperty(env, wl.value()));
}
if (withLabels) {
final WithLabels wls = serviceNameField.getAnnotation(WithLabels.class);
labels.putAll(
Stream.of(wls.value())
.collect(
Collectors.toMap(
wl -> resolveProperty(env, wl.name()),
wl -> resolveProperty(env, wl.value()))));
}
return labels;
}
private static ServiceList getServicesByLabel(Map labels, KubernetesClient client)
throws KubernetesClientException {
Objects.requireNonNull(client, "no client available");
final MixedOperation>
services = client.services();
final FilterWatchListDeletable>
listable = createLabelFilterQuery(labels, services);
return listable.list();
}
private static FilterWatchListDeletable>
createLabelFilterQuery(
Map labels,
MixedOperation>
services) {
FilterWatchListDeletable> listable =
null;
for (Entry entry : labels.entrySet()) {
listable =
listable == null
? services.withLabel(entry.getKey(), entry.getValue())
: listable.withLabel(entry.getKey(), entry.getValue());
}
return listable;
}
private static List findServiceFields(Object bean) {
return Stream.of(bean.getClass().getDeclaredFields())
.filter((Field filed) -> filed.isAnnotationPresent(ServiceName.class))
.collect(Collectors.toList());
}
private static List findWithLabeFields(Object bean) {
return Stream.of(bean.getClass().getDeclaredFields())
.filter((Field filed) -> filed.isAnnotationPresent(WithLabel.class))
.collect(Collectors.toList());
}
private static List findWithLabesFields(Object bean) {
return Stream.of(bean.getClass().getDeclaredFields())
.filter((Field filed) -> filed.isAnnotationPresent(WithLabels.class))
.collect(Collectors.toList());
}
private static Optional findServiceEntry(
JsonObject env, KubernetesClient client, String serviceName) {
Objects.requireNonNull(client, "no client available");
final String resolvedServiceName = resolveProperty(env, serviceName);
return Optional.ofNullable(client.services().inNamespace(client.getNamespace()).list())
.orElse(new ServiceList())
.getItems()
.stream()
.filter(item -> item.getMetadata().getName().equalsIgnoreCase(resolvedServiceName))
.findFirst();
}
private static Optional findOfflineServiceEntry(JsonObject env, String serviceName) {
final String resolvedServiceName = resolveProperty(env, serviceName);
final String resolvedValue =
ConfigurationUtil.getStringConfiguration(env, resolvedServiceName, resolvedServiceName);
return Optional.ofNullable(resolvedValue);
}
private static String resolveProperty(JsonObject env, String serviceName) {
String result = serviceName;
if (serviceName.contains("$")) {
result = serviceName.substring(2, serviceName.length() - 1); // scheme: ${value}
result = ConfigurationUtil.getStringConfiguration(env, result, result);
}
return result;
}
}