diff --git a/spring-cloud-kubernetes-leader/src/main/java/org/springframework/cloud/kubernetes/leader/LeaderAutoConfiguration.java b/spring-cloud-kubernetes-leader/src/main/java/org/springframework/cloud/kubernetes/leader/LeaderAutoConfiguration.java new file mode 100644 index 00000000..149c692d --- /dev/null +++ b/spring-cloud-kubernetes-leader/src/main/java/org/springframework/cloud/kubernetes/leader/LeaderAutoConfiguration.java @@ -0,0 +1,50 @@ +/* + * Copyright 2018 Red Hat, Inc, and individual contributors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.cloud.kubernetes.leader; + +import java.net.Inet4Address; +import java.net.UnknownHostException; + +import io.fabric8.kubernetes.client.KubernetesClient; +import org.springframework.boot.autoconfigure.condition.ConditionalOnBean; +import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; +import org.springframework.boot.context.properties.EnableConfigurationProperties; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.integration.leader.Candidate; +import org.springframework.integration.leader.DefaultCandidate; + +/** + * @author Gytis Trikleris + */ +@Configuration +@EnableConfigurationProperties(LeaderProperties.class) +@ConditionalOnBean(KubernetesClient.class) +@ConditionalOnProperty(value = "spring.cloud.kubernetes.leader.enabled", matchIfMissing = true) +public class LeaderAutoConfiguration { + + @Bean + public LeaderInitiator leaderInitiator(KubernetesClient kubernetesClient, LeaderProperties leaderProperties) + throws UnknownHostException { + String candidateId = Inet4Address.getLocalHost().getHostName(); + Candidate candidate = new DefaultCandidate(candidateId, leaderProperties.getRole()); + LeaderInitiator leaderInitiator = new LeaderInitiator(kubernetesClient, candidate, leaderProperties); + leaderInitiator.start(); + return leaderInitiator; + } + +} diff --git a/spring-cloud-kubernetes-leader/src/main/java/org/springframework/cloud/kubernetes/leader/LeaderInitiator.java b/spring-cloud-kubernetes-leader/src/main/java/org/springframework/cloud/kubernetes/leader/LeaderInitiator.java index 079b6cd9..3e17a9b5 100644 --- a/spring-cloud-kubernetes-leader/src/main/java/org/springframework/cloud/kubernetes/leader/LeaderInitiator.java +++ b/spring-cloud-kubernetes-leader/src/main/java/org/springframework/cloud/kubernetes/leader/LeaderInitiator.java @@ -42,22 +42,21 @@ public class LeaderInitiator implements SmartLifecycle { private final Candidate candidate; - private final LeaderConfiguration leaderConfiguration; + private final LeaderProperties leaderProperties; private ScheduledExecutorService scheduledExecutorService; private String currentLeaderId; // TODO remove once events are implemented - public LeaderInitiator(KubernetesClient kubernetesClient, Candidate candidate, - LeaderConfiguration leaderConfiguration) { + public LeaderInitiator(KubernetesClient kubernetesClient, Candidate candidate, LeaderProperties leaderProperties) { this.kubernetesClient = kubernetesClient; this.candidate = candidate; - this.leaderConfiguration = leaderConfiguration; + this.leaderProperties = leaderProperties; } @Override public boolean isAutoStartup() { - return leaderConfiguration.isAutoStartup(); + return leaderProperties.isAutoStartup(); } @Override @@ -115,40 +114,40 @@ public class LeaderInitiator implements SmartLifecycle { try { takeLeadership(configMap); currentLeaderId = candidate.getId(); - scheduleUpdate(leaderConfiguration.getLeaseDuration()); + scheduleUpdate(leaderProperties.getLeaseDuration()); } catch (Exception e) { // Leadership takeover failed, try again later System.out.println("Leadership takeover failed: " + e.getMessage()); - scheduleUpdate(leaderConfiguration.getRetryPeriod()); + scheduleUpdate(leaderProperties.getRetryPeriod()); } } else if (!isValidLeader(leader)) { System.out.println("Old leader is not valid any more, try to take over"); try { takeLeadership(configMap); currentLeaderId = candidate.getId(); - scheduleUpdate(leaderConfiguration.getLeaseDuration()); // Is this needed? + scheduleUpdate(leaderProperties.getLeaseDuration()); // Is this needed? } catch (Exception e) { // Leadership takeover failed, try again later System.out.println("Leadership takeover failed: " + e.getMessage()); - scheduleUpdate(leaderConfiguration.getRetryPeriod()); + scheduleUpdate(leaderProperties.getRetryPeriod()); } } else if (!isCandidateALeader(leader)) { currentLeaderId = leader.getId(); System.out.println(currentLeaderId + " is a leader, check in later"); - scheduleUpdate(leaderConfiguration.getLeaseDuration()); + scheduleUpdate(leaderProperties.getLeaseDuration()); } else { System.out.println("I am a leader, check in later"); currentLeaderId = candidate.getId(); - scheduleUpdate(leaderConfiguration.getLeaseDuration()); // Is this needed? + scheduleUpdate(leaderProperties.getLeaseDuration()); // Is this needed? } } private void takeLeadership(ConfigMap oldConfigMap) { - String leaderIdKey = leaderConfiguration.getLeaderIdPrefix() + candidate.getRole(); + String leaderIdKey = leaderProperties.getLeaderIdPrefix() + candidate.getRole(); if (oldConfigMap == null) { ConfigMap newConfigMap = new ConfigMapBuilder().withNewMetadata() - .withName(leaderConfiguration.getConfigMapName()) + .withName(leaderProperties.getConfigMapName()) .addToLabels("provider", "spring-cloud-kubernetes") .addToLabels("kind", "locks") .endMetadata() @@ -156,7 +155,7 @@ public class LeaderInitiator implements SmartLifecycle { .build(); kubernetesClient.configMaps() - .inNamespace(leaderConfiguration.getNamespace(kubernetesClient.getNamespace())) + .inNamespace(leaderProperties.getNamespace(kubernetesClient.getNamespace())) .create(newConfigMap); } else { ConfigMap newConfigMap = new ConfigMapBuilder(oldConfigMap) @@ -164,8 +163,8 @@ public class LeaderInitiator implements SmartLifecycle { .build(); kubernetesClient.configMaps() - .inNamespace(leaderConfiguration.getNamespace(kubernetesClient.getNamespace())) - .withName(leaderConfiguration.getConfigMapName()) + .inNamespace(leaderProperties.getNamespace(kubernetesClient.getNamespace())) + .withName(leaderProperties.getConfigMapName()) .lockResourceVersion(oldConfigMap.getMetadata().getResourceVersion()) .replace(newConfigMap); } @@ -176,8 +175,8 @@ public class LeaderInitiator implements SmartLifecycle { private ConfigMap getConfigMap() { try { return kubernetesClient.configMaps() - .inNamespace(leaderConfiguration.getNamespace(kubernetesClient.getNamespace())) - .withName(leaderConfiguration.getConfigMapName()) + .inNamespace(leaderProperties.getNamespace(kubernetesClient.getNamespace())) + .withName(leaderProperties.getConfigMapName()) .get(); } catch (Exception e) { System.out.println("Failed to get a ConfigMap: " + e.getMessage()); @@ -191,7 +190,7 @@ public class LeaderInitiator implements SmartLifecycle { } Map data = configMap.getData(); - String leaderIdKey = leaderConfiguration.getLeaderIdPrefix() + candidate.getRole(); + String leaderIdKey = leaderProperties.getLeaderIdPrefix() + candidate.getRole(); String leaderId = data.get(leaderIdKey); if (leaderId == null) { return null; @@ -205,7 +204,7 @@ public class LeaderInitiator implements SmartLifecycle { } private long jitter(long num) { - return (long) (num * (1 + Math.random() * (leaderConfiguration.getJitterFactor() - 1))); + return (long) (num * (1 + Math.random() * (leaderProperties.getJitterFactor() - 1))); } private boolean isCandidateALeader(Leader leader) { @@ -214,8 +213,8 @@ public class LeaderInitiator implements SmartLifecycle { private boolean isValidLeader(Leader leader) { return kubernetesClient.pods() - .inNamespace(leaderConfiguration.getNamespace(kubernetesClient.getNamespace())) - .withLabels(leaderConfiguration.getLabels()) + .inNamespace(leaderProperties.getNamespace(kubernetesClient.getNamespace())) + .withLabels(leaderProperties.getLabels()) .list() .getItems() .stream() diff --git a/spring-cloud-kubernetes-leader/src/main/java/org/springframework/cloud/kubernetes/leader/LeaderConfiguration.java b/spring-cloud-kubernetes-leader/src/main/java/org/springframework/cloud/kubernetes/leader/LeaderProperties.java similarity index 73% rename from spring-cloud-kubernetes-leader/src/main/java/org/springframework/cloud/kubernetes/leader/LeaderConfiguration.java rename to spring-cloud-kubernetes-leader/src/main/java/org/springframework/cloud/kubernetes/leader/LeaderProperties.java index 0f646a7f..37926512 100644 --- a/spring-cloud-kubernetes-leader/src/main/java/org/springframework/cloud/kubernetes/leader/LeaderConfiguration.java +++ b/spring-cloud-kubernetes-leader/src/main/java/org/springframework/cloud/kubernetes/leader/LeaderProperties.java @@ -19,10 +19,13 @@ package org.springframework.cloud.kubernetes.leader; import java.util.Collections; import java.util.Map; +import org.springframework.boot.context.properties.ConfigurationProperties; + /** * @author Gytis Trikleris */ -public class LeaderConfiguration { +@ConfigurationProperties("spring.cloud.kubernetes.leader") +public class LeaderProperties { private static final String DEFAULT_LEADER_ID_PREFIX = "leader.id."; @@ -36,20 +39,56 @@ public class LeaderConfiguration { private static final double DEFAULT_JITTER_FACTOR = 1.2; + /** + * Should leader election be started automatically on startup. + * Default: true + */ private boolean autoStartup = DEFAULT_AUTO_STARTUP; + /** + * Role for which leadership this candidate will compete. + */ + private String role; + + /** + * Kubernetes namespace where the leaders ConfigMap and candidates are located. + */ private String namespace; + /** + * Kubernetes ConfigMap where leaders information will be stored. + * Default: leaders + */ private String configMapName = DEFAULT_CONFIG_MAP_NAME; + /** + * Kubernetes labels common to all leadership candidates. + * Default: empty + */ private Map labels = Collections.emptyMap(); + /** + * Leader id property prefix for the ConfigMap. + * Default: leader.id. + */ private String leaderIdPrefix = DEFAULT_LEADER_ID_PREFIX; + /** + * Time period after which leader should check it's leadership or new leader should be elected. + * Default: 30s + */ private long leaseDuration = DEFAULT_LEASE_DURATION; + /** + * Time period after connections should be retired after failure. + * Default: 5s + */ private long retryPeriod = DEFAULT_RETRY_PERIOD; + /** + * A parameter to randomise scheduler. + * Default: 1.2 + */ private double jitterFactor = DEFAULT_JITTER_FACTOR; public boolean isAutoStartup() { @@ -60,6 +99,14 @@ public class LeaderConfiguration { this.autoStartup = autoStartup; } + public String getRole() { + return role; + } + + public void setRole(String role) { + this.role = role; + } + public String getNamespace() { return namespace; } diff --git a/spring-cloud-kubernetes-leader/src/main/resources/META-INF/spring.factories b/spring-cloud-kubernetes-leader/src/main/resources/META-INF/spring.factories new file mode 100644 index 00000000..6c05ac37 --- /dev/null +++ b/spring-cloud-kubernetes-leader/src/main/resources/META-INF/spring.factories @@ -0,0 +1,2 @@ +org.springframework.boot.autoconfigure.EnableAutoConfiguration=\ +org.springframework.cloud.kubernetes.leader.LeaderAutoConfiguration