From 45b48c2d0fbf6596b7fe6a75af1a458e2aa6d544 Mon Sep 17 00:00:00 2001 From: Ioannis Canellos Date: Fri, 25 Mar 2016 20:54:36 +0200 Subject: [PATCH] Add a profile listener that will automatically add the kubernetes profile when running inside kubernetes. --- .../KubernetesAutoConfiguration.java | 12 ++- .../kubernetes/KubernetesHealthIndicator.java | 36 ++------ .../cloud/kubernetes/LazilyInstantiate.java | 45 ++++++++++ .../spring/cloud/kubernetes/PodUtils.java | 35 ++++++++ .../cloud/kubernetes/StandardPodUtils.java | 66 +++++++++++++++ ...bernetesApplicationContextInitializer.java | 55 ++++++++++++ .../KubernetesProfileApplicationListener.java | 83 +++++++++++++++++++ .../main/resources/META-INF/spring.factories | 5 +- 8 files changed, 304 insertions(+), 33 deletions(-) create mode 100644 spring-cloud-kubernetes-core/src/main/java/io/fabric8/spring/cloud/kubernetes/LazilyInstantiate.java create mode 100644 spring-cloud-kubernetes-core/src/main/java/io/fabric8/spring/cloud/kubernetes/PodUtils.java create mode 100644 spring-cloud-kubernetes-core/src/main/java/io/fabric8/spring/cloud/kubernetes/StandardPodUtils.java create mode 100644 spring-cloud-kubernetes-core/src/main/java/io/fabric8/spring/cloud/kubernetes/profile/KubernetesApplicationContextInitializer.java create mode 100644 spring-cloud-kubernetes-core/src/main/java/io/fabric8/spring/cloud/kubernetes/profile/KubernetesProfileApplicationListener.java diff --git a/spring-cloud-kubernetes-core/src/main/java/io/fabric8/spring/cloud/kubernetes/KubernetesAutoConfiguration.java b/spring-cloud-kubernetes-core/src/main/java/io/fabric8/spring/cloud/kubernetes/KubernetesAutoConfiguration.java index b6edccfd..39ef0e15 100644 --- a/spring-cloud-kubernetes-core/src/main/java/io/fabric8/spring/cloud/kubernetes/KubernetesAutoConfiguration.java +++ b/spring-cloud-kubernetes-core/src/main/java/io/fabric8/spring/cloud/kubernetes/KubernetesAutoConfiguration.java @@ -80,10 +80,18 @@ public class KubernetesAutoConfiguration { @Bean @ConditionalOnMissingBean - public KubernetesHealthIndicator kubernetesHealthIndicator(KubernetesClient client) { - return new KubernetesHealthIndicator(client); + StandardPodUtils kubernetesPodUtils(KubernetesClient client) { + return new StandardPodUtils(client); } + @Bean + @ConditionalOnMissingBean + public KubernetesHealthIndicator kubernetesHealthIndicator(KubernetesClient client, StandardPodUtils podUtils) { + return new KubernetesHealthIndicator(client, podUtils); + } + + + private static D or(D dis, D dat) { if (dis != null) { return dis; diff --git a/spring-cloud-kubernetes-core/src/main/java/io/fabric8/spring/cloud/kubernetes/KubernetesHealthIndicator.java b/spring-cloud-kubernetes-core/src/main/java/io/fabric8/spring/cloud/kubernetes/KubernetesHealthIndicator.java index a6c125cf..d09e4d8c 100644 --- a/spring-cloud-kubernetes-core/src/main/java/io/fabric8/spring/cloud/kubernetes/KubernetesHealthIndicator.java +++ b/spring-cloud-kubernetes-core/src/main/java/io/fabric8/spring/cloud/kubernetes/KubernetesHealthIndicator.java @@ -17,61 +17,37 @@ package io.fabric8.spring.cloud.kubernetes; import io.fabric8.kubernetes.api.model.Pod; -import io.fabric8.kubernetes.client.Config; import io.fabric8.kubernetes.client.KubernetesClient; import org.springframework.boot.actuate.health.AbstractHealthIndicator; import org.springframework.boot.actuate.health.Health; -import java.nio.file.Paths; - public class KubernetesHealthIndicator extends AbstractHealthIndicator { - private static final String HOSTNAME = "HOSTNAME"; - - private String hostName; private KubernetesClient client; + private PodUtils utils; - public KubernetesHealthIndicator(KubernetesClient client) { + public KubernetesHealthIndicator(KubernetesClient client, PodUtils utils) { this.client = client; - this.hostName = System.getenv(HOSTNAME); } @Override protected void doHealthCheck(Health.Builder builder) throws Exception { try { - Pod current = getCurrentPod(); + Pod current = utils.currentPod().get(); if (current != null) { builder.up() - .withDetail("internal", true) - .withDetail("podName", hostName) + .withDetail("inside", true) + .withDetail("podName", current.getMetadata().getName()) .withDetail("podIp", current.getStatus().getPodIP()) .withDetail("serviceAccount", current.getSpec().getServiceAccountName()) .withDetail("nodeName", current.getSpec().getNodeName()) .withDetail("hostIp", current.getStatus().getHostIP()); } else { builder.up() - .withDetail("internal", false); + .withDetail("inside", false); } } catch (Exception e) { builder.down(e); } } - - - private Pod getCurrentPod() { - if (isServiceAccountFound() && isHostNameEnvVarPresent()) { - return client.pods().withName(hostName).get(); - } else { - return null; - } - } - - private boolean isHostNameEnvVarPresent() { - return hostName != null && !hostName.isEmpty(); - } - - private boolean isServiceAccountFound() { - return Paths.get(Config.KUBERNETES_SERVICE_ACCOUNT_TOKEN_PATH).toFile().exists() && - Paths.get(Config.KUBERNETES_SERVICE_ACCOUNT_CA_CRT_PATH).toFile().exists(); - } } \ No newline at end of file diff --git a/spring-cloud-kubernetes-core/src/main/java/io/fabric8/spring/cloud/kubernetes/LazilyInstantiate.java b/spring-cloud-kubernetes-core/src/main/java/io/fabric8/spring/cloud/kubernetes/LazilyInstantiate.java new file mode 100644 index 00000000..3e905dfc --- /dev/null +++ b/spring-cloud-kubernetes-core/src/main/java/io/fabric8/spring/cloud/kubernetes/LazilyInstantiate.java @@ -0,0 +1,45 @@ +/* + * Copyright (C) 2016 to the original authors. + * + * 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.spring.cloud.kubernetes; + + +import java.util.function.Supplier; + +public class LazilyInstantiate implements Supplier { + + public static LazilyInstantiate using(Supplier supplier) { + return new LazilyInstantiate(supplier); + } + + public synchronized T get() { + return current.get(); + } + + private LazilyInstantiate(Supplier supplier) { + this.supplier = supplier; + this.current = () -> swapper(); + } + + private final Supplier supplier; + private Supplier current; + + private T swapper() { + T obj = supplier.get(); + current = () -> obj; + return obj; + } +} diff --git a/spring-cloud-kubernetes-core/src/main/java/io/fabric8/spring/cloud/kubernetes/PodUtils.java b/spring-cloud-kubernetes-core/src/main/java/io/fabric8/spring/cloud/kubernetes/PodUtils.java new file mode 100644 index 00000000..aeca31d9 --- /dev/null +++ b/spring-cloud-kubernetes-core/src/main/java/io/fabric8/spring/cloud/kubernetes/PodUtils.java @@ -0,0 +1,35 @@ +/* + * Copyright (C) 2016 to the original authors. + * + * 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.spring.cloud.kubernetes; + +import io.fabric8.kubernetes.api.model.Pod; + +import java.util.function.Supplier; + +public interface PodUtils { + + /** + * @return A supplier of the currentPod {@link Pod}. The supplier will hold the currentPod {@Pod} if inside Kubernetes + * or false, otherwise. + */ + Supplier currentPod(); + + /** + * @return true if called from within Kubernetes, false otherwise. + */ + Boolean isInsideKubernetes(); +} diff --git a/spring-cloud-kubernetes-core/src/main/java/io/fabric8/spring/cloud/kubernetes/StandardPodUtils.java b/spring-cloud-kubernetes-core/src/main/java/io/fabric8/spring/cloud/kubernetes/StandardPodUtils.java new file mode 100644 index 00000000..4d544f23 --- /dev/null +++ b/spring-cloud-kubernetes-core/src/main/java/io/fabric8/spring/cloud/kubernetes/StandardPodUtils.java @@ -0,0 +1,66 @@ +/* + * Copyright (C) 2016 to the original authors. + * + * 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.spring.cloud.kubernetes; + +import io.fabric8.kubernetes.api.model.Pod; +import io.fabric8.kubernetes.client.Config; +import io.fabric8.kubernetes.client.KubernetesClient; + +import java.nio.file.Paths; +import java.util.function.Supplier; + +public class StandardPodUtils implements PodUtils { + + public static final String HOSTNAME = "HOSTNAME"; + + private final KubernetesClient client; + private final String hostName; + private Supplier current; + + public StandardPodUtils(KubernetesClient client) { + this.client = client; + this.hostName = System.getenv(HOSTNAME); + this.current = LazilyInstantiate.using(() -> internalGetPod()); + } + + @Override + public Supplier currentPod() { + return current; + } + + @Override + public Boolean isInsideKubernetes() { + return currentPod().get() != null; + } + + private synchronized Pod internalGetPod() { + if (isServiceAccountFound() && isHostNameEnvVarPresent()) { + return client.pods().withName(hostName).get(); + } else { + return null; + } + } + + private boolean isHostNameEnvVarPresent() { + return hostName != null && !hostName.isEmpty(); + } + + private boolean isServiceAccountFound() { + return Paths.get(Config.KUBERNETES_SERVICE_ACCOUNT_TOKEN_PATH).toFile().exists() && + Paths.get(Config.KUBERNETES_SERVICE_ACCOUNT_CA_CRT_PATH).toFile().exists(); + } +} diff --git a/spring-cloud-kubernetes-core/src/main/java/io/fabric8/spring/cloud/kubernetes/profile/KubernetesApplicationContextInitializer.java b/spring-cloud-kubernetes-core/src/main/java/io/fabric8/spring/cloud/kubernetes/profile/KubernetesApplicationContextInitializer.java new file mode 100644 index 00000000..b5e65561 --- /dev/null +++ b/spring-cloud-kubernetes-core/src/main/java/io/fabric8/spring/cloud/kubernetes/profile/KubernetesApplicationContextInitializer.java @@ -0,0 +1,55 @@ +/* + * Copyright (C) 2016 to the original authors. + * + * 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.spring.cloud.kubernetes.profile; + +import io.fabric8.kubernetes.client.DefaultKubernetesClient; +import io.fabric8.spring.cloud.kubernetes.PodUtils; +import io.fabric8.spring.cloud.kubernetes.StandardPodUtils; +import org.springframework.context.ApplicationContextInitializer; +import org.springframework.context.ConfigurableApplicationContext; +import org.springframework.core.Ordered; + +public class KubernetesApplicationContextInitializer implements + ApplicationContextInitializer, Ordered { + + private final KubernetesProfileApplicationListener listener; + private static final int ORDER = 100; + + public KubernetesApplicationContextInitializer() { + //If we are inside Kubernetes this should be perfectly valid. + //If not then we won't add the Kubernetes profile anyway. + this(new StandardPodUtils(new DefaultKubernetesClient())); + } + + public KubernetesApplicationContextInitializer(PodUtils utils) { + this(new KubernetesProfileApplicationListener(utils)); + } + + public KubernetesApplicationContextInitializer(KubernetesProfileApplicationListener listener) { + this.listener = listener; + } + + @Override + public int getOrder() { + return ORDER; + } + + @Override + public void initialize(ConfigurableApplicationContext applicationContext) { + listener.addKubernetesProfile(applicationContext.getEnvironment()); + } +} \ No newline at end of file diff --git a/spring-cloud-kubernetes-core/src/main/java/io/fabric8/spring/cloud/kubernetes/profile/KubernetesProfileApplicationListener.java b/spring-cloud-kubernetes-core/src/main/java/io/fabric8/spring/cloud/kubernetes/profile/KubernetesProfileApplicationListener.java new file mode 100644 index 00000000..b6ac779c --- /dev/null +++ b/spring-cloud-kubernetes-core/src/main/java/io/fabric8/spring/cloud/kubernetes/profile/KubernetesProfileApplicationListener.java @@ -0,0 +1,83 @@ +/* + * Copyright (C) 2016 to the original authors. + * + * 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.spring.cloud.kubernetes.profile; + +import io.fabric8.kubernetes.api.model.Pod; +import io.fabric8.spring.cloud.kubernetes.PodUtils; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.boot.context.event.ApplicationEnvironmentPreparedEvent; +import org.springframework.context.ApplicationListener; +import org.springframework.core.Ordered; +import org.springframework.core.env.ConfigurableEnvironment; +import org.springframework.core.env.Environment; + +public class KubernetesProfileApplicationListener implements ApplicationListener, Ordered { + + private static final Logger LOGGER = LoggerFactory.getLogger(KubernetesProfileApplicationListener.class); + + private static final String KUBERNETES_PROFILE = "kubernetes"; + private static final int OFFSET = 1; + private static final int ORDER = Ordered.HIGHEST_PRECEDENCE + OFFSET; + private final PodUtils utils; + + public KubernetesProfileApplicationListener(PodUtils utils) { + this.utils = utils; + } + + @Override + public void onApplicationEvent(ApplicationEnvironmentPreparedEvent event) { + ConfigurableEnvironment environment = event.getEnvironment(); + addKubernetesProfile(environment); + } + + void addKubernetesProfile(ConfigurableEnvironment environment) { + Pod current = utils.currentPod().get(); + if (current != null) { + if (!hasKubernetesProfile(environment)) { + environment.addActiveProfile(KUBERNETES_PROFILE); + } + } + + if (utils.isInsideKubernetes()) { + if (hasKubernetesProfile(environment)) { + LOGGER.debug("'kubernetes' already in list of active profiles"); + } else { + LOGGER.debug("Adding 'kubernetes' to list of active profiles"); + environment.addActiveProfile(KUBERNETES_PROFILE); + } + } else { + if (LOGGER.isDebugEnabled()) { + LOGGER.warn("Not running inside kubernetes. Skipping 'kuberntes' profile activation."); + } + } + } + + private boolean hasKubernetesProfile(Environment environment) { + for (String activeProfile : environment.getActiveProfiles()) { + if (KUBERNETES_PROFILE.equalsIgnoreCase(activeProfile)) { + return true; + } + } + return false; + } + + @Override + public int getOrder() { + return ORDER; + } +} diff --git a/spring-cloud-kubernetes-core/src/main/resources/META-INF/spring.factories b/spring-cloud-kubernetes-core/src/main/resources/META-INF/spring.factories index 25406ddf..6ed9c329 100644 --- a/spring-cloud-kubernetes-core/src/main/resources/META-INF/spring.factories +++ b/spring-cloud-kubernetes-core/src/main/resources/META-INF/spring.factories @@ -1,2 +1,5 @@ org.springframework.boot.autoconfigure.EnableAutoConfiguration=\ -io.fabric8.spring.cloud.kubernetes.KubernetesAutoConfiguration \ No newline at end of file +io.fabric8.spring.cloud.kubernetes.KubernetesAutoConfiguration + +org.springframework.context.ApplicationContextInitializer=\ +io.fabric8.spring.cloud.kubernetes.profile.KubernetesApplicationContextInitializer