From 6b0da6a2bbeb259a902436d181247c33229c9aff Mon Sep 17 00:00:00 2001 From: Christoph Deppisch Date: Sun, 11 Feb 2024 10:29:17 +0100 Subject: [PATCH] chore(knative): Add local Knative support - Support local mode when creating Knative broker - Add capability for local Knative consumer service - Enable/disable broker response verification --- java/docs/steps-knative.adoc | 20 ++--- .../yaks/camelk/actions/CamelKAction.java | 2 +- .../actions/kamelet/VerifyKameletAction.java | 4 +- .../yaks/knative/KnativeSettings.java | 25 +++++- .../yaks/knative/KnativeVariableNames.java | 2 + .../yaks/knative/ReceiveEventSteps.java | 22 ++++- .../yaks/knative/SendEventSteps.java | 17 ++-- .../actions/AbstractKnativeAction.java | 24 +++++ .../yaks/knative/actions/KnativeAction.java | 27 +++++- .../knative/actions/KnativeActionBuilder.java | 9 +- .../actions/eventing/CreateBrokerAction.java | 46 +++++++++- .../actions/eventing/CreateTriggerAction.java | 6 ++ .../actions/eventing/DeleteBrokerAction.java | 88 +++++++++++++++++++ .../actions/eventing/VerifyBrokerAction.java | 25 ++++++ .../yaks/knative/ce/CloudEvent.java | 14 +-- .../knative/KnativeServiceConfiguration.java | 10 +-- .../knative/knative.event.consumer.feature | 12 +-- .../knative/knative.event.producer.feature | 12 +-- 18 files changed, 307 insertions(+), 58 deletions(-) create mode 100644 java/steps/yaks-knative/src/main/java/org/citrusframework/yaks/knative/actions/eventing/DeleteBrokerAction.java diff --git a/java/docs/steps-knative.adoc b/java/docs/steps-knative.adoc index cbddea58..3b9bb9f8 100644 --- a/java/docs/steps-knative.adoc +++ b/java/docs/steps-knative.adoc @@ -264,11 +264,11 @@ As we are using the Http cloud event model we can also use Http property equival [source,gherkin] ---- When send Knative event -| Ce-Specversion | 1.0 | -| Ce-Type | greeting | -| Ce-Source | https://github.com/citrusframework/yaks | -| Ce-Subject | hello | -| Ce-Id | say-hello-${id} | +| ce-specversion | 1.0 | +| ce-type | greeting | +| ce-source | https://github.com/citrusframework/yaks | +| ce-subject | hello | +| ce-id | say-hello-${id} | | Content-Type | application/json;charset=UTF-8 | | data | {"msg": "Hello Knative!"} | ---- @@ -409,11 +409,11 @@ As we are using the Http cloud event model we can also use Http property equival [source,gherkin] ---- Then receive Knative event -| Ce-Specversion | 1.0 | -| Ce-Type | greeting | -| Ce-Source | https://github.com/citrusframework/yaks | -| Ce-Subject | hello | -| Ce-Id | say-hello-${id} | +| ce-specversion | 1.0 | +| ce-type | greeting | +| ce-source | https://github.com/citrusframework/yaks | +| ce-subject | hello | +| ce-id | say-hello-${id} | | Content-Type | application/json;charset=UTF-8 | | data | {"msg": "Hello Knative!"} | ---- diff --git a/java/steps/yaks-camel-k/src/main/java/org/citrusframework/yaks/camelk/actions/CamelKAction.java b/java/steps/yaks-camel-k/src/main/java/org/citrusframework/yaks/camelk/actions/CamelKAction.java index 35c574e6..4a784ba1 100644 --- a/java/steps/yaks-camel-k/src/main/java/org/citrusframework/yaks/camelk/actions/CamelKAction.java +++ b/java/steps/yaks-camel-k/src/main/java/org/citrusframework/yaks/camelk/actions/CamelKAction.java @@ -73,7 +73,7 @@ default String operatorNamespace(TestContext context) { /** * Resolves cluster type from given test context using the stored test variable. - * Fallback to retreiving the cluster type from environment settings when no test variable is present. + * Fallback to retrieving the cluster type from environment settings when no test variable is present. * * @param context * @return diff --git a/java/steps/yaks-camel-k/src/main/java/org/citrusframework/yaks/camelk/actions/kamelet/VerifyKameletAction.java b/java/steps/yaks-camel-k/src/main/java/org/citrusframework/yaks/camelk/actions/kamelet/VerifyKameletAction.java index 0ce89bb9..3eb22b10 100644 --- a/java/steps/yaks-camel-k/src/main/java/org/citrusframework/yaks/camelk/actions/kamelet/VerifyKameletAction.java +++ b/java/steps/yaks-camel-k/src/main/java/org/citrusframework/yaks/camelk/actions/kamelet/VerifyKameletAction.java @@ -21,7 +21,7 @@ import org.citrusframework.context.TestContext; import org.citrusframework.exceptions.ValidationException; -import org.citrusframework.yaks.YaksClusterType; +import org.citrusframework.yaks.YaksSettings; import org.citrusframework.yaks.camelk.CamelKSettings; import org.citrusframework.yaks.camelk.model.Kamelet; import org.citrusframework.yaks.camelk.model.KameletList; @@ -60,7 +60,7 @@ public void doExecute(TestContext context) { @Override public boolean isDisabled(TestContext context) { - return clusterType(context) == YaksClusterType.LOCAL; + return YaksSettings.isLocal(clusterType(context)); } /** diff --git a/java/steps/yaks-knative/src/main/java/org/citrusframework/yaks/knative/KnativeSettings.java b/java/steps/yaks-knative/src/main/java/org/citrusframework/yaks/knative/KnativeSettings.java index 876f8ea7..8eb7d391 100644 --- a/java/steps/yaks-knative/src/main/java/org/citrusframework/yaks/knative/KnativeSettings.java +++ b/java/steps/yaks-knative/src/main/java/org/citrusframework/yaks/knative/KnativeSettings.java @@ -72,6 +72,13 @@ public class KnativeSettings { private static final String AUTO_REMOVE_RESOURCES_ENV = KNATIVE_ENV_PREFIX + "AUTO_REMOVE_RESOURCES"; private static final String AUTO_REMOVE_RESOURCES_DEFAULT = "true"; + private static final String VERIFY_BROKER_RESPONSE_PROPERTY = KNATIVE_PROPERTY_PREFIX + "verify.broker.resources"; + private static final String VERIFY_BROKER_RESPONSE_ENV = KNATIVE_ENV_PREFIX + "VERIFY_BROKER_RESPONSE"; + private static final String VERIFY_BROKER_RESPONSE_DEFAULT = "true"; + + private static final String BROKER_RESPONSE_STATUS_PROPERTY = KNATIVE_PROPERTY_PREFIX + "broker.response"; + private static final String BROKER_RESPONSE_STATUS_ENV = KNATIVE_ENV_PREFIX + "BROKER_RESPONSE_STATUS"; + private static final String DEFAULT_LABELS_PROPERTY = KNATIVE_PROPERTY_PREFIX + "default.labels"; private static final String DEFAULT_LABELS_ENV = KNATIVE_ENV_PREFIX + "DEFAULT_LABELS"; @@ -207,6 +214,23 @@ public static boolean isAutoRemoveResources() { System.getenv(AUTO_REMOVE_RESOURCES_ENV) != null ? System.getenv(AUTO_REMOVE_RESOURCES_ENV) : AUTO_REMOVE_RESOURCES_DEFAULT)); } + public static boolean isVerifyBrokerResponse() { + return Boolean.parseBoolean(System.getProperty(VERIFY_BROKER_RESPONSE_PROPERTY, + System.getenv(VERIFY_BROKER_RESPONSE_ENV) != null ? System.getenv(VERIFY_BROKER_RESPONSE_ENV) : VERIFY_BROKER_RESPONSE_DEFAULT)); + } + + public static int getBrokerResponseStatus() { + String defaultResponseStatus; + if (YaksSettings.isLocal()) { + defaultResponseStatus = "204"; // NO_CONTENT + } else { + defaultResponseStatus = "202"; // ACCEPTED + } + + return Integer.parseInt(System.getProperty(BROKER_RESPONSE_STATUS_PROPERTY, + System.getenv(BROKER_RESPONSE_STATUS_ENV) != null ? System.getenv(BROKER_RESPONSE_STATUS_ENV) : defaultResponseStatus)); + } + public static String getKnativeMessagingGroup() { return "messaging.knative.dev"; } @@ -214,5 +238,4 @@ public static String getKnativeMessagingGroup() { public static String getKnativeEventingGroup() { return "eventing.knative.dev"; } - } diff --git a/java/steps/yaks-knative/src/main/java/org/citrusframework/yaks/knative/KnativeVariableNames.java b/java/steps/yaks-knative/src/main/java/org/citrusframework/yaks/knative/KnativeVariableNames.java index c4618fbc..65c5d056 100644 --- a/java/steps/yaks-knative/src/main/java/org/citrusframework/yaks/knative/KnativeVariableNames.java +++ b/java/steps/yaks-knative/src/main/java/org/citrusframework/yaks/knative/KnativeVariableNames.java @@ -22,7 +22,9 @@ */ public enum KnativeVariableNames { + CLUSTER_TYPE("YAKS_CLUSTER_TYPE"), BROKER_NAME("KNATIVE_BROKER"), + BROKER_PORT("KNATIVE_BROKER_PORT"), NAMESPACE("KNATIVE_NAMESPACE"); private final String variableName; diff --git a/java/steps/yaks-knative/src/main/java/org/citrusframework/yaks/knative/ReceiveEventSteps.java b/java/steps/yaks-knative/src/main/java/org/citrusframework/yaks/knative/ReceiveEventSteps.java index 6d4be097..641dcbd8 100644 --- a/java/steps/yaks-knative/src/main/java/org/citrusframework/yaks/knative/ReceiveEventSteps.java +++ b/java/steps/yaks-knative/src/main/java/org/citrusframework/yaks/knative/ReceiveEventSteps.java @@ -28,6 +28,8 @@ import io.cucumber.java.Scenario; import io.cucumber.java.en.Given; import io.cucumber.java.en.Then; +import org.citrusframework.http.server.HttpServer; +import org.citrusframework.yaks.YaksSettings; import org.citrusframework.yaks.knative.ce.CloudEventMessage; import org.citrusframework.yaks.knative.ce.CloudEventSupport; import org.citrusframework.yaks.kubernetes.KubernetesSteps; @@ -99,12 +101,28 @@ public void receiveEventJson(String json) { @Given("^create Knative event consumer service ([^\\s]+)$") public void createService(String serviceName) { - kubernetesSteps.createService(serviceName); + if (YaksSettings.isLocal() && context.getVariables().containsKey(KnativeVariableNames.BROKER_NAME.value()) && + context.getReferenceResolver().isResolvable(context.getVariable(KnativeVariableNames.BROKER_NAME.value()))) { + HttpServer brokerServer = context.getReferenceResolver().resolve(context.getVariable(KnativeVariableNames.BROKER_NAME.value()), HttpServer.class); + context.getReferenceResolver().bind(serviceName, brokerServer); + setServiceName(serviceName); + setServicePort(String.valueOf(brokerServer.getPort())); + } else { + kubernetesSteps.createService(serviceName); + } } @Given("^create Knative event consumer service ([^\\s]+) with target port ([^\\s]+)$") public void createService(String serviceName, String targetPort) { - kubernetesSteps.createService(serviceName, targetPort); + if (YaksSettings.isLocal() && context.getVariables().containsKey(KnativeVariableNames.BROKER_NAME.value()) && + context.getReferenceResolver().isResolvable(context.getVariable(KnativeVariableNames.BROKER_NAME.value()))) { + HttpServer brokerServer = context.getReferenceResolver().resolve(context.getVariable(KnativeVariableNames.BROKER_NAME.value()), HttpServer.class); + context.getReferenceResolver().bind(serviceName, brokerServer); + setServiceName(serviceName); + setServicePort(String.valueOf(brokerServer.getPort())); + } else { + kubernetesSteps.createService(serviceName, targetPort); + } } /** diff --git a/java/steps/yaks-knative/src/main/java/org/citrusframework/yaks/knative/SendEventSteps.java b/java/steps/yaks-knative/src/main/java/org/citrusframework/yaks/knative/SendEventSteps.java index 9a6d803e..68ce8912 100644 --- a/java/steps/yaks-knative/src/main/java/org/citrusframework/yaks/knative/SendEventSteps.java +++ b/java/steps/yaks-knative/src/main/java/org/citrusframework/yaks/knative/SendEventSteps.java @@ -21,6 +21,7 @@ import java.security.KeyStoreException; import java.security.NoSuchAlgorithmException; import java.util.Objects; +import java.util.UUID; import javax.net.ssl.SSLContext; import io.cucumber.datatable.DataTable; @@ -138,15 +139,15 @@ private void sendEvent(CloudEventMessage request) { } if (request.getEventId() == null) { - request.eventId("yaks-test-event"); + request.eventId(UUID.randomUUID().toString()); } if (request.getEventType() == null) { - request.eventType("yaks-test"); + request.eventType("org.citrusframework.yaks.event.test"); } if (request.getSource() == null) { - request.source("yaks-test-source"); + request.source("yaks-test"); } request.setHeader("Host", KnativeSettings.getBrokerHost()); @@ -162,10 +163,12 @@ private void sendEvent(CloudEventMessage request) { runner.run(requestBuilder); - runner.run(http().client(httpClient) - .receive() - .response(HttpStatus.ACCEPTED) - .timeout(timeout)); + if (KnativeSettings.isVerifyBrokerResponse()) { + runner.run(http().client(httpClient) + .receive() + .response(HttpStatus.valueOf(KnativeSettings.getBrokerResponseStatus())) + .timeout(timeout)); + } } /** diff --git a/java/steps/yaks-knative/src/main/java/org/citrusframework/yaks/knative/actions/AbstractKnativeAction.java b/java/steps/yaks-knative/src/main/java/org/citrusframework/yaks/knative/actions/AbstractKnativeAction.java index 7f15c015..f4499e87 100644 --- a/java/steps/yaks-knative/src/main/java/org/citrusframework/yaks/knative/actions/AbstractKnativeAction.java +++ b/java/steps/yaks-knative/src/main/java/org/citrusframework/yaks/knative/actions/AbstractKnativeAction.java @@ -21,6 +21,8 @@ import org.citrusframework.actions.AbstractTestAction; import io.fabric8.knative.client.KnativeClient; import io.fabric8.kubernetes.client.KubernetesClient; +import org.citrusframework.context.TestContext; +import org.citrusframework.yaks.YaksClusterType; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -35,11 +37,14 @@ public abstract class AbstractKnativeAction extends AbstractTestAction implement private final KnativeClient knativeClient; private final KubernetesClient kubernetesClient; + private final YaksClusterType clusterType; + public AbstractKnativeAction(String name, Builder builder) { super("knative:" + name, builder); this.knativeClient = builder.knativeClient; this.kubernetesClient = builder.kubernetesClient; + this.clusterType = builder.clusterType; } @Override @@ -52,6 +57,15 @@ public KnativeClient getKnativeClient() { return knativeClient; } + @Override + public YaksClusterType clusterType(TestContext context) { + if (clusterType != null) { + return clusterType; + } + + return KnativeAction.super.clusterType(context); + } + /** * Action builder. */ @@ -60,6 +74,8 @@ public static abstract class Builder { + + private String brokerName; + + public Builder name(String brokerName) { + this.brokerName = brokerName; + return this; + } + + @Override + public DeleteBrokerAction build() { + return new DeleteBrokerAction(this); + } + } +} diff --git a/java/steps/yaks-knative/src/main/java/org/citrusframework/yaks/knative/actions/eventing/VerifyBrokerAction.java b/java/steps/yaks-knative/src/main/java/org/citrusframework/yaks/knative/actions/eventing/VerifyBrokerAction.java index 9cc409e5..37ff28e3 100644 --- a/java/steps/yaks-knative/src/main/java/org/citrusframework/yaks/knative/actions/eventing/VerifyBrokerAction.java +++ b/java/steps/yaks-knative/src/main/java/org/citrusframework/yaks/knative/actions/eventing/VerifyBrokerAction.java @@ -21,6 +21,8 @@ import org.citrusframework.exceptions.ValidationException; import io.fabric8.knative.eventing.v1.Broker; import io.fabric8.kubernetes.client.KubernetesClientException; +import org.citrusframework.http.server.HttpServer; +import org.citrusframework.yaks.YaksSettings; import org.citrusframework.yaks.knative.actions.AbstractKnativeAction; /** @@ -38,6 +40,29 @@ public VerifyBrokerAction(Builder builder) { @Override public void doExecute(TestContext context) { + if (YaksSettings.isLocal(clusterType(context))) { + verifyLocalBroker(context); + } else { + verifyBroker(context); + } + } + + private void verifyLocalBroker(TestContext context) { + String resolvedBrokerName = context.replaceDynamicContentInString(brokerName); + + if (!context.getReferenceResolver().isResolvable(resolvedBrokerName, HttpServer.class)) { + throw new ValidationException(String.format("Knative broker '%s' not found", brokerName)); + } + + HttpServer brokerServer = context.getReferenceResolver().resolve(resolvedBrokerName, HttpServer.class); + if (!brokerServer.isRunning()) { + throw new ValidationException(String.format("Knative broker '%s' is not ready", brokerName)); + } + + LOG.info(String.format("Knative broker %s is ready", brokerName)); + } + + private void verifyBroker(TestContext context) { try { Broker broker = getKnativeClient().brokers() .inNamespace(namespace(context)) diff --git a/java/steps/yaks-knative/src/main/java/org/citrusframework/yaks/knative/ce/CloudEvent.java b/java/steps/yaks-knative/src/main/java/org/citrusframework/yaks/knative/ce/CloudEvent.java index fce0a5e1..209d4968 100644 --- a/java/steps/yaks-knative/src/main/java/org/citrusframework/yaks/knative/ce/CloudEvent.java +++ b/java/steps/yaks-knative/src/main/java/org/citrusframework/yaks/knative/ce/CloudEvent.java @@ -68,13 +68,13 @@ public static CloudEvent v1_0() { */ public enum Attribute { - ID("Ce-Id", "id"), - SOURCE("Ce-Source", "source"), - SPEC_VERSION("Ce-Specversion", "specversion", "1.0"), - TYPE("Ce-Type", "type"), - SUBJECT("Ce-Subject", "subject"), - DATA_SCHEMA("Ce-Dataschema", "dataschema"), - TIME("Ce-Time", "time"), + ID("ce-id", "id"), + SOURCE("ce-source", "source"), + SPEC_VERSION("ce-specversion", "specversion", "1.0"), + TYPE("ce-type", "type"), + SUBJECT("ce-subject", "subject"), + DATA_SCHEMA("ce-dataschema", "dataschema"), + TIME("ce-time", "time"), CONTENT_TYPE("Content-Type", "datacontenttype"); private final String http; diff --git a/java/steps/yaks-knative/src/test/java/org/citrusframework/yaks/knative/KnativeServiceConfiguration.java b/java/steps/yaks-knative/src/test/java/org/citrusframework/yaks/knative/KnativeServiceConfiguration.java index d2e005f4..01efe31b 100644 --- a/java/steps/yaks-knative/src/test/java/org/citrusframework/yaks/knative/KnativeServiceConfiguration.java +++ b/java/steps/yaks-knative/src/test/java/org/citrusframework/yaks/knative/KnativeServiceConfiguration.java @@ -80,11 +80,11 @@ public EndpointAdapter handleCloudEventAdapter() { return new StaticEndpointAdapter() { @Override protected Message handleMessageInternal(Message message) { - Assertions.assertThat(message.getHeader("Ce-Id")).isEqualTo("say-hello"); - Assertions.assertThat(message.getHeader("Ce-Specversion")).isEqualTo("1.0"); - Assertions.assertThat(message.getHeader("Ce-Subject")).isEqualTo("hello"); - Assertions.assertThat(message.getHeader("Ce-Type")).isEqualTo("greeting"); - Assertions.assertThat(message.getHeader("Ce-Source")).isEqualTo("https://github.com/citrusframework/yaks"); + Assertions.assertThat(message.getHeader("ce-id")).isEqualTo("say-hello"); + Assertions.assertThat(message.getHeader("ce-specversion")).isEqualTo("1.0"); + Assertions.assertThat(message.getHeader("ce-subject")).isEqualTo("hello"); + Assertions.assertThat(message.getHeader("ce-type")).isEqualTo("greeting"); + Assertions.assertThat(message.getHeader("ce-source")).isEqualTo("https://github.com/citrusframework/yaks"); Assertions.assertThat(message.getHeader("Content-Type").toString()).isEqualTo(MediaType.APPLICATION_JSON_UTF8_VALUE); Assertions.assertThat(message.getPayload(String.class)).isEqualTo("{\"msg\": \"Hello Knative!\"}"); diff --git a/java/steps/yaks-knative/src/test/resources/org/citrusframework/yaks/knative/knative.event.consumer.feature b/java/steps/yaks-knative/src/test/resources/org/citrusframework/yaks/knative/knative.event.consumer.feature index 93e5d599..ba1f15fe 100644 --- a/java/steps/yaks-knative/src/test/resources/org/citrusframework/yaks/knative/knative.event.consumer.feature +++ b/java/steps/yaks-knative/src/test/resources/org/citrusframework/yaks/knative/knative.event.consumer.feature @@ -52,12 +52,12 @@ Feature: Knative event consumer Scenario: Receive event http Given expect Knative event data: {"msg": "Hello Knative!"} When receive Knative event - | Ce-Specversion | 1.0 | - | Ce-Type | greeting | - | Ce-Source | https://github.com/citrusframework/yaks | - | Ce-Subject | hello | - | Ce-Id | say-hello-${id} | - | Ce-Time | @matchesDatePattern('yyyy-MM-dd'T'HH:mm:ss')@ | + | ce-specversion | 1.0 | + | ce-type | greeting | + | ce-source | https://github.com/citrusframework/yaks | + | ce-subject | hello | + | ce-id | say-hello-${id} | + | ce-time | @matchesDatePattern('yyyy-MM-dd'T'HH:mm:ss')@ | | Content-Type | application/json;charset=UTF-8 | Then verify test event accepted diff --git a/java/steps/yaks-knative/src/test/resources/org/citrusframework/yaks/knative/knative.event.producer.feature b/java/steps/yaks-knative/src/test/resources/org/citrusframework/yaks/knative/knative.event.producer.feature index 0c9f57e0..36cba441 100644 --- a/java/steps/yaks-knative/src/test/resources/org/citrusframework/yaks/knative/knative.event.producer.feature +++ b/java/steps/yaks-knative/src/test/resources/org/citrusframework/yaks/knative/knative.event.producer.feature @@ -27,12 +27,12 @@ Feature: Knative event producer Scenario: Send event http Given Knative event data: {"msg": "Hello Knative!"} When send Knative event - | Ce-Specversion | 1.0 | - | Ce-Type | greeting | - | Ce-Source | https://github.com/citrusframework/yaks | - | Ce-Subject | hello | - | Ce-Id | say-hello | - | Ce-Time | citrus:currentDate('yyyy-MM-dd'T'HH:mm:ss') | + | ce-specversion | 1.0 | + | ce-type | greeting | + | ce-source | https://github.com/citrusframework/yaks | + | ce-subject | hello | + | ce-id | say-hello | + | ce-time | citrus:currentDate('yyyy-MM-dd'T'HH:mm:ss') | | Content-Type | application/json | Scenario: Send multiline event data