diff --git a/spring-cloud-kubernetes-core/pom.xml b/spring-cloud-kubernetes-core/pom.xml index c0b8b36d..0c1862ce 100644 --- a/spring-cloud-kubernetes-core/pom.xml +++ b/spring-cloud-kubernetes-core/pom.xml @@ -82,6 +82,11 @@ spring-boot-starter-web test + + org.springframework.boot + spring-boot-starter-webflux + test + io.fabric8 kubernetes-client @@ -108,11 +113,6 @@ spock-spring test - - io.rest-assured - rest-assured - test - org.springframework.boot spring-boot-starter-actuator diff --git a/spring-cloud-kubernetes-core/src/test/java/org/springframework/cloud/kubernetes/HealthIndicatorTest.java b/spring-cloud-kubernetes-core/src/test/java/org/springframework/cloud/kubernetes/HealthIndicatorTest.java index 3c08fa44..1375d107 100644 --- a/spring-cloud-kubernetes-core/src/test/java/org/springframework/cloud/kubernetes/HealthIndicatorTest.java +++ b/spring-cloud-kubernetes-core/src/test/java/org/springframework/cloud/kubernetes/HealthIndicatorTest.java @@ -19,19 +19,20 @@ package org.springframework.cloud.kubernetes; import io.fabric8.kubernetes.client.Config; import io.fabric8.kubernetes.client.KubernetesClient; import io.fabric8.kubernetes.server.mock.KubernetesServer; -import io.restassured.RestAssured; import org.junit.BeforeClass; import org.junit.ClassRule; import org.junit.Test; import org.junit.runner.RunWith; +import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Value; import org.springframework.boot.test.context.SpringBootTest; import org.springframework.cloud.kubernetes.example.App; +import org.springframework.http.MediaType; import org.springframework.test.context.junit4.SpringRunner; +import org.springframework.test.web.reactive.server.WebTestClient; -import static io.restassured.RestAssured.given; -import static org.hamcrest.core.StringContains.containsString; +import static org.hamcrest.Matchers.containsString; @RunWith(SpringRunner.class) @SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT, classes = App.class, properties = { @@ -43,6 +44,9 @@ public class HealthIndicatorTest { private static KubernetesClient mockClient; + @Autowired + private WebTestClient webClient; + @Value("${local.server.port}") private int port; @@ -62,10 +66,9 @@ public class HealthIndicatorTest { @Test public void healthEndpointShouldContainKubernetes() { - RestAssured.baseURI = String.format("http://localhost:%d/actuator/health", - this.port); - given().contentType("application/json").get().then().statusCode(200) - .body(containsString("kubernetes")); + this.webClient.get().uri("http://localhost:{port}/actuator/health", this.port) + .accept(MediaType.APPLICATION_JSON).exchange().expectStatus().isOk() + .expectBody(String.class).value(containsString("kubernetes")); } } diff --git a/spring-cloud-kubernetes-core/src/test/java/org/springframework/cloud/kubernetes/InfoContributorTest.java b/spring-cloud-kubernetes-core/src/test/java/org/springframework/cloud/kubernetes/InfoContributorTest.java index 278dc19b..bb5d67d1 100644 --- a/spring-cloud-kubernetes-core/src/test/java/org/springframework/cloud/kubernetes/InfoContributorTest.java +++ b/spring-cloud-kubernetes-core/src/test/java/org/springframework/cloud/kubernetes/InfoContributorTest.java @@ -19,19 +19,20 @@ package org.springframework.cloud.kubernetes; import io.fabric8.kubernetes.client.Config; import io.fabric8.kubernetes.client.KubernetesClient; import io.fabric8.kubernetes.server.mock.KubernetesServer; -import io.restassured.RestAssured; import org.junit.BeforeClass; import org.junit.ClassRule; import org.junit.Test; import org.junit.runner.RunWith; +import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Value; import org.springframework.boot.test.context.SpringBootTest; import org.springframework.cloud.kubernetes.example.App; +import org.springframework.http.MediaType; import org.springframework.test.context.junit4.SpringRunner; +import org.springframework.test.web.reactive.server.WebTestClient; -import static io.restassured.RestAssured.given; -import static org.hamcrest.core.StringContains.containsString; +import static org.hamcrest.Matchers.containsString; @RunWith(SpringRunner.class) @SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT, classes = App.class) @@ -42,6 +43,9 @@ public class InfoContributorTest { private static KubernetesClient mockClient; + @Autowired + private WebTestClient webClient; + @Value("${local.server.port}") private int port; @@ -61,10 +65,9 @@ public class InfoContributorTest { @Test public void infoEndpointShouldContainKubernetes() { - RestAssured.baseURI = String.format("http://localhost:%d/actuator/info", - this.port); - given().contentType("application/json").get().then().statusCode(200) - .body(containsString("kubernetes")); + this.webClient.get().uri("http://localhost:{port}/actuator/info", this.port) + .accept(MediaType.APPLICATION_JSON).exchange().expectStatus().isOk() + .expectBody(String.class).value(containsString("kubernetes")); } } diff --git a/spring-cloud-kubernetes-leader/pom.xml b/spring-cloud-kubernetes-leader/pom.xml index a4874046..ea8a3f36 100644 --- a/spring-cloud-kubernetes-leader/pom.xml +++ b/spring-cloud-kubernetes-leader/pom.xml @@ -37,11 +37,21 @@ org.springframework.integration spring-integration-core + + org.springframework.boot + spring-boot-starter-actuator + true + org.springframework.boot spring-boot-starter-web test + + org.springframework.boot + spring-boot-starter-webflux + test + org.springframework.boot spring-boot-starter-test 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 index 7687e13a..bb0ec4e6 100644 --- 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 @@ -21,7 +21,9 @@ import java.net.UnknownHostException; import io.fabric8.kubernetes.client.KubernetesClient; +import org.springframework.boot.actuate.info.InfoContributor; import org.springframework.boot.autoconfigure.condition.ConditionalOnBean; +import org.springframework.boot.autoconfigure.condition.ConditionalOnClass; import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean; import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; import org.springframework.boot.context.properties.EnableConfigurationProperties; @@ -90,4 +92,11 @@ public class LeaderAutoConfiguration { leaderRecordWatcher, hostPodWatcher); } + @Bean + @ConditionalOnClass(InfoContributor.class) + public LeaderInfoContributor leaderInfoContributor( + LeadershipController leadershipController, Candidate candidate) { + return new LeaderInfoContributor(leadershipController, candidate); + } + } diff --git a/spring-cloud-kubernetes-leader/src/main/java/org/springframework/cloud/kubernetes/leader/LeaderInfoContributor.java b/spring-cloud-kubernetes-leader/src/main/java/org/springframework/cloud/kubernetes/leader/LeaderInfoContributor.java new file mode 100644 index 00000000..965b39a7 --- /dev/null +++ b/spring-cloud-kubernetes-leader/src/main/java/org/springframework/cloud/kubernetes/leader/LeaderInfoContributor.java @@ -0,0 +1,55 @@ +/* + * 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 + * + * 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.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; +import org.springframework.integration.leader.Candidate; + +public class LeaderInfoContributor implements InfoContributor { + + private final LeadershipController leadershipController; + + private final Candidate candidate; + + public LeaderInfoContributor(LeadershipController leadershipController, + Candidate candidate) { + this.leadershipController = leadershipController; + this.candidate = candidate; + } + + @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"); + } + builder.withDetail("leaderElection", details); + } + +} diff --git a/spring-cloud-kubernetes-leader/src/test/java/org/springframework/cloud/kubernetes/leader/LeaderAutoConfigurationTests.java b/spring-cloud-kubernetes-leader/src/test/java/org/springframework/cloud/kubernetes/leader/LeaderAutoConfigurationTests.java index e169f0e4..5691489f 100644 --- a/spring-cloud-kubernetes-leader/src/test/java/org/springframework/cloud/kubernetes/leader/LeaderAutoConfigurationTests.java +++ b/spring-cloud-kubernetes-leader/src/test/java/org/springframework/cloud/kubernetes/leader/LeaderAutoConfigurationTests.java @@ -19,10 +19,16 @@ package org.springframework.cloud.kubernetes.leader; import org.junit.Test; import org.junit.runner.RunWith; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.beans.factory.annotation.Value; import org.springframework.boot.SpringBootConfiguration; import org.springframework.boot.autoconfigure.EnableAutoConfiguration; import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.http.MediaType; import org.springframework.test.context.junit4.SpringRunner; +import org.springframework.test.web.reactive.server.WebTestClient; + +import static org.hamcrest.Matchers.containsString; @RunWith(SpringRunner.class) @SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT, properties = { @@ -31,10 +37,23 @@ import org.springframework.test.context.junit4.SpringRunner; }) public class LeaderAutoConfigurationTests { + @Value("${local.server.port}") + private int port; + + @Autowired + private WebTestClient webClient; + @Test public void contextLoads() { } + @Test + public void infoEndpointShouldContainLeaderElection() { + this.webClient.get().uri("http://localhost:{port}/actuator/info", this.port) + .accept(MediaType.APPLICATION_JSON).exchange().expectStatus().isOk() + .expectBody(String.class).value(containsString("kubernetes")); + } + @SpringBootConfiguration @EnableAutoConfiguration protected static class TestConfig { diff --git a/spring-cloud-kubernetes-leader/src/test/java/org/springframework/cloud/kubernetes/leader/LeaderInfoContributorTest.java b/spring-cloud-kubernetes-leader/src/test/java/org/springframework/cloud/kubernetes/leader/LeaderInfoContributorTest.java new file mode 100644 index 00000000..39c6b76c --- /dev/null +++ b/spring-cloud-kubernetes-leader/src/test/java/org/springframework/cloud/kubernetes/leader/LeaderInfoContributorTest.java @@ -0,0 +1,103 @@ +/* + * 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 + * + * 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.util.Map; +import java.util.Optional; + +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.Mock; +import org.mockito.junit.MockitoJUnitRunner; + +import org.springframework.boot.actuate.info.Info; +import org.springframework.integration.leader.Candidate; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.mockito.BDDMockito.given; + +@RunWith(MockitoJUnitRunner.class) +public class LeaderInfoContributorTest { + + @Mock + private Candidate mockCandidate; + + @Mock + private LeadershipController mockLeadershipController; + + @Mock + private Leader mockLeader; + + private LeaderInfoContributor leaderInfoContributor; + + @Before + public void before() { + this.leaderInfoContributor = new LeaderInfoContributor( + this.mockLeadershipController, 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.mockLeadershipController.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.mockLeadershipController.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"); + } + +}