From 3d6feef167f8e8eb716312599132c41dd89457cb Mon Sep 17 00:00:00 2001 From: Piotr Jaszczyk Date: Mon, 3 Jun 2019 14:20:40 +0200 Subject: Monitoring API - write framework adapters Change-Id: Iaba9c4ef6022d01c1f572055316700a3a4dfa8f2 Issue-ID: DCAEGEN2-1589 Signed-off-by: Piotr Jaszczyk --- .../server-adapters/reactor-netty/pom.xml | 30 +++++ .../moher/adapters/reactornetty/HealthRoutes.java | 80 ++++++++++++++ .../moher/adapters/reactornetty/MetricsRoutes.java | 49 +++++++++ .../adapters/reactornetty/HealthRoutesIT.java | 122 +++++++++++++++++++++ .../adapters/reactornetty/MetricsRoutesIT.java | 71 ++++++++++++ 5 files changed, 352 insertions(+) create mode 100644 standardization/moher-api/server-adapters/reactor-netty/src/main/java/org/onap/dcaegen2/services/sdk/standardization/moher/adapters/reactornetty/HealthRoutes.java create mode 100644 standardization/moher-api/server-adapters/reactor-netty/src/main/java/org/onap/dcaegen2/services/sdk/standardization/moher/adapters/reactornetty/MetricsRoutes.java create mode 100644 standardization/moher-api/server-adapters/reactor-netty/src/test/java/org/onap/dcaegen2/services/sdk/standardization/moher/adapters/reactornetty/HealthRoutesIT.java create mode 100644 standardization/moher-api/server-adapters/reactor-netty/src/test/java/org/onap/dcaegen2/services/sdk/standardization/moher/adapters/reactornetty/MetricsRoutesIT.java (limited to 'standardization/moher-api/server-adapters/reactor-netty') diff --git a/standardization/moher-api/server-adapters/reactor-netty/pom.xml b/standardization/moher-api/server-adapters/reactor-netty/pom.xml index f2f37412..9ae7df4c 100644 --- a/standardization/moher-api/server-adapters/reactor-netty/pom.xml +++ b/standardization/moher-api/server-adapters/reactor-netty/pom.xml @@ -32,4 +32,34 @@ MoHeR Project Reactor's Netty server adapter dcae-sdk-moher-reactor-netty + + + io.projectreactor.netty + reactor-netty + + + org.onap.dcaegen2.services.sdk + dcaegen2-sdk-moher-metrics + ${project.version} + + + org.onap.dcaegen2.services.sdk + dcaegen2-sdk-moher-healthstate + ${project.version} + + + org.junit.jupiter + junit-jupiter-engine + + + org.assertj + assertj-core + + + org.onap.dcaegen2.services.sdk.rest.services + http-client + ${project.version} + test + + \ No newline at end of file diff --git a/standardization/moher-api/server-adapters/reactor-netty/src/main/java/org/onap/dcaegen2/services/sdk/standardization/moher/adapters/reactornetty/HealthRoutes.java b/standardization/moher-api/server-adapters/reactor-netty/src/main/java/org/onap/dcaegen2/services/sdk/standardization/moher/adapters/reactornetty/HealthRoutes.java new file mode 100644 index 00000000..1b6411de --- /dev/null +++ b/standardization/moher-api/server-adapters/reactor-netty/src/main/java/org/onap/dcaegen2/services/sdk/standardization/moher/adapters/reactornetty/HealthRoutes.java @@ -0,0 +1,80 @@ +/* + * ============LICENSE_START==================================== + * DCAEGEN2-SERVICES-SDK + * ========================================================= + * Copyright (C) 2019 Nokia. All rights reserved. + * ========================================================= + * 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. + * ============LICENSE_END===================================== + */ + +package org.onap.dcaegen2.services.sdk.standardization.moher.adapters.reactornetty; + +import com.google.gson.Gson; +import com.google.gson.GsonBuilder; +import io.netty.handler.codec.http.HttpHeaderNames; +import io.netty.handler.codec.http.HttpResponseStatus; +import java.util.function.Consumer; +import org.onap.dcaegen2.services.sdk.standardization.moher.health.api.AliveMessage; +import org.onap.dcaegen2.services.sdk.standardization.moher.health.api.GsonAdaptersHealth; +import org.onap.dcaegen2.services.sdk.standardization.moher.health.api.Health; +import org.onap.dcaegen2.services.sdk.standardization.moher.health.api.HealthProvider; +import org.reactivestreams.Publisher; +import reactor.core.publisher.Mono; +import reactor.netty.http.server.HttpServerRequest; +import reactor.netty.http.server.HttpServerResponse; +import reactor.netty.http.server.HttpServerRoutes; + +public class HealthRoutes implements Consumer { + + public static final String APPLICATION_JSON = "application/json"; + private final Gson gson; + private final HealthProvider healthProvider; + + public HealthRoutes(Gson gson, + HealthProvider healthProvider) { + this.gson = gson; + this.healthProvider = healthProvider; + } + + public static HealthRoutes create(HealthProvider healthProvider) { + GsonBuilder gson = new GsonBuilder(); + gson.registerTypeAdapterFactory(new GsonAdaptersHealth()); + return new HealthRoutes(gson.create(), healthProvider); + } + + @Override + public void accept(HttpServerRoutes routes) { + routes.get("/health/ready", this::readinessCheck); + routes.get("/health/alive", this::livenessCheck); + } + + private Publisher readinessCheck(HttpServerRequest httpServerRequest, HttpServerResponse httpServerResponse) { + return healthProvider.currentHealth() + .flatMapMany(health -> + httpServerResponse.status(statusForHealth(health)) + .header(HttpHeaderNames.CONTENT_TYPE, APPLICATION_JSON) + .sendString(Mono.just(health).map(gson::toJson)) + ); + } + + private Publisher livenessCheck(HttpServerRequest httpServerRequest, HttpServerResponse httpServerResponse) { + return httpServerResponse.status(HttpResponseStatus.OK) + .header(HttpHeaderNames.CONTENT_TYPE, APPLICATION_JSON) + .sendString(Mono.just(AliveMessage.ALIVE_MESSAGE_JSON)); + } + + private HttpResponseStatus statusForHealth(Health health) { + return health.healthy() ? HttpResponseStatus.OK : HttpResponseStatus.SERVICE_UNAVAILABLE; + } +} diff --git a/standardization/moher-api/server-adapters/reactor-netty/src/main/java/org/onap/dcaegen2/services/sdk/standardization/moher/adapters/reactornetty/MetricsRoutes.java b/standardization/moher-api/server-adapters/reactor-netty/src/main/java/org/onap/dcaegen2/services/sdk/standardization/moher/adapters/reactornetty/MetricsRoutes.java new file mode 100644 index 00000000..b6db874d --- /dev/null +++ b/standardization/moher-api/server-adapters/reactor-netty/src/main/java/org/onap/dcaegen2/services/sdk/standardization/moher/adapters/reactornetty/MetricsRoutes.java @@ -0,0 +1,49 @@ +/* + * ============LICENSE_START==================================== + * DCAEGEN2-SERVICES-SDK + * ========================================================= + * Copyright (C) 2019 Nokia. All rights reserved. + * ========================================================= + * 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. + * ============LICENSE_END===================================== + */ + +package org.onap.dcaegen2.services.sdk.standardization.moher.adapters.reactornetty; + +import java.util.function.Consumer; +import org.onap.dcaegen2.services.sdk.standardization.moher.metrics.api.Metrics; +import org.reactivestreams.Publisher; +import reactor.netty.http.server.HttpServerRequest; +import reactor.netty.http.server.HttpServerResponse; +import reactor.netty.http.server.HttpServerRoutes; + +public class MetricsRoutes implements Consumer { + + private final Metrics metrics; + + public MetricsRoutes(Metrics metrics) { + this.metrics = metrics; + } + + @Override + public void accept(HttpServerRoutes routes) { + routes.get("/metrics", this::prometheusMetrics); + } + + private Publisher prometheusMetrics( + HttpServerRequest httpServerRequest, + HttpServerResponse httpServerResponse) { + return httpServerResponse.sendString(metrics.collect()); + } + +} diff --git a/standardization/moher-api/server-adapters/reactor-netty/src/test/java/org/onap/dcaegen2/services/sdk/standardization/moher/adapters/reactornetty/HealthRoutesIT.java b/standardization/moher-api/server-adapters/reactor-netty/src/test/java/org/onap/dcaegen2/services/sdk/standardization/moher/adapters/reactornetty/HealthRoutesIT.java new file mode 100644 index 00000000..367f50e3 --- /dev/null +++ b/standardization/moher-api/server-adapters/reactor-netty/src/test/java/org/onap/dcaegen2/services/sdk/standardization/moher/adapters/reactornetty/HealthRoutesIT.java @@ -0,0 +1,122 @@ +/* + * ============LICENSE_START==================================== + * DCAEGEN2-SERVICES-SDK + * ========================================================= + * Copyright (C) 2019 Nokia. All rights reserved. + * ========================================================= + * 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. + * ============LICENSE_END===================================== + */ + +package org.onap.dcaegen2.services.sdk.standardization.moher.adapters.reactornetty; + +import static org.assertj.core.api.Assertions.assertThat; + +import com.google.gson.Gson; +import com.google.gson.GsonBuilder; +import java.nio.charset.StandardCharsets; +import java.time.Duration; +import java.util.concurrent.atomic.AtomicReference; +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.onap.dcaegen2.services.sdk.rest.services.adapters.http.HttpMethod; +import org.onap.dcaegen2.services.sdk.rest.services.adapters.http.HttpResponse; +import org.onap.dcaegen2.services.sdk.rest.services.adapters.http.ImmutableHttpRequest; +import org.onap.dcaegen2.services.sdk.rest.services.adapters.http.RxHttpClient; +import org.onap.dcaegen2.services.sdk.rest.services.adapters.http.RxHttpClientFactory; +import org.onap.dcaegen2.services.sdk.standardization.moher.health.api.AliveMessage; +import org.onap.dcaegen2.services.sdk.standardization.moher.health.api.GsonAdaptersHealth; +import org.onap.dcaegen2.services.sdk.standardization.moher.health.api.Health; +import org.onap.dcaegen2.services.sdk.standardization.moher.health.api.HealthProvider; +import reactor.netty.DisposableServer; +import reactor.netty.http.server.HttpServer; + +class HealthRoutesIT { + + private static final Duration TIMEOUT = Duration.ofSeconds(5); + private final AtomicReference currentHealth = new AtomicReference<>(); + private final HealthRoutes sut = HealthRoutes.create(HealthProvider.fromSupplier(currentHealth::get)); + private final RxHttpClient rxHttpClient = RxHttpClientFactory.create(); + private final Gson gsonForDeserialization = new GsonBuilder() + .registerTypeAdapterFactory(new GsonAdaptersHealth()) + .create(); + private DisposableServer server; + private String baseUrl; + + @BeforeEach + void setUp() { + server = HttpServer.create().route(sut).bindNow(); + baseUrl = String.format("http://%s:%d", server.host(), server.port()); + } + + @AfterEach + void tearDown() { + server.disposeNow(TIMEOUT); + } + + @Test + void readinessProbeShouldReturnOkWhenHealthy() { + // given + final Health expectedHealth = Health.createHealthy("Ready to go"); + currentHealth.set(expectedHealth); + final String url = baseUrl + "/health/ready"; + + // when + final HttpResponse response = rxHttpClient + .call(ImmutableHttpRequest.builder().method(HttpMethod.GET).url(url).build()) + .block(TIMEOUT); + + // then + assertThat(response.successful()).describedAs("response should be successful").isTrue(); + final Health actualHealth = response.bodyAsJson(StandardCharsets.UTF_8, gsonForDeserialization, Health.class); + assertThat(actualHealth).describedAs("response body").isEqualTo(expectedHealth); + } + + @Test + void readinessProbeShouldReturnUnavailableWhenNotHealthy() { + // given + final Health expectedHealth = Health.createUnhealthy("Waiting for CBS update"); + currentHealth.set(expectedHealth); + final String url = baseUrl + "/health/ready"; + + // when + final HttpResponse response = rxHttpClient + .call(ImmutableHttpRequest.builder().method(HttpMethod.GET).url(url).build()) + .block(TIMEOUT); + + // then + assertThat(response.statusCode()).describedAs("response status code") + .isGreaterThanOrEqualTo(500) + .isLessThan(600); + assertThat(response.successful()).describedAs("response should not be successful").isFalse(); + final Health actualHealth = response.bodyAsJson(StandardCharsets.UTF_8, gsonForDeserialization, Health.class); + assertThat(actualHealth).describedAs("response body").isEqualTo(expectedHealth); + } + + @Test + void livenessProbeShouldAlwaysReturnOk() { + // given + final String url = baseUrl + "/health/alive"; + + // when + final HttpResponse response = rxHttpClient + .call(ImmutableHttpRequest.builder().method(HttpMethod.GET).url(url).build()) + .block(TIMEOUT); + + // then + assertThat(response.successful()).describedAs("response should be successful").isTrue(); + assertThat(response.bodyAsString()).describedAs("response body").isEqualTo(AliveMessage.ALIVE_MESSAGE_JSON); + } + +} \ No newline at end of file diff --git a/standardization/moher-api/server-adapters/reactor-netty/src/test/java/org/onap/dcaegen2/services/sdk/standardization/moher/adapters/reactornetty/MetricsRoutesIT.java b/standardization/moher-api/server-adapters/reactor-netty/src/test/java/org/onap/dcaegen2/services/sdk/standardization/moher/adapters/reactornetty/MetricsRoutesIT.java new file mode 100644 index 00000000..55c24c73 --- /dev/null +++ b/standardization/moher-api/server-adapters/reactor-netty/src/test/java/org/onap/dcaegen2/services/sdk/standardization/moher/adapters/reactornetty/MetricsRoutesIT.java @@ -0,0 +1,71 @@ +/* + * ============LICENSE_START==================================== + * DCAEGEN2-SERVICES-SDK + * ========================================================= + * Copyright (C) 2019 Nokia. All rights reserved. + * ========================================================= + * 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. + * ============LICENSE_END===================================== + */ + +package org.onap.dcaegen2.services.sdk.standardization.moher.adapters.reactornetty; + +import static org.assertj.core.api.Assertions.assertThat; + +import java.time.Duration; +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.onap.dcaegen2.services.sdk.rest.services.adapters.http.HttpMethod; +import org.onap.dcaegen2.services.sdk.rest.services.adapters.http.HttpResponse; +import org.onap.dcaegen2.services.sdk.rest.services.adapters.http.ImmutableHttpRequest; +import org.onap.dcaegen2.services.sdk.rest.services.adapters.http.RxHttpClient; +import org.onap.dcaegen2.services.sdk.rest.services.adapters.http.RxHttpClientFactory; +import org.onap.dcaegen2.services.sdk.standardization.moher.metrics.api.Metrics; +import org.onap.dcaegen2.services.sdk.standardization.moher.metrics.api.MetricsFactory; +import reactor.netty.DisposableServer; +import reactor.netty.http.server.HttpServer; + +class MetricsRoutesIT { + + private static final Duration TIMEOUT = Duration.ofSeconds(5); + private final Metrics metrics = MetricsFactory.createMetrics(MetricsFactory.createDefaultRegistry()); + private final MetricsRoutes sut = new MetricsRoutes(metrics); + private final RxHttpClient rxHttpClient = RxHttpClientFactory.create(); + private DisposableServer server; + + @BeforeEach + void setUp() { + server = HttpServer.create().route(sut).bindNow(); + } + + @AfterEach + void tearDown() { + server.disposeNow(TIMEOUT); + } + + @Test + void prometheusMetrics() { + // given + final String url = String.format("http://%s:%d/metrics", server.host(), server.port()); + + // when + final HttpResponse response = rxHttpClient + .call(ImmutableHttpRequest.builder().method(HttpMethod.GET).url(url).build()) + .block(TIMEOUT); + + // then + assertThat(response.successful()).describedAs("response should be successfull").isTrue(); + assertThat(response.bodyAsString()).describedAs("response body").contains("system_cpu", "jvm_classes"); + } +} \ No newline at end of file -- cgit 1.2.3-korg