diff --git a/spring-cloud-kubernetes-commons/src/main/java/org/springframework/cloud/kubernetes/commons/leader/Leader.java b/spring-cloud-kubernetes-commons/src/main/java/org/springframework/cloud/kubernetes/commons/leader/Leader.java index fa179fe4..7076c918 100644 --- a/spring-cloud-kubernetes-commons/src/main/java/org/springframework/cloud/kubernetes/commons/leader/Leader.java +++ b/spring-cloud-kubernetes-commons/src/main/java/org/springframework/cloud/kubernetes/commons/leader/Leader.java @@ -47,7 +47,7 @@ public class Leader { return false; } - return Objects.equals(this.role, candidate.getRole()) && Objects.equals(this.id, candidate.getId()); + return Objects.equals(role, candidate.getRole()) && Objects.equals(id, candidate.getId()); } @Override diff --git a/spring-cloud-kubernetes-commons/src/main/java/org/springframework/cloud/kubernetes/commons/leader/LeaderInfoContributor.java b/spring-cloud-kubernetes-commons/src/main/java/org/springframework/cloud/kubernetes/commons/leader/LeaderInfoContributor.java index d49f48ed..d2189556 100644 --- a/spring-cloud-kubernetes-commons/src/main/java/org/springframework/cloud/kubernetes/commons/leader/LeaderInfoContributor.java +++ b/spring-cloud-kubernetes-commons/src/main/java/org/springframework/cloud/kubernetes/commons/leader/LeaderInfoContributor.java @@ -1,5 +1,5 @@ /* - * Copyright 2019-2019 the original author or authors. + * Copyright 2019-2024 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -18,7 +18,6 @@ package org.springframework.cloud.kubernetes.commons.leader; import java.util.HashMap; import java.util.Map; -import java.util.Optional; import org.springframework.boot.actuate.info.Info.Builder; import org.springframework.boot.actuate.info.InfoContributor; @@ -38,16 +37,12 @@ public class LeaderInfoContributor implements InfoContributor { @Override public void contribute(Builder builder) { Map details = new HashMap<>(); - Optional leader = leadershipController.getLocalLeader(); - if (leader.isPresent()) { - Leader l = leader.get(); - details.put("leaderId", l.getId()); - details.put("role", l.getRole()); - details.put("isLeader", l.isCandidate(candidate)); - } - else { - details.put("leaderId", "Unknown"); - } + leadershipController.getLocalLeader().ifPresentOrElse(leader -> { + details.put("leaderId", leader.getId()); + details.put("role", leader.getRole()); + details.put("isLeader", leader.isCandidate(candidate)); + }, () -> details.put("leaderId", "Unknown")); + builder.withDetail("leaderElection", details); } diff --git a/spring-cloud-kubernetes-commons/src/test/java/org/springframework/cloud/kubernetes/commons/leader/LeaderInfoContributorTests.java b/spring-cloud-kubernetes-commons/src/test/java/org/springframework/cloud/kubernetes/commons/leader/LeaderInfoContributorTests.java new file mode 100644 index 00000000..941d3775 --- /dev/null +++ b/spring-cloud-kubernetes-commons/src/test/java/org/springframework/cloud/kubernetes/commons/leader/LeaderInfoContributorTests.java @@ -0,0 +1,93 @@ +/* + * Copyright 2013-2024 the original author or 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 + * + * https://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.commons.leader; + +import java.util.Map; + +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.Test; +import org.mockito.Mockito; + +import org.springframework.boot.actuate.info.Info; +import org.springframework.integration.leader.Candidate; +import org.springframework.integration.leader.DefaultCandidate; +import org.springframework.integration.leader.event.LeaderEventPublisher; + +/** + * @author wind57 + */ +class LeaderInfoContributorTests { + + @Test + void testLeaderMissing() { + + Candidate candidate = new DefaultCandidate("id", "role"); + LeaderProperties leaderProperties = new LeaderProperties(); + LeaderEventPublisher leaderEventPublisher = Mockito.mock(LeaderEventPublisher.class); + LeadershipController leadershipController = new LeadershipControllerStub(candidate, leaderProperties, + leaderEventPublisher); + + LeaderInfoContributor leaderInfoContributor = new LeaderInfoContributor(leadershipController, candidate); + Info.Builder builder = new Info.Builder(); + leaderInfoContributor.contribute(builder); + + Assertions.assertEquals(builder.build().getDetails().get("leaderElection"), Map.of("leaderId", "Unknown")); + } + + @Test + void testLeaderPresentIsLeader() { + + Candidate candidate = new DefaultCandidate("leaderId", "leaderRole"); + LeaderProperties leaderProperties = new LeaderProperties(); + LeaderEventPublisher leaderEventPublisher = Mockito.mock(LeaderEventPublisher.class); + LeadershipController leadershipController = new LeadershipControllerStub(candidate, leaderProperties, + leaderEventPublisher); + + Leader leader = new Leader("leaderRole", "leaderId"); + + leadershipController.handleLeaderChange(leader); + + LeaderInfoContributor leaderInfoContributor = new LeaderInfoContributor(leadershipController, candidate); + Info.Builder builder = new Info.Builder(); + leaderInfoContributor.contribute(builder); + + Assertions.assertEquals(builder.build().getDetails().get("leaderElection"), + Map.of("role", "leaderRole", "isLeader", true, "leaderId", "leaderId")); + } + + @Test + void testLeaderPresentIsNotLeader() { + + Candidate candidate = new DefaultCandidate("leaderId", "notLeaderRole"); + LeaderProperties leaderProperties = new LeaderProperties(); + LeaderEventPublisher leaderEventPublisher = Mockito.mock(LeaderEventPublisher.class); + LeadershipController leadershipController = new LeadershipControllerStub(candidate, leaderProperties, + leaderEventPublisher); + + Leader leader = new Leader("leaderRole", "leaderId"); + + leadershipController.handleLeaderChange(leader); + + LeaderInfoContributor leaderInfoContributor = new LeaderInfoContributor(leadershipController, candidate); + Info.Builder builder = new Info.Builder(); + leaderInfoContributor.contribute(builder); + + Assertions.assertEquals(builder.build().getDetails().get("leaderElection"), + Map.of("role", "leaderRole", "isLeader", false, "leaderId", "leaderId")); + } + +} diff --git a/spring-cloud-kubernetes-commons/src/test/java/org/springframework/cloud/kubernetes/commons/leader/LeadershipControllerStub.java b/spring-cloud-kubernetes-commons/src/test/java/org/springframework/cloud/kubernetes/commons/leader/LeadershipControllerStub.java new file mode 100644 index 00000000..2d5a5aba --- /dev/null +++ b/spring-cloud-kubernetes-commons/src/test/java/org/springframework/cloud/kubernetes/commons/leader/LeadershipControllerStub.java @@ -0,0 +1,61 @@ +/* + * Copyright 2013-2024 the original author or 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 + * + * https://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.commons.leader; + +import java.util.Optional; + +import org.springframework.integration.leader.Candidate; +import org.springframework.integration.leader.event.LeaderEventPublisher; + +/** + * @author wind57 + */ +final class LeadershipControllerStub extends LeadershipController { + + private Leader leader; + + LeadershipControllerStub(Candidate candidate, LeaderProperties leaderProperties, + LeaderEventPublisher leaderEventPublisher) { + super(candidate, leaderProperties, leaderEventPublisher); + } + + @Override + public void update() { + + } + + @Override + public void revoke() { + + } + + @Override + protected PodReadinessWatcher createPodReadinessWatcher(String localLeaderId) { + return null; + } + + @Override + protected void handleLeaderChange(Leader leader) { + this.leader = leader; + } + + @Override + public Optional getLocalLeader() { + return Optional.ofNullable(leader); + } + +} diff --git a/spring-cloud-kubernetes-fabric8-leader/src/main/java/org/springframework/cloud/kubernetes/fabric8/leader/Fabric8LeaderAutoConfiguration.java b/spring-cloud-kubernetes-fabric8-leader/src/main/java/org/springframework/cloud/kubernetes/fabric8/leader/Fabric8LeaderAutoConfiguration.java index 7e588191..a8fd8147 100644 --- a/spring-cloud-kubernetes-fabric8-leader/src/main/java/org/springframework/cloud/kubernetes/fabric8/leader/Fabric8LeaderAutoConfiguration.java +++ b/spring-cloud-kubernetes-fabric8-leader/src/main/java/org/springframework/cloud/kubernetes/fabric8/leader/Fabric8LeaderAutoConfiguration.java @@ -47,20 +47,38 @@ import org.springframework.integration.leader.event.LeaderEventPublisher; @ConditionalOnProperty(value = "spring.cloud.kubernetes.leader.enabled", matchIfMissing = true) public class Fabric8LeaderAutoConfiguration { + /* + * Used for publishing application events that happen: granted, revoked or failed to + * acquire mutex. + */ @Bean @ConditionalOnMissingBean(LeaderEventPublisher.class) public LeaderEventPublisher defaultLeaderEventPublisher(ApplicationEventPublisher applicationEventPublisher) { return new DefaultLeaderEventPublisher(applicationEventPublisher); } + /* + * This can be thought as "self" or the pod that participates in leader election + * process. The implementation that we return simply logs events that happen during + * that process. + */ @Bean public Candidate candidate(LeaderProperties leaderProperties) throws UnknownHostException { String id = LeaderUtils.hostName(); String role = leaderProperties.getRole(); - return new DefaultCandidate(id, role); } + /* + * Add an info contributor with leader information. + */ + @Bean + @ConditionalOnClass(InfoContributor.class) + public LeaderInfoContributor leaderInfoContributor(Fabric8LeadershipController fabric8LeadershipController, + Candidate candidate) { + return new LeaderInfoContributor(fabric8LeadershipController, candidate); + } + @Bean public Fabric8LeadershipController leadershipController(Candidate candidate, LeaderProperties leaderProperties, LeaderEventPublisher leaderEventPublisher, KubernetesClient kubernetesClient) { @@ -87,11 +105,4 @@ public class Fabric8LeaderAutoConfiguration { hostPodWatcher); } - @Bean - @ConditionalOnClass(InfoContributor.class) - public LeaderInfoContributor leaderInfoContributor(Fabric8LeadershipController fabric8LeadershipController, - Candidate candidate) { - return new LeaderInfoContributor(fabric8LeadershipController, candidate); - } - } diff --git a/spring-cloud-kubernetes-fabric8-leader/src/test/java/org/springframework/cloud/kubernetes/fabric8/leader/LeaderInfoContributorTest.java b/spring-cloud-kubernetes-fabric8-leader/src/test/java/org/springframework/cloud/kubernetes/fabric8/leader/LeaderInfoContributorTest.java deleted file mode 100644 index 40931ca2..00000000 --- a/spring-cloud-kubernetes-fabric8-leader/src/test/java/org/springframework/cloud/kubernetes/fabric8/leader/LeaderInfoContributorTest.java +++ /dev/null @@ -1,100 +0,0 @@ -/* - * Copyright 2019-2019 the original author or 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 - * - * https://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.fabric8.leader; - -import java.util.Map; -import java.util.Optional; - -import org.junit.jupiter.api.BeforeEach; -import org.junit.jupiter.api.Test; -import org.junit.jupiter.api.extension.ExtendWith; -import org.mockito.Mock; -import org.mockito.junit.jupiter.MockitoExtension; - -import org.springframework.boot.actuate.info.Info; -import org.springframework.cloud.kubernetes.commons.leader.Leader; -import org.springframework.cloud.kubernetes.commons.leader.LeaderInfoContributor; -import org.springframework.integration.leader.Candidate; - -import static org.assertj.core.api.Assertions.assertThat; -import static org.mockito.BDDMockito.given; - -@ExtendWith(MockitoExtension.class) -public class LeaderInfoContributorTest { - - @Mock - private Candidate mockCandidate; - - @Mock - private Fabric8LeadershipController mockFabric8LeadershipController; - - @Mock - private Leader mockLeader; - - private LeaderInfoContributor leaderInfoContributor; - - @BeforeEach - public void before() { - this.leaderInfoContributor = new LeaderInfoContributor(this.mockFabric8LeadershipController, - this.mockCandidate); - } - - @SuppressWarnings("unchecked") - @Test - public void infoWithoutLeader() { - Info.Builder builder = new Info.Builder(); - - leaderInfoContributor.contribute(builder); - - Map details = (Map) builder.build().get("leaderElection"); - assertThat(details).containsEntry("leaderId", "Unknown"); - } - - @SuppressWarnings("unchecked") - @Test - public void infoWhenLeader() { - given(this.mockFabric8LeadershipController.getLocalLeader()).willReturn(Optional.of(this.mockLeader)); - given(this.mockLeader.isCandidate(this.mockCandidate)).willReturn(true); - given(this.mockLeader.getRole()).willReturn("testRole"); - given(this.mockLeader.getId()).willReturn("id"); - Info.Builder builder = new Info.Builder(); - - leaderInfoContributor.contribute(builder); - - Map details = (Map) builder.build().get("leaderElection"); - assertThat(details).containsEntry("isLeader", true); - assertThat(details).containsEntry("leaderId", "id"); - assertThat(details).containsEntry("role", "testRole"); - } - - @SuppressWarnings("unchecked") - @Test - public void infoWhenAnotherIsLeader() { - given(this.mockFabric8LeadershipController.getLocalLeader()).willReturn(Optional.of(this.mockLeader)); - given(this.mockLeader.getRole()).willReturn("testRole"); - given(this.mockLeader.getId()).willReturn("id"); - Info.Builder builder = new Info.Builder(); - - leaderInfoContributor.contribute(builder); - - Map details = (Map) builder.build().get("leaderElection"); - assertThat(details).containsEntry("isLeader", false); - assertThat(details).containsEntry("leaderId", "id"); - assertThat(details).containsEntry("role", "testRole"); - } - -} diff --git a/spring-cloud-kubernetes-fabric8-leader/src/test/java/org/springframework/cloud/kubernetes/fabric8/leader/LeaderTest.java b/spring-cloud-kubernetes-fabric8-leader/src/test/java/org/springframework/cloud/kubernetes/fabric8/leader/LeaderTest.java index e5d6619f..7ac9b62f 100644 --- a/spring-cloud-kubernetes-fabric8-leader/src/test/java/org/springframework/cloud/kubernetes/fabric8/leader/LeaderTest.java +++ b/spring-cloud-kubernetes-fabric8-leader/src/test/java/org/springframework/cloud/kubernetes/fabric8/leader/LeaderTest.java @@ -1,5 +1,5 @@ /* - * Copyright 2013-2019 the original author or authors. + * Copyright 2013-2024 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -18,57 +18,48 @@ package org.springframework.cloud.kubernetes.fabric8.leader; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; -import org.junit.jupiter.api.extension.ExtendWith; -import org.mockito.Mock; -import org.mockito.junit.jupiter.MockitoExtension; import org.springframework.cloud.kubernetes.commons.leader.Leader; import org.springframework.integration.leader.Candidate; +import org.springframework.integration.leader.DefaultCandidate; import static org.assertj.core.api.Assertions.assertThat; -import static org.mockito.BDDMockito.given; /** * @author Gytis Trikleris */ -@ExtendWith(MockitoExtension.class) -public class LeaderTest { +class LeaderTest { private static final String ROLE = "test-role"; private static final String ID = "test-id"; - @Mock - private Candidate mockCandidate; - private Leader leader; @BeforeEach - public void before() { - this.leader = new Leader(ROLE, ID); + void before() { + leader = new Leader(ROLE, ID); } @Test - public void shouldGetRole() { - assertThat(this.leader.getRole()).isEqualTo(ROLE); + void shouldGetRole() { + assertThat(leader.getRole()).isEqualTo(ROLE); } @Test - public void shouldGetId() { - assertThat(this.leader.getId()).isEqualTo(ID); + void shouldGetId() { + assertThat(leader.getId()).isEqualTo(ID); } @Test - public void shouldCheckWithNullCandidate() { - assertThat(this.leader.isCandidate(null)).isEqualTo(false); + void shouldCheckWithNullCandidate() { + assertThat(leader.isCandidate(null)).isEqualTo(false); } @Test - public void shouldCheckCandidate() { - given(this.mockCandidate.getId()).willReturn(ID); - given(this.mockCandidate.getRole()).willReturn(ROLE); - - assertThat(this.leader.isCandidate(this.mockCandidate)).isTrue(); + void shouldCheckCandidate() { + Candidate candidate = new DefaultCandidate(ID, ROLE); + assertThat(leader.isCandidate(candidate)).isTrue(); } }