diff --git a/CHANGELOG.md b/CHANGELOG.md index 242ae27da99..3a05f21c66e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -21,6 +21,7 @@ * Fix #5636: Add new extension `open-virtual-networking` to manage resources in `k8s.ovn.org/v1` API group. * Fix #5711: Kube API Test - Kubernetes API Server JUnit Test Support * Fix #5772: Add openshift model `io.fabric8.openshift.api.model.DeploymentConfigRollback` +* Add a `kubernetes-log4j` module to lookup Kubernetes attributes in a Log4j Core configuration. #### _**Note**_: Breaking changes * KubeSchema and Validation Schema generated classes are no longer annotated with Jackson, Lombok, and Sundrio annotations. diff --git a/doc/KubernetesLog4j.md b/doc/KubernetesLog4j.md new file mode 100644 index 00000000000..8abb3ab16bf --- /dev/null +++ b/doc/KubernetesLog4j.md @@ -0,0 +1,97 @@ +# Kubernetes Log4j Lookup + +The Kubernetes Log4j Lookup provides a [Log4j Core Lookup](https://logging.apache.org/log4j/2.x/manual/lookups) that +can be used to logs files data specific to the Kubernetes container in which the application is running. + +## Usage + +In order to use it, you only need to add the following artifact to your Maven dependencies: + +```xml + + + + io.fabric8 + kubernetes-log4j + ${fabric8.version} + runtime + + ... + +``` + +The following lookups can be use in a `log4j2.xml` configuration file. + +| Supported keys | Description | +|-------------------------------|--------------------------------------------| +| `${k8s:masterUrl}` | the master URL of the Kubernetes cluster | +| `${k8s:namespaceId}` | the id of the namespace | +| `${k8s:namespaceName}` | the name of the namespace | +| `${k8s:namespaceAnnotations}` | the annotations of the namespace | +| `${k8s:namespaceLabels}` | the labels of the namespace | +| `${k8s:podId}` | the id of the pod | +| `${k8s:podIp}` | the IP of the pod | +| `${k8s:podName}` | the name of the pod | +| `${k8s:accountName}` | the name of the pod service account | +| `${k8s:annotations}` | the annotations of the pod | +| `${k8s:labels}` | the labels of the pod | +| `${k8s:labels.}` | the value of the `` label of the pod | +| `${k8s:containerId}` | the id of the container | +| `${k8s:containerName}` | the name of the container | +| `${k8s:imageId}` | the id of the container image | +| `${k8s:imageName}` | the name of the container image | +| `${k8s:host}` | the node name of the pod | +| `${k8s:hostIp}` | the IP of the pod | + +## Configuration + +In order to access data from the Kubernetes cluster, the Kubernetes Log4j lookup uses the automatic configuration +procedure of the Fabric8 Kubernetes client. + +### Automatic configuration + +See [Configuring the client](https://github.com/fabric8io/kubernetes-client/tree/main?tab=readme-ov-file#configuring-the-client) + +### Legacy configuration + +To ease the transition between the [`log4j-kubernetes`](https://logging.apache.org/log4j/2.x/log4j-kubernetes) +artifact and Fabric8's Log4j lookup, the Kubernetes client can also be configured via one of the [Log4j property +sources](https://logging.apache.org/log4j/2.x/manual/configuration#SystemProperties). + +To enable the legacy configuration, the Java System property `kubernetes.log4j.useProperties` must be set to `true`. + +The following configuration properties are recognized. + +| Log4j Property Name | Default | Description | +|---------------------------------------------------------------------------------------------------------------|-----------------------:|-----------------------------------:| +| `log4j2.kubernetes.client.apiVersion`
`spring.cloud.kubernetes.client.apiVersion` | v1 | Kubernetes API Version | +| `log4j2.kubernetes.client.caCertData`
`spring.cloud.kubernetes.client.caCertData` | | Kubernetes API CACertData | +| `log4j2.kubernetes.client.caCertFile`
`spring.cloud.kubernetes.client.caCertFile` | | Kubernetes API CACertFile | +| `log4j2.kubernetes.client.clientCertData`
`spring.cloud.kubernetes.client.clientCertData` | | Kubernetes API ClientCertData | +| `log4j2.kubernetes.client.clientCertFile`
`spring.cloud.kubernetes.client.clientCertFile` | | Kubernetes API ClientCertFile | +| `log4j2.kubernetes.client.clientKeyAlgo`
`spring.cloud.kubernetes.client.clientKeyAlgo` | RSA | Kubernetes API ClientKeyAlgo | +| `log4j2.kubernetes.client.clientKeyData`
`spring.cloud.kubernetes.client.clientKeyData` | | Kubernetes API ClientKeyData | +| `log4j2.kubernetes.client.clientKeyFile`
`spring.cloud.kubernetes.client.clientKeyFile` | | Kubernetes API ClientKeyFile | +| `log4j2.kubernetes.client.clientKeyPassPhrase`
`spring.cloud.kubernetes.client.clientKeyPassphrase` | changeit | Kubernetes API ClientKeyPassphrase | +| `log4j2.kubernetes.client.connectionTimeout`
`spring.cloud.kubernetes.client.connectionTimeout` | 10s | Connection timeout | +| `log4j2.kubernetes.client.httpProxy`
`spring.cloud.kubernetes.client.http-proxy` | | | +| `log4j2.kubernetes.client.httpsProxy`
`spring.cloud.kubernetes.client.https-proxy` | | | +| `log4j2.kubernetes.client.loggingInterval`
`spring.cloud.kubernetes.client.loggingInterval` | 20s | Logging interval | +| `log4j2.kubernetes.client.masterUrl`
`spring.cloud.kubernetes.client.masterUrl` | kubernetes.default.svc | Kubernetes API Master Node URL | +| `log4j2.kubernetes.client.namespace`
`spring.cloud.kubernetes.client.namespace` | default | Kubernetes Namespace | +| `log4j2.kubernetes.client.noProxy`
`spring.cloud.kubernetes.client.noProxy` | | | +| `log4j2.kubernetes.client.password`
`spring.cloud.kubernetes.client.password` | | Kubernetes API Password | +| `log4j2.kubernetes.client.proxyPassword`
`spring.cloud.kubernetes.client.proxyPassword` | | | +| `log4j2.kubernetes.client.proxyUsername`
`spring.cloud.kubernetes.client.proxyUsername` | | | +| `log4j2.kubernetes.client.requestTimeout`
`spring.cloud.kubernetes.client.requestTimeout` | 10s | Request timeout | +| `log4j2.kubernetes.client.rollingTimeout`
`spring.cloud.kubernetes.client.rollingTimeout` | 900s | Rolling timeout | +| `log4j2.kubernetes.client.trustCerts`
`spring.cloud.kubernetes.client.trustCerts` | false | Kubernetes API Trust Certificates | +| `log4j2.kubernetes.client.username`
`spring.cloud.kubernetes.client.username` | | Kubernetes API Username | +| `log4j2.kubernetes.client.watchReconnectInterval`
`spring.cloud.kubernetes.client.watchReconnectInterval` | 1s | Reconnect Interval | +| `log4j2.kubernetes.client.watchReconnectLimit`
`spring.cloud.kubernetes.client.watchReconnectLimit` | -1 | Reconnect Interval limit retries | + +### Usage in Spring Boot + +Note that Log4j Core is initialized at least twice by Spring Boot and since the Spring `Environment` is only +available +during the last Log4j initialization Spring properties will only be available to Log4j in the last initialization. diff --git a/log4j/pom.xml b/log4j/pom.xml new file mode 100644 index 00000000000..1f5a096b149 --- /dev/null +++ b/log4j/pom.xml @@ -0,0 +1,81 @@ + + + + 4.0.0 + + io.fabric8 + kubernetes-client-project + 6.11-SNAPSHOT + + + kubernetes-log4j + bundle + Fabric8 :: Kubernetes :: Log4j Core components + Provides a lookup to use Kubernetes attributes in a Log4j Core configuration. + + + io.fabric8.kubernetes.log4j.* + * + + + + + io.fabric8 + kubernetes-client + + + org.apache.logging.log4j + log4j-api + ${log4j.version} + compile + + + org.apache.logging.log4j + log4j-core + ${log4j.version} + compile + + + org.assertj + assertj-core + test + + + org.junit.jupiter + junit-jupiter-api + test + + + org.junit.jupiter + junit-jupiter-params + test + + + io.fabric8 + kubernetes-server-mock + test + + + org.mockito + mockito-core + test + + + + diff --git a/log4j/src/main/java/io/fabric8/kubernetes/log4j/lookup/ClientBuilder.java b/log4j/src/main/java/io/fabric8/kubernetes/log4j/lookup/ClientBuilder.java new file mode 100644 index 00000000000..55ccc05793f --- /dev/null +++ b/log4j/src/main/java/io/fabric8/kubernetes/log4j/lookup/ClientBuilder.java @@ -0,0 +1,85 @@ +/* + * Copyright (C) 2015 Red Hat, Inc. + * + * 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 io.fabric8.kubernetes.log4j.lookup; + +import io.fabric8.kubernetes.client.Config; +import io.fabric8.kubernetes.client.ConfigBuilder; +import io.fabric8.kubernetes.client.KubernetesClient; +import io.fabric8.kubernetes.client.KubernetesClientBuilder; +import org.apache.logging.log4j.status.StatusLogger; +import org.apache.logging.log4j.util.PropertiesUtil; + +import static io.fabric8.kubernetes.client.utils.Utils.getSystemPropertyOrEnvVar; + +/** + * Builds a Kubernetes Client. + */ +final class ClientBuilder { + + /** + * If this system property is set to {@code true}, the client configuration is retrieved from Log4j Properties. + */ + public static final String KUBERNETES_LOG4J_USE_PROPERTIES = "kubernetes.log4j.useProperties"; + + private ClientBuilder() { + } + + public static KubernetesClient createClient() { + final Config config = kubernetesClientConfig(PropertiesUtil.getProperties()); + return config != null ? new KubernetesClientBuilder() + .withConfig(config).build() : null; + } + + static Config kubernetesClientConfig(final PropertiesUtil props) { + try { + final Config base = Config.autoConfigure(null); + if (getSystemPropertyOrEnvVar(KUBERNETES_LOG4J_USE_PROPERTIES, false)) { + final Log4jConfig log4jConfig = new Log4jConfig(props, base); + return new ConfigBuilder() + .withApiVersion(log4jConfig.getApiVersion()) + .withCaCertData(log4jConfig.getCaCertData()) + .withCaCertFile(log4jConfig.getCaCertFile()) + .withClientCertData(log4jConfig.getClientCertData()) + .withClientCertFile(log4jConfig.getClientCertFile()) + .withClientKeyAlgo(log4jConfig.getClientKeyAlgo()) + .withClientKeyData(log4jConfig.getClientKeyData()) + .withClientKeyFile(log4jConfig.getClientKeyFile()) + .withClientKeyPassphrase(log4jConfig.getClientKeyPassphrase()) + .withConnectionTimeout(log4jConfig.getConnectionTimeout()) + .withHttpProxy(log4jConfig.getHttpProxy()) + .withHttpsProxy(log4jConfig.getHttpsProxy()) + .withLoggingInterval(log4jConfig.getLoggingInterval()) + .withMasterUrl(log4jConfig.getMasterUrl()) + .withNamespace(log4jConfig.getNamespace()) + .withNoProxy(log4jConfig.getNoProxy()) + .withPassword(log4jConfig.getPassword()) + .withProxyPassword(log4jConfig.getProxyPassword()) + .withProxyUsername(log4jConfig.getProxyUsername()) + .withRequestTimeout(log4jConfig.getRequestTimeout()) + .withTrustCerts(log4jConfig.isTrustCerts()) + .withUsername(log4jConfig.getUsername()) + .withWatchReconnectInterval(log4jConfig.getWatchReconnectInterval()) + .withWatchReconnectLimit(log4jConfig.getWatchReconnectLimit()) + .build(); + } + return base; + } catch (final Exception e) { + StatusLogger.getLogger().warn("An error occurred while retrieving Kubernetes Client configuration: {}.", + e.getMessage(), e); + } + return null; + } +} diff --git a/log4j/src/main/java/io/fabric8/kubernetes/log4j/lookup/ContainerUtil.java b/log4j/src/main/java/io/fabric8/kubernetes/log4j/lookup/ContainerUtil.java new file mode 100644 index 00000000000..8170713ce53 --- /dev/null +++ b/log4j/src/main/java/io/fabric8/kubernetes/log4j/lookup/ContainerUtil.java @@ -0,0 +1,116 @@ +/* + * Copyright (C) 2015 Red Hat, Inc. + * + * 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 io.fabric8.kubernetes.log4j.lookup; + +import org.apache.logging.log4j.Logger; +import org.apache.logging.log4j.status.StatusLogger; + +import java.io.IOException; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.Paths; +import java.util.Objects; +import java.util.Optional; +import java.util.regex.Matcher; +import java.util.regex.Pattern; +import java.util.stream.Stream; + +/** + * Locate the current docker container. + */ +final class ContainerUtil { + + private static final Logger LOGGER = StatusLogger.getLogger(); + private static final Pattern DOCKER_ID_PATTERN = Pattern.compile("[0-9a-fA-F]{64}"); + static final Path CGROUP_PATH = Paths.get("/proc/self/cgroup"); + + private ContainerUtil() { + } + + /** + * Returns the container id when running in a Docker container. + *

+ * This inspects /proc/self/cgroup looking for a Kubernetes Control Group. Once it finds one it attempts + * to isolate just the docker container id. There doesn't appear to be a standard way to do this, but + * it seems to be the only way to determine what the current container is in a multi-container pod. It would have + * been much nicer if Kubernetes would just put the container id in a standard environment variable. + *

+ * + * @param path Path to a {@code /proc/pid/cgroup} file. + * @return A container id or {@code null} if not found. + */ + public static String getContainerId(Path path) { + try { + if (Files.exists(path)) { + try (final Stream lines = Files.lines(path)) { + final String id = lines + .map(ContainerUtil::getContainerId) + .filter(Objects::nonNull) + .findFirst() + .orElse(null); + LOGGER.debug("Found container id {}", id); + return id; + } + } + LOGGER.warn("Unable to access container information"); + } catch (IOException ioe) { + LOGGER.warn("Error obtaining container id: {}", ioe.getMessage()); + } + return null; + } + + private static String getContainerId(String line) { + return Optional.ofNullable(getCGroupPath(line)) + .map(ContainerUtil::getDockerId) + .orElse(null); + } + + /** + * Retrieves a container id from a hierarchy of CGroups + *

+ * Based on + * ControlGroup.java + *

+ * + * @param cgroupPath a slash-separated hierarchy of CGroups. + * @return a Docker ID + */ + private static String getDockerId(String cgroupPath) { + String[] elements = cgroupPath.split("/", -1); + String dockerId = null; + for (String element : elements) { + Matcher matcher = DOCKER_ID_PATTERN.matcher(element); + if (matcher.find()) { + dockerId = matcher.group(); + } + } + return dockerId; + } + + /** + * Retrieves the full hierarchy of CGroups the process belongs + *

+ * See /proc/pid/cgroups + *

+ * + * @param line A line from a {@code /proc/pid/cgroups} file + */ + private static String getCGroupPath(String line) { + String[] fields = line.split(":", -1); + return fields.length > 2 ? fields[2] : null; + } +} diff --git a/log4j/src/main/java/io/fabric8/kubernetes/log4j/lookup/KubernetesLookup.java b/log4j/src/main/java/io/fabric8/kubernetes/log4j/lookup/KubernetesLookup.java new file mode 100644 index 00000000000..216156aa067 --- /dev/null +++ b/log4j/src/main/java/io/fabric8/kubernetes/log4j/lookup/KubernetesLookup.java @@ -0,0 +1,468 @@ +/* + * Copyright (C) 2015 Red Hat, Inc. + * + * 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 io.fabric8.kubernetes.log4j.lookup; + +import io.fabric8.kubernetes.api.model.Container; +import io.fabric8.kubernetes.api.model.ContainerStatus; +import io.fabric8.kubernetes.api.model.Namespace; +import io.fabric8.kubernetes.api.model.ObjectMeta; +import io.fabric8.kubernetes.api.model.Pod; +import io.fabric8.kubernetes.api.model.PodSpec; +import io.fabric8.kubernetes.api.model.PodStatus; +import io.fabric8.kubernetes.client.KubernetesClient; +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; +import org.apache.logging.log4j.core.LogEvent; +import org.apache.logging.log4j.core.config.plugins.Plugin; +import org.apache.logging.log4j.core.lookup.AbstractLookup; +import org.apache.logging.log4j.core.lookup.StrLookup; +import org.apache.logging.log4j.status.StatusLogger; +import org.apache.logging.log4j.util.LoaderUtil; + +import java.net.InetAddress; +import java.net.URL; +import java.net.UnknownHostException; +import java.nio.file.Path; +import java.util.List; +import java.util.Map; +import java.util.concurrent.locks.Lock; +import java.util.concurrent.locks.ReadWriteLock; +import java.util.concurrent.locks.ReentrantReadWriteLock; + +/** + * Retrieves various attributes from the Kubernetes server. + *

+ * The supported keys are listed in the following table: + *

+ * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + *
Supported keys
KeyDescription
{@value ACCOUNT_NAME}the name of the account
{@value ANNOTATIONS}the annotations of the Kubernetes pod
{@value CONTAINER_ID}the id of the Kubernetes container
{@value CONTAINER_NAME}the name of the Kubernetes container
{@value HOST}the host name of the Kubernetes pod
{@value HOST_IP}the IP of the Kubernetes pod
{@value IMAGE_ID}the id of the Kubernetes container image
{@value IMAGE_NAME}the name of the Kubernetes container image
{@value LABELS}the labels of the Kubernetes pod
"labels.<name>"the value of the "<name>" label of the Kubernetes pod
{@value MASTER_URL}the master URL of the Kubernetes cluster
{@value NAMESPACE_ANNOTATIONS}the annotations of the namespace
{@value NAMESPACE_ID}the id of the namespace
{@value NAMESPACE_LABELS}the labels of the namespace
{@value NAMESPACE_NAME}the name of the namespace
{@value POD_ID}the id of the pod
{@value POD_IP}the IP of the pod
{@value POD_NAME}the name of the pod
+ */ +@Plugin(name = "k8s", category = StrLookup.CATEGORY) +public class KubernetesLookup extends AbstractLookup { + + private static final Logger LOGGER = StatusLogger.getLogger(); + private static final String HOSTNAME = "HOSTNAME"; + private static final String SPRING_ENVIRONMENT_KEY = "SpringEnvironment"; + + /** + * Supported keys + */ + private static final String ACCOUNT_NAME = "accountName"; + private static final String ANNOTATIONS = "annotations"; + private static final String CONTAINER_ID = "containerId"; + private static final String CONTAINER_NAME = "containerName"; + private static final String HOST = "host"; + private static final String HOST_IP = "hostIp"; + private static final String IMAGE_ID = "imageId"; + private static final String IMAGE_NAME = "imageName"; + private static final String LABELS = "labels"; + private static final String LABELS_PREFIX = "labels."; + private static final String MASTER_URL = "masterUrl"; + private static final String NAMESPACE_ANNOTATIONS = "namespaceAnnotations"; + private static final String NAMESPACE_ID = "namespaceId"; + private static final String NAMESPACE_LABELS = "namespaceLabels"; + private static final String NAMESPACE_NAME = "namespaceName"; + private static final String POD_ID = "podId"; + private static final String POD_IP = "podIp"; + private static final String POD_NAME = "podName"; + + private static KubernetesInfo kubernetesInfo; + // Used in tests + static Path cgroupPath = ContainerUtil.CGROUP_PATH; + private static final ReadWriteLock LOCK = new ReentrantReadWriteLock(); + private static final Lock READ_LOCK = LOCK.readLock(); + private static final Lock WRITE_LOCK = LOCK.writeLock(); + private static final boolean IS_SPRING_INCLUDED = LoaderUtil + .isClassAvailable("org.apache.logging.log4j.spring.cloud.config.client.SpringEnvironmentHolder") + || LoaderUtil.isClassAvailable("org.apache.logging.log4j.spring.boot.SpringEnvironmentHolder"); + + private Pod pod; + private Namespace namespace; + private URL masterUrl; + + /** + * Default constructor, called by reflection. + */ + public KubernetesLookup() { + this.pod = null; + this.namespace = null; + this.masterUrl = null; + initialize(this); + } + + KubernetesLookup(Pod pod, Namespace namespace, URL masterUrl) { + this.pod = pod; + this.namespace = namespace; + this.masterUrl = masterUrl; + initialize(this); + } + + private static void initialize(KubernetesLookup lookup) { + KubernetesInfo kubernetesInfo = KubernetesLookup.kubernetesInfo; + if (kubernetesInfo == null || isSpringStatusChanged(kubernetesInfo)) { + WRITE_LOCK.lock(); + try { + kubernetesInfo = KubernetesLookup.kubernetesInfo; + if (kubernetesInfo == null || isSpringStatusChanged(kubernetesInfo)) { + if (lookup.pod == null || lookup.namespace == null || lookup.masterUrl == null) { + tryInitializeFields(lookup); + } + // Retrieve the data from the fields + kubernetesInfo = new KubernetesInfo(); + kubernetesInfo.isSpringActive = isSpringActive(); + kubernetesInfo.masterUrl = lookup.masterUrl; + if (lookup.namespace != null) { + fillNamespaceData(lookup.namespace, kubernetesInfo); + } + if (lookup.pod != null) { + fillPodData(lookup.pod, kubernetesInfo); + } + KubernetesLookup.kubernetesInfo = kubernetesInfo; + } + } finally { + WRITE_LOCK.unlock(); + } + } + } + + private static boolean isSpringStatusChanged(KubernetesInfo kubernetesInfo) { + return IS_SPRING_INCLUDED + && isSpringActive() != (kubernetesInfo != null && kubernetesInfo.isSpringActive); + } + + private static boolean isSpringActive() { + return IS_SPRING_INCLUDED + && LogManager.getFactory() != null + && LogManager.getFactory().hasContext(KubernetesLookup.class.getName(), null, false) + && LogManager.getContext(false).getObject(SPRING_ENVIRONMENT_KEY) != null; + } + + /** + * Tries to initialize the fields of the lookup. + */ + private static void tryInitializeFields(KubernetesLookup lookup) { + KubernetesClient client = lookup.createClient(); + if (client != null) { + if (lookup.pod == null) { + lookup.pod = getCurrentPod(client); + } + if (lookup.pod != null && lookup.namespace == null) { + lookup.namespace = getNamespace(client, lookup.pod); + } + if (lookup.masterUrl == null) { + lookup.masterUrl = client.getMasterUrl(); + } + } else { + LOGGER.warn("Kubernetes is not available for access"); + } + } + + /** + * Creates a Kubernetes client used to retrieve K8S configuration. + *

+ * Used in tests to provide a mock client. + *

+ * + * @return A Kubernetes client. + */ + protected KubernetesClient createClient() { + return ClientBuilder.createClient(); + } + + private static Pod getCurrentPod(final KubernetesClient kubernetesClient) { + final String hostName = getHostName(); + try { + if (hostName != null && !hostName.isEmpty()) { + return kubernetesClient.pods().withName(hostName).get(); + } + } catch (Exception e) { + LOGGER.debug("Unable to locate pod with name {}.", hostName, e); + } + return null; + } + + static String getHostName() { + String hostName = null; + try { + hostName = InetAddress.getLocalHost().getHostName(); + } catch (final UnknownHostException ignored) { + // NOP + } + return hostName != null && !"localhost".equals(hostName) ? hostName : System.getenv(HOSTNAME); + } + + private static Namespace getNamespace(KubernetesClient client, Pod pod) { + return client.namespaces() + .withName(pod.getMetadata().getNamespace()) + .get(); + } + + private static void fillNamespaceData(Namespace namespace, KubernetesInfo kubernetesInfo) { + final ObjectMeta namespaceMetadata = namespace.getMetadata(); + if (namespaceMetadata != null) { + kubernetesInfo.namespaceAnnotations = namespaceMetadata.getAnnotations(); + kubernetesInfo.namespaceId = namespaceMetadata.getUid(); + kubernetesInfo.namespaceLabels = namespaceMetadata.getLabels(); + } + } + + private static void fillPodData(Pod pod, KubernetesInfo kubernetesInfo) { + final ObjectMeta podMetadata = pod.getMetadata(); + if (podMetadata != null) { + kubernetesInfo.annotations = podMetadata.getAnnotations(); + kubernetesInfo.labels = podMetadata.getLabels(); + kubernetesInfo.namespace = podMetadata.getNamespace(); + kubernetesInfo.podId = podMetadata.getUid(); + kubernetesInfo.podName = podMetadata.getName(); + } + fillStatuses(pod, kubernetesInfo); + // The container name is filled as a result + String containerName = kubernetesInfo.containerName; + + final PodSpec podSpec = pod.getSpec(); + if (podSpec != null) { + kubernetesInfo.hostName = podSpec.getNodeName(); + kubernetesInfo.accountName = podSpec.getServiceAccountName(); + + Container container = getContainer(podSpec, containerName); + if (container != null) { + kubernetesInfo.containerName = container.getName(); + kubernetesInfo.imageName = container.getImage(); + } + } + } + + private static void fillStatuses(Pod pod, KubernetesInfo kubernetesInfo) { + final PodStatus podStatus = pod.getStatus(); + if (podStatus != null) { + kubernetesInfo.hostIp = podStatus.getHostIP(); + kubernetesInfo.podIp = podStatus.getPodIP(); + + ContainerStatus containerStatus = getContainerStatus(podStatus); + if (containerStatus != null) { + kubernetesInfo.containerId = containerStatus.getContainerID(); + kubernetesInfo.imageId = containerStatus.getImageID(); + kubernetesInfo.containerName = containerStatus.getName(); + } + } + } + + private static ContainerStatus getContainerStatus(PodStatus podStatus) { + List statuses = podStatus.getContainerStatuses(); + switch (statuses.size()) { + case 0: + return null; + case 1: + return statuses.get(0); + default: + final String containerId = ContainerUtil.getContainerId(cgroupPath); + return containerId != null ? statuses.stream() + .filter(cs -> cs.getContainerID().contains(containerId)) + .findFirst() + .orElse(null) : null; + } + } + + private static Container getContainer(PodSpec podSpec, String containerName) { + final List containers = podSpec.getContainers(); + switch (containers.size()) { + case 0: + return null; + case 1: + return containers.get(0); + default: + return containerName != null ? containers.stream() + .filter(c -> c.getName().equals(containerName)) + .findFirst() + .orElse(null) : null; + } + } + + @Override + public String lookup(final LogEvent event, final String key) { + KubernetesInfo info; + READ_LOCK.lock(); + try { + info = kubernetesInfo; + } finally { + READ_LOCK.unlock(); + } + if (key.startsWith(LABELS_PREFIX)) { + return info.labels != null ? info.labels.get(key.substring(LABELS_PREFIX.length())) : null; + } + switch (key) { + case ACCOUNT_NAME: { + return info.accountName; + } + case ANNOTATIONS: { + return info.annotations != null ? info.annotations.toString() : null; + } + case CONTAINER_ID: { + return info.containerId; + } + case CONTAINER_NAME: { + return info.containerName; + } + case HOST: { + return info.hostName; + } + case HOST_IP: { + return info.hostIp; + } + case LABELS: { + return info.labels != null ? info.labels.toString() : null; + } + case MASTER_URL: { + return info.masterUrl != null ? info.masterUrl.toString() : null; + } + case NAMESPACE_ANNOTATIONS: { + return info.namespaceAnnotations != null ? info.namespaceAnnotations.toString() : null; + } + case NAMESPACE_ID: { + return info.namespaceId; + } + case NAMESPACE_LABELS: { + return info.namespaceLabels != null ? info.namespaceLabels.toString() : null; + } + case NAMESPACE_NAME: { + return info.namespace; + } + case POD_ID: { + return info.podId; + } + case POD_IP: { + return info.podIp; + } + case POD_NAME: { + return info.podName; + } + case IMAGE_ID: { + return info.imageId; + } + case IMAGE_NAME: { + return info.imageName; + } + default: + return null; + } + } + + /** + * For unit testing only. + */ + static void clear() { + kubernetesInfo = null; + cgroupPath = ContainerUtil.CGROUP_PATH; + } + + private static class KubernetesInfo { + boolean isSpringActive; + String accountName; + Map annotations; + String containerId; + String containerName; + String hostName; + String hostIp; + String imageId; + String imageName; + Map labels; + URL masterUrl; + String namespace; + Map namespaceAnnotations; + String namespaceId; + Map namespaceLabels; + String podId; + String podIp; + String podName; + } +} diff --git a/log4j/src/main/java/io/fabric8/kubernetes/log4j/lookup/Log4jConfig.java b/log4j/src/main/java/io/fabric8/kubernetes/log4j/lookup/Log4jConfig.java new file mode 100644 index 00000000000..b30751752c3 --- /dev/null +++ b/log4j/src/main/java/io/fabric8/kubernetes/log4j/lookup/Log4jConfig.java @@ -0,0 +1,165 @@ +/* + * Copyright (C) 2015 Red Hat, Inc. + * + * 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 io.fabric8.kubernetes.log4j.lookup; + +import io.fabric8.kubernetes.client.Config; +import org.apache.logging.log4j.util.PropertiesUtil; + +import java.time.Duration; + +/** + * Obtains properties used to configure the Kubernetes client. + */ +final class Log4jConfig { + + // Prefixes used in Log4j properties + private static final String[] PREFIXES = { "log4j2.kubernetes.client.", "spring.cloud.kubernetes.client." }; + // Recognized Log4j properties + private static final String API_VERSION = "apiVersion"; + private static final String CA_CERT_FILE = "caCertFile"; + private static final String CA_CERT_DATA = "caCertData"; + private static final String CLIENT_CERT_FILE = "clientCertFile"; + private static final String CLIENT_CERT_DATA = "clientCertData"; + private static final String CLIENT_KEY_FILE = "clientKeyFile"; + private static final String CLIENT_KEY_DATA = "clientKeyData"; + private static final String CLIENT_KEY_ALGO = "clientKeyAlgo"; + private static final String CLIENT_KEY_PASSPHRASE = "clientKeyPassphrase"; + private static final String CONNECTION_TIMEOUT = "connectionTimeout"; + private static final String HTTP_PROXY = "httpProxy"; + private static final String HTTPS_PROXY = "httpsProxy"; + private static final String LOGGING_INTERVAL = "loggingInterval"; + private static final String MASTER_URL = "masterUrl"; + private static final String NAMESPACE = "namespace"; + private static final String NO_PROXY = "noProxy"; + private static final String PASSWORD = "password"; + private static final String PROXY_USERNAME = "proxyUsername"; + private static final String PROXY_PASSWORD = "proxyPassword"; + private static final String REQUEST_TIMEOUT = "requestTimeout"; + private static final String TRUST_CERTS = "trustCerts"; + private static final String USERNAME = "username"; + private static final String WATCH_RECONNECT_INTERVAL = "watchReconnectInterval"; + private static final String WATCH_RECONNECT_LIMIT = "watchReconnectLimit"; + + private final PropertiesUtil props; + private final Config base; + + public Log4jConfig(final PropertiesUtil props, final Config base) { + this.props = props; + this.base = base; + } + + public String getApiVersion() { + return props.getStringProperty(PREFIXES, API_VERSION, base::getApiVersion); + } + + public String getCaCertFile() { + return props.getStringProperty(PREFIXES, CA_CERT_FILE, base::getCaCertFile); + } + + public String getCaCertData() { + return props.getStringProperty(PREFIXES, CA_CERT_DATA, base::getCaCertData); + } + + public String getClientCertFile() { + return props.getStringProperty(PREFIXES, CLIENT_CERT_FILE, base::getClientCertFile); + } + + public String getClientCertData() { + return props.getStringProperty(PREFIXES, CLIENT_CERT_DATA, base::getClientCertData); + } + + public String getClientKeyFile() { + return props.getStringProperty(PREFIXES, CLIENT_KEY_FILE, base::getClientKeyFile); + } + + public String getClientKeyData() { + return props.getStringProperty(PREFIXES, CLIENT_KEY_DATA, base::getClientKeyData); + } + + public String getClientKeyAlgo() { + return props.getStringProperty(PREFIXES, CLIENT_KEY_ALGO, base::getClientKeyAlgo); + } + + public String getClientKeyPassphrase() { + return props.getStringProperty(PREFIXES, CLIENT_KEY_PASSPHRASE, base::getClientKeyPassphrase); + } + + public int getConnectionTimeout() { + final Duration timeout = props.getDurationProperty(PREFIXES, CONNECTION_TIMEOUT, null); + return timeout != null ? (int) timeout.toMillis() : base.getConnectionTimeout(); + } + + public String getHttpProxy() { + return props.getStringProperty(PREFIXES, HTTP_PROXY, base::getHttpProxy); + } + + public String getHttpsProxy() { + return props.getStringProperty(PREFIXES, HTTPS_PROXY, base::getHttpsProxy); + } + + public int getLoggingInterval() { + final Duration interval = props.getDurationProperty(PREFIXES, LOGGING_INTERVAL, null); + return interval != null ? (int) interval.toMillis() : base.getLoggingInterval(); + } + + public String getMasterUrl() { + return props.getStringProperty(PREFIXES, MASTER_URL, base::getMasterUrl); + } + + public String getNamespace() { + return props.getStringProperty(PREFIXES, NAMESPACE, base::getNamespace); + } + + public String[] getNoProxy() { + final String result = props.getStringProperty(PREFIXES, NO_PROXY, null); + return result != null ? result.replace("\\s", "").split(",") : base.getNoProxy(); + } + + public String getPassword() { + return props.getStringProperty(PREFIXES, PASSWORD, base::getPassword); + } + + public String getProxyUsername() { + return props.getStringProperty(PREFIXES, PROXY_USERNAME, base::getProxyUsername); + } + + public String getProxyPassword() { + return props.getStringProperty(PREFIXES, PROXY_PASSWORD, base::getProxyPassword); + } + + public int getRequestTimeout() { + final Duration interval = props.getDurationProperty(PREFIXES, REQUEST_TIMEOUT, null); + return interval != null ? (int) interval.toMillis() : base.getRequestTimeout(); + } + + public Boolean isTrustCerts() { + return props.getBooleanProperty(PREFIXES, TRUST_CERTS, base::isTrustCerts); + } + + public String getUsername() { + return props.getStringProperty(PREFIXES, USERNAME, base::getUsername); + } + + public int getWatchReconnectInterval() { + final Duration interval = props.getDurationProperty(PREFIXES, WATCH_RECONNECT_INTERVAL, null); + return interval != null ? (int) interval.toMillis() : base.getWatchReconnectInterval(); + } + + public int getWatchReconnectLimit() { + final Duration interval = props.getDurationProperty(PREFIXES, WATCH_RECONNECT_LIMIT, null); + return interval != null ? (int) interval.toMillis() : base.getWatchReconnectLimit(); + } +} diff --git a/log4j/src/main/java/io/fabric8/kubernetes/log4j/lookup/package-info.java b/log4j/src/main/java/io/fabric8/kubernetes/log4j/lookup/package-info.java new file mode 100644 index 00000000000..22d06b59e3d --- /dev/null +++ b/log4j/src/main/java/io/fabric8/kubernetes/log4j/lookup/package-info.java @@ -0,0 +1,19 @@ +/* + * Copyright (C) 2015 Red Hat, Inc. + * + * 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. + */ +/** + * Provides a lookup to use Kubernetes attributes in a Log4j Core configuration. + */ +package io.fabric8.kubernetes.log4j.lookup; diff --git a/log4j/src/test/java/io/fabric8/kubernetes/log4j/lookup/ClientBuilderTest.java b/log4j/src/test/java/io/fabric8/kubernetes/log4j/lookup/ClientBuilderTest.java new file mode 100644 index 00000000000..f6827d478b4 --- /dev/null +++ b/log4j/src/test/java/io/fabric8/kubernetes/log4j/lookup/ClientBuilderTest.java @@ -0,0 +1,73 @@ +/* + * Copyright (C) 2015 Red Hat, Inc. + * + * 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 io.fabric8.kubernetes.log4j.lookup; + +import io.fabric8.kubernetes.client.Config; +import org.apache.logging.log4j.util.PropertiesUtil; +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.Test; + +import static org.assertj.core.api.Assertions.assertThat; + +class ClientBuilderTest { + + @AfterEach + void cleanUp() { + System.getProperties().remove(ClientBuilder.KUBERNETES_LOG4J_USE_PROPERTIES); + } + + @Test + void uses_log4j_properties_if_requested() { + System.setProperty(ClientBuilder.KUBERNETES_LOG4J_USE_PROPERTIES, "true"); + final PropertiesUtil props = new PropertiesUtil("log4j2.kubernetes.client.properties"); + final Config config = ClientBuilder.kubernetesClientConfig(props); + assertThat(config).isNotNull(); + assertThat(config.getApiVersion()).isEqualTo("apiVersion"); + assertThat(config.getCaCertFile()).isEqualTo("caCertFile"); + assertThat(config.getCaCertData()).isEqualTo("caCertData"); + assertThat(config.getClientCertFile()).isEqualTo("clientCertFile"); + assertThat(config.getClientCertData()).isEqualTo("clientCertData"); + assertThat(config.getClientKeyFile()).isEqualTo("clientKeyFile"); + assertThat(config.getClientKeyData()).isEqualTo("clientKeyData"); + assertThat(config.getClientKeyAlgo()).isEqualTo("clientKeyAlgo"); + assertThat(config.getClientKeyPassphrase()).isEqualTo("clientKeyPassphrase"); + assertThat(config.getConnectionTimeout()).isEqualTo(123); + assertThat(config.getHttpProxy()).isEqualTo("httpProxy"); + assertThat(config.getHttpsProxy()).isEqualTo("httpsProxy"); + assertThat(config.getLoggingInterval()).isEqualTo(345); + assertThat(config.getMasterUrl()).isEqualTo("https://masterUrl/"); + assertThat(config.getNamespace()).isEqualTo("namespace"); + assertThat(config.getNoProxy()).isEqualTo(new String[] { "noProxy" }); + assertThat(config.getPassword()).isEqualTo("password"); + assertThat(config.getProxyUsername()).isEqualTo("proxyUsername"); + assertThat(config.getProxyPassword()).isEqualTo("proxyPassword"); + assertThat(config.getRequestTimeout()).isEqualTo(678); + assertThat(config.isTrustCerts()).isTrue(); + assertThat(config.getUsername()).isEqualTo("username"); + assertThat(config.getWatchReconnectInterval()).isEqualTo(1234); + assertThat(config.getWatchReconnectLimit()).isEqualTo(5678); + } + + @Test + void uses_native_properties() { + System.setProperty(ClientBuilder.KUBERNETES_LOG4J_USE_PROPERTIES, "false"); + final PropertiesUtil props = new PropertiesUtil("log4j2.kubernetes.client.properties"); + final Config autoConfig = Config.autoConfigure(null); + final Config config = ClientBuilder.kubernetesClientConfig(props); + assertThat(config).isNotNull(); + assertThat(config.getApiVersion()).isEqualTo(autoConfig.getApiVersion()); + } +} diff --git a/log4j/src/test/java/io/fabric8/kubernetes/log4j/lookup/ContainerUtilTest.java b/log4j/src/test/java/io/fabric8/kubernetes/log4j/lookup/ContainerUtilTest.java new file mode 100644 index 00000000000..8fac6d69b39 --- /dev/null +++ b/log4j/src/test/java/io/fabric8/kubernetes/log4j/lookup/ContainerUtilTest.java @@ -0,0 +1,59 @@ +/* + * Copyright (C) 2015 Red Hat, Inc. + * + * 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 io.fabric8.kubernetes.log4j.lookup; + +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.Arguments; +import org.junit.jupiter.params.provider.MethodSource; + +import java.net.URL; +import java.nio.file.Paths; +import java.util.stream.Stream; + +import static org.assertj.core.api.Assertions.assertThat; + +class ContainerUtilTest { + + // Also used in KubernetesLookupTest + static final String CONTAINER_ID = "3dd988081e7149463c043b5d9c57d7309e079c5e9290f91feba1cc45a04d6a5b"; + static final String TEST_GOOD_CGROUP_RESOURCE = "cgroups/positive/case_0.dat"; + static final String TEST_BAD_CGROUP_RESOURCE = "cgroups/negative/case_0.dat"; + + static Stream should_recognize_container_id() { + return Stream.of( + // Some possible example /proc/self/cgroup + Arguments.of(TEST_GOOD_CGROUP_RESOURCE, CONTAINER_ID), + Arguments.of("cgroups/positive/case_1.dat", CONTAINER_ID), + Arguments.of("cgroups/positive/case_2.dat", CONTAINER_ID), + Arguments.of("cgroups/positive/case_3.dat", CONTAINER_ID), + Arguments.of("cgroups/positive/case_4.dat", CONTAINER_ID), + Arguments.of("cgroups/positive/case_5.dat", CONTAINER_ID), + Arguments.of("cgroups/positive/case_6.dat", CONTAINER_ID), + Arguments.of("cgroups/positive/case_7.dat", CONTAINER_ID), + Arguments.of("cgroups/positive/case_8.dat", CONTAINER_ID), + // Smoke test in case the file changes format + Arguments.of(TEST_BAD_CGROUP_RESOURCE, null), + Arguments.of("cgroups/negative/case_1.dat", null)); + } + + @ParameterizedTest + @MethodSource + void should_recognize_container_id(String resourceFile, String expectedContainerId) throws Exception { + final URL url = ContainerUtilTest.class.getClassLoader().getResource(resourceFile); + assertThat(url).hasProtocol("file"); + assertThat(ContainerUtil.getContainerId(Paths.get(url.toURI()))).isEqualTo(expectedContainerId); + } +} diff --git a/log4j/src/test/java/io/fabric8/kubernetes/log4j/lookup/KubernetesLookupTest.java b/log4j/src/test/java/io/fabric8/kubernetes/log4j/lookup/KubernetesLookupTest.java new file mode 100644 index 00000000000..0e734fa4108 --- /dev/null +++ b/log4j/src/test/java/io/fabric8/kubernetes/log4j/lookup/KubernetesLookupTest.java @@ -0,0 +1,223 @@ +/* + * Copyright (C) 2015 Red Hat, Inc. + * + * 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 io.fabric8.kubernetes.log4j.lookup; + +import com.fasterxml.jackson.databind.ObjectMapper; +import io.fabric8.kubernetes.api.model.Container; +import io.fabric8.kubernetes.api.model.ContainerBuilder; +import io.fabric8.kubernetes.api.model.ContainerStatus; +import io.fabric8.kubernetes.api.model.ContainerStatusBuilder; +import io.fabric8.kubernetes.api.model.Namespace; +import io.fabric8.kubernetes.api.model.NamespaceBuilder; +import io.fabric8.kubernetes.api.model.Pod; +import io.fabric8.kubernetes.api.model.PodBuilder; +import io.fabric8.kubernetes.client.KubernetesClient; +import io.fabric8.kubernetes.client.server.mock.EnableKubernetesMockClient; +import org.apache.logging.log4j.core.lookup.StrLookup; +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.MethodSource; + +import java.net.URL; +import java.nio.file.Paths; +import java.util.stream.Stream; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.junit.jupiter.api.Assertions.assertDoesNotThrow; + +/** + * Validate the Kubernetes Lookup. + */ +@EnableKubernetesMockClient(crud = true) +class KubernetesLookupTest { + + private static final int MAX_CONTAINER_COUNT = 2; + private static KubernetesClient mockClient; + + private static final ObjectMapper objectMapper = new ObjectMapper(); + + @AfterEach + void cleanUp() { + KubernetesLookup.clear(); + } + + @Test + void localPod() throws Exception { + final Pod pod = objectMapper.readValue(KubernetesLookupTest.class.getResource("/localPod.json"), Pod.class); + final Namespace namespace = createNamespace(); + final URL masterUrl = new URL("http://localhost:443/"); + final StrLookup lookup = new KubernetesLookup(pod, namespace, masterUrl); + assertThat(lookup.lookup("accountName")).isEqualTo("default"); + assertThat(lookup.lookup("annotations")).isEqualTo("{}"); + assertThat(lookup.lookup("containerId")) + .isEqualTo("docker://818b0098946c67e6ac56cb7c0934b7c2a9f50feb7244b422b2a7f566f7e5d0df"); + assertThat(lookup.lookup("containerName")).isEqualTo("sampleapp"); + assertThat(lookup.lookup("host")).isEqualTo("docker-desktop"); + assertThat(lookup.lookup("hostIp")).isEqualTo("192.168.65.3"); + assertThat(lookup.lookup("imageId")).isEqualTo( + "docker-pullable://localhost:5000/sampleapp@sha256:3cefb2db514db73c69854fee8abd072f27240519432d08aad177a57ee34b7d39"); + assertThat(lookup.lookup("imageName")).isEqualTo("localhost:5000/sampleapp:latest"); + assertThat(lookup.lookup("labels")).isEqualTo("{app=sampleapp, pod-template-hash=584f99476d}"); + assertThat(lookup.lookup("labels.app")).isEqualTo("sampleapp"); + assertThat(lookup.lookup("labels.pod-template-hash")).isEqualTo("584f99476d"); + assertThat(lookup.lookup("masterUrl")).isEqualTo("http://localhost:443/"); + assertThat(lookup.lookup("podId")).isEqualTo("9213879a-479c-42ce-856b-7e2666d21829"); + assertThat(lookup.lookup("podIp")).isEqualTo("10.1.0.47"); + assertThat(lookup.lookup("podName")).isEqualTo("sampleapp-584f99476d-mnrp4"); + assertThat(lookup.lookup("nonExistentProperty")).isNull(); + assertNamespaceLookups(lookup, namespace.getMetadata().getUid()); + } + + @Test + void clusterPod() throws Exception { + final Pod pod = objectMapper.readValue(KubernetesLookupTest.class.getResource("/clusterPod.json"), Pod.class); + final Namespace namespace = createNamespace(); + final URL masterUrl = new URL("http://localhost:443/"); + final StrLookup lookup = new KubernetesLookup(pod, namespace, masterUrl); + assertThat(lookup.lookup("accountName")).isEqualTo("default"); + assertThat(lookup.lookup("annotations")).isEqualTo( + "{cni.projectcalico.org/podIP=172.16.55.101/32, cni.projectcalico.org/podIPs=172.16.55.101/32, flagger-id=94d53b7b-cc06-41b3-bbac-a2d14a16d95d, prometheus.io/port=9797, prometheus.io/scrape=true}"); + assertThat(lookup.lookup("containerId")) + .isEqualTo("docker://2b7c2a93dfb48334aa549e29fdd38039ddd256eec43ba64c145fa4b75a1542f0"); + assertThat(lookup.lookup("containerName")).isEqualTo("platform-forms-service"); + assertThat(lookup.lookup("host")).isEqualTo("k8s-tmpcrm-worker-s03-04"); + assertThat(lookup.lookup("hostIp")).isEqualTo("10.103.220.170"); + assertThat(lookup.lookup("imageId")).isEqualTo( + "docker-pullable://docker.apache.xyz/platform-forms-service@sha256:45fd19ccd99e218a7685c4cee5bc5b16aeae1cdb8e8773f9c066d4cfb22ee195"); + assertThat(lookup.lookup("imageName")).isEqualTo("docker.apache.xyz/platform-forms-service:0.15.0"); + assertThat(lookup.lookup("labels")).isEqualTo("{app=platform-forms-service-primary, pod-template-hash=5ddfc4f9b8}"); + assertThat(lookup.lookup("labels.app")).isEqualTo("platform-forms-service-primary"); + assertThat(lookup.lookup("labels.pod-template-hash")).isEqualTo("5ddfc4f9b8"); + assertThat(lookup.lookup("masterUrl")).isEqualTo("http://localhost:443/"); + assertThat(lookup.lookup("podId")).isEqualTo("df8cbac1-129c-4cd3-b5bc-65d72d8ba5f0"); + assertThat(lookup.lookup("podIp")).isEqualTo("172.16.55.101"); + assertThat(lookup.lookup("podName")).isEqualTo("platform-forms-service-primary-5ddfc4f9b8-kfpzv"); + assertThat(lookup.lookup("nonExistentProperty")).isNull(); + assertNamespaceLookups(lookup, namespace.getMetadata().getUid()); + } + + static Stream container_statuses_smoke_test() { + return Stream.of( + ContainerUtilTest.TEST_GOOD_CGROUP_RESOURCE, + ContainerUtilTest.TEST_BAD_CGROUP_RESOURCE); + } + + @ParameterizedTest + @MethodSource + void container_statuses_smoke_test(String cgroupPath) throws Exception { + Namespace namespace = new NamespaceBuilder().build(); + URL masterUrl = new URL("http://localhost:443/"); + // Supports any number of container statuses + for (int containerCount = 0; containerCount <= MAX_CONTAINER_COUNT; containerCount++) { + // If more than a status is available ContainerUtil is used, so we set a test file to scan for the container id + KubernetesLookup.cgroupPath = Paths.get(KubernetesLookupTest.class.getClassLoader().getResource(cgroupPath).toURI()); + ContainerStatus[] containerStatuses = new ContainerStatus[containerCount]; + for (int i = 0; i < containerCount; i++) { + containerStatuses[i] = new ContainerStatusBuilder().withName("container" + i) + .withContainerID(i == 0 ? ContainerUtilTest.CONTAINER_ID : null).build(); + } + Pod pod = new PodBuilder().withNewStatus() + .withContainerStatuses(containerStatuses) + .endStatus() + .build(); + assertDoesNotThrow(() -> new KubernetesLookup(pod, namespace, masterUrl)); + cleanUp(); + } + } + + static Stream containers_smoke_test() { + return Stream.of( + null, + "container0"); + } + + @ParameterizedTest + @MethodSource + void containers_smoke_test(String containerName) throws Exception { + Namespace namespace = new NamespaceBuilder().build(); + URL masterUrl = new URL("http://localhost:443/"); + // Supports any number of containers in the spec + for (int containerCount = 0; containerCount <= MAX_CONTAINER_COUNT; containerCount++) { + Container[] containers = new Container[containerCount]; + for (int i = 0; i < containerCount; i++) { + containers[i] = new ContainerBuilder().withName("container" + i).build(); + } + Pod pod = new PodBuilder().withNewSpec() + .withContainers(containers) + .endSpec() + .withNewStatus() + .addNewContainerStatus() + .withName( + containerName) + .endContainerStatus() + .endStatus() + .build(); + assertDoesNotThrow(() -> new KubernetesLookup(pod, namespace, masterUrl)); + cleanUp(); + } + } + + @Test + void initialize_works_with_mock_client() { + final Pod pod = mockClient.pods().resource(createPod()).create(); + final Namespace namespace = mockClient.namespaces().resource(createNamespace()).create(); + final StrLookup lookup = new KubernetesLookup() { + @Override + protected KubernetesClient createClient() { + return mockClient; + } + }; + assertThat(lookup.lookup("podId")).isEqualTo(pod.getMetadata().getUid()); + assertThat(lookup.lookup("podName")).isEqualTo(KubernetesLookup.getHostName()); + assertNamespaceLookups(lookup, namespace.getMetadata().getUid()); + } + + @Test + void no_client_should_return_no_data() { + final StrLookup lookup = new KubernetesLookup() { + @Override + protected KubernetesClient createClient() { + return null; + } + }; + assertThat(lookup.lookup("accountName")).isNull(); + } + + private void assertNamespaceLookups(final StrLookup lookup, final String uid) { + assertThat(lookup.lookup("namespaceAnnotations")).isEqualTo("{test=name}"); + assertThat(lookup.lookup("namespaceLabels")).isEqualTo("{ns=my-namespace}"); + assertThat(lookup.lookup("namespaceName")).isEqualTo("test"); + assertThat(lookup.lookup("namespaceId")).isEqualTo(uid); + } + + private static Pod createPod() { + return new PodBuilder().withNewMetadata() + .withName(KubernetesLookup.getHostName()) + .withNamespace("test") + .endMetadata() + .build(); + } + + private Namespace createNamespace() { + return new NamespaceBuilder().withNewMetadata() + .withName("test") + .addToAnnotations("test", "name") + .addToLabels("ns", "my-namespace") + .endMetadata() + .build(); + } +} diff --git a/log4j/src/test/java/io/fabric8/kubernetes/log4j/lookup/Log4jConfigTest.java b/log4j/src/test/java/io/fabric8/kubernetes/log4j/lookup/Log4jConfigTest.java new file mode 100644 index 00000000000..d481bb7451b --- /dev/null +++ b/log4j/src/test/java/io/fabric8/kubernetes/log4j/lookup/Log4jConfigTest.java @@ -0,0 +1,66 @@ +/* + * Copyright (C) 2015 Red Hat, Inc. + * + * 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 io.fabric8.kubernetes.log4j.lookup; + +import io.fabric8.kubernetes.client.Config; +import org.apache.logging.log4j.util.PropertiesUtil; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.MethodSource; + +import java.util.stream.Stream; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.mockito.Mockito.mock; + +class Log4jConfigTest { + + static Stream all_properties_should_be_recognized() { + return Stream.of("log4j2.kubernetes.client.properties", + "spring.cloud.kubernetes.client.properties"); + } + + @ParameterizedTest + @MethodSource + void all_properties_should_be_recognized(final String propertyFile) { + final PropertiesUtil props = new PropertiesUtil(propertyFile); + final Config base = mock(Config.class); + final Log4jConfig config = new Log4jConfig(props, base); + assertThat(config.getApiVersion()).isEqualTo("apiVersion"); + assertThat(config.getCaCertFile()).isEqualTo("caCertFile"); + assertThat(config.getCaCertData()).isEqualTo("caCertData"); + assertThat(config.getClientCertFile()).isEqualTo("clientCertFile"); + assertThat(config.getClientCertData()).isEqualTo("clientCertData"); + assertThat(config.getClientKeyFile()).isEqualTo("clientKeyFile"); + assertThat(config.getClientKeyData()).isEqualTo("clientKeyData"); + assertThat(config.getClientKeyAlgo()).isEqualTo("clientKeyAlgo"); + assertThat(config.getClientKeyPassphrase()).isEqualTo("clientKeyPassphrase"); + assertThat(config.getConnectionTimeout()).isEqualTo(123); + assertThat(config.getHttpProxy()).isEqualTo("httpProxy"); + assertThat(config.getHttpsProxy()).isEqualTo("httpsProxy"); + assertThat(config.getLoggingInterval()).isEqualTo(345); + assertThat(config.getMasterUrl()).isEqualTo("https://masterUrl/"); + assertThat(config.getNamespace()).isEqualTo("namespace"); + assertThat(config.getNoProxy()).isEqualTo(new String[] { "noProxy" }); + assertThat(config.getPassword()).isEqualTo("password"); + assertThat(config.getProxyUsername()).isEqualTo("proxyUsername"); + assertThat(config.getProxyPassword()).isEqualTo("proxyPassword"); + assertThat(config.getRequestTimeout()).isEqualTo(678); + assertThat(config.isTrustCerts()).isTrue(); + assertThat(config.getUsername()).isEqualTo("username"); + assertThat(config.getWatchReconnectInterval()).isEqualTo(1234); + assertThat(config.getWatchReconnectLimit()).isEqualTo(5678); + } +} diff --git a/log4j/src/test/resources/cgroups/negative/case_0.dat b/log4j/src/test/resources/cgroups/negative/case_0.dat new file mode 100644 index 00000000000..870794472f6 --- /dev/null +++ b/log4j/src/test/resources/cgroups/negative/case_0.dat @@ -0,0 +1 @@ +# A file with different format diff --git a/log4j/src/test/resources/cgroups/negative/case_1.dat b/log4j/src/test/resources/cgroups/negative/case_1.dat new file mode 100644 index 00000000000..3ad0ba5a3a2 --- /dev/null +++ b/log4j/src/test/resources/cgroups/negative/case_1.dat @@ -0,0 +1 @@ +0::/user.slice/user-1000.slice/user@1000.service/app.slice/app-org.gnome.Terminal.slice/vte-spawn-cf1f1289-4a5f-4f2a-88fe-7f88e7552831.scope diff --git a/log4j/src/test/resources/cgroups/positive/case_0.dat b/log4j/src/test/resources/cgroups/positive/case_0.dat new file mode 100644 index 00000000000..d5bd33f9071 --- /dev/null +++ b/log4j/src/test/resources/cgroups/positive/case_0.dat @@ -0,0 +1 @@ +4:cpuset:/system.slice/docker-3dd988081e7149463c043b5d9c57d7309e079c5e9290f91feba1cc45a04d6a5b.scope diff --git a/log4j/src/test/resources/cgroups/positive/case_1.dat b/log4j/src/test/resources/cgroups/positive/case_1.dat new file mode 100644 index 00000000000..88fa5833827 --- /dev/null +++ b/log4j/src/test/resources/cgroups/positive/case_1.dat @@ -0,0 +1 @@ +2:cpu:/docker/3dd988081e7149463c043b5d9c57d7309e079c5e9290f91feba1cc45a04d6a5b diff --git a/log4j/src/test/resources/cgroups/positive/case_2.dat b/log4j/src/test/resources/cgroups/positive/case_2.dat new file mode 100644 index 00000000000..1dd8ef30555 --- /dev/null +++ b/log4j/src/test/resources/cgroups/positive/case_2.dat @@ -0,0 +1 @@ +2:cpu:/docker-ce/docker/3dd988081e7149463c043b5d9c57d7309e079c5e9290f91feba1cc45a04d6a5b diff --git a/log4j/src/test/resources/cgroups/positive/case_3.dat b/log4j/src/test/resources/cgroups/positive/case_3.dat new file mode 100644 index 00000000000..51e2ef76638 --- /dev/null +++ b/log4j/src/test/resources/cgroups/positive/case_3.dat @@ -0,0 +1 @@ +10:cpu,cpuacct:/docker/a9f3c3932cd81c4a74cc7e0a18c3300255159512f1d000545c42895adaf68932/docker/3dd988081e7149463c043b5d9c57d7309e079c5e9290f91feba1cc45a04d6a5b diff --git a/log4j/src/test/resources/cgroups/positive/case_4.dat b/log4j/src/test/resources/cgroups/positive/case_4.dat new file mode 100644 index 00000000000..675b386aeeb --- /dev/null +++ b/log4j/src/test/resources/cgroups/positive/case_4.dat @@ -0,0 +1 @@ +3:cpu:/docker/4193df6bcf5fce75f3fc77f303b2ac06fb664adeb269b959b7ae17b3f8dcf329/3dd988081e7149463c043b5d9c57d7309e079c5e9290f91feba1cc45a04d6a5b diff --git a/log4j/src/test/resources/cgroups/positive/case_5.dat b/log4j/src/test/resources/cgroups/positive/case_5.dat new file mode 100644 index 00000000000..65a56183dc6 --- /dev/null +++ b/log4j/src/test/resources/cgroups/positive/case_5.dat @@ -0,0 +1 @@ +7:cpu:/ecs/0410eff2-7e59-4111-823e-1e0d98ef7f30/3dd988081e7149463c043b5d9c57d7309e079c5e9290f91feba1cc45a04d6a5b diff --git a/log4j/src/test/resources/cgroups/positive/case_6.dat b/log4j/src/test/resources/cgroups/positive/case_6.dat new file mode 100644 index 00000000000..5150948ec0d --- /dev/null +++ b/log4j/src/test/resources/cgroups/positive/case_6.dat @@ -0,0 +1 @@ +8:cpuset:/kubepods.slice/kubepods-pod9c26dfb6_b9c9_11e7_bfb9_02c6c1fc4861.slice/docker-3dd988081e7149463c043b5d9c57d7309e079c5e9290f91feba1cc45a04d6a5b.scope diff --git a/log4j/src/test/resources/cgroups/positive/case_7.dat b/log4j/src/test/resources/cgroups/positive/case_7.dat new file mode 100644 index 00000000000..ff7deb5c7b5 --- /dev/null +++ b/log4j/src/test/resources/cgroups/positive/case_7.dat @@ -0,0 +1 @@ +12:freezer:/actions_job/3dd988081e7149463c043b5d9c57d7309e079c5e9290f91feba1cc45a04d6a5b diff --git a/log4j/src/test/resources/cgroups/positive/case_8.dat b/log4j/src/test/resources/cgroups/positive/case_8.dat new file mode 100644 index 00000000000..b37a56e49ee --- /dev/null +++ b/log4j/src/test/resources/cgroups/positive/case_8.dat @@ -0,0 +1 @@ +11:pids:/kubepods/burstable/pod1fe52ba4-5709-11ea-9ee3-00505682780f/3dd988081e7149463c043b5d9c57d7309e079c5e9290f91feba1cc45a04d6a5b diff --git a/log4j/src/test/resources/clusterPod.json b/log4j/src/test/resources/clusterPod.json new file mode 100644 index 00000000000..709da6d36e1 --- /dev/null +++ b/log4j/src/test/resources/clusterPod.json @@ -0,0 +1,177 @@ +{ + "apiVersion": "v1", + "kind": "Pod", + "metadata": { + "annotations": { + "cni.projectcalico.org/podIP": "172.16.55.101/32", + "cni.projectcalico.org/podIPs": "172.16.55.101/32", + "flagger-id": "94d53b7b-cc06-41b3-bbac-a2d14a16d95d", + "prometheus.io/port": "9797", + "prometheus.io/scrape": "true" + }, + "creationTimestamp": "2020-06-15T15:44:16Z", + "generateName": "platform-forms-service-primary-5ddfc4f9b8-", + "labels": { + "app": "platform-forms-service-primary", + "pod-template-hash": "5ddfc4f9b8" + }, + "name": "platform-forms-service-primary-5ddfc4f9b8-kfpzv", + "namespace": "test", + "ownerReferences": [ + { + "apiVersion": "apps/v1", + "kind": "ReplicaSet", + "blockOwnerDeletion": true, + "controller": true, + "name": "platform-forms-service-primary-5ddfc4f9b8", + "uid": "d2e89c56-7623-439e-a9ee-4a67e2f3a81a" + }], + "resourceVersion": "37382150", + "selfLink": "/api/v1/namespaces/default/pods/platform-forms-service-primary-5ddfc4f9b8-kfpzv", + "uid": "df8cbac1-129c-4cd3-b5bc-65d72d8ba5f0" + }, + "spec": { + "containers": [ + { + "env": [ + { + "name": "APACHE_ENV", + "value": "tmpcrm" + }, + { + "name": "SPRING_PROFILES_ACTIVE", + "value": "tmpcrm" + }, + { + "name": "JAVA_OPTS", + "value": "-Dlogging.label=crm" + }], + "image": "docker.apache.xyz/platform-forms-service:0.15.0", + "imagePullPolicy": "Always", + "livenessProbe": { + "failureThreshold": 3, + "httpGet": { + "path": "/info", + "port": "http", + "scheme": "HTTP" + }, + "periodSeconds": 10, + "successThreshold": 1, + "timeoutSeconds": 1 + }, + "name": "platform-forms-service", + "ports": [ + { + "containerPort": 8080, + "name": "http", + "protocol": "TCP" + }], + "readinessProbe": { + "failureThreshold": 3, + "httpGet": { + "path": "/health", + "port": "http", + "scheme": "HTTP" + }, + "periodSeconds": 10, + "successThreshold": 1, + "timeoutSeconds": 1 + }, + "resources": { + }, + "securityContext": { + }, + "terminationMessagePath": "/dev/termination-log", + "terminationMessagePolicy": "File", + "volumeMounts": [ + { + "mountPath": "/var/run/secrets/kubernetes.io/serviceaccount", + "name": "default-token-2nqlw", + "readOnly": true + }] + }], + "dnsPolicy": "ClusterFirst", + "enableServiceLinks": true, + "nodeName": "k8s-tmpcrm-worker-s03-04", + "priority": 0, + "restartPolicy": "Always", + "schedulerName": "default-scheduler", + "securityContext": { + }, + "serviceAccount": "default", + "serviceAccountName": "default", + "terminationGracePeriodSeconds": 30, + "tolerations": [ + { + "effect": "NoExecute", + "key": "node.kubernetes.io/not-ready", + "operator": "Exists", + "tolerationSeconds": 300 + }, + { + "effect": "NoExecute", + "key": "node.kubernetes.io/unreachable", + "operator": "Exists", + "tolerationSeconds": 300 + }], + "volumes": [ + { + "name": "default-token-2nqlw", + "secret": { + "defaultMode": 420, + "secretName": "default-token-2nqlw" + } + }] + }, + "status": { + "conditions": [ + { + "lastTransitionTime": "2020-06-15T15:44:16Z", + "status": "True", + "type": "Initialized" + }, + { + "lastTransitionTime": "2020-06-15T15:44:46Z", + "status": "True", + "type": "Ready" + }, + { + "lastTransitionTime": "2020-06-15T15:44:46Z", + "status": "True", + "type": "ContainersReady" + }, + { + "lastTransitionTime": "2020-06-15T15:44:16Z", + "status": "True", + "type": "PodScheduled" + }], + "containerStatuses": [ + { + "containerID": "docker://2b7c2a93dfb48334aa549e29fdd38039ddd256eec43ba64c145fa4b75a1542f0", + "image": "docker.apache.xyz/platform-forms-service:0.15.0", + "imageID": + "docker-pullable://docker.apache.xyz/platform-forms-service@sha256:45fd19ccd99e218a7685c4cee5bc5b16aeae1cdb8e8773f9c066d4cfb22ee195", + "lastState": { + }, + "name": "platform-forms-service", + "ready": true, + "restartCount": 0, + "state": { + "running": { + "startedAt": "2020-06-15T15:44:21Z" + } + }, + "started": true + }], + "hostIP": "10.103.220.170", + "phase": "Running", + "podIP": "172.16.55.101", + "qosClass": "BestEffort", + "startTime": "2020-06-15T15:44:16Z", + "podIPs": [ + { + "ip": "172.16.55.101" + }] + } +} + diff --git a/log4j/src/test/resources/localPod.json b/log4j/src/test/resources/localPod.json new file mode 100644 index 00000000000..347a1fe9efb --- /dev/null +++ b/log4j/src/test/resources/localPod.json @@ -0,0 +1,141 @@ +{ + "apiVersion": "v1", + "kind": "Pod", + "metadata": { + "creationTimestamp": "2020-06-14T21:50:09Z", + "generateName": "sampleapp-584f99476d-", + "labels": { + "app": "sampleapp", + "pod-template-hash": "584f99476d" + }, + "name": "sampleapp-584f99476d-mnrp4", + "namespace": "test", + "ownerReferences": [ + { + "apiVersion": "apps/v1", + "kind": "ReplicaSet", + "blockOwnerDeletion": true, + "controller": true, + "name": "sampleapp-584f99476d", + "uid": "d68146d1-17c4-486e-aa8d-07d7d5d38b94" + }], + "resourceVersion": "1200430", + "selfLink": "/api/v1/namespaces/default/pods/sampleapp-584f99476d-mnrp4", + "uid": "9213879a-479c-42ce-856b-7e2666d21829" + }, + "spec": { + "containers": [ + { + "env": [ + { + "name": "JAVA_OPTS", + "value": "-Delastic.search.host=host.docker.internal" + }], + "image": "localhost:5000/sampleapp:latest", + "imagePullPolicy": "Always", + "name": "sampleapp", + "ports": [ + { + "containerPort": 8080, + "protocol": "TCP" + }, + { + "containerPort": 5005, + "protocol": "TCP" + }], + "resources": { + }, + "terminationMessagePath": "/dev/termination-log", + "terminationMessagePolicy": "File", + "volumeMounts": [ + { + "mountPath": "/var/run/secrets/kubernetes.io/serviceaccount", + "name": "default-token-jzq7d", + "readOnly": true + }] + }], + "dnsPolicy": "ClusterFirst", + "nodeName": "docker-desktop", + "priority": 0, + "restartPolicy": "Always", + "schedulerName": "default-scheduler", + "securityContext": { + }, + "serviceAccount": "default", + "serviceAccountName": "default", + "terminationGracePeriodSeconds": 30, + "tolerations": [ + { + "effect": "NoExecute", + "key": "node.kubernetes.io/not-ready", + "operator": "Exists", + "tolerationSeconds": 300 + }, + { + "effect": "NoExecute", + "key": "node.kubernetes.io/unreachable", + "operator": "Exists", + "tolerationSeconds": 300 + }], + "volumes": [ + { + "name": "default-token-jzq7d", + "secret": { + "defaultMode": 420, + "secretName": "default-token-jzq7d" + } + }], + "enableServiceLinks": true + }, + "status": { + "conditions": [ + { + "lastTransitionTime": "2020-06-14T21:50:09Z", + "status": "True", + "type": "Initialized" + }, + { + "lastTransitionTime": "2020-06-14T21:50:10Z", + "status": "True", + "type": "Ready" + }, + { + "lastTransitionTime": "2020-06-14T21:50:10Z", + "status": "True", + "type": "ContainersReady" + }, + { + "lastTransitionTime": "2020-06-14T21:50:09Z", + "status": "True", + "type": "PodScheduled" + }], + "containerStatuses": [ + { + "containerID": "docker://818b0098946c67e6ac56cb7c0934b7c2a9f50feb7244b422b2a7f566f7e5d0df", + "image": "sampleapp:latest", + "imageID": + "docker-pullable://localhost:5000/sampleapp@sha256:3cefb2db514db73c69854fee8abd072f27240519432d08aad177a57ee34b7d39", + "lastState": { + }, + "name": "sampleapp", + "ready": true, + "restartCount": 0, + "state": { + "running": { + "startedAt": "2020-06-14T21:50:10Z" + } + }, + "started": true + }], + "hostIP": "192.168.65.3", + "phase": "Running", + "podIP": "10.1.0.47", + "qosClass": "BestEffort", + "startTime": "2020-06-14T21:50:09Z", + "podIPs": [ + { + "ip": "10.1.0.47" + }] + } +} + diff --git a/log4j/src/test/resources/log4j2.kubernetes.client.properties b/log4j/src/test/resources/log4j2.kubernetes.client.properties new file mode 100644 index 00000000000..4ad7b914280 --- /dev/null +++ b/log4j/src/test/resources/log4j2.kubernetes.client.properties @@ -0,0 +1,42 @@ +# +# Copyright (C) 2015 Red Hat, Inc. +# +# 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. +# + +## +# Properties file to test Log4jConfig. +log4j2.kubernetes.client.apiVersion = apiVersion +log4j2.kubernetes.client.caCertFile = caCertFile +log4j2.kubernetes.client.caCertData = caCertData +log4j2.kubernetes.client.clientCertFile = clientCertFile +log4j2.kubernetes.client.clientCertData = clientCertData +log4j2.kubernetes.client.clientKeyFile = clientKeyFile +log4j2.kubernetes.client.clientKeyData = clientKeyData +log4j2.kubernetes.client.clientKeyAlgo = clientKeyAlgo +log4j2.kubernetes.client.clientKeyPassphrase = clientKeyPassphrase +log4j2.kubernetes.client.connectionTimeout = 123milli +log4j2.kubernetes.client.httpProxy = httpProxy +log4j2.kubernetes.client.httpsProxy = httpsProxy +log4j2.kubernetes.client.loggingInterval = 345milli +log4j2.kubernetes.client.masterUrl = https://masterUrl/ +log4j2.kubernetes.client.namespace = namespace +log4j2.kubernetes.client.noProxy = noProxy +log4j2.kubernetes.client.password = password +log4j2.kubernetes.client.proxyUsername = proxyUsername +log4j2.kubernetes.client.proxyPassword = proxyPassword +log4j2.kubernetes.client.requestTimeout = 678milli +log4j2.kubernetes.client.trustCerts = true +log4j2.kubernetes.client.username = username +log4j2.kubernetes.client.watchReconnectInterval = 1234milli +log4j2.kubernetes.client.watchReconnectLimit = 5678milli diff --git a/log4j/src/test/resources/spring.cloud.kubernetes.client.properties b/log4j/src/test/resources/spring.cloud.kubernetes.client.properties new file mode 100644 index 00000000000..716685923a3 --- /dev/null +++ b/log4j/src/test/resources/spring.cloud.kubernetes.client.properties @@ -0,0 +1,42 @@ +# +# Copyright (C) 2015 Red Hat, Inc. +# +# 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. +# + +## +# Properties file to test Log4jConfig. +spring.cloud.kubernetes.client.apiVersion = apiVersion +spring.cloud.kubernetes.client.caCertFile = caCertFile +spring.cloud.kubernetes.client.caCertData = caCertData +spring.cloud.kubernetes.client.clientCertFile = clientCertFile +spring.cloud.kubernetes.client.clientCertData = clientCertData +spring.cloud.kubernetes.client.clientKeyFile = clientKeyFile +spring.cloud.kubernetes.client.clientKeyData = clientKeyData +spring.cloud.kubernetes.client.clientKeyAlgo = clientKeyAlgo +spring.cloud.kubernetes.client.clientKeyPassphrase = clientKeyPassphrase +spring.cloud.kubernetes.client.connectionTimeout = 123milli +spring.cloud.kubernetes.client.httpProxy = httpProxy +spring.cloud.kubernetes.client.httpsProxy = httpsProxy +spring.cloud.kubernetes.client.loggingInterval = 345milli +spring.cloud.kubernetes.client.masterUrl = https://masterUrl/ +spring.cloud.kubernetes.client.namespace = namespace +spring.cloud.kubernetes.client.noProxy = noProxy +spring.cloud.kubernetes.client.password = password +spring.cloud.kubernetes.client.proxyUsername = proxyUsername +spring.cloud.kubernetes.client.proxyPassword = proxyPassword +spring.cloud.kubernetes.client.requestTimeout = 678milli +spring.cloud.kubernetes.client.trustCerts = true +spring.cloud.kubernetes.client.username = username +spring.cloud.kubernetes.client.watchReconnectInterval = 1234milli +spring.cloud.kubernetes.client.watchReconnectLimit = 5678milli diff --git a/pom.xml b/pom.xml index 13c64d64a8b..a0d3b6e600a 100644 --- a/pom.xml +++ b/pom.xml @@ -228,6 +228,7 @@ httpclient-okhttp httpclient-vertx kubernetes-client-deps-compatibility-tests + log4j @@ -253,6 +254,11 @@ crd-generator-api ${project.version} + + io.fabric8 + kubernetes-log4j + ${project.version} + io.fabric8 kubernetes-model-core