diff --git a/.circleci/config.yml b/.circleci/config.yml index cf810187..787d875d 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -4,32 +4,32 @@ version: 2.1 setup: true orbs: - gravitee: gravitee-io/gravitee@2.1 + gravitee: gravitee-io/gravitee@4.0.0 # our single workflow, that triggers the setup job defined above, filters on tag and branches are needed otherwise # some workflow and job will not be triggered for tags (default CircleCI behavior) workflows: - setup_build: - when: - not: << pipeline.git.tag >> - jobs: - - gravitee/setup_plugin-build-config: - filters: - tags: - ignore: - - /.*/ + setup_build: + when: + not: << pipeline.git.tag >> + jobs: + - gravitee/setup_plugin-build-config: + filters: + tags: + ignore: + - /.*/ - setup_release: - when: - matches: - pattern: "^[0-9]+\\.[0-9]+\\.[0-9]+$" - value: << pipeline.git.tag >> - jobs: - - gravitee/setup_plugin-release-config: - filters: - branches: - ignore: - - /.*/ - tags: - only: - - /^[0-9]+\.[0-9]+\.[0-9]+$/ \ No newline at end of file + setup_release: + when: + matches: + pattern: "/^[0-9]+\\.[0-9]+\\.[0-9]+(-(alpha|beta|rc)\\.[0-9]+)?$/" + value: << pipeline.git.tag >> + jobs: + - gravitee/setup_plugin-release-config: + filters: + branches: + ignore: + - /.*/ + tags: + only: + - /^[0-9]+\.[0-9]+\.[0-9]+(-(alpha|beta|rc)\.[0-9]+)?$/ \ No newline at end of file diff --git a/README.adoc b/README.adoc index 2f92d2fb..0762ec9d 100644 --- a/README.adoc +++ b/README.adoc @@ -7,16 +7,44 @@ image:https://img.shields.io/badge/semantic--release-conventional%20commits-e100 image:https://circleci.com/gh/gravitee-io/gravitee-policy-latency.svg?style=svg["CircleCI", link="https://circleci.com/gh/gravitee-io/gravitee-policy-latency"] endif::[] -== Phase +== Compatibility with APIM + +|=== +|Plugin version | APIM version + +| 2.x and upper | 4.x and upper +| 1.4.x | 3.10.x and upper +| Up to 1.3.x | Up to 3.9.x +|=== + +=== V3 engine -[cols="2*", options="header"] +[cols="4*", options="header"] |=== ^|onRequest ^|onResponse +^|onRequestContent +^|onResponseContent ^.^| X -^.^| +^.^| +^.^| +^.^| +|=== +=== V4 engine + +[cols="4*", options="header"] +|=== +^|onRequest +^|onResponse +^|onMessageRequest +^|onMessageResponse + +^.^| X +^.^| +^.^| X +^.^| X |=== == Description @@ -28,15 +56,6 @@ This policy is particularly useful in two scenarios: * Testing: adding latency allows you to test client applications when APIs are slow to respond. * Monetization: a longer latency can be added to free plans to encourage clients to move to a better (or paid) plan. -== Compatibility with APIM - -|=== -|Plugin version | APIM version - -| 1.4.x and upper | 3.10.x and upper -| Up to 1.3.x | Up to 3.9.x -|=== - == Configuration You can configure the policy with the following options: diff --git a/pom.xml b/pom.xml index c50c3b7b..51e62263 100644 --- a/pom.xml +++ b/pom.xml @@ -22,7 +22,7 @@ io.gravitee.policy gravitee-policy-latency - 1.4.1 + 2.0.0 jar Gravitee.io APIM - Policy - Latency Description of the Latency Gravitee Policy @@ -30,18 +30,27 @@ io.gravitee gravitee-parent - 20.2 + 21.0.1 - 3.18.0-SNAPSHOT - 2.5 - 1.32.2 + 5.0.0 + 3.0.0-alpha.6 + 3.1.0-alpha.10 1.11.0 + 2.1.1 + 4.0.0-SNAPSHOT + 3.1.0-alpha.3 + 1.0.0-alpha.2 + 1.0.0-alpha.7 + 1.3.0 ${project.build.directory}/schemas - 3.3.0 + 3.6.0 + 0.19 + 1.6.2 + 1.1.0 graviteeio-apim/plugins/policies @@ -56,6 +65,16 @@ import pom + + io.gravitee.policy + gravitee-policy-api + ${gravitee-policy-api.version} + + + io.gravitee.gateway + gravitee-gateway-api + ${gravitee-gateway-api.version} + @@ -81,15 +100,86 @@ vertx-core provided - - + + io.vertx + vertx-rx-java3 + provided + org.slf4j slf4j-api provided + + + org.projectlombok + lombok + provided + + + + io.gravitee.apim.gateway + gravitee-apim-gateway-tests-sdk + ${gravitee-apim.version} + test + + + io.gravitee.apim.plugin.entrypoint + gravitee-apim-plugin-entrypoint-http-proxy + ${gravitee-apim.version} + test + + + io.gravitee.apim.plugin.endpoint + gravitee-apim-plugin-endpoint-http-proxy + ${gravitee-apim.version} + test + + + com.graviteesource.reactor + gravitee-reactor-message + ${gravitee-reactor-message.version} + test + + + io.gravitee.apim.plugin.endpoint + gravitee-apim-plugin-endpoint-mock + ${gravitee-apim.version} + test + + + com.graviteesource.entrypoint + gravitee-entrypoint-sse + ${gravitee-sse.version} + test + + + com.graviteesource.entrypoint + gravitee-entrypoint-http-post + ${gravitee-http-post.version} + test + + + + org.junit.jupiter + junit-jupiter-engine + test + + + + org.junit.jupiter + junit-jupiter-api + test + + + + org.junit.jupiter + junit-jupiter-params + test + + org.junit.vintage junit-vintage-engine @@ -103,9 +193,14 @@ - io.gravitee.apim.gateway - gravitee-apim-gateway-tests-sdk - ${gravitee-apim-gateway-tests-sdk.version} + org.mockito + mockito-junit-jupiter + test + + + + org.assertj + assertj-core test @@ -121,7 +216,7 @@ org.codehaus.mojo properties-maven-plugin - 1.0.0 + ${maven-plugin-properties.version} initialize @@ -157,9 +252,11 @@ + + org.apache.maven.plugins maven-assembly-plugin - ${maven-assembly-plugin.version} + ${maven-plugin-assembly.version} false @@ -179,10 +276,10 @@ com.hubspot.maven.plugins prettier-maven-plugin - 0.17 + ${maven-plugin-prettier.version} - 12.13.0 - 1.6.1 + ${maven-plugin-prettier.prettierjava.version} + ${skip.validation} diff --git a/src/main/java/io/gravitee/policy/latency/LatencyPolicy.java b/src/main/java/io/gravitee/policy/latency/LatencyPolicy.java index 5cbad3ba..7af1c34b 100644 --- a/src/main/java/io/gravitee/policy/latency/LatencyPolicy.java +++ b/src/main/java/io/gravitee/policy/latency/LatencyPolicy.java @@ -15,38 +15,41 @@ */ package io.gravitee.policy.latency; -import io.gravitee.gateway.api.ExecutionContext; -import io.gravitee.gateway.api.Request; -import io.gravitee.gateway.api.Response; -import io.gravitee.policy.api.PolicyChain; -import io.gravitee.policy.api.annotations.OnRequest; +import io.gravitee.gateway.reactive.api.context.HttpExecutionContext; +import io.gravitee.gateway.reactive.api.context.MessageExecutionContext; +import io.gravitee.gateway.reactive.api.policy.Policy; import io.gravitee.policy.latency.configuration.LatencyPolicyConfiguration; -import io.vertx.core.Vertx; +import io.gravitee.policy.latency.v3.LatencyPolicyV3; +import io.reactivex.rxjava3.core.Completable; +import io.reactivex.rxjava3.core.Maybe; /** - * @author Azize ELAMRANI (azize.elamrani at graviteesource.com) + * @author Guillaume Lamirand (guillaume.lamirand at graviteesource.com) * @author GraviteeSource Team */ -public class LatencyPolicy { - - private final LatencyPolicyConfiguration latencyPolicyConfiguration; +public class LatencyPolicy extends LatencyPolicyV3 implements Policy { public LatencyPolicy(final LatencyPolicyConfiguration latencyPolicyConfiguration) { - this.latencyPolicyConfiguration = latencyPolicyConfiguration; + super(latencyPolicyConfiguration); + } + + @Override + public String id() { + return "latency"; + } + + @Override + public Completable onRequest(final HttpExecutionContext ctx) { + return Completable.complete().delay(configuration.getTime(), configuration.getTimeUnit()); + } + + @Override + public Completable onMessageRequest(final MessageExecutionContext ctx) { + return ctx.request().onMessage(message -> Maybe.just(message).delay(configuration.getTime(), configuration.getTimeUnit())); } - @OnRequest - public void onRequest( - final Request request, - final Response response, - final ExecutionContext executionContext, - final PolicyChain policyChain - ) { - executionContext - .getComponent(Vertx.class) - .setTimer( - latencyPolicyConfiguration.getTimeUnit().toMillis(latencyPolicyConfiguration.getTime()), - timerId -> policyChain.doNext(request, response) - ); + @Override + public Completable onMessageResponse(final MessageExecutionContext ctx) { + return ctx.response().onMessage(message -> Maybe.just(message).delay(configuration.getTime(), configuration.getTimeUnit())); } } diff --git a/src/main/java/io/gravitee/policy/latency/configuration/LatencyPolicyConfiguration.java b/src/main/java/io/gravitee/policy/latency/configuration/LatencyPolicyConfiguration.java index 1d9fe0f1..e7937094 100644 --- a/src/main/java/io/gravitee/policy/latency/configuration/LatencyPolicyConfiguration.java +++ b/src/main/java/io/gravitee/policy/latency/configuration/LatencyPolicyConfiguration.java @@ -17,29 +17,23 @@ import io.gravitee.policy.api.PolicyConfiguration; import java.util.concurrent.TimeUnit; +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Getter; +import lombok.NoArgsConstructor; +import lombok.Setter; /** * @author Azize ELAMRANI (azize.elamrani at graviteesource.com) * @author GraviteeSource Team */ +@Getter +@Setter +@NoArgsConstructor +@AllArgsConstructor +@Builder public class LatencyPolicyConfiguration implements PolicyConfiguration { private long time; private TimeUnit timeUnit; - - public long getTime() { - return time; - } - - public void setTime(long time) { - this.time = time; - } - - public TimeUnit getTimeUnit() { - return timeUnit; - } - - public void setTimeUnit(TimeUnit timeUnit) { - this.timeUnit = timeUnit; - } } diff --git a/src/main/java/io/gravitee/policy/latency/v3/LatencyPolicyV3.java b/src/main/java/io/gravitee/policy/latency/v3/LatencyPolicyV3.java new file mode 100644 index 00000000..7c553f54 --- /dev/null +++ b/src/main/java/io/gravitee/policy/latency/v3/LatencyPolicyV3.java @@ -0,0 +1,47 @@ +/** + * Copyright (C) 2015 The Gravitee team (http://gravitee.io) + * + * 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 io.gravitee.policy.latency.v3; + +import io.gravitee.gateway.api.ExecutionContext; +import io.gravitee.gateway.api.Request; +import io.gravitee.gateway.api.Response; +import io.gravitee.policy.api.PolicyChain; +import io.gravitee.policy.api.annotations.OnRequest; +import io.gravitee.policy.latency.configuration.LatencyPolicyConfiguration; +import io.vertx.core.Vertx; +import lombok.RequiredArgsConstructor; + +/** + * @author Azize ELAMRANI (azize.elamrani at graviteesource.com) + * @author GraviteeSource Team + */ +@RequiredArgsConstructor +public class LatencyPolicyV3 { + + protected final LatencyPolicyConfiguration configuration; + + @OnRequest + public void onRequest( + final Request request, + final Response response, + final ExecutionContext executionContext, + final PolicyChain policyChain + ) { + executionContext + .getComponent(Vertx.class) + .setTimer(configuration.getTimeUnit().toMillis(configuration.getTime()), timerId -> policyChain.doNext(request, response)); + } +} diff --git a/src/main/resources/plugin.properties b/src/main/resources/plugin.properties index 900b258b..dbb702e0 100644 --- a/src/main/resources/plugin.properties +++ b/src/main/resources/plugin.properties @@ -6,3 +6,5 @@ class=io.gravitee.policy.latency.LatencyPolicy type=policy category=others icon=latency.svg +proxy=REQUEST +message=REQUEST,MESSAGE_REQUEST,MESSAGE_RESPONSE diff --git a/src/main/resources/schemas/schema-form.json b/src/main/resources/schemas/schema-form.json index 755ff502..f3932340 100644 --- a/src/main/resources/schemas/schema-form.json +++ b/src/main/resources/schemas/schema-form.json @@ -5,7 +5,8 @@ "time" : { "title": "Time duration", "type" : "integer", - "default": 100 + "default": 100, + "minimum": 0 }, "timeUnit" : { "title": "Time unit", diff --git a/src/test/java/io/gravitee/policy/latency/LatencyPolicyIntegrationTest.java b/src/test/java/io/gravitee/policy/latency/LatencyPolicyIntegrationV3Test.java similarity index 55% rename from src/test/java/io/gravitee/policy/latency/LatencyPolicyIntegrationTest.java rename to src/test/java/io/gravitee/policy/latency/LatencyPolicyIntegrationV3Test.java index 388bcaba..5b989d9a 100644 --- a/src/test/java/io/gravitee/policy/latency/LatencyPolicyIntegrationTest.java +++ b/src/test/java/io/gravitee/policy/latency/LatencyPolicyIntegrationV3Test.java @@ -25,13 +25,20 @@ import io.gravitee.apim.gateway.tests.sdk.AbstractPolicyTest; import io.gravitee.apim.gateway.tests.sdk.annotations.DeployApi; import io.gravitee.apim.gateway.tests.sdk.annotations.GatewayTest; +import io.gravitee.apim.gateway.tests.sdk.configuration.GatewayConfigurationBuilder; import io.gravitee.apim.gateway.tests.sdk.reporter.FakeReporter; +import io.gravitee.definition.model.Api; +import io.gravitee.definition.model.ExecutionMode; import io.gravitee.policy.latency.configuration.LatencyPolicyConfiguration; import io.gravitee.reporter.api.http.Metrics; -import io.reactivex.observers.TestObserver; -import io.vertx.reactivex.core.buffer.Buffer; -import io.vertx.reactivex.ext.web.client.HttpResponse; -import io.vertx.reactivex.ext.web.client.WebClient; +import io.reactivex.rxjava3.observers.TestObserver; +import io.vertx.core.http.HttpMethod; +import io.vertx.junit5.Checkpoint; +import io.vertx.junit5.VertxTestContext; +import io.vertx.rxjava3.core.buffer.Buffer; +import io.vertx.rxjava3.core.http.HttpClient; +import io.vertx.rxjava3.core.http.HttpClientRequest; +import java.util.concurrent.TimeUnit; import java.util.concurrent.atomic.AtomicReference; import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.Test; @@ -41,30 +48,52 @@ * @author GraviteeSource Team */ @GatewayTest -@DeployApi("/apis/latency.json") -class LatencyPolicyIntegrationTest extends AbstractPolicyTest { +@DeployApi("/apis/latency-v2.json") +class LatencyPolicyIntegrationV3Test extends AbstractPolicyTest { + + @Override + protected void configureGateway(GatewayConfigurationBuilder gatewayConfigurationBuilder) { + super.configureGateway(gatewayConfigurationBuilder); + gatewayConfigurationBuilder.set("api.jupiterMode.enabled", "false"); + } + + @Override + public void configureApi(Api api) { + super.configureApi(api); + api.setExecutionMode(ExecutionMode.V3); + } @Test - @DisplayName("Should apply latency") - void shouldApplyLatency(WebClient webClient) { + void should_apply_latency_on_request(HttpClient httpClient, VertxTestContext vertxTestContext) throws InterruptedException { + Checkpoint fakeReporterCheckpoint = vertxTestContext.checkpoint(); FakeReporter fakeReporter = getBean(FakeReporter.class); AtomicReference metricsRef = new AtomicReference<>(); fakeReporter.setReportableHandler(reportable -> { + fakeReporterCheckpoint.flag(); metricsRef.set((Metrics) reportable); }); wiremock.stubFor(get("/endpoint").willReturn(ok("I'm the backend"))); - final TestObserver> obs = webClient.get("/test").rxSend().test(); - awaitTerminalEvent(obs); - obs - .assertComplete() - .assertValue(response -> { + Checkpoint responseCheckpoint = vertxTestContext.checkpoint(); + TestObserver testObserver = httpClient + .request(HttpMethod.GET, "/test") + .flatMap(HttpClientRequest::rxSend) + .flatMap(response -> { assertThat(response.statusCode()).isEqualTo(200); - assertThat(response.bodyAsString()).isEqualTo("I'm the backend"); + return response.rxBody(); + }) + .test(); + awaitTerminalEvent(testObserver); + testObserver + .assertComplete() + .assertValue(body -> { + responseCheckpoint.flag(); + assertThat(body).hasToString("I'm the backend"); return true; }); + assertThat(vertxTestContext.awaitCompletion(30, TimeUnit.SECONDS)).isTrue(); wiremock.verify(exactly(1), getRequestedFor(urlPathEqualTo("/endpoint"))); assertThat(metricsRef.get().getProxyResponseTimeMs()).isGreaterThan(2000); } diff --git a/src/test/java/io/gravitee/policy/latency/LatencyPolicyIntegrationV4CompatibilityTest.java b/src/test/java/io/gravitee/policy/latency/LatencyPolicyIntegrationV4CompatibilityTest.java new file mode 100644 index 00000000..2adca5ee --- /dev/null +++ b/src/test/java/io/gravitee/policy/latency/LatencyPolicyIntegrationV4CompatibilityTest.java @@ -0,0 +1,41 @@ +/** + * Copyright (C) 2015 The Gravitee team (http://gravitee.io) + * + * 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 io.gravitee.policy.latency; + +import io.gravitee.apim.gateway.tests.sdk.annotations.DeployApi; +import io.gravitee.apim.gateway.tests.sdk.annotations.GatewayTest; +import io.gravitee.apim.gateway.tests.sdk.configuration.GatewayConfigurationBuilder; +import io.gravitee.definition.model.Api; +import io.gravitee.definition.model.ExecutionMode; + +/** + * @author Guillaume Lamirand (guillaume.lamirand at graviteesource.com) + * @author GraviteeSource Team + */ +class LatencyPolicyIntegrationV4CompatibilityTest extends LatencyPolicyIntegrationV3Test { + + @Override + protected void configureGateway(GatewayConfigurationBuilder gatewayConfigurationBuilder) { + super.configureGateway(gatewayConfigurationBuilder); + gatewayConfigurationBuilder.set("api.jupiterMode.enabled", "true"); + } + + @Override + public void configureApi(Api api) { + super.configureApi(api); + api.setExecutionMode(ExecutionMode.JUPITER); + } +} diff --git a/src/test/java/io/gravitee/policy/latency/LatencyPolicyIntegrationV4Test.java b/src/test/java/io/gravitee/policy/latency/LatencyPolicyIntegrationV4Test.java new file mode 100644 index 00000000..5fe5e9ba --- /dev/null +++ b/src/test/java/io/gravitee/policy/latency/LatencyPolicyIntegrationV4Test.java @@ -0,0 +1,206 @@ +/** + * Copyright (C) 2015 The Gravitee team (http://gravitee.io) + * + * 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 io.gravitee.policy.latency; + +import static com.github.tomakehurst.wiremock.client.WireMock.exactly; +import static com.github.tomakehurst.wiremock.client.WireMock.get; +import static com.github.tomakehurst.wiremock.client.WireMock.getRequestedFor; +import static com.github.tomakehurst.wiremock.client.WireMock.ok; +import static com.github.tomakehurst.wiremock.client.WireMock.urlPathEqualTo; +import static io.vertx.core.http.HttpMethod.POST; +import static org.assertj.core.api.Assertions.assertThat; + +import com.graviteesource.entrypoint.http.post.HttpPostEntrypointConnectorFactory; +import com.graviteesource.entrypoint.sse.SseEntrypointConnectorFactory; +import com.graviteesource.reactor.message.MessageApiReactorFactory; +import io.gravitee.apim.gateway.tests.sdk.AbstractPolicyTest; +import io.gravitee.apim.gateway.tests.sdk.annotations.DeployApi; +import io.gravitee.apim.gateway.tests.sdk.annotations.GatewayTest; +import io.gravitee.apim.gateway.tests.sdk.configuration.GatewayConfigurationBuilder; +import io.gravitee.apim.gateway.tests.sdk.connector.EndpointBuilder; +import io.gravitee.apim.gateway.tests.sdk.connector.EntrypointBuilder; +import io.gravitee.apim.gateway.tests.sdk.connector.fakes.PersistentMockEndpointConnectorFactory; +import io.gravitee.apim.gateway.tests.sdk.reactor.ReactorBuilder; +import io.gravitee.apim.gateway.tests.sdk.reporter.FakeReporter; +import io.gravitee.apim.plugin.reactor.ReactorPlugin; +import io.gravitee.common.http.MediaType; +import io.gravitee.gateway.api.http.HttpHeaderNames; +import io.gravitee.gateway.reactive.reactor.v4.reactor.ReactorFactory; +import io.gravitee.plugin.endpoint.EndpointConnectorPlugin; +import io.gravitee.plugin.endpoint.http.proxy.HttpProxyEndpointConnectorFactory; +import io.gravitee.plugin.entrypoint.EntrypointConnectorPlugin; +import io.gravitee.plugin.entrypoint.http.proxy.HttpProxyEntrypointConnectorFactory; +import io.gravitee.policy.latency.configuration.LatencyPolicyConfiguration; +import io.gravitee.reporter.api.v4.metric.MessageMetrics; +import io.gravitee.reporter.api.v4.metric.Metrics; +import io.reactivex.rxjava3.observers.TestObserver; +import io.vertx.core.http.HttpMethod; +import io.vertx.junit5.Checkpoint; +import io.vertx.junit5.VertxExtension; +import io.vertx.junit5.VertxTestContext; +import io.vertx.rxjava3.core.buffer.Buffer; +import io.vertx.rxjava3.core.http.HttpClient; +import io.vertx.rxjava3.core.http.HttpClientRequest; +import java.util.Map; +import java.util.Set; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.atomic.AtomicReference; +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; + +/** + * @author Guillaume Lamirand (guillaume.lamirand at graviteesource.com) + * @author GraviteeSource Team + */ +@GatewayTest +@ExtendWith(VertxExtension.class) +class LatencyPolicyIntegrationV4Test extends AbstractPolicyTest { + + private FakeReporter fakeReporter; + private AtomicReference metricsRef; + private AtomicReference messageMetricsRef; + + @AfterEach + void afterEach() { + fakeReporter.reset(); + } + + @Override + protected void configureGateway(GatewayConfigurationBuilder gatewayConfigurationBuilder) { + super.configureGateway(gatewayConfigurationBuilder); + gatewayConfigurationBuilder.set("api.jupiterMode.enabled", "true"); + } + + @Override + public void configureReactors(Set>> reactors) { + reactors.add(ReactorBuilder.build(MessageApiReactorFactory.class)); + } + + @Override + public void configureEntrypoints(Map> entrypoints) { + entrypoints.putIfAbsent("http-proxy", EntrypointBuilder.build("http-proxy", HttpProxyEntrypointConnectorFactory.class)); + entrypoints.putIfAbsent("sse", EntrypointBuilder.build("sse", SseEntrypointConnectorFactory.class)); + entrypoints.putIfAbsent("http-post", EntrypointBuilder.build("http-post", HttpPostEntrypointConnectorFactory.class)); + } + + @Override + public void configureEndpoints(Map> endpoints) { + endpoints.putIfAbsent("http-proxy", EndpointBuilder.build("http-proxy", HttpProxyEndpointConnectorFactory.class)); + endpoints.putIfAbsent("mock", EndpointBuilder.build("mock", PersistentMockEndpointConnectorFactory.class)); + } + + @Test + @DeployApi("/apis/latency-v4-proxy.json") + void should_apply_latency_on_proxy_request(HttpClient httpClient, VertxTestContext vertxTestContext) throws InterruptedException { + prepareReporter(vertxTestContext, 1); + Checkpoint responseCheckpoint = vertxTestContext.checkpoint(); + wiremock.stubFor(get("/endpoint").willReturn(ok("I'm the backend"))); + + TestObserver testObserver = httpClient + .request(HttpMethod.GET, "/test") + .flatMap(HttpClientRequest::rxSend) + .flatMap(response -> { + assertThat(response.statusCode()).isEqualTo(200); + return response.rxBody(); + }) + .test(); + awaitTerminalEvent(testObserver); + testObserver + .assertComplete() + .assertValue(body -> { + responseCheckpoint.flag(); + assertThat(body).hasToString("I'm the backend"); + return true; + }); + + wiremock.verify(exactly(1), getRequestedFor(urlPathEqualTo("/endpoint"))); + + assertThat(vertxTestContext.awaitCompletion(30, TimeUnit.SECONDS)).isTrue(); + Metrics metrics = metricsRef.get(); + assertThat(metrics).isNotNull(); + assertThat(metrics.getGatewayResponseTimeMs()).isGreaterThan(2000); + } + + @Test + @DeployApi("/apis/latency-v4-message-publish.json") + void should_apply_latency_on_request_message(HttpClient httpClient, VertxTestContext vertxTestContext) throws InterruptedException { + prepareReporter(vertxTestContext, 3); + Checkpoint responseCheckpoint = vertxTestContext.checkpoint(); + httpClient + .rxRequest(POST, "/test") + .flatMap(request -> request.rxSend("message")) + .flatMap(response -> { + assertThat(response.statusCode()).isEqualTo(202); + return response.body(); + }) + .test() + .awaitDone(5, TimeUnit.SECONDS) + .assertValue(body -> { + responseCheckpoint.flag(); + assertThat(body.length()).isZero(); + return true; + }); + assertThat(vertxTestContext.awaitCompletion(30, TimeUnit.SECONDS)).isTrue(); + + MessageMetrics messageMetrics = messageMetricsRef.get(); + assertThat(messageMetrics).isNotNull(); + assertThat(messageMetrics.getGatewayLatencyMs()).isGreaterThan(2000); + } + + @Test + @DeployApi("/apis/latency-v4-message-subscribe.json") + void should_apply_latency_on_response_message(HttpClient httpClient, VertxTestContext vertxTestContext) throws InterruptedException { + prepareReporter(vertxTestContext, 3); + Checkpoint responseCheckpoint = vertxTestContext.checkpoint(); + httpClient + .rxRequest(HttpMethod.GET, "/test") + .flatMap(request -> { + request.putHeader(HttpHeaderNames.ACCEPT.toString(), MediaType.TEXT_EVENT_STREAM); + return request.rxSend(); + }) + .flatMapPublisher(response -> { + assertThat(response.statusCode()).isEqualTo(200); + return response.toFlowable(); + }) + .filter(buffer -> !buffer.toString().startsWith("retry:") && !buffer.toString().startsWith(":")) + .test() + .awaitCount(1) + .assertValue(body -> { + responseCheckpoint.flag(); + return true; + }); + assertThat(vertxTestContext.awaitCompletion(30, TimeUnit.SECONDS)).isTrue(); + MessageMetrics messageMetrics = messageMetricsRef.get(); + assertThat(messageMetrics).isNotNull(); + assertThat(messageMetrics.getGatewayLatencyMs()).isGreaterThan(2000); + } + + void prepareReporter(final VertxTestContext vertxTestContext, final int flagCount) { + Checkpoint fakeReporterCheckpoint = vertxTestContext.checkpoint(flagCount); + fakeReporter = getBean(FakeReporter.class); + metricsRef = new AtomicReference<>(); + messageMetricsRef = new AtomicReference<>(); + fakeReporter.setReportableHandler(reportable -> { + fakeReporterCheckpoint.flag(); + if (reportable instanceof Metrics) { + metricsRef.set((Metrics) reportable); + } else if (reportable instanceof MessageMetrics) { + messageMetricsRef.set((MessageMetrics) reportable); + } + }); + } +} diff --git a/src/test/java/io/gravitee/policy/latency/LatencyPolicyTest.java b/src/test/java/io/gravitee/policy/latency/LatencyPolicyTest.java deleted file mode 100644 index 3df4d3a6..00000000 --- a/src/test/java/io/gravitee/policy/latency/LatencyPolicyTest.java +++ /dev/null @@ -1,70 +0,0 @@ -/** - * Copyright (C) 2015 The Gravitee team (http://gravitee.io) - * - * 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 io.gravitee.policy.latency; - -import static org.mockito.MockitoAnnotations.initMocks; - -import io.gravitee.el.TemplateContext; -import io.gravitee.el.TemplateEngine; -import io.gravitee.gateway.api.ExecutionContext; -import io.gravitee.gateway.api.stream.exception.TransformationException; -import org.junit.Before; -import org.junit.Ignore; -import org.junit.Test; -import org.junit.runner.RunWith; -import org.mockito.Mock; -import org.mockito.runners.MockitoJUnitRunner; - -/** - * @author Azize ELAMRANI (azize.elamrani at graviteesource.com) - * @author GraviteeSource Team - */ -@RunWith(MockitoJUnitRunner.class) -@Ignore -public class LatencyPolicyTest { - - @Mock - protected ExecutionContext executionContext; - - @Before - public void init() { - initMocks(this); - } - - @Test - public void shouldTransformInput() throws Exception {} - - @Test(expected = TransformationException.class) - public void shouldThrowException() throws Exception {} - - private class MockTemplateEngine implements TemplateEngine { - - @Override - public String convert(String s) { - return s; - } - - @Override - public T getValue(String expression, Class clazz) { - return null; - } - - @Override - public TemplateContext getTemplateContext() { - return null; - } - } -} diff --git a/src/test/resources/apis/latency.json b/src/test/resources/apis/latency-v2.json similarity index 100% rename from src/test/resources/apis/latency.json rename to src/test/resources/apis/latency-v2.json diff --git a/src/test/resources/apis/latency-v4-message-publish.json b/src/test/resources/apis/latency-v4-message-publish.json new file mode 100644 index 00000000..4c67a88b --- /dev/null +++ b/src/test/resources/apis/latency-v4-message-publish.json @@ -0,0 +1,64 @@ +{ + "id": "my-message-publish-api", + "name": "my-message-publish-api", + "apiVersion": "1.0", + "definitionVersion": "4.0.0", + "type": "message", + "listeners": [ + { + "type": "http", + "paths": [ + { + "path": "/test" + } + ], + "entrypoints": [ + { + "type": "http-post" + } + ] + } + ], + "endpointGroups": [ + { + "name": "default-group", + "type": "mock", + "endpoints": [ + { + "name": "default", + "type": "mock", + "weight": 1, + "inheritConfiguration": false, + "configuration": { + "messageCount": 1, + "messageContent": "{ \"message\": \"hello\" }" + } + } + ] + } + ], + "flows": [ + { + "name": "flow-1", + "enabled": true, + "publish": [ + { + "name": "Latency policy", + "description": "", + "enabled": true, + "policy": "latency", + "configuration": { + "time": 2, + "timeUnit": "SECONDS" + } + } + ], + "request": [], + "response": [], + "subscribe": [] + } + ], + "analytics" : { + "enabled " : true + } +} diff --git a/src/test/resources/apis/latency-v4-message-subscribe.json b/src/test/resources/apis/latency-v4-message-subscribe.json new file mode 100644 index 00000000..2cab4082 --- /dev/null +++ b/src/test/resources/apis/latency-v4-message-subscribe.json @@ -0,0 +1,67 @@ +{ + "id": "my-message-subscribe-api", + "name": "my-message-subscribe-api", + "apiVersion": "1.0", + "definitionVersion": "4.0.0", + "type": "message", + "listeners": [ + { + "type": "http", + "paths": [ + { + "path": "/test" + } + ], + "entrypoints": [ + { + "type": "sse", + "configuration": { + "headersAsComment" : true + } + } + ] + } + ], + "endpointGroups": [ + { + "name": "default-group", + "type": "mock", + "endpoints": [ + { + "name": "default", + "type": "mock", + "weight": 1, + "inheritConfiguration": false, + "configuration": { + "messageContent": "{ \"message\": \"hello\" }", + "messageCount": 1 + } + } + ] + } + ], + "flows": [ + { + "name": "flow-1", + "enabled": true, + "subscribe": [ + { + "name": "Latency policy", + "description": "", + "enabled": true, + "policy": "latency", + "configuration": { + "time": 2, + "timeUnit": "SECONDS" + } + } + ], + "request": [], + "response": [], + "publish": [] + } + ], + "analytics" : { + "enabled " : true + } +} diff --git a/src/test/resources/apis/latency-v4-proxy.json b/src/test/resources/apis/latency-v4-proxy.json new file mode 100644 index 00000000..d968cdb8 --- /dev/null +++ b/src/test/resources/apis/latency-v4-proxy.json @@ -0,0 +1,63 @@ +{ + "id": "my-proxy-api", + "name": "my-proxy-api", + "apiVersion": "1.0", + "definitionVersion": "4.0.0", + "type": "proxy", + "listeners": [ + { + "type": "http", + "paths": [ + { + "path": "/test" + } + ], + "entrypoints": [ + { + "type": "http-proxy" + } + ] + } + ], + "endpointGroups": [ + { + "name": "default-group", + "type": "http-proxy", + "endpoints": [ + { + "name": "default", + "type": "http-proxy", + "weight": 1, + "inheritConfiguration": false, + "configuration": { + "target": "http://localhost:8080/endpoint" + } + } + ] + } + ], + "flows": [ + { + "name": "flow-1", + "enabled": true, + "request": [ + { + "name": "Latency policy", + "description": "", + "enabled": true, + "policy": "latency", + "configuration": { + "time": 2, + "timeUnit": "SECONDS" + } + } + ], + "response": [], + "subscribe": [], + "publish": [] + } + ], + "analytics" : { + "enabled " : true + } +} diff --git a/src/test/resources/logback-test.xml b/src/test/resources/logback-test.xml index 3e842fd9..d5478327 100644 --- a/src/test/resources/logback-test.xml +++ b/src/test/resources/logback-test.xml @@ -10,12 +10,12 @@ - + - +