Skip to content

Commit dd16115

Browse files
HealthIndicator implementation for PubSub. Fixes spring-attic#2030 (spring-attic#2040)
Fixes: spring-attic#2030.
1 parent 9ce0a80 commit dd16115

File tree

7 files changed

+222
-10
lines changed

7 files changed

+222
-10
lines changed

spring-cloud-gcp-autoconfigure/src/main/java/org/springframework/cloud/gcp/autoconfigure/core/GcpContextAutoConfiguration.java

+1-1
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2017-2020 the original author or authors.
2+
* Copyright 2017-2018 the original author or authors.
33
*
44
* Licensed under the Apache License, Version 2.0 (the "License");
55
* you may not use this file except in compliance with the License.
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,68 @@
1+
/*
2+
* Copyright 2017-2019 the original author or authors.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* https://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
package org.springframework.cloud.gcp.autoconfigure.pubsub.health;
18+
19+
import java.util.UUID;
20+
21+
import com.google.api.gax.rpc.ApiException;
22+
import com.google.api.gax.rpc.StatusCode;
23+
24+
import org.springframework.boot.actuate.health.AbstractHealthIndicator;
25+
import org.springframework.boot.actuate.health.Health;
26+
import org.springframework.cloud.gcp.pubsub.core.PubSubTemplate;
27+
import org.springframework.util.Assert;
28+
29+
/**
30+
* Default implemenation of
31+
* {@link org.springframework.boot.actuate.health.HealthIndicator} for Pub/Sub. Validates
32+
* if connection is successful by looking for a random generated subscription name that
33+
* won't be found. If no subscription is found we know the client is able to connect to
34+
* GCP Pub/Sub APIs.
35+
*
36+
* @author Vinicius Carvalho
37+
*
38+
* @since 1.3
39+
*/
40+
public class PubSubHealthIndicator extends AbstractHealthIndicator {
41+
42+
private final PubSubTemplate pubSubTemplate;
43+
44+
public PubSubHealthIndicator(PubSubTemplate pubSubTemplate) {
45+
super("Failed to connect to Pub/Sub APIs. Check your credentials and verify you have proper access to the service.");
46+
Assert.notNull(pubSubTemplate, "PubSubTemplate can't be null");
47+
this.pubSubTemplate = pubSubTemplate;
48+
}
49+
50+
@Override
51+
protected void doHealthCheck(Health.Builder builder) throws Exception {
52+
try {
53+
this.pubSubTemplate.pull("subscription-" + UUID.randomUUID().toString(), 1, true);
54+
}
55+
catch (ApiException aex) {
56+
if (aex.getStatusCode().getCode() == StatusCode.Code.NOT_FOUND) {
57+
builder.up();
58+
}
59+
else {
60+
builder.withException(aex).down();
61+
}
62+
}
63+
catch (Exception e) {
64+
builder.withException(e).down();
65+
}
66+
}
67+
68+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,52 @@
1+
/*
2+
* Copyright 2018-2019 the original author or authors.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* https://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
package org.springframework.cloud.gcp.autoconfigure.pubsub.health;
18+
19+
import org.springframework.boot.actuate.autoconfigure.health.ConditionalOnEnabledHealthIndicator;
20+
import org.springframework.boot.actuate.autoconfigure.health.HealthContributorAutoConfiguration;
21+
import org.springframework.boot.actuate.health.HealthIndicator;
22+
import org.springframework.boot.autoconfigure.AutoConfigureAfter;
23+
import org.springframework.boot.autoconfigure.AutoConfigureBefore;
24+
import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
25+
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
26+
import org.springframework.cloud.gcp.autoconfigure.pubsub.GcpPubSubAutoConfiguration;
27+
import org.springframework.cloud.gcp.pubsub.core.PubSubTemplate;
28+
import org.springframework.context.annotation.Bean;
29+
import org.springframework.context.annotation.Configuration;
30+
31+
/**
32+
* {@link HealthContributorAutoConfiguration Auto-configuration} for
33+
* {@link PubSubHealthIndicator}.
34+
*
35+
* @author Vinicius Carvalho
36+
*
37+
* @since 1.3
38+
*/
39+
@Configuration
40+
@ConditionalOnClass(HealthIndicator.class)
41+
@ConditionalOnEnabledHealthIndicator("pubsub")
42+
@AutoConfigureBefore(HealthContributorAutoConfiguration.class)
43+
@AutoConfigureAfter(GcpPubSubAutoConfiguration.class)
44+
public class PubSubHealthIndicatorAutoConfiguration {
45+
46+
@Bean
47+
@ConditionalOnMissingBean
48+
public PubSubHealthIndicator pubSubHealthIndicator(PubSubTemplate pubSubTemplate) {
49+
return new PubSubHealthIndicator(pubSubTemplate);
50+
}
51+
52+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
/**
2+
* Auto-configuration for Spring Data Cloud Pub/Sub Health module.
3+
*/
4+
package org.springframework.cloud.gcp.autoconfigure.pubsub.health;

spring-cloud-gcp-autoconfigure/src/main/resources/META-INF/spring.factories

+2-1
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,8 @@ org.springframework.cloud.gcp.autoconfigure.vision.CloudVisionAutoConfiguration,
1818
org.springframework.cloud.gcp.autoconfigure.datastore.GcpDatastoreEmulatorAutoConfiguration,\
1919
org.springframework.cloud.gcp.autoconfigure.bigquery.GcpBigQueryAutoConfiguration,\
2020
org.springframework.cloud.gcp.autoconfigure.datastore.DatastoreTransactionManagerAutoConfiguration,\
21-
org.springframework.cloud.gcp.autoconfigure.firestore.FirestoreRepositoriesAutoConfiguration
21+
org.springframework.cloud.gcp.autoconfigure.firestore.FirestoreRepositoriesAutoConfiguration,\
22+
org.springframework.cloud.gcp.autoconfigure.pubsub.health.PubSubHealthIndicatorAutoConfiguration
2223

2324
org.springframework.cloud.bootstrap.BootstrapConfiguration=\
2425
org.springframework.cloud.gcp.autoconfigure.config.GcpConfigBootstrapConfiguration

spring-cloud-gcp-autoconfigure/src/test/java/org/springframework/cloud/gcp/autoconfigure/pubsub/GcpPubSubAutoConfigurationTests.java

+23-8
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,8 @@
2424

2525
import org.springframework.boot.autoconfigure.AutoConfigurations;
2626
import org.springframework.boot.test.context.runner.ApplicationContextRunner;
27+
import org.springframework.cloud.gcp.autoconfigure.pubsub.health.PubSubHealthIndicator;
28+
import org.springframework.cloud.gcp.autoconfigure.pubsub.health.PubSubHealthIndicatorAutoConfiguration;
2729
import org.springframework.cloud.gcp.core.GcpProjectIdProvider;
2830
import org.springframework.context.annotation.Bean;
2931

@@ -40,33 +42,47 @@ public class GcpPubSubAutoConfigurationTests {
4042
@Test
4143
public void keepAliveValue_default() {
4244
ApplicationContextRunner contextRunner = new ApplicationContextRunner()
43-
.withConfiguration(AutoConfigurations.of(GcpPubSubAutoConfiguration.class))
44-
.withUserConfiguration(TestConfig.class);
45+
.withConfiguration(AutoConfigurations.of(GcpPubSubAutoConfiguration.class))
46+
.withUserConfiguration(TestConfig.class);
4547

4648
contextRunner.run(ctx -> {
4749
GcpPubSubProperties props = ctx.getBean(GcpPubSubProperties.class);
4850
assertThat(props.getKeepAliveIntervalMinutes()).isEqualTo(5);
4951

5052
TransportChannelProvider tcp = ctx.getBean(TransportChannelProvider.class);
5153
assertThat(((InstantiatingGrpcChannelProvider) tcp).getKeepAliveTime().toMinutes())
52-
.isEqualTo(5);
54+
.isEqualTo(5);
5355
});
5456
}
5557

5658
@Test
5759
public void keepAliveValue_custom() {
5860
ApplicationContextRunner contextRunner = new ApplicationContextRunner()
59-
.withConfiguration(AutoConfigurations.of(GcpPubSubAutoConfiguration.class))
60-
.withUserConfiguration(TestConfig.class)
61-
.withPropertyValues("spring.cloud.gcp.pubsub.keepAliveIntervalMinutes=2");
61+
.withConfiguration(AutoConfigurations.of(GcpPubSubAutoConfiguration.class))
62+
.withUserConfiguration(TestConfig.class)
63+
.withPropertyValues("spring.cloud.gcp.pubsub.keepAliveIntervalMinutes=2");
6264

6365
contextRunner.run(ctx -> {
6466
GcpPubSubProperties props = ctx.getBean(GcpPubSubProperties.class);
6567
assertThat(props.getKeepAliveIntervalMinutes()).isEqualTo(2);
6668

6769
TransportChannelProvider tcp = ctx.getBean(TransportChannelProvider.class);
6870
assertThat(((InstantiatingGrpcChannelProvider) tcp).getKeepAliveTime().toMinutes())
69-
.isEqualTo(2);
71+
.isEqualTo(2);
72+
});
73+
}
74+
75+
@Test
76+
public void healthIndicatorPresent() {
77+
ApplicationContextRunner contextRunner = new ApplicationContextRunner()
78+
.withConfiguration(AutoConfigurations.of(PubSubHealthIndicatorAutoConfiguration.class,
79+
GcpPubSubAutoConfiguration.class))
80+
.withUserConfiguration(TestConfig.class)
81+
.withPropertyValues("spring.cloud.gcp.datastore.project-id=test-project",
82+
"management.health.pubsub.enabled=true");
83+
contextRunner.run(ctx -> {
84+
PubSubHealthIndicator healthIndicator = ctx.getBean(PubSubHealthIndicator.class);
85+
assertThat(healthIndicator).isNotNull();
7086
});
7187
}
7288

@@ -82,6 +98,5 @@ public CredentialsProvider googleCredentials() {
8298
return () -> mock(Credentials.class);
8399
}
84100

85-
86101
}
87102
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,72 @@
1+
/*
2+
* Copyright 2019-2019 the original author or authors.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* https://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
package org.springframework.cloud.gcp.autoconfigure.pubsub;
18+
19+
import com.google.api.gax.grpc.GrpcStatusCode;
20+
import com.google.api.gax.rpc.ApiException;
21+
import org.junit.Test;
22+
import org.junit.runner.RunWith;
23+
import org.mockito.Mock;
24+
import org.mockito.junit.MockitoJUnitRunner;
25+
26+
import org.springframework.boot.actuate.health.Status;
27+
import org.springframework.cloud.gcp.autoconfigure.pubsub.health.PubSubHealthIndicator;
28+
import org.springframework.cloud.gcp.pubsub.core.PubSubTemplate;
29+
30+
import static org.assertj.core.api.Assertions.assertThat;
31+
import static org.mockito.ArgumentMatchers.anyBoolean;
32+
import static org.mockito.ArgumentMatchers.anyInt;
33+
import static org.mockito.ArgumentMatchers.anyString;
34+
import static org.mockito.Mockito.when;
35+
36+
/**
37+
* Tests for the PubSub Health Indicator.
38+
*
39+
* @author Vinicius Carvalho
40+
*/
41+
@RunWith(MockitoJUnitRunner.class)
42+
public class PubSubHealthIndicatorTests {
43+
44+
@Mock
45+
private PubSubTemplate pubSubTemplate;
46+
47+
@Test
48+
public void healthUp() throws Exception {
49+
when(pubSubTemplate.pull(anyString(), anyInt(), anyBoolean())).thenThrow(new ApiException(
50+
new IllegalStateException("Illegal State"), GrpcStatusCode.of(io.grpc.Status.Code.NOT_FOUND), false));
51+
PubSubHealthIndicator healthIndicator = new PubSubHealthIndicator(pubSubTemplate);
52+
assertThat(healthIndicator.health().getStatus()).isEqualTo(Status.UP);
53+
}
54+
55+
@Test
56+
public void healthDown() {
57+
when(pubSubTemplate.pull(anyString(), anyInt(), anyBoolean()))
58+
.thenThrow(new ApiException(new IllegalStateException("Illegal State"),
59+
GrpcStatusCode.of(io.grpc.Status.Code.INVALID_ARGUMENT), false));
60+
PubSubHealthIndicator healthIndicator = new PubSubHealthIndicator(pubSubTemplate);
61+
assertThat(healthIndicator.health().getStatus()).isEqualTo(Status.DOWN);
62+
}
63+
64+
@Test
65+
public void healthDownGenericException() {
66+
when(pubSubTemplate.pull(anyString(), anyInt(), anyBoolean()))
67+
.thenThrow(new IllegalStateException("Illegal State"));
68+
PubSubHealthIndicator healthIndicator = new PubSubHealthIndicator(pubSubTemplate);
69+
assertThat(healthIndicator.health().getStatus()).isEqualTo(Status.DOWN);
70+
}
71+
72+
}

0 commit comments

Comments
 (0)