From b9427fcca91ea6f373a9faf0553df8ed7fd506e1 Mon Sep 17 00:00:00 2001 From: Ralph Goers Date: Mon, 28 Oct 2019 11:03:18 -0700 Subject: [PATCH 01/13] Add `kubernetes-log4j` module This module adds the ability to Log4j Core to use Kubernetes attributes in a configuration file. It is a cleaned-up version of the `org.apache.logging.log4j:log4j-kubernetes`. As explained in #5682, it does make more sense to host is here since: * it only depends on a very stable `StrLookup` dependency from `log4j-core`, * the number and kind of properties available through `kubernetes-client` depend on its version. --- CHANGELOG.md | 1 + log4j/pom.xml | 89 ++++ .../log4j/lookup/ClientBuilder.java | 69 ++++ .../log4j/lookup/ClientProperties.java | 180 +++++++++ .../log4j/lookup/ContainerUtil.java | 94 +++++ .../log4j/lookup/KubernetesLookup.java | 379 ++++++++++++++++++ .../kubernetes/log4j/lookup/package-info.java | 16 + .../log4j/lookup/KubernetesLookupTest.java | 92 +++++ log4j/src/test/resources/clusterPod.json | 177 ++++++++ log4j/src/test/resources/localPod.json | 141 +++++++ pom.xml | 1 + 11 files changed, 1239 insertions(+) create mode 100644 log4j/pom.xml create mode 100644 log4j/src/main/java/io/fabric8/kubernetes/log4j/lookup/ClientBuilder.java create mode 100644 log4j/src/main/java/io/fabric8/kubernetes/log4j/lookup/ClientProperties.java create mode 100644 log4j/src/main/java/io/fabric8/kubernetes/log4j/lookup/ContainerUtil.java create mode 100644 log4j/src/main/java/io/fabric8/kubernetes/log4j/lookup/KubernetesLookup.java create mode 100644 log4j/src/main/java/io/fabric8/kubernetes/log4j/lookup/package-info.java create mode 100644 log4j/src/test/java/io/fabric8/kubernetes/log4j/lookup/KubernetesLookupTest.java create mode 100644 log4j/src/test/resources/clusterPod.json create mode 100644 log4j/src/test/resources/localPod.json 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/log4j/pom.xml b/log4j/pom.xml new file mode 100644 index 00000000000..8bb81cabce4 --- /dev/null +++ b/log4j/pom.xml @@ -0,0 +1,89 @@ + + + + 4.0.0 + + io.fabric8 + kubernetes-client-project + 6.11-SNAPSHOT + + + kubernetes-log4j + jar + 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 + + + io.fabric8 + kubernetes-model-core + + + org.apache.logging.log4j + log4j-core + + + org.junit.jupiter + junit-jupiter-api + test + + + org.junit.jupiter + junit-jupiter-params + test + + + + + + + + org.apache.maven.plugins + maven-jar-plugin + + + default-jar + none + + + + + org.apache.felix + maven-bundle-plugin + + + generate-osgi-bundle + + bundle + + + + + + + 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..40b2d4259b5 --- /dev/null +++ b/log4j/src/main/java/io/fabric8/kubernetes/log4j/lookup/ClientBuilder.java @@ -0,0 +1,69 @@ +/** + * 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; + +/** + * Builds a Kubernetes Client. + */ +class ClientBuilder { + + public KubernetesClient createClient() { + final Config config = kubernetesClientConfig(); + return config != null ? new KubernetesClientBuilder() + .withConfig(config).build() : null; + } + + private Config kubernetesClientConfig() { + Config base = null; + try { + base = Config.autoConfigure(null); + } catch (Exception ex) { + if (ex instanceof NullPointerException) { + return null; + } + } + final ClientProperties props = new ClientProperties(base); + final Config properties = new ConfigBuilder(base) + .withApiVersion(props.getApiVersion()) + .withCaCertData(props.getCaCertData()) + .withCaCertFile(props.getCaCertFile()) + .withClientCertData(props.getClientCertData()) + .withClientCertFile(props.getClientCertFile()) + .withClientKeyAlgo(props.getClientKeyAlgo()) + .withClientKeyData(props.getClientKeyData()) + .withClientKeyFile(props.getClientKeyFile()) + .withClientKeyPassphrase(props.getClientKeyPassphrase()) + .withConnectionTimeout(props.getConnectionTimeout()) + .withHttpProxy(props.getHttpProxy()) + .withHttpsProxy(props.getHttpsProxy()) + .withMasterUrl(props.getMasterUrl()) + .withNamespace(props.getNamespace()) + .withNoProxy(props.getNoProxy()) + .withPassword(props.getPassword()) + .withProxyPassword(props.getProxyPassword()) + .withProxyUsername(props.getProxyUsername()) + .withRequestTimeout(props.getRequestTimeout()) + .withTrustCerts(props.isTrustCerts()) + .withUsername(props.getUsername()) + .build(); + return properties; + } +} diff --git a/log4j/src/main/java/io/fabric8/kubernetes/log4j/lookup/ClientProperties.java b/log4j/src/main/java/io/fabric8/kubernetes/log4j/lookup/ClientProperties.java new file mode 100644 index 00000000000..6204cd19d46 --- /dev/null +++ b/log4j/src/main/java/io/fabric8/kubernetes/log4j/lookup/ClientProperties.java @@ -0,0 +1,180 @@ +/** + * 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. + */ +class ClientProperties { + + private static final String[] PREFIXES = { "log4j2.kubernetes.client.", "spring.cloud.kubernetes.client." }; + 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 = PropertiesUtil.getProperties(); + private final Config base; + + public ClientProperties(final Config base) { + 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); + if (timeout != null) { + return (int) timeout.toMillis(); + } + return 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); + if (interval != null) { + return (int) interval.toMillis(); + } + return 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); + if (result != null) { + return result.replace("\\s", "").split(","); + } + return 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); + if (interval != null) { + return (int) interval.toMillis(); + } + return 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); + if (interval != null) { + return (int) interval.toMillis(); + } + return base.getWatchReconnectInterval(); + } + + public int getWatchReconnectLimit() { + final Duration interval = props.getDurationProperty(PREFIXES, WATCH_RECONNECT_LIMIT, null); + if (interval != null) { + return (int) interval.toMillis(); + } + return base.getWatchReconnectLimit(); + } +} 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..a17f876dc9a --- /dev/null +++ b/log4j/src/main/java/io/fabric8/kubernetes/log4j/lookup/ContainerUtil.java @@ -0,0 +1,94 @@ +/** + * 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.File; +import java.io.IOException; +import java.nio.file.Files; +import java.nio.file.Path; +import java.util.Objects; + +/** + * Locate the current docker container. + */ +final class ContainerUtil { + + private static final Logger LOGGER = StatusLogger.getLogger(); + private static final int MAXLENGTH = 65; + + /** + * 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. + * + * @see Stackoverflow for a discussion on retrieving the containerId. + * @see ControlGroup + * for the original version of this. Not much is actually left but it provided good inspiration. + * @return The container id. + */ + public static String getContainerId() { + try { + final File file = new File("/proc/self/cgroup"); + if (file.exists()) { + final Path path = file.toPath(); + final String id = Files.lines(path) + .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) { + // Every control group in Kubernetes will use + if (line.contains("/kubepods")) { + // Strip off everything up to the last slash. + int i = line.lastIndexOf('/'); + if (i < 0) { + return null; + } + // If the remainder has a period then take everything up to it. + line = line.substring(i + 1); + i = line.lastIndexOf('.'); + if (i > 0) { + line = line.substring(0, i); + } + // Everything ending with a '/' has already been stripped but the remainder might start with "docker-" + if (line.contains("docker-")) { + // 8:cpuset:/kubepods.slice/kubepods-pod9c26dfb6_b9c9_11e7_bfb9_02c6c1fc4861.slice/docker-3dd988081e7149463c043b5d9c57d7309e079c5e9290f91feba1cc45a04d6a5b.scope + i = line.lastIndexOf("docker-"); + line = line.substring(i + 7); + } + return line.length() <= MAXLENGTH ? line : line.substring(0, MAXLENGTH); + } + + return 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..679fd56efdd --- /dev/null +++ b/log4j/src/main/java/io/fabric8/kubernetes/log4j/lookup/KubernetesLookup.java @@ -0,0 +1,379 @@ +/** + * 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.Pod; +import io.fabric8.kubernetes.client.Config; +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 org.apache.logging.log4j.util.Strings; + +import java.net.URL; +import java.nio.file.Paths; +import java.util.List; +import java.util.Map; +import java.util.concurrent.locks.Lock; +import java.util.concurrent.locks.ReentrantLock; + +/** + * Retrieves various attributes from the Kubernetes server. + *

+ * Supported keys are: + *

+ * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + *
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 volatile KubernetesInfo kubernetesInfo; + private static final Lock initLock = new ReentrantLock(); + private static final boolean isSpringIncluded = 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; + + public KubernetesLookup() { + this.pod = null; + this.namespace = null; + this.masterUrl = null; + initialize(); + } + + KubernetesLookup(Pod pod, Namespace namespace, URL masterUrl) { + this.pod = pod; + this.namespace = namespace; + this.masterUrl = masterUrl; + initialize(); + } + + private boolean initialize() { + if (kubernetesInfo == null || (isSpringIncluded && !kubernetesInfo.isSpringActive)) { + initLock.lock(); + try { + final boolean isSpringActive = isSpringActive(); + if (kubernetesInfo == null || (!kubernetesInfo.isSpringActive && isSpringActive)) { + final KubernetesInfo info = new KubernetesInfo(); + KubernetesClient client = null; + info.isSpringActive = isSpringActive; + if (pod == null) { + client = new ClientBuilder().createClient(); + if (client != null) { + pod = getCurrentPod(client); + info.masterUrl = client.getMasterUrl(); + if (pod != null) { + info.namespace = pod.getMetadata().getNamespace(); + namespace = client.namespaces() + .withName(info.namespace) + .get(); + } + } else { + LOGGER.warn("Kubernetes is not available for access"); + } + } else { + info.masterUrl = masterUrl; + } + if (pod != null) { + if (namespace != null) { + info.namespaceId = namespace.getMetadata().getUid(); + info.namespaceAnnotations = namespace.getMetadata().getAnnotations(); + info.namespaceLabels = namespace.getMetadata().getLabels(); + } + info.hostName = pod.getSpec().getNodeName(); + info.annotations = pod.getMetadata().getAnnotations(); + info.accountName = pod.getSpec().getServiceAccountName(); + info.hostIp = pod.getStatus().getHostIP(); + info.labels = pod.getMetadata().getLabels(); + info.podId = pod.getMetadata().getUid(); + info.podIp = pod.getStatus().getPodIP(); + info.podName = pod.getMetadata().getName(); + ContainerStatus containerStatus = null; + final List statuses = pod.getStatus().getContainerStatuses(); + if (statuses.size() == 1) { + containerStatus = statuses.get(0); + } else if (statuses.size() > 1) { + final String containerId = ContainerUtil.getContainerId(); + if (containerId != null) { + containerStatus = statuses.stream() + .filter(cs -> cs.getContainerID().contains(containerId)) + .findFirst() + .orElse(null); + } + } + final String containerName; + if (containerStatus != null) { + info.containerId = containerStatus.getContainerID(); + info.imageId = containerStatus.getImageID(); + containerName = containerStatus.getName(); + } else { + containerName = null; + } + Container container = null; + final List containers = pod.getSpec().getContainers(); + if (containers.size() == 1) { + container = containers.get(0); + } else if (containers.size() > 1 && containerName != null) { + container = containers.stream() + .filter(c -> c.getName().equals(containerName)) + .findFirst() + .orElse(null); + } + if (container != null) { + info.containerName = container.getName(); + info.imageName = container.getImage(); + } + + kubernetesInfo = info; + } + } + } finally { + initLock.unlock(); + } + } + return kubernetesInfo != null; + } + + @Override + public String lookup(final LogEvent event, final String key) { + if (kubernetesInfo == null) { + return null; + } + if (key.startsWith(LABELS_PREFIX)) { + return kubernetesInfo.labels.get(key.substring(LABELS_PREFIX.length())); + } + switch (key) { + case ACCOUNT_NAME: { + return kubernetesInfo.accountName; + } + case ANNOTATIONS: { + return kubernetesInfo.annotations.toString(); + } + case CONTAINER_ID: { + return kubernetesInfo.containerId; + } + case CONTAINER_NAME: { + return kubernetesInfo.containerName; + } + case HOST: { + return kubernetesInfo.hostName; + } + case HOST_IP: { + return kubernetesInfo.hostIp; + } + case LABELS: { + return kubernetesInfo.labels.toString(); + } + case MASTER_URL: { + return kubernetesInfo.masterUrl.toString(); + } + case NAMESPACE_ANNOTATIONS: { + return kubernetesInfo.namespaceAnnotations.toString(); + } + case NAMESPACE_ID: { + return kubernetesInfo.namespaceId; + } + case NAMESPACE_LABELS: { + return kubernetesInfo.namespaceLabels.toString(); + } + case NAMESPACE_NAME: { + return kubernetesInfo.namespace; + } + case POD_ID: { + return kubernetesInfo.podId; + } + case POD_IP: { + return kubernetesInfo.podIp; + } + case POD_NAME: { + return kubernetesInfo.podName; + } + case IMAGE_ID: { + return kubernetesInfo.imageId; + } + case IMAGE_NAME: { + return kubernetesInfo.imageName; + } + default: + return null; + } + } + + /** + * For unit testing only. + */ + void clearInfo() { + kubernetesInfo = null; + } + + private Pod getCurrentPod(final KubernetesClient kubernetesClient) { + final String hostName = System.getenv(HOSTNAME); + try { + if (isServiceAccount() && Strings.isNotBlank(hostName)) { + return kubernetesClient.pods().withName(hostName).get(); + } + } catch (Throwable t) { + LOGGER.debug("Unable to locate pod with name {}.", hostName); + } + return null; + } + + private boolean isServiceAccount() { + return Paths.get(Config.KUBERNETES_SERVICE_ACCOUNT_TOKEN_PATH).toFile().exists() + && Paths.get(Config.KUBERNETES_SERVICE_ACCOUNT_CA_CRT_PATH) + .toFile() + .exists(); + } + + private boolean isSpringActive() { + return isSpringIncluded + && LogManager.getFactory() != null + && LogManager.getFactory().hasContext(KubernetesLookup.class.getName(), null, false) + && LogManager.getContext(false).getObject(SPRING_ENVIRONMENT_KEY) != null; + } + + 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/package-info.java b/log4j/src/main/java/io/fabric8/kubernetes/log4j/lookup/package-info.java new file mode 100644 index 00000000000..6d67ed4ce7d --- /dev/null +++ b/log4j/src/main/java/io/fabric8/kubernetes/log4j/lookup/package-info.java @@ -0,0 +1,16 @@ +/** + * 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; 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..92231df7e50 --- /dev/null +++ b/log4j/src/test/java/io/fabric8/kubernetes/log4j/lookup/KubernetesLookupTest.java @@ -0,0 +1,92 @@ +/** + * 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.Namespace; +import io.fabric8.kubernetes.api.model.ObjectMeta; +import io.fabric8.kubernetes.api.model.Pod; +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.util.HashMap; +import java.util.Map; +import java.util.UUID; +import java.util.stream.Stream; + +import static org.junit.jupiter.api.Assertions.assertEquals; + +/** + * Validate the Kubernetes Lookup. + */ +class KubernetesLookupTest { + + private static final ObjectMapper objectMapper = new ObjectMapper(); + + static Stream basicLookups() { + return Stream.of( + Arguments.of("/localPod.json", + "sampleapp", + "docker://818b0098946c67e6ac56cb7c0934b7c2a9f50feb7244b422b2a7f566f7e5d0df", + "docker-desktop", + "sampleapp-584f99476d-mnrp4"), + Arguments.of( + "/clusterPod.json", + "platform-forms-service", + "docker://2b7c2a93dfb48334aa549e29fdd38039ddd256eec43ba64c145fa4b75a1542f0", + "k8s-tmpcrm-worker-s03-04", + "platform-forms-service-primary-5ddfc4f9b8-kfpzv")); + } + + @ParameterizedTest + @MethodSource + void basicLookups(String jsonResource, + String containerName, + String containerId, + String hostName, + String podName) throws Exception { + final Pod pod = objectMapper.readValue(KubernetesLookupTest.class.getResource(jsonResource), Pod.class); + final Namespace namespace = createNamespace(); + final URL masterUrl = new URL("http://localhost:443/"); + final KubernetesLookup lookup = new KubernetesLookup(pod, namespace, masterUrl); + try { + assertEquals(containerName, lookup.lookup("containerName"), "Incorrect container name"); + assertEquals( + containerId, + lookup.lookup("containerId"), "Incorrect container id"); + assertEquals(hostName, lookup.lookup("host"), "Incorrect host name"); + assertEquals(podName, lookup.lookup("podName"), "Incorrect pod name"); + } finally { + lookup.clearInfo(); + } + } + + private Namespace createNamespace() { + final Namespace namespace = new Namespace(); + final ObjectMeta meta = new ObjectMeta(); + final Map annotations = new HashMap<>(); + annotations.put("test", "name"); + meta.setAnnotations(annotations); + final Map labels = new HashMap<>(); + labels.put("ns", "my-namespace"); + meta.setLabels(labels); + meta.setUid(UUID.randomUUID().toString()); + namespace.setMetadata(meta); + return namespace; + } +} diff --git a/log4j/src/test/resources/clusterPod.json b/log4j/src/test/resources/clusterPod.json new file mode 100644 index 00000000000..7bae9c35ebe --- /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": "default", + "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..3aeef467249 --- /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": "default", + "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/pom.xml b/pom.xml index 13c64d64a8b..f028e71239f 100644 --- a/pom.xml +++ b/pom.xml @@ -228,6 +228,7 @@ httpclient-okhttp httpclient-vertx kubernetes-client-deps-compatibility-tests + log4j From 0dd1541d254c2ba526f677cbc2f94ae53ec23cae Mon Sep 17 00:00:00 2001 From: "Piotr P. Karwasz" Date: Fri, 19 Jan 2024 17:08:48 +0100 Subject: [PATCH 02/13] Fix license header --- .../io/fabric8/kubernetes/log4j/lookup/package-info.java | 2 +- .../kubernetes/log4j/lookup/KubernetesLookupTest.java | 8 ++++---- 2 files changed, 5 insertions(+), 5 deletions(-) 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 index 6d67ed4ce7d..9028ee008a0 100644 --- 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 @@ -5,7 +5,7 @@ * 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 + * 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, 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 index 92231df7e50..7fcdfbbadfc 100644 --- a/log4j/src/test/java/io/fabric8/kubernetes/log4j/lookup/KubernetesLookupTest.java +++ b/log4j/src/test/java/io/fabric8/kubernetes/log4j/lookup/KubernetesLookupTest.java @@ -1,12 +1,12 @@ /** * 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 - *

+ * + * 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. From 10d1692e893f5252d77c975b950cf53fa530fc42 Mon Sep 17 00:00:00 2001 From: "Piotr P. Karwasz" Date: Sun, 18 Feb 2024 21:56:20 +0100 Subject: [PATCH 03/13] Add killswitch for Log4j properties Adds a `kubernetes.log4j.useProperties` Java system property to disable the usage of Log4j properties. Increases test coverage. --- log4j/pom.xml | 10 ++ .../log4j/lookup/ClientBuilder.java | 105 +++++++++------- .../log4j/lookup/ContainerUtil.java | 3 + .../log4j/lookup/KubernetesLookup.java | 30 ++--- ...ClientProperties.java => Log4jConfig.java} | 60 ++++----- .../kubernetes/log4j/lookup/package-info.java | 2 +- .../log4j/lookup/ClientBuilderTest.java | 58 +++++++++ .../log4j/lookup/KubernetesLookupTest.java | 116 +++++++++++------- .../log4j/lookup/Log4jConfigTest.java | 51 ++++++++ log4j/src/test/resources/clusterPod.json | 2 +- log4j/src/test/resources/localPod.json | 2 +- .../log4j2.kubernetes.client.properties | 26 ++++ .../spring.cloud.kubernetes.client.properties | 26 ++++ 13 files changed, 340 insertions(+), 151 deletions(-) rename log4j/src/main/java/io/fabric8/kubernetes/log4j/lookup/{ClientProperties.java => Log4jConfig.java} (80%) create mode 100644 log4j/src/test/java/io/fabric8/kubernetes/log4j/lookup/ClientBuilderTest.java create mode 100644 log4j/src/test/java/io/fabric8/kubernetes/log4j/lookup/Log4jConfigTest.java create mode 100644 log4j/src/test/resources/log4j2.kubernetes.client.properties create mode 100644 log4j/src/test/resources/spring.cloud.kubernetes.client.properties diff --git a/log4j/pom.xml b/log4j/pom.xml index 8bb81cabce4..34339721ef1 100644 --- a/log4j/pom.xml +++ b/log4j/pom.xml @@ -47,6 +47,11 @@ org.apache.logging.log4j log4j-core + + org.assertj + assertj-core + test + org.junit.jupiter junit-jupiter-api @@ -57,6 +62,11 @@ junit-jupiter-params 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 index 40b2d4259b5..9647572a636 100644 --- a/log4j/src/main/java/io/fabric8/kubernetes/log4j/lookup/ClientBuilder.java +++ b/log4j/src/main/java/io/fabric8/kubernetes/log4j/lookup/ClientBuilder.java @@ -1,17 +1,14 @@ /** * 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. + *

+ * 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; @@ -19,51 +16,67 @@ 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. */ -class ClientBuilder { +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 KubernetesClient createClient() { - final Config config = kubernetesClientConfig(); + public static KubernetesClient createClient() { + final Config config = kubernetesClientConfig(PropertiesUtil.getProperties()); return config != null ? new KubernetesClientBuilder() .withConfig(config).build() : null; } - private Config kubernetesClientConfig() { - Config base = null; + static Config kubernetesClientConfig(final PropertiesUtil props) { try { - base = Config.autoConfigure(null); - } catch (Exception ex) { - if (ex instanceof NullPointerException) { - return null; + 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 Throwable t) { + StatusLogger.getLogger().warn("An error occurred while retrieving Kubernetes Client configuration: {}.", + t.getMessage(), t); } - final ClientProperties props = new ClientProperties(base); - final Config properties = new ConfigBuilder(base) - .withApiVersion(props.getApiVersion()) - .withCaCertData(props.getCaCertData()) - .withCaCertFile(props.getCaCertFile()) - .withClientCertData(props.getClientCertData()) - .withClientCertFile(props.getClientCertFile()) - .withClientKeyAlgo(props.getClientKeyAlgo()) - .withClientKeyData(props.getClientKeyData()) - .withClientKeyFile(props.getClientKeyFile()) - .withClientKeyPassphrase(props.getClientKeyPassphrase()) - .withConnectionTimeout(props.getConnectionTimeout()) - .withHttpProxy(props.getHttpProxy()) - .withHttpsProxy(props.getHttpsProxy()) - .withMasterUrl(props.getMasterUrl()) - .withNamespace(props.getNamespace()) - .withNoProxy(props.getNoProxy()) - .withPassword(props.getPassword()) - .withProxyPassword(props.getProxyPassword()) - .withProxyUsername(props.getProxyUsername()) - .withRequestTimeout(props.getRequestTimeout()) - .withTrustCerts(props.isTrustCerts()) - .withUsername(props.getUsername()) - .build(); - return properties; + 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 index a17f876dc9a..3fd9db149a5 100644 --- a/log4j/src/main/java/io/fabric8/kubernetes/log4j/lookup/ContainerUtil.java +++ b/log4j/src/main/java/io/fabric8/kubernetes/log4j/lookup/ContainerUtil.java @@ -32,6 +32,9 @@ final class ContainerUtil { private static final Logger LOGGER = StatusLogger.getLogger(); private static final int MAXLENGTH = 65; + private ContainerUtil() { + } + /** * Returns the container id when running in a Docker container. * 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 index 679fd56efdd..e49c17a3bc0 100644 --- a/log4j/src/main/java/io/fabric8/kubernetes/log4j/lookup/KubernetesLookup.java +++ b/log4j/src/main/java/io/fabric8/kubernetes/log4j/lookup/KubernetesLookup.java @@ -1,17 +1,14 @@ /** * 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. + *

+ * 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; @@ -172,7 +169,7 @@ public KubernetesLookup() { initialize(); } - private boolean initialize() { + private void initialize() { if (kubernetesInfo == null || (isSpringIncluded && !kubernetesInfo.isSpringActive)) { initLock.lock(); try { @@ -182,14 +179,13 @@ private boolean initialize() { KubernetesClient client = null; info.isSpringActive = isSpringActive; if (pod == null) { - client = new ClientBuilder().createClient(); + client = ClientBuilder.createClient(); if (client != null) { pod = getCurrentPod(client); info.masterUrl = client.getMasterUrl(); if (pod != null) { - info.namespace = pod.getMetadata().getNamespace(); namespace = client.namespaces() - .withName(info.namespace) + .withName(pod.getMetadata().getNamespace()) .get(); } } else { @@ -199,6 +195,7 @@ private boolean initialize() { info.masterUrl = masterUrl; } if (pod != null) { + info.namespace = pod.getMetadata().getNamespace(); if (namespace != null) { info.namespaceId = namespace.getMetadata().getUid(); info.namespaceAnnotations = namespace.getMetadata().getAnnotations(); @@ -255,7 +252,6 @@ private boolean initialize() { initLock.unlock(); } } - return kubernetesInfo != null; } @Override diff --git a/log4j/src/main/java/io/fabric8/kubernetes/log4j/lookup/ClientProperties.java b/log4j/src/main/java/io/fabric8/kubernetes/log4j/lookup/Log4jConfig.java similarity index 80% rename from log4j/src/main/java/io/fabric8/kubernetes/log4j/lookup/ClientProperties.java rename to log4j/src/main/java/io/fabric8/kubernetes/log4j/lookup/Log4jConfig.java index 6204cd19d46..c408b941c83 100644 --- a/log4j/src/main/java/io/fabric8/kubernetes/log4j/lookup/ClientProperties.java +++ b/log4j/src/main/java/io/fabric8/kubernetes/log4j/lookup/Log4jConfig.java @@ -1,17 +1,14 @@ /** * 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. + *

+ * 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; @@ -23,9 +20,11 @@ /** * Obtains properties used to configure the Kubernetes client. */ -class ClientProperties { +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"; @@ -51,10 +50,11 @@ class ClientProperties { private static final String WATCH_RECONNECT_INTERVAL = "watchReconnectInterval"; private static final String WATCH_RECONNECT_LIMIT = "watchReconnectLimit"; - private final PropertiesUtil props = PropertiesUtil.getProperties(); + private final PropertiesUtil props; private final Config base; - public ClientProperties(final Config base) { + public Log4jConfig(final PropertiesUtil props, final Config base) { + this.props = props; this.base = base; } @@ -96,10 +96,7 @@ public String getClientKeyPassphrase() { public int getConnectionTimeout() { final Duration timeout = props.getDurationProperty(PREFIXES, CONNECTION_TIMEOUT, null); - if (timeout != null) { - return (int) timeout.toMillis(); - } - return base.getConnectionTimeout(); + return timeout != null ? (int) timeout.toMillis() : base.getConnectionTimeout(); } public String getHttpProxy() { @@ -112,10 +109,7 @@ public String getHttpsProxy() { public int getLoggingInterval() { final Duration interval = props.getDurationProperty(PREFIXES, LOGGING_INTERVAL, null); - if (interval != null) { - return (int) interval.toMillis(); - } - return base.getLoggingInterval(); + return interval != null ? (int) interval.toMillis() : base.getLoggingInterval(); } public String getMasterUrl() { @@ -128,10 +122,7 @@ public String getNamespace() { public String[] getNoProxy() { final String result = props.getStringProperty(PREFIXES, NO_PROXY, null); - if (result != null) { - return result.replace("\\s", "").split(","); - } - return base.getNoProxy(); + return result != null ? result.replace("\\s", "").split(",") : base.getNoProxy(); } public String getPassword() { @@ -148,10 +139,7 @@ public String getProxyPassword() { public int getRequestTimeout() { final Duration interval = props.getDurationProperty(PREFIXES, REQUEST_TIMEOUT, null); - if (interval != null) { - return (int) interval.toMillis(); - } - return base.getRequestTimeout(); + return interval != null ? (int) interval.toMillis() : base.getRequestTimeout(); } public Boolean isTrustCerts() { @@ -164,17 +152,11 @@ public String getUsername() { public int getWatchReconnectInterval() { final Duration interval = props.getDurationProperty(PREFIXES, WATCH_RECONNECT_INTERVAL, null); - if (interval != null) { - return (int) interval.toMillis(); - } - return base.getWatchReconnectInterval(); + return interval != null ? (int) interval.toMillis() : base.getWatchReconnectInterval(); } public int getWatchReconnectLimit() { final Duration interval = props.getDurationProperty(PREFIXES, WATCH_RECONNECT_LIMIT, null); - if (interval != null) { - return (int) interval.toMillis(); - } - return base.getWatchReconnectLimit(); + 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 index 9028ee008a0..6d67ed4ce7d 100644 --- 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 @@ -5,7 +5,7 @@ * 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 + * 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, 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..c9d43cb709b --- /dev/null +++ b/log4j/src/test/java/io/fabric8/kubernetes/log4j/lookup/ClientBuilderTest.java @@ -0,0 +1,58 @@ +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()).isEqualTo(true); + 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/KubernetesLookupTest.java b/log4j/src/test/java/io/fabric8/kubernetes/log4j/lookup/KubernetesLookupTest.java index 7fcdfbbadfc..88faccc622d 100644 --- a/log4j/src/test/java/io/fabric8/kubernetes/log4j/lookup/KubernetesLookupTest.java +++ b/log4j/src/test/java/io/fabric8/kubernetes/log4j/lookup/KubernetesLookupTest.java @@ -1,17 +1,14 @@ /** * 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. + *

+ * 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; @@ -19,17 +16,14 @@ import io.fabric8.kubernetes.api.model.Namespace; import io.fabric8.kubernetes.api.model.ObjectMeta; import io.fabric8.kubernetes.api.model.Pod; -import org.junit.jupiter.params.ParameterizedTest; -import org.junit.jupiter.params.provider.Arguments; -import org.junit.jupiter.params.provider.MethodSource; +import org.apache.logging.log4j.core.lookup.StrLookup; +import org.junit.jupiter.api.Test; import java.net.URL; import java.util.HashMap; import java.util.Map; -import java.util.UUID; -import java.util.stream.Stream; -import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.assertj.core.api.Assertions.assertThat; /** * Validate the Kubernetes Lookup. @@ -38,44 +32,74 @@ class KubernetesLookupTest { private static final ObjectMapper objectMapper = new ObjectMapper(); - static Stream basicLookups() { - return Stream.of( - Arguments.of("/localPod.json", - "sampleapp", - "docker://818b0098946c67e6ac56cb7c0934b7c2a9f50feb7244b422b2a7f566f7e5d0df", - "docker-desktop", - "sampleapp-584f99476d-mnrp4"), - Arguments.of( - "/clusterPod.json", - "platform-forms-service", - "docker://2b7c2a93dfb48334aa549e29fdd38039ddd256eec43ba64c145fa4b75a1542f0", - "k8s-tmpcrm-worker-s03-04", - "platform-forms-service-primary-5ddfc4f9b8-kfpzv")); + @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 KubernetesLookup lookup = new KubernetesLookup(pod, namespace, masterUrl); + try { + 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"); + assertNamespaceLookups(lookup); + } finally { + lookup.clearInfo(); + } } - @ParameterizedTest - @MethodSource - void basicLookups(String jsonResource, - String containerName, - String containerId, - String hostName, - String podName) throws Exception { - final Pod pod = objectMapper.readValue(KubernetesLookupTest.class.getResource(jsonResource), Pod.class); + @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 KubernetesLookup lookup = new KubernetesLookup(pod, namespace, masterUrl); try { - assertEquals(containerName, lookup.lookup("containerName"), "Incorrect container name"); - assertEquals( - containerId, - lookup.lookup("containerId"), "Incorrect container id"); - assertEquals(hostName, lookup.lookup("host"), "Incorrect host name"); - assertEquals(podName, lookup.lookup("podName"), "Incorrect pod name"); + 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"); + assertNamespaceLookups(lookup); } finally { lookup.clearInfo(); } } + private void assertNamespaceLookups(final StrLookup lookup) { + assertThat(lookup.lookup("namespaceAnnotations")).isEqualTo("{test=name}"); + assertThat(lookup.lookup("namespaceId")).isEqualTo("878f1143-df6d-47eb-81e4-d576597fca24"); + assertThat(lookup.lookup("namespaceLabels")).isEqualTo("{ns=my-namespace}"); + assertThat(lookup.lookup("namespaceName")).isEqualTo("my-namespace"); + } + private Namespace createNamespace() { final Namespace namespace = new Namespace(); final ObjectMeta meta = new ObjectMeta(); @@ -85,7 +109,7 @@ private Namespace createNamespace() { final Map labels = new HashMap<>(); labels.put("ns", "my-namespace"); meta.setLabels(labels); - meta.setUid(UUID.randomUUID().toString()); + meta.setUid("878f1143-df6d-47eb-81e4-d576597fca24"); namespace.setMetadata(meta); return namespace; } 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..1366dbbbe08 --- /dev/null +++ b/log4j/src/test/java/io/fabric8/kubernetes/log4j/lookup/Log4jConfigTest.java @@ -0,0 +1,51 @@ +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()).isEqualTo(true); + assertThat(config.getUsername()).isEqualTo("username"); + assertThat(config.getWatchReconnectInterval()).isEqualTo(1234); + assertThat(config.getWatchReconnectLimit()).isEqualTo(5678); + } +} diff --git a/log4j/src/test/resources/clusterPod.json b/log4j/src/test/resources/clusterPod.json index 7bae9c35ebe..638e807c9a4 100644 --- a/log4j/src/test/resources/clusterPod.json +++ b/log4j/src/test/resources/clusterPod.json @@ -16,7 +16,7 @@ "pod-template-hash": "5ddfc4f9b8" }, "name": "platform-forms-service-primary-5ddfc4f9b8-kfpzv", - "namespace": "default", + "namespace": "my-namespace", "ownerReferences": [ { "apiVersion": "apps/v1", diff --git a/log4j/src/test/resources/localPod.json b/log4j/src/test/resources/localPod.json index 3aeef467249..93cede5a11a 100644 --- a/log4j/src/test/resources/localPod.json +++ b/log4j/src/test/resources/localPod.json @@ -9,7 +9,7 @@ "pod-template-hash": "584f99476d" }, "name": "sampleapp-584f99476d-mnrp4", - "namespace": "default", + "namespace": "my-namespace", "ownerReferences": [ { "apiVersion": "apps/v1", 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..92499e6bbdc --- /dev/null +++ b/log4j/src/test/resources/log4j2.kubernetes.client.properties @@ -0,0 +1,26 @@ +## +# 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..e10f19d1733 --- /dev/null +++ b/log4j/src/test/resources/spring.cloud.kubernetes.client.properties @@ -0,0 +1,26 @@ +## +# 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 From 656da367600b9bbecd8d2ce5a22ad8caf31954c0 Mon Sep 17 00:00:00 2001 From: "Piotr P. Karwasz" Date: Sun, 18 Feb 2024 22:04:17 +0100 Subject: [PATCH 04/13] Fix dependencies and packaging --- log4j/pom.xml | 37 +++++-------------------------------- pom.xml | 5 +++++ 2 files changed, 10 insertions(+), 32 deletions(-) diff --git a/log4j/pom.xml b/log4j/pom.xml index 34339721ef1..d98b05499ba 100644 --- a/log4j/pom.xml +++ b/log4j/pom.xml @@ -25,13 +25,16 @@ kubernetes-log4j - jar + bundle Fabric8 :: Kubernetes :: Log4j Core components Provides a lookup to use Kubernetes attributes in a Log4j Core configuration. io.fabric8.kubernetes.log4j.* * + + + 2.22.1 @@ -39,13 +42,10 @@ io.fabric8 kubernetes-client - - io.fabric8 - kubernetes-model-core - org.apache.logging.log4j log4j-core + ${log4j.version} org.assertj @@ -69,31 +69,4 @@ - - - - - org.apache.maven.plugins - maven-jar-plugin - - - default-jar - none - - - - - org.apache.felix - maven-bundle-plugin - - - generate-osgi-bundle - - bundle - - - - - - diff --git a/pom.xml b/pom.xml index f028e71239f..a0d3b6e600a 100644 --- a/pom.xml +++ b/pom.xml @@ -254,6 +254,11 @@ crd-generator-api ${project.version} + + io.fabric8 + kubernetes-log4j + ${project.version} + io.fabric8 kubernetes-model-core From bc80d3fee618c4bb5559549ad14217d6e90c4022 Mon Sep 17 00:00:00 2001 From: "Piotr P. Karwasz" Date: Sun, 18 Feb 2024 22:17:30 +0100 Subject: [PATCH 05/13] Use `NamespaceBuilder` in test --- .../log4j/lookup/KubernetesLookupTest.java | 21 +++++++------------ 1 file changed, 7 insertions(+), 14 deletions(-) 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 index 88faccc622d..192e063accb 100644 --- a/log4j/src/test/java/io/fabric8/kubernetes/log4j/lookup/KubernetesLookupTest.java +++ b/log4j/src/test/java/io/fabric8/kubernetes/log4j/lookup/KubernetesLookupTest.java @@ -14,14 +14,12 @@ import com.fasterxml.jackson.databind.ObjectMapper; import io.fabric8.kubernetes.api.model.Namespace; -import io.fabric8.kubernetes.api.model.ObjectMeta; +import io.fabric8.kubernetes.api.model.NamespaceBuilder; import io.fabric8.kubernetes.api.model.Pod; import org.apache.logging.log4j.core.lookup.StrLookup; import org.junit.jupiter.api.Test; import java.net.URL; -import java.util.HashMap; -import java.util.Map; import static org.assertj.core.api.Assertions.assertThat; @@ -101,16 +99,11 @@ private void assertNamespaceLookups(final StrLookup lookup) { } private Namespace createNamespace() { - final Namespace namespace = new Namespace(); - final ObjectMeta meta = new ObjectMeta(); - final Map annotations = new HashMap<>(); - annotations.put("test", "name"); - meta.setAnnotations(annotations); - final Map labels = new HashMap<>(); - labels.put("ns", "my-namespace"); - meta.setLabels(labels); - meta.setUid("878f1143-df6d-47eb-81e4-d576597fca24"); - namespace.setMetadata(meta); - return namespace; + return new NamespaceBuilder().editMetadata() + .addToAnnotations("test", "name") + .addToLabels("ns", "my-namespace") + .withUid("878f1143-df6d-47eb-81e4-d576597fca24") + .endMetadata() + .build(); } } From f3234718645eb61f4d1a96ae279ecb7469e8ff7d Mon Sep 17 00:00:00 2001 From: "Piotr P. Karwasz" Date: Mon, 19 Feb 2024 00:26:44 +0100 Subject: [PATCH 06/13] Add tests with mock client --- log4j/pom.xml | 5 + .../log4j/lookup/KubernetesLookup.java | 153 +++++++++++------- .../log4j/lookup/KubernetesLookupTest.java | 139 ++++++++++------ log4j/src/test/resources/clusterPod.json | 2 +- log4j/src/test/resources/localPod.json | 2 +- 5 files changed, 185 insertions(+), 116 deletions(-) diff --git a/log4j/pom.xml b/log4j/pom.xml index d98b05499ba..74bd30e07cc 100644 --- a/log4j/pom.xml +++ b/log4j/pom.xml @@ -62,6 +62,11 @@ junit-jupiter-params test + + io.fabric8 + kubernetes-server-mock + test + org.mockito mockito-core 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 index e49c17a3bc0..0345eb02ea4 100644 --- a/log4j/src/main/java/io/fabric8/kubernetes/log4j/lookup/KubernetesLookup.java +++ b/log4j/src/main/java/io/fabric8/kubernetes/log4j/lookup/KubernetesLookup.java @@ -15,8 +15,10 @@ 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.client.Config; +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; @@ -26,10 +28,10 @@ import org.apache.logging.log4j.core.lookup.StrLookup; import org.apache.logging.log4j.status.StatusLogger; import org.apache.logging.log4j.util.LoaderUtil; -import org.apache.logging.log4j.util.Strings; +import java.net.InetAddress; import java.net.URL; -import java.nio.file.Paths; +import java.net.UnknownHostException; import java.util.List; import java.util.Map; import java.util.concurrent.locks.Lock; @@ -126,7 +128,9 @@ public class KubernetesLookup extends AbstractLookup { private static final String HOSTNAME = "HOSTNAME"; private static final String SPRING_ENVIRONMENT_KEY = "SpringEnvironment"; - /** Supported keys */ + /** + * Supported keys + */ private static final String ACCOUNT_NAME = "accountName"; private static final String ANNOTATIONS = "annotations"; private static final String CONTAINER_ID = "containerId"; @@ -179,7 +183,7 @@ private void initialize() { KubernetesClient client = null; info.isSpringActive = isSpringActive; if (pod == null) { - client = ClientBuilder.createClient(); + client = createClient(); if (client != null) { pod = getCurrentPod(client); info.masterUrl = client.getMasterUrl(); @@ -194,55 +198,72 @@ private void initialize() { } else { info.masterUrl = masterUrl; } - if (pod != null) { - info.namespace = pod.getMetadata().getNamespace(); - if (namespace != null) { - info.namespaceId = namespace.getMetadata().getUid(); - info.namespaceAnnotations = namespace.getMetadata().getAnnotations(); - info.namespaceLabels = namespace.getMetadata().getLabels(); + if (namespace != null) { + final ObjectMeta namespaceMetadata = namespace.getMetadata(); + if (namespaceMetadata != null) { + info.namespaceAnnotations = namespaceMetadata.getAnnotations(); + info.namespaceId = namespaceMetadata.getUid(); + info.namespaceLabels = namespaceMetadata.getLabels(); } - info.hostName = pod.getSpec().getNodeName(); - info.annotations = pod.getMetadata().getAnnotations(); - info.accountName = pod.getSpec().getServiceAccountName(); - info.hostIp = pod.getStatus().getHostIP(); - info.labels = pod.getMetadata().getLabels(); - info.podId = pod.getMetadata().getUid(); - info.podIp = pod.getStatus().getPodIP(); - info.podName = pod.getMetadata().getName(); - ContainerStatus containerStatus = null; - final List statuses = pod.getStatus().getContainerStatuses(); - if (statuses.size() == 1) { - containerStatus = statuses.get(0); - } else if (statuses.size() > 1) { - final String containerId = ContainerUtil.getContainerId(); - if (containerId != null) { - containerStatus = statuses.stream() - .filter(cs -> cs.getContainerID().contains(containerId)) - .findFirst() - .orElse(null); - } + } + if (pod != null) { + final ObjectMeta podMetadata = pod.getMetadata(); + if (podMetadata != null) { + info.annotations = podMetadata.getAnnotations(); + info.labels = podMetadata.getLabels(); + info.namespace = podMetadata.getNamespace(); + info.podId = podMetadata.getUid(); + info.podName = podMetadata.getName(); } + final String containerName; - if (containerStatus != null) { - info.containerId = containerStatus.getContainerID(); - info.imageId = containerStatus.getImageID(); - containerName = containerStatus.getName(); + final PodStatus podStatus = pod.getStatus(); + if (podStatus != null) { + info.hostIp = podStatus.getHostIP(); + info.podIp = podStatus.getPodIP(); + + ContainerStatus containerStatus = null; + final List statuses = podStatus.getContainerStatuses(); + if (statuses.size() == 1) { + containerStatus = statuses.get(0); + } else if (statuses.size() > 1) { + final String containerId = ContainerUtil.getContainerId(); + if (containerId != null) { + containerStatus = statuses.stream() + .filter(cs -> cs.getContainerID().contains(containerId)) + .findFirst() + .orElse(null); + } + } + if (containerStatus != null) { + info.containerId = containerStatus.getContainerID(); + info.imageId = containerStatus.getImageID(); + containerName = containerStatus.getName(); + } else { + containerName = null; + } } else { containerName = null; } - Container container = null; - final List containers = pod.getSpec().getContainers(); - if (containers.size() == 1) { - container = containers.get(0); - } else if (containers.size() > 1 && containerName != null) { - container = containers.stream() - .filter(c -> c.getName().equals(containerName)) - .findFirst() - .orElse(null); - } - if (container != null) { - info.containerName = container.getName(); - info.imageName = container.getImage(); + + final PodSpec podSpec = pod.getSpec(); + if (podSpec != null) { + info.hostName = podSpec.getNodeName(); + info.accountName = podSpec.getServiceAccountName(); + Container container = null; + final List containers = podSpec.getContainers(); + if (containers.size() == 1) { + container = containers.get(0); + } else if (containers.size() > 1 && containerName != null) { + container = containers.stream() + .filter(c -> c.getName().equals(containerName)) + .findFirst() + .orElse(null); + } + if (container != null) { + info.containerName = container.getName(); + info.imageName = container.getImage(); + } } kubernetesInfo = info; @@ -254,20 +275,25 @@ private void initialize() { } } + // Used for testing + protected KubernetesClient createClient() { + return ClientBuilder.createClient(); + } + @Override public String lookup(final LogEvent event, final String key) { if (kubernetesInfo == null) { return null; } if (key.startsWith(LABELS_PREFIX)) { - return kubernetesInfo.labels.get(key.substring(LABELS_PREFIX.length())); + return kubernetesInfo.labels != null ? kubernetesInfo.labels.get(key.substring(LABELS_PREFIX.length())) : null; } switch (key) { case ACCOUNT_NAME: { return kubernetesInfo.accountName; } case ANNOTATIONS: { - return kubernetesInfo.annotations.toString(); + return kubernetesInfo.annotations != null ? kubernetesInfo.annotations.toString() : null; } case CONTAINER_ID: { return kubernetesInfo.containerId; @@ -282,19 +308,19 @@ public String lookup(final LogEvent event, final String key) { return kubernetesInfo.hostIp; } case LABELS: { - return kubernetesInfo.labels.toString(); + return kubernetesInfo.labels != null ? kubernetesInfo.labels.toString() : null; } case MASTER_URL: { - return kubernetesInfo.masterUrl.toString(); + return kubernetesInfo.masterUrl != null ? kubernetesInfo.masterUrl.toString() : null; } case NAMESPACE_ANNOTATIONS: { - return kubernetesInfo.namespaceAnnotations.toString(); + return kubernetesInfo.namespaceAnnotations != null ? kubernetesInfo.namespaceAnnotations.toString() : null; } case NAMESPACE_ID: { return kubernetesInfo.namespaceId; } case NAMESPACE_LABELS: { - return kubernetesInfo.namespaceLabels.toString(); + return kubernetesInfo.namespaceLabels != null ? kubernetesInfo.namespaceLabels.toString() : null; } case NAMESPACE_NAME: { return kubernetesInfo.namespace; @@ -322,14 +348,14 @@ public String lookup(final LogEvent event, final String key) { /** * For unit testing only. */ - void clearInfo() { + static void clearInfo() { kubernetesInfo = null; } private Pod getCurrentPod(final KubernetesClient kubernetesClient) { - final String hostName = System.getenv(HOSTNAME); + final String hostName = getHostName(); try { - if (isServiceAccount() && Strings.isNotBlank(hostName)) { + if (hostName != null && !hostName.isEmpty()) { return kubernetesClient.pods().withName(hostName).get(); } } catch (Throwable t) { @@ -338,11 +364,14 @@ private Pod getCurrentPod(final KubernetesClient kubernetesClient) { return null; } - private boolean isServiceAccount() { - return Paths.get(Config.KUBERNETES_SERVICE_ACCOUNT_TOKEN_PATH).toFile().exists() - && Paths.get(Config.KUBERNETES_SERVICE_ACCOUNT_CA_CRT_PATH) - .toFile() - .exists(); + 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 boolean isSpringActive() { 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 index 192e063accb..db97e4118e7 100644 --- a/log4j/src/test/java/io/fabric8/kubernetes/log4j/lookup/KubernetesLookupTest.java +++ b/log4j/src/test/java/io/fabric8/kubernetes/log4j/lookup/KubernetesLookupTest.java @@ -16,7 +16,11 @@ 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 java.net.URL; @@ -26,38 +30,43 @@ /** * Validate the Kubernetes Lookup. */ +@EnableKubernetesMockClient(crud = true) class KubernetesLookupTest { + private static KubernetesClient mockClient; + private static final ObjectMapper objectMapper = new ObjectMapper(); + @AfterEach + void cleanUp() { + KubernetesLookup.clearInfo(); + } + @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 KubernetesLookup lookup = new KubernetesLookup(pod, namespace, masterUrl); - try { - 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"); - assertNamespaceLookups(lookup); - } finally { - lookup.clearInfo(); - } + 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 @@ -65,44 +74,70 @@ 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 KubernetesLookup lookup = new KubernetesLookup(pod, namespace, masterUrl); - try { - 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"); - assertNamespaceLookups(lookup); - } finally { - lookup.clearInfo(); - } + 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()); } - private void assertNamespaceLookups(final StrLookup lookup) { + @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(); + assertThat(lookup.lookup("accountName")).isNull(); + } + + private void assertNamespaceLookups(final StrLookup lookup, final String uid) { assertThat(lookup.lookup("namespaceAnnotations")).isEqualTo("{test=name}"); - assertThat(lookup.lookup("namespaceId")).isEqualTo("878f1143-df6d-47eb-81e4-d576597fca24"); assertThat(lookup.lookup("namespaceLabels")).isEqualTo("{ns=my-namespace}"); - assertThat(lookup.lookup("namespaceName")).isEqualTo("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().editMetadata() + return new NamespaceBuilder().withNewMetadata() + .withName("test") .addToAnnotations("test", "name") .addToLabels("ns", "my-namespace") - .withUid("878f1143-df6d-47eb-81e4-d576597fca24") .endMetadata() .build(); } diff --git a/log4j/src/test/resources/clusterPod.json b/log4j/src/test/resources/clusterPod.json index 638e807c9a4..709da6d36e1 100644 --- a/log4j/src/test/resources/clusterPod.json +++ b/log4j/src/test/resources/clusterPod.json @@ -16,7 +16,7 @@ "pod-template-hash": "5ddfc4f9b8" }, "name": "platform-forms-service-primary-5ddfc4f9b8-kfpzv", - "namespace": "my-namespace", + "namespace": "test", "ownerReferences": [ { "apiVersion": "apps/v1", diff --git a/log4j/src/test/resources/localPod.json b/log4j/src/test/resources/localPod.json index 93cede5a11a..347a1fe9efb 100644 --- a/log4j/src/test/resources/localPod.json +++ b/log4j/src/test/resources/localPod.json @@ -9,7 +9,7 @@ "pod-template-hash": "584f99476d" }, "name": "sampleapp-584f99476d-mnrp4", - "namespace": "my-namespace", + "namespace": "test", "ownerReferences": [ { "apiVersion": "apps/v1", From 8e832400882ea0bbca7ea36bb55cce42e2e31e2d Mon Sep 17 00:00:00 2001 From: "Piotr P. Karwasz" Date: Thu, 7 Mar 2024 11:41:29 +0100 Subject: [PATCH 07/13] Add tests for `ContainerUtil` --- .../log4j/lookup/ContainerUtil.java | 99 +++++++++++-------- .../log4j/lookup/KubernetesLookup.java | 2 +- .../log4j/lookup/ContainerUtilTest.java | 41 ++++++++ .../resources/cgroups/negative/case_0.txt | 1 + .../resources/cgroups/negative/case_1.txt | 1 + .../resources/cgroups/positive/case_0.txt | 1 + .../resources/cgroups/positive/case_1.txt | 1 + .../resources/cgroups/positive/case_2.txt | 1 + .../resources/cgroups/positive/case_3.txt | 1 + .../resources/cgroups/positive/case_4.txt | 1 + .../resources/cgroups/positive/case_5.txt | 1 + .../resources/cgroups/positive/case_6.txt | 1 + .../resources/cgroups/positive/case_7.txt | 1 + .../resources/cgroups/positive/case_8.txt | 1 + 14 files changed, 112 insertions(+), 41 deletions(-) create mode 100644 log4j/src/test/java/io/fabric8/kubernetes/log4j/lookup/ContainerUtilTest.java create mode 100644 log4j/src/test/resources/cgroups/negative/case_0.txt create mode 100644 log4j/src/test/resources/cgroups/negative/case_1.txt create mode 100644 log4j/src/test/resources/cgroups/positive/case_0.txt create mode 100644 log4j/src/test/resources/cgroups/positive/case_1.txt create mode 100644 log4j/src/test/resources/cgroups/positive/case_2.txt create mode 100644 log4j/src/test/resources/cgroups/positive/case_3.txt create mode 100644 log4j/src/test/resources/cgroups/positive/case_4.txt create mode 100644 log4j/src/test/resources/cgroups/positive/case_5.txt create mode 100644 log4j/src/test/resources/cgroups/positive/case_6.txt create mode 100644 log4j/src/test/resources/cgroups/positive/case_7.txt create mode 100644 log4j/src/test/resources/cgroups/positive/case_8.txt 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 index 3fd9db149a5..e22b30d6510 100644 --- a/log4j/src/main/java/io/fabric8/kubernetes/log4j/lookup/ContainerUtil.java +++ b/log4j/src/main/java/io/fabric8/kubernetes/log4j/lookup/ContainerUtil.java @@ -18,11 +18,15 @@ import org.apache.logging.log4j.Logger; import org.apache.logging.log4j.status.StatusLogger; -import java.io.File; 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. @@ -30,37 +34,36 @@ final class ContainerUtil { private static final Logger LOGGER = StatusLogger.getLogger(); - private static final int MAXLENGTH = 65; + private static final Pattern DOCKER_ID_PATTERN = Pattern.compile("[0-9a-fA-F]{64}"); + static final Path CGROUP_FILE = 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. - * - * @see Stackoverflow for a discussion on retrieving the containerId. - * @see ControlGroup - * for the original version of this. Not much is actually left but it provided good inspiration. - * @return The container id. + *

+ * + * @param path Path to a {@code /proc/pid/cgroup} file. + * @return A container id or {@code null} if not found. */ - public static String getContainerId() { + public static String getContainerId(Path path) { try { - final File file = new File("/proc/self/cgroup"); - if (file.exists()) { - final Path path = file.toPath(); - final String id = Files.lines(path) - .map(ContainerUtil::getContainerId) - .filter(Objects::nonNull) - .findFirst() - .orElse(null); - LOGGER.debug("Found container id {}", id); - return id; + 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) { @@ -70,28 +73,44 @@ public static String getContainerId() { } private static String getContainerId(String line) { - // Every control group in Kubernetes will use - if (line.contains("/kubepods")) { - // Strip off everything up to the last slash. - int i = line.lastIndexOf('/'); - if (i < 0) { - return null; - } - // If the remainder has a period then take everything up to it. - line = line.substring(i + 1); - i = line.lastIndexOf('.'); - if (i > 0) { - line = line.substring(0, i); - } - // Everything ending with a '/' has already been stripped but the remainder might start with "docker-" - if (line.contains("docker-")) { - // 8:cpuset:/kubepods.slice/kubepods-pod9c26dfb6_b9c9_11e7_bfb9_02c6c1fc4861.slice/docker-3dd988081e7149463c043b5d9c57d7309e079c5e9290f91feba1cc45a04d6a5b.scope - i = line.lastIndexOf("docker-"); - line = line.substring(i + 7); + 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 line.length() <= MAXLENGTH ? line : line.substring(0, MAXLENGTH); } + return dockerId; + } - return null; + /** + * 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 index 0345eb02ea4..5ae49022bd8 100644 --- a/log4j/src/main/java/io/fabric8/kubernetes/log4j/lookup/KubernetesLookup.java +++ b/log4j/src/main/java/io/fabric8/kubernetes/log4j/lookup/KubernetesLookup.java @@ -227,7 +227,7 @@ private void initialize() { if (statuses.size() == 1) { containerStatus = statuses.get(0); } else if (statuses.size() > 1) { - final String containerId = ContainerUtil.getContainerId(); + final String containerId = ContainerUtil.getContainerId(ContainerUtil.CGROUP_FILE); if (containerId != null) { containerStatus = statuses.stream() .filter(cs -> cs.getContainerID().contains(containerId)) 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..e96a42188f5 --- /dev/null +++ b/log4j/src/test/java/io/fabric8/kubernetes/log4j/lookup/ContainerUtilTest.java @@ -0,0 +1,41 @@ +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 { + + private static final String CONTAINER_ID = "3dd988081e7149463c043b5d9c57d7309e079c5e9290f91feba1cc45a04d6a5b"; + + static Stream should_recognize_container_id() { + return Stream.of( + // Some possible example /proc/self/cgroup + Arguments.of("cgroups/positive/case_0.txt", CONTAINER_ID), + Arguments.of("cgroups/positive/case_1.txt", CONTAINER_ID), + Arguments.of("cgroups/positive/case_2.txt", CONTAINER_ID), + Arguments.of("cgroups/positive/case_3.txt", CONTAINER_ID), + Arguments.of("cgroups/positive/case_4.txt", CONTAINER_ID), + Arguments.of("cgroups/positive/case_5.txt", CONTAINER_ID), + Arguments.of("cgroups/positive/case_6.txt", CONTAINER_ID), + Arguments.of("cgroups/positive/case_7.txt", CONTAINER_ID), + Arguments.of("cgroups/positive/case_8.txt", CONTAINER_ID), + // Smoke test in case the file changes format + Arguments.of("cgroups/negative/case_0.txt", null), + Arguments.of("cgroups/negative/case_1.txt", 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/resources/cgroups/negative/case_0.txt b/log4j/src/test/resources/cgroups/negative/case_0.txt new file mode 100644 index 00000000000..870794472f6 --- /dev/null +++ b/log4j/src/test/resources/cgroups/negative/case_0.txt @@ -0,0 +1 @@ +# A file with different format diff --git a/log4j/src/test/resources/cgroups/negative/case_1.txt b/log4j/src/test/resources/cgroups/negative/case_1.txt new file mode 100644 index 00000000000..3ad0ba5a3a2 --- /dev/null +++ b/log4j/src/test/resources/cgroups/negative/case_1.txt @@ -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.txt b/log4j/src/test/resources/cgroups/positive/case_0.txt new file mode 100644 index 00000000000..d5bd33f9071 --- /dev/null +++ b/log4j/src/test/resources/cgroups/positive/case_0.txt @@ -0,0 +1 @@ +4:cpuset:/system.slice/docker-3dd988081e7149463c043b5d9c57d7309e079c5e9290f91feba1cc45a04d6a5b.scope diff --git a/log4j/src/test/resources/cgroups/positive/case_1.txt b/log4j/src/test/resources/cgroups/positive/case_1.txt new file mode 100644 index 00000000000..88fa5833827 --- /dev/null +++ b/log4j/src/test/resources/cgroups/positive/case_1.txt @@ -0,0 +1 @@ +2:cpu:/docker/3dd988081e7149463c043b5d9c57d7309e079c5e9290f91feba1cc45a04d6a5b diff --git a/log4j/src/test/resources/cgroups/positive/case_2.txt b/log4j/src/test/resources/cgroups/positive/case_2.txt new file mode 100644 index 00000000000..1dd8ef30555 --- /dev/null +++ b/log4j/src/test/resources/cgroups/positive/case_2.txt @@ -0,0 +1 @@ +2:cpu:/docker-ce/docker/3dd988081e7149463c043b5d9c57d7309e079c5e9290f91feba1cc45a04d6a5b diff --git a/log4j/src/test/resources/cgroups/positive/case_3.txt b/log4j/src/test/resources/cgroups/positive/case_3.txt new file mode 100644 index 00000000000..51e2ef76638 --- /dev/null +++ b/log4j/src/test/resources/cgroups/positive/case_3.txt @@ -0,0 +1 @@ +10:cpu,cpuacct:/docker/a9f3c3932cd81c4a74cc7e0a18c3300255159512f1d000545c42895adaf68932/docker/3dd988081e7149463c043b5d9c57d7309e079c5e9290f91feba1cc45a04d6a5b diff --git a/log4j/src/test/resources/cgroups/positive/case_4.txt b/log4j/src/test/resources/cgroups/positive/case_4.txt new file mode 100644 index 00000000000..675b386aeeb --- /dev/null +++ b/log4j/src/test/resources/cgroups/positive/case_4.txt @@ -0,0 +1 @@ +3:cpu:/docker/4193df6bcf5fce75f3fc77f303b2ac06fb664adeb269b959b7ae17b3f8dcf329/3dd988081e7149463c043b5d9c57d7309e079c5e9290f91feba1cc45a04d6a5b diff --git a/log4j/src/test/resources/cgroups/positive/case_5.txt b/log4j/src/test/resources/cgroups/positive/case_5.txt new file mode 100644 index 00000000000..65a56183dc6 --- /dev/null +++ b/log4j/src/test/resources/cgroups/positive/case_5.txt @@ -0,0 +1 @@ +7:cpu:/ecs/0410eff2-7e59-4111-823e-1e0d98ef7f30/3dd988081e7149463c043b5d9c57d7309e079c5e9290f91feba1cc45a04d6a5b diff --git a/log4j/src/test/resources/cgroups/positive/case_6.txt b/log4j/src/test/resources/cgroups/positive/case_6.txt new file mode 100644 index 00000000000..5150948ec0d --- /dev/null +++ b/log4j/src/test/resources/cgroups/positive/case_6.txt @@ -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.txt b/log4j/src/test/resources/cgroups/positive/case_7.txt new file mode 100644 index 00000000000..ff7deb5c7b5 --- /dev/null +++ b/log4j/src/test/resources/cgroups/positive/case_7.txt @@ -0,0 +1 @@ +12:freezer:/actions_job/3dd988081e7149463c043b5d9c57d7309e079c5e9290f91feba1cc45a04d6a5b diff --git a/log4j/src/test/resources/cgroups/positive/case_8.txt b/log4j/src/test/resources/cgroups/positive/case_8.txt new file mode 100644 index 00000000000..b37a56e49ee --- /dev/null +++ b/log4j/src/test/resources/cgroups/positive/case_8.txt @@ -0,0 +1 @@ +11:pids:/kubepods/burstable/pod1fe52ba4-5709-11ea-9ee3-00505682780f/3dd988081e7149463c043b5d9c57d7309e079c5e9290f91feba1cc45a04d6a5b From 4add50b175838b9dd9483443fe901209d1f23ec6 Mon Sep 17 00:00:00 2001 From: "Piotr P. Karwasz" Date: Thu, 7 Mar 2024 12:14:40 +0100 Subject: [PATCH 08/13] Split data-gathering code into methods --- .../log4j/lookup/KubernetesLookup.java | 270 ++++++++++-------- 1 file changed, 153 insertions(+), 117 deletions(-) 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 index 5ae49022bd8..be5cc2e2671 100644 --- a/log4j/src/main/java/io/fabric8/kubernetes/log4j/lookup/KubernetesLookup.java +++ b/log4j/src/main/java/io/fabric8/kubernetes/log4j/lookup/KubernetesLookup.java @@ -174,100 +174,24 @@ public KubernetesLookup() { } private void initialize() { - if (kubernetesInfo == null || (isSpringIncluded && !kubernetesInfo.isSpringActive)) { + KubernetesInfo kubernetesInfo = KubernetesLookup.kubernetesInfo; + if (kubernetesInfo == null || isSpringStatusChanged(kubernetesInfo)) { initLock.lock(); try { - final boolean isSpringActive = isSpringActive(); - if (kubernetesInfo == null || (!kubernetesInfo.isSpringActive && isSpringActive)) { - final KubernetesInfo info = new KubernetesInfo(); - KubernetesClient client = null; - info.isSpringActive = isSpringActive; - if (pod == null) { - client = createClient(); - if (client != null) { - pod = getCurrentPod(client); - info.masterUrl = client.getMasterUrl(); - if (pod != null) { - namespace = client.namespaces() - .withName(pod.getMetadata().getNamespace()) - .get(); - } - } else { - LOGGER.warn("Kubernetes is not available for access"); - } - } else { - info.masterUrl = masterUrl; - } + kubernetesInfo = KubernetesLookup.kubernetesInfo; + if (kubernetesInfo == null || isSpringStatusChanged(kubernetesInfo)) { + tryInitializeFields(); + // Retrieve the data from the fields + kubernetesInfo = new KubernetesInfo(); + kubernetesInfo.isSpringActive = isSpringActive(); + kubernetesInfo.masterUrl = masterUrl; if (namespace != null) { - final ObjectMeta namespaceMetadata = namespace.getMetadata(); - if (namespaceMetadata != null) { - info.namespaceAnnotations = namespaceMetadata.getAnnotations(); - info.namespaceId = namespaceMetadata.getUid(); - info.namespaceLabels = namespaceMetadata.getLabels(); - } + fillNamespaceData(namespace, kubernetesInfo); } if (pod != null) { - final ObjectMeta podMetadata = pod.getMetadata(); - if (podMetadata != null) { - info.annotations = podMetadata.getAnnotations(); - info.labels = podMetadata.getLabels(); - info.namespace = podMetadata.getNamespace(); - info.podId = podMetadata.getUid(); - info.podName = podMetadata.getName(); - } - - final String containerName; - final PodStatus podStatus = pod.getStatus(); - if (podStatus != null) { - info.hostIp = podStatus.getHostIP(); - info.podIp = podStatus.getPodIP(); - - ContainerStatus containerStatus = null; - final List statuses = podStatus.getContainerStatuses(); - if (statuses.size() == 1) { - containerStatus = statuses.get(0); - } else if (statuses.size() > 1) { - final String containerId = ContainerUtil.getContainerId(ContainerUtil.CGROUP_FILE); - if (containerId != null) { - containerStatus = statuses.stream() - .filter(cs -> cs.getContainerID().contains(containerId)) - .findFirst() - .orElse(null); - } - } - if (containerStatus != null) { - info.containerId = containerStatus.getContainerID(); - info.imageId = containerStatus.getImageID(); - containerName = containerStatus.getName(); - } else { - containerName = null; - } - } else { - containerName = null; - } - - final PodSpec podSpec = pod.getSpec(); - if (podSpec != null) { - info.hostName = podSpec.getNodeName(); - info.accountName = podSpec.getServiceAccountName(); - Container container = null; - final List containers = podSpec.getContainers(); - if (containers.size() == 1) { - container = containers.get(0); - } else if (containers.size() > 1 && containerName != null) { - container = containers.stream() - .filter(c -> c.getName().equals(containerName)) - .findFirst() - .orElse(null); - } - if (container != null) { - info.containerName = container.getName(); - info.imageName = container.getImage(); - } - } - - kubernetesInfo = info; + fillPodData(pod, kubernetesInfo); } + KubernetesLookup.kubernetesInfo = kubernetesInfo; } } finally { initLock.unlock(); @@ -275,11 +199,152 @@ private void initialize() { } } + private static boolean isSpringStatusChanged(KubernetesInfo kubernetesInfo) { + return isSpringIncluded + && isSpringActive() != (kubernetesInfo != null && kubernetesInfo.isSpringActive); + } + + private static boolean isSpringActive() { + return isSpringIncluded + && 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 void tryInitializeFields() { + KubernetesClient client = createClient(); + if (client != null) { + if (pod == null) { + pod = getCurrentPod(client); + } + if (pod != null && namespace == null) { + namespace = getNamespace(client, pod); + } + if (masterUrl == null) { + masterUrl = client.getMasterUrl(); + } + } else { + LOGGER.warn("Kubernetes is not available for access"); + } + } + // Used for testing 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 (Throwable t) { + LOGGER.debug("Unable to locate pod with name {}.", hostName); + } + 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(ContainerUtil.CGROUP_FILE); + 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) { if (kubernetesInfo == null) { @@ -352,35 +417,6 @@ static void clearInfo() { kubernetesInfo = null; } - private Pod getCurrentPod(final KubernetesClient kubernetesClient) { - final String hostName = getHostName(); - try { - if (hostName != null && !hostName.isEmpty()) { - return kubernetesClient.pods().withName(hostName).get(); - } - } catch (Throwable t) { - LOGGER.debug("Unable to locate pod with name {}.", hostName); - } - 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 boolean isSpringActive() { - return isSpringIncluded - && LogManager.getFactory() != null - && LogManager.getFactory().hasContext(KubernetesLookup.class.getName(), null, false) - && LogManager.getContext(false).getObject(SPRING_ENVIRONMENT_KEY) != null; - } - private static class KubernetesInfo { boolean isSpringActive; String accountName; From aa3a0b9f32d137076cddde0d539f8720c42af62f Mon Sep 17 00:00:00 2001 From: "Piotr P. Karwasz" Date: Thu, 7 Mar 2024 12:29:52 +0100 Subject: [PATCH 09/13] Apply Sonarqube suggestions --- log4j/pom.xml | 10 ++- .../log4j/lookup/KubernetesLookup.java | 61 +++++++++++-------- .../log4j/lookup/ClientBuilderTest.java | 2 +- .../log4j/lookup/Log4jConfigTest.java | 2 +- 4 files changed, 45 insertions(+), 30 deletions(-) diff --git a/log4j/pom.xml b/log4j/pom.xml index 74bd30e07cc..1f5a096b149 100644 --- a/log4j/pom.xml +++ b/log4j/pom.xml @@ -32,9 +32,6 @@ io.fabric8.kubernetes.log4j.* * - - - 2.22.1 @@ -42,10 +39,17 @@ io.fabric8 kubernetes-client + + org.apache.logging.log4j + log4j-api + ${log4j.version} + compile + org.apache.logging.log4j log4j-core ${log4j.version} + compile org.assertj 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 index be5cc2e2671..b65854b8972 100644 --- a/log4j/src/main/java/io/fabric8/kubernetes/log4j/lookup/KubernetesLookup.java +++ b/log4j/src/main/java/io/fabric8/kubernetes/log4j/lookup/KubernetesLookup.java @@ -35,7 +35,8 @@ import java.util.List; import java.util.Map; import java.util.concurrent.locks.Lock; -import java.util.concurrent.locks.ReentrantLock; +import java.util.concurrent.locks.ReadWriteLock; +import java.util.concurrent.locks.ReentrantReadWriteLock; /** * Retrieves various attributes from the Kubernetes server. @@ -150,11 +151,14 @@ public class KubernetesLookup extends AbstractLookup { private static final String POD_IP = "podIp"; private static final String POD_NAME = "podName"; - private static volatile KubernetesInfo kubernetesInfo; - private static final Lock initLock = new ReentrantLock(); - private static final boolean isSpringIncluded = LoaderUtil + private static KubernetesInfo kubernetesInfo; + 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; @@ -163,49 +167,49 @@ public KubernetesLookup() { this.pod = null; this.namespace = null; this.masterUrl = null; - initialize(); + initialize(this); } KubernetesLookup(Pod pod, Namespace namespace, URL masterUrl) { this.pod = pod; this.namespace = namespace; this.masterUrl = masterUrl; - initialize(); + initialize(this); } - private void initialize() { + private static void initialize(KubernetesLookup lookup) { KubernetesInfo kubernetesInfo = KubernetesLookup.kubernetesInfo; if (kubernetesInfo == null || isSpringStatusChanged(kubernetesInfo)) { - initLock.lock(); + WRITE_LOCK.lock(); try { kubernetesInfo = KubernetesLookup.kubernetesInfo; if (kubernetesInfo == null || isSpringStatusChanged(kubernetesInfo)) { - tryInitializeFields(); + tryInitializeFields(lookup); // Retrieve the data from the fields kubernetesInfo = new KubernetesInfo(); kubernetesInfo.isSpringActive = isSpringActive(); - kubernetesInfo.masterUrl = masterUrl; - if (namespace != null) { - fillNamespaceData(namespace, kubernetesInfo); + kubernetesInfo.masterUrl = lookup.masterUrl; + if (lookup.namespace != null) { + fillNamespaceData(lookup.namespace, kubernetesInfo); } - if (pod != null) { - fillPodData(pod, kubernetesInfo); + if (lookup.pod != null) { + fillPodData(lookup.pod, kubernetesInfo); } KubernetesLookup.kubernetesInfo = kubernetesInfo; } } finally { - initLock.unlock(); + WRITE_LOCK.unlock(); } } } private static boolean isSpringStatusChanged(KubernetesInfo kubernetesInfo) { - return isSpringIncluded + return IS_SPRING_INCLUDED && isSpringActive() != (kubernetesInfo != null && kubernetesInfo.isSpringActive); } private static boolean isSpringActive() { - return isSpringIncluded + return IS_SPRING_INCLUDED && LogManager.getFactory() != null && LogManager.getFactory().hasContext(KubernetesLookup.class.getName(), null, false) && LogManager.getContext(false).getObject(SPRING_ENVIRONMENT_KEY) != null; @@ -214,17 +218,17 @@ private static boolean isSpringActive() { /** * Tries to initialize the fields of the lookup. */ - private void tryInitializeFields() { - KubernetesClient client = createClient(); + private static void tryInitializeFields(KubernetesLookup lookup) { + KubernetesClient client = lookup.createClient(); if (client != null) { - if (pod == null) { - pod = getCurrentPod(client); + if (lookup.pod == null) { + lookup.pod = getCurrentPod(client); } - if (pod != null && namespace == null) { - namespace = getNamespace(client, pod); + if (lookup.pod != null && lookup.namespace == null) { + lookup.namespace = getNamespace(client, lookup.pod); } - if (masterUrl == null) { - masterUrl = client.getMasterUrl(); + if (lookup.masterUrl == null) { + lookup.masterUrl = client.getMasterUrl(); } } else { LOGGER.warn("Kubernetes is not available for access"); @@ -347,6 +351,13 @@ private static Container getContainer(PodSpec podSpec, String containerName) { @Override public String lookup(final LogEvent event, final String key) { + KubernetesInfo kubernetesInfo = null; + READ_LOCK.lock(); + try { + kubernetesInfo = KubernetesLookup.kubernetesInfo; + } finally { + READ_LOCK.unlock(); + } if (kubernetesInfo == null) { return null; } 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 index c9d43cb709b..538b2702cf0 100644 --- a/log4j/src/test/java/io/fabric8/kubernetes/log4j/lookup/ClientBuilderTest.java +++ b/log4j/src/test/java/io/fabric8/kubernetes/log4j/lookup/ClientBuilderTest.java @@ -40,7 +40,7 @@ void uses_log4j_properties_if_requested() { assertThat(config.getProxyUsername()).isEqualTo("proxyUsername"); assertThat(config.getProxyPassword()).isEqualTo("proxyPassword"); assertThat(config.getRequestTimeout()).isEqualTo(678); - assertThat(config.isTrustCerts()).isEqualTo(true); + 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/java/io/fabric8/kubernetes/log4j/lookup/Log4jConfigTest.java b/log4j/src/test/java/io/fabric8/kubernetes/log4j/lookup/Log4jConfigTest.java index 1366dbbbe08..e1a77f81194 100644 --- a/log4j/src/test/java/io/fabric8/kubernetes/log4j/lookup/Log4jConfigTest.java +++ b/log4j/src/test/java/io/fabric8/kubernetes/log4j/lookup/Log4jConfigTest.java @@ -43,7 +43,7 @@ void all_properties_should_be_recognized(final String propertyFile) { assertThat(config.getProxyUsername()).isEqualTo("proxyUsername"); assertThat(config.getProxyPassword()).isEqualTo("proxyPassword"); assertThat(config.getRequestTimeout()).isEqualTo(678); - assertThat(config.isTrustCerts()).isEqualTo(true); + assertThat(config.isTrustCerts()).isTrue(); assertThat(config.getUsername()).isEqualTo("username"); assertThat(config.getWatchReconnectInterval()).isEqualTo(1234); assertThat(config.getWatchReconnectLimit()).isEqualTo(5678); From 32a62babe6accda3028600e53f7a22ea6355359f Mon Sep 17 00:00:00 2001 From: "Piotr P. Karwasz" Date: Thu, 7 Mar 2024 13:52:17 +0100 Subject: [PATCH 10/13] Fix license formatting --- .../log4j/lookup/ClientBuilder.java | 23 +++++++----- .../log4j/lookup/ContainerUtil.java | 2 +- .../log4j/lookup/KubernetesLookup.java | 23 +++++++----- .../kubernetes/log4j/lookup/Log4jConfig.java | 23 +++++++----- .../kubernetes/log4j/lookup/package-info.java | 4 +- .../log4j/lookup/ClientBuilderTest.java | 15 ++++++++ .../log4j/lookup/ContainerUtilTest.java | 37 +++++++++++++------ .../log4j/lookup/KubernetesLookupTest.java | 23 +++++++----- .../log4j/lookup/Log4jConfigTest.java | 15 ++++++++ .../negative/{case_0.txt => case_0.dat} | 0 .../negative/{case_1.txt => case_1.dat} | 0 .../positive/{case_0.txt => case_0.dat} | 0 .../positive/{case_1.txt => case_1.dat} | 0 .../positive/{case_2.txt => case_2.dat} | 0 .../positive/{case_3.txt => case_3.dat} | 0 .../positive/{case_4.txt => case_4.dat} | 0 .../positive/{case_5.txt => case_5.dat} | 0 .../positive/{case_6.txt => case_6.dat} | 0 .../positive/{case_7.txt => case_7.dat} | 0 .../positive/{case_8.txt => case_8.dat} | 0 .../log4j2.kubernetes.client.properties | 16 ++++++++ .../spring.cloud.kubernetes.client.properties | 16 ++++++++ 22 files changed, 143 insertions(+), 54 deletions(-) rename log4j/src/test/resources/cgroups/negative/{case_0.txt => case_0.dat} (100%) rename log4j/src/test/resources/cgroups/negative/{case_1.txt => case_1.dat} (100%) rename log4j/src/test/resources/cgroups/positive/{case_0.txt => case_0.dat} (100%) rename log4j/src/test/resources/cgroups/positive/{case_1.txt => case_1.dat} (100%) rename log4j/src/test/resources/cgroups/positive/{case_2.txt => case_2.dat} (100%) rename log4j/src/test/resources/cgroups/positive/{case_3.txt => case_3.dat} (100%) rename log4j/src/test/resources/cgroups/positive/{case_4.txt => case_4.dat} (100%) rename log4j/src/test/resources/cgroups/positive/{case_5.txt => case_5.dat} (100%) rename log4j/src/test/resources/cgroups/positive/{case_6.txt => case_6.dat} (100%) rename log4j/src/test/resources/cgroups/positive/{case_7.txt => case_7.dat} (100%) rename log4j/src/test/resources/cgroups/positive/{case_8.txt => case_8.dat} (100%) 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 index 9647572a636..c782e902bd6 100644 --- a/log4j/src/main/java/io/fabric8/kubernetes/log4j/lookup/ClientBuilder.java +++ b/log4j/src/main/java/io/fabric8/kubernetes/log4j/lookup/ClientBuilder.java @@ -1,14 +1,17 @@ -/** +/* * 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. + * + * 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; 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 index e22b30d6510..5182c2a5e24 100644 --- a/log4j/src/main/java/io/fabric8/kubernetes/log4j/lookup/ContainerUtil.java +++ b/log4j/src/main/java/io/fabric8/kubernetes/log4j/lookup/ContainerUtil.java @@ -1,4 +1,4 @@ -/** +/* * Copyright (C) 2015 Red Hat, Inc. * * Licensed under the Apache License, Version 2.0 (the "License"); 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 index b65854b8972..ca6d81e6f3c 100644 --- a/log4j/src/main/java/io/fabric8/kubernetes/log4j/lookup/KubernetesLookup.java +++ b/log4j/src/main/java/io/fabric8/kubernetes/log4j/lookup/KubernetesLookup.java @@ -1,14 +1,17 @@ -/** +/* * 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. + * + * 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; 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 index c408b941c83..b30751752c3 100644 --- a/log4j/src/main/java/io/fabric8/kubernetes/log4j/lookup/Log4jConfig.java +++ b/log4j/src/main/java/io/fabric8/kubernetes/log4j/lookup/Log4jConfig.java @@ -1,14 +1,17 @@ -/** +/* * 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. + * + * 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; 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 index 6d67ed4ce7d..372be419e8c 100644 --- 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 @@ -1,11 +1,11 @@ -/** +/* * 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 + * 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, 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 index 538b2702cf0..f6827d478b4 100644 --- a/log4j/src/test/java/io/fabric8/kubernetes/log4j/lookup/ClientBuilderTest.java +++ b/log4j/src/test/java/io/fabric8/kubernetes/log4j/lookup/ClientBuilderTest.java @@ -1,3 +1,18 @@ +/* + * 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; 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 index e96a42188f5..ad9eb460dcf 100644 --- a/log4j/src/test/java/io/fabric8/kubernetes/log4j/lookup/ContainerUtilTest.java +++ b/log4j/src/test/java/io/fabric8/kubernetes/log4j/lookup/ContainerUtilTest.java @@ -1,3 +1,18 @@ +/* + * 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; @@ -17,18 +32,18 @@ class ContainerUtilTest { static Stream should_recognize_container_id() { return Stream.of( // Some possible example /proc/self/cgroup - Arguments.of("cgroups/positive/case_0.txt", CONTAINER_ID), - Arguments.of("cgroups/positive/case_1.txt", CONTAINER_ID), - Arguments.of("cgroups/positive/case_2.txt", CONTAINER_ID), - Arguments.of("cgroups/positive/case_3.txt", CONTAINER_ID), - Arguments.of("cgroups/positive/case_4.txt", CONTAINER_ID), - Arguments.of("cgroups/positive/case_5.txt", CONTAINER_ID), - Arguments.of("cgroups/positive/case_6.txt", CONTAINER_ID), - Arguments.of("cgroups/positive/case_7.txt", CONTAINER_ID), - Arguments.of("cgroups/positive/case_8.txt", CONTAINER_ID), + Arguments.of("cgroups/positive/case_0.dat", 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("cgroups/negative/case_0.txt", null), - Arguments.of("cgroups/negative/case_1.txt", null)); + Arguments.of("cgroups/negative/case_0.dat", null), + Arguments.of("cgroups/negative/case_1.dat", null)); } @ParameterizedTest 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 index db97e4118e7..34062df9128 100644 --- a/log4j/src/test/java/io/fabric8/kubernetes/log4j/lookup/KubernetesLookupTest.java +++ b/log4j/src/test/java/io/fabric8/kubernetes/log4j/lookup/KubernetesLookupTest.java @@ -1,14 +1,17 @@ -/** +/* * 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. + * + * 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; 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 index e1a77f81194..d481bb7451b 100644 --- a/log4j/src/test/java/io/fabric8/kubernetes/log4j/lookup/Log4jConfigTest.java +++ b/log4j/src/test/java/io/fabric8/kubernetes/log4j/lookup/Log4jConfigTest.java @@ -1,3 +1,18 @@ +/* + * 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; diff --git a/log4j/src/test/resources/cgroups/negative/case_0.txt b/log4j/src/test/resources/cgroups/negative/case_0.dat similarity index 100% rename from log4j/src/test/resources/cgroups/negative/case_0.txt rename to log4j/src/test/resources/cgroups/negative/case_0.dat diff --git a/log4j/src/test/resources/cgroups/negative/case_1.txt b/log4j/src/test/resources/cgroups/negative/case_1.dat similarity index 100% rename from log4j/src/test/resources/cgroups/negative/case_1.txt rename to log4j/src/test/resources/cgroups/negative/case_1.dat diff --git a/log4j/src/test/resources/cgroups/positive/case_0.txt b/log4j/src/test/resources/cgroups/positive/case_0.dat similarity index 100% rename from log4j/src/test/resources/cgroups/positive/case_0.txt rename to log4j/src/test/resources/cgroups/positive/case_0.dat diff --git a/log4j/src/test/resources/cgroups/positive/case_1.txt b/log4j/src/test/resources/cgroups/positive/case_1.dat similarity index 100% rename from log4j/src/test/resources/cgroups/positive/case_1.txt rename to log4j/src/test/resources/cgroups/positive/case_1.dat diff --git a/log4j/src/test/resources/cgroups/positive/case_2.txt b/log4j/src/test/resources/cgroups/positive/case_2.dat similarity index 100% rename from log4j/src/test/resources/cgroups/positive/case_2.txt rename to log4j/src/test/resources/cgroups/positive/case_2.dat diff --git a/log4j/src/test/resources/cgroups/positive/case_3.txt b/log4j/src/test/resources/cgroups/positive/case_3.dat similarity index 100% rename from log4j/src/test/resources/cgroups/positive/case_3.txt rename to log4j/src/test/resources/cgroups/positive/case_3.dat diff --git a/log4j/src/test/resources/cgroups/positive/case_4.txt b/log4j/src/test/resources/cgroups/positive/case_4.dat similarity index 100% rename from log4j/src/test/resources/cgroups/positive/case_4.txt rename to log4j/src/test/resources/cgroups/positive/case_4.dat diff --git a/log4j/src/test/resources/cgroups/positive/case_5.txt b/log4j/src/test/resources/cgroups/positive/case_5.dat similarity index 100% rename from log4j/src/test/resources/cgroups/positive/case_5.txt rename to log4j/src/test/resources/cgroups/positive/case_5.dat diff --git a/log4j/src/test/resources/cgroups/positive/case_6.txt b/log4j/src/test/resources/cgroups/positive/case_6.dat similarity index 100% rename from log4j/src/test/resources/cgroups/positive/case_6.txt rename to log4j/src/test/resources/cgroups/positive/case_6.dat diff --git a/log4j/src/test/resources/cgroups/positive/case_7.txt b/log4j/src/test/resources/cgroups/positive/case_7.dat similarity index 100% rename from log4j/src/test/resources/cgroups/positive/case_7.txt rename to log4j/src/test/resources/cgroups/positive/case_7.dat diff --git a/log4j/src/test/resources/cgroups/positive/case_8.txt b/log4j/src/test/resources/cgroups/positive/case_8.dat similarity index 100% rename from log4j/src/test/resources/cgroups/positive/case_8.txt rename to log4j/src/test/resources/cgroups/positive/case_8.dat diff --git a/log4j/src/test/resources/log4j2.kubernetes.client.properties b/log4j/src/test/resources/log4j2.kubernetes.client.properties index 92499e6bbdc..4ad7b914280 100644 --- a/log4j/src/test/resources/log4j2.kubernetes.client.properties +++ b/log4j/src/test/resources/log4j2.kubernetes.client.properties @@ -1,3 +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. +# + ## # Properties file to test Log4jConfig. log4j2.kubernetes.client.apiVersion = apiVersion diff --git a/log4j/src/test/resources/spring.cloud.kubernetes.client.properties b/log4j/src/test/resources/spring.cloud.kubernetes.client.properties index e10f19d1733..716685923a3 100644 --- a/log4j/src/test/resources/spring.cloud.kubernetes.client.properties +++ b/log4j/src/test/resources/spring.cloud.kubernetes.client.properties @@ -1,3 +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. +# + ## # Properties file to test Log4jConfig. spring.cloud.kubernetes.client.apiVersion = apiVersion From a64e58f1e8e05a9f8827b368a029d44ede52bba1 Mon Sep 17 00:00:00 2001 From: "Piotr P. Karwasz" Date: Thu, 7 Mar 2024 14:13:35 +0100 Subject: [PATCH 11/13] Add missing JavaDoc --- .../kubernetes/log4j/lookup/KubernetesLookup.java | 15 +++++++++++++-- .../kubernetes/log4j/lookup/package-info.java | 3 +++ 2 files changed, 16 insertions(+), 2 deletions(-) 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 index ca6d81e6f3c..a00cfccd8d6 100644 --- a/log4j/src/main/java/io/fabric8/kubernetes/log4j/lookup/KubernetesLookup.java +++ b/log4j/src/main/java/io/fabric8/kubernetes/log4j/lookup/KubernetesLookup.java @@ -44,9 +44,10 @@ /** * Retrieves various attributes from the Kubernetes server. *

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

* + * * * * @@ -166,6 +167,9 @@ public class KubernetesLookup extends AbstractLookup { private Namespace namespace; private URL masterUrl; + /** + * Default constructor, called by reflection. + */ public KubernetesLookup() { this.pod = null; this.namespace = null; @@ -238,7 +242,14 @@ private static void tryInitializeFields(KubernetesLookup lookup) { } } - // Used for testing + /** + * 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(); } 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 index 372be419e8c..22d06b59e3d 100644 --- 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 @@ -13,4 +13,7 @@ * 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; From 069692ffac922b9383ff8703f4deaa8e3a492ee7 Mon Sep 17 00:00:00 2001 From: "Piotr P. Karwasz" Date: Thu, 7 Mar 2024 16:05:38 +0100 Subject: [PATCH 12/13] Reach 80% test coverage --- .../log4j/lookup/ClientBuilder.java | 4 +- .../log4j/lookup/ContainerUtil.java | 2 +- .../log4j/lookup/KubernetesLookup.java | 59 +++++++------- .../log4j/lookup/ContainerUtilTest.java | 9 ++- .../log4j/lookup/KubernetesLookupTest.java | 80 ++++++++++++++++++- 5 files changed, 118 insertions(+), 36 deletions(-) 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 index c782e902bd6..55ccc05793f 100644 --- a/log4j/src/main/java/io/fabric8/kubernetes/log4j/lookup/ClientBuilder.java +++ b/log4j/src/main/java/io/fabric8/kubernetes/log4j/lookup/ClientBuilder.java @@ -76,9 +76,9 @@ static Config kubernetesClientConfig(final PropertiesUtil props) { .build(); } return base; - } catch (final Throwable t) { + } catch (final Exception e) { StatusLogger.getLogger().warn("An error occurred while retrieving Kubernetes Client configuration: {}.", - t.getMessage(), t); + 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 index 5182c2a5e24..8170713ce53 100644 --- a/log4j/src/main/java/io/fabric8/kubernetes/log4j/lookup/ContainerUtil.java +++ b/log4j/src/main/java/io/fabric8/kubernetes/log4j/lookup/ContainerUtil.java @@ -35,7 +35,7 @@ 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_FILE = Paths.get("/proc/self/cgroup"); + static final Path CGROUP_PATH = Paths.get("/proc/self/cgroup"); private ContainerUtil() { } 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 index a00cfccd8d6..216156aa067 100644 --- a/log4j/src/main/java/io/fabric8/kubernetes/log4j/lookup/KubernetesLookup.java +++ b/log4j/src/main/java/io/fabric8/kubernetes/log4j/lookup/KubernetesLookup.java @@ -35,6 +35,7 @@ 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; @@ -156,6 +157,8 @@ public class KubernetesLookup extends AbstractLookup { 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(); @@ -191,7 +194,9 @@ private static void initialize(KubernetesLookup lookup) { try { kubernetesInfo = KubernetesLookup.kubernetesInfo; if (kubernetesInfo == null || isSpringStatusChanged(kubernetesInfo)) { - tryInitializeFields(lookup); + if (lookup.pod == null || lookup.namespace == null || lookup.masterUrl == null) { + tryInitializeFields(lookup); + } // Retrieve the data from the fields kubernetesInfo = new KubernetesInfo(); kubernetesInfo.isSpringActive = isSpringActive(); @@ -260,8 +265,8 @@ private static Pod getCurrentPod(final KubernetesClient kubernetesClient) { if (hostName != null && !hostName.isEmpty()) { return kubernetesClient.pods().withName(hostName).get(); } - } catch (Throwable t) { - LOGGER.debug("Unable to locate pod with name {}.", hostName); + } catch (Exception e) { + LOGGER.debug("Unable to locate pod with name {}.", hostName, e); } return null; } @@ -340,7 +345,7 @@ private static ContainerStatus getContainerStatus(PodStatus podStatus) { case 1: return statuses.get(0); default: - final String containerId = ContainerUtil.getContainerId(ContainerUtil.CGROUP_FILE); + final String containerId = ContainerUtil.getContainerId(cgroupPath); return containerId != null ? statuses.stream() .filter(cs -> cs.getContainerID().contains(containerId)) .findFirst() @@ -365,70 +370,67 @@ private static Container getContainer(PodSpec podSpec, String containerName) { @Override public String lookup(final LogEvent event, final String key) { - KubernetesInfo kubernetesInfo = null; + KubernetesInfo info; READ_LOCK.lock(); try { - kubernetesInfo = KubernetesLookup.kubernetesInfo; + info = kubernetesInfo; } finally { READ_LOCK.unlock(); } - if (kubernetesInfo == null) { - return null; - } if (key.startsWith(LABELS_PREFIX)) { - return kubernetesInfo.labels != null ? kubernetesInfo.labels.get(key.substring(LABELS_PREFIX.length())) : null; + return info.labels != null ? info.labels.get(key.substring(LABELS_PREFIX.length())) : null; } switch (key) { case ACCOUNT_NAME: { - return kubernetesInfo.accountName; + return info.accountName; } case ANNOTATIONS: { - return kubernetesInfo.annotations != null ? kubernetesInfo.annotations.toString() : null; + return info.annotations != null ? info.annotations.toString() : null; } case CONTAINER_ID: { - return kubernetesInfo.containerId; + return info.containerId; } case CONTAINER_NAME: { - return kubernetesInfo.containerName; + return info.containerName; } case HOST: { - return kubernetesInfo.hostName; + return info.hostName; } case HOST_IP: { - return kubernetesInfo.hostIp; + return info.hostIp; } case LABELS: { - return kubernetesInfo.labels != null ? kubernetesInfo.labels.toString() : null; + return info.labels != null ? info.labels.toString() : null; } case MASTER_URL: { - return kubernetesInfo.masterUrl != null ? kubernetesInfo.masterUrl.toString() : null; + return info.masterUrl != null ? info.masterUrl.toString() : null; } case NAMESPACE_ANNOTATIONS: { - return kubernetesInfo.namespaceAnnotations != null ? kubernetesInfo.namespaceAnnotations.toString() : null; + return info.namespaceAnnotations != null ? info.namespaceAnnotations.toString() : null; } case NAMESPACE_ID: { - return kubernetesInfo.namespaceId; + return info.namespaceId; } case NAMESPACE_LABELS: { - return kubernetesInfo.namespaceLabels != null ? kubernetesInfo.namespaceLabels.toString() : null; + return info.namespaceLabels != null ? info.namespaceLabels.toString() : null; } case NAMESPACE_NAME: { - return kubernetesInfo.namespace; + return info.namespace; } case POD_ID: { - return kubernetesInfo.podId; + return info.podId; } case POD_IP: { - return kubernetesInfo.podIp; + return info.podIp; } case POD_NAME: { - return kubernetesInfo.podName; + return info.podName; } case IMAGE_ID: { - return kubernetesInfo.imageId; + return info.imageId; } case IMAGE_NAME: { - return kubernetesInfo.imageName; + return info.imageName; } default: return null; @@ -438,8 +440,9 @@ public String lookup(final LogEvent event, final String key) { /** * For unit testing only. */ - static void clearInfo() { + static void clear() { kubernetesInfo = null; + cgroupPath = ContainerUtil.CGROUP_PATH; } private static class KubernetesInfo { 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 index ad9eb460dcf..8fac6d69b39 100644 --- a/log4j/src/test/java/io/fabric8/kubernetes/log4j/lookup/ContainerUtilTest.java +++ b/log4j/src/test/java/io/fabric8/kubernetes/log4j/lookup/ContainerUtilTest.java @@ -27,12 +27,15 @@ class ContainerUtilTest { - private static final String CONTAINER_ID = "3dd988081e7149463c043b5d9c57d7309e079c5e9290f91feba1cc45a04d6a5b"; + // 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("cgroups/positive/case_0.dat", CONTAINER_ID), + 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), @@ -42,7 +45,7 @@ static Stream should_recognize_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("cgroups/negative/case_0.dat", null), + Arguments.of(TEST_BAD_CGROUP_RESOURCE, null), Arguments.of("cgroups/negative/case_1.dat", null)); } 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 index 34062df9128..0e734fa4108 100644 --- a/log4j/src/test/java/io/fabric8/kubernetes/log4j/lookup/KubernetesLookupTest.java +++ b/log4j/src/test/java/io/fabric8/kubernetes/log4j/lookup/KubernetesLookupTest.java @@ -16,6 +16,10 @@ 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; @@ -25,10 +29,15 @@ 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. @@ -36,13 +45,14 @@ @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.clearInfo(); + KubernetesLookup.clear(); } @Test @@ -100,6 +110,67 @@ void clusterPod() throws Exception { 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(); @@ -117,7 +188,12 @@ protected KubernetesClient createClient() { @Test void no_client_should_return_no_data() { - final StrLookup lookup = new KubernetesLookup(); + final StrLookup lookup = new KubernetesLookup() { + @Override + protected KubernetesClient createClient() { + return null; + } + }; assertThat(lookup.lookup("accountName")).isNull(); } From 67cdd06a1dfb7fe7b2736bed910ffd17f3f1924a Mon Sep 17 00:00:00 2001 From: "Piotr P. Karwasz" Date: Fri, 22 Mar 2024 21:34:22 +0100 Subject: [PATCH 13/13] Add documentation --- doc/KubernetesLog4j.md | 97 ++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 97 insertions(+) create mode 100644 doc/KubernetesLog4j.md 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.
Supported keys
KeyDescription