This is an automated email from the ASF dual-hosted git repository. nfilotto pushed a commit to branch 276/integration-test-framework in repository https://gitbox.apache.org/repos/asf/camel-karaf.git
commit 99965f44f646ad705344425b93599f3a7245ddad Author: Francois de Parscau <francois.depars...@qlik.com> AuthorDate: Mon Apr 29 22:11:45 2024 +0200 Ref #276: Propose an integration test framework --- features/src/main/feature/camel-features.xml | 16 ++- tests/camel-integration-test/pom.xml | 23 +++- .../karaf/camel/itests/AbstractCamelComponent.java | 9 +- .../camel/itests/AbstractCamelKarafITest.java | 13 +- .../karaf/camel/itests/ExternalResource.java | 30 +++++ .../camel/itests/GenericContainerResource.java | 97 +++++++++++++++ .../camel/itests/PaxExamWithExternalResource.java | 138 +++++++++++++++++++++ .../apache/karaf/camel/itests/TemporaryFile.java | 74 +++++++++++ .../camel/itests/UseExternalResourceProvider.java | 26 ++++ .../java/org/apache/karaf/camel/itests/Utils.java | 6 +- tests/components/camel-elasticsearch/pom.xml | 49 ++++++++ .../camel/test/CamelElasticsearchComponent.java | 79 ++++++++++++ .../camel/itests/CamelElasticsearchITest.java | 77 ++++++++++++ .../karaf/camel/test/CamelJettyComponent.java | 3 +- tests/components/pom.xml | 5 +- 15 files changed, 623 insertions(+), 22 deletions(-) diff --git a/features/src/main/feature/camel-features.xml b/features/src/main/feature/camel-features.xml index 23548c9f..97c12f59 100644 --- a/features/src/main/feature/camel-features.xml +++ b/features/src/main/feature/camel-features.xml @@ -828,12 +828,18 @@ <feature name='camel-elasticsearch' version='${project.version}' start-level='50'> <feature version='${camel.osgi.version.range}'>camel-core</feature> <feature prerequisite='true'>wrap</feature> + <feature prerequisite="true">spifly</feature> <feature version='[2.16,2.17)'>jackson</feature> <feature version="[4,5)">http-client</feature> + <bundle dependency="true">mvn:org.glassfish.hk2/osgi-resource-locator/2.5.0-b42</bundle> + <bundle dependency="true">mvn:org.eclipse.parsson/parsson/${parson-version}</bundle> + <bundle dependency='true'>mvn:jakarta.json/jakarta.json-api/${jakarta-json-api-version}</bundle> + <bundle dependency='true'>wrap:mvn:io.opentelemetry/opentelemetry-api/${opentelemetry-version}</bundle> + <bundle dependency='true'>wrap:mvn:io.opentelemetry/opentelemetry-context/${opentelemetry-version}</bundle> <bundle dependency='true'>wrap:mvn:org.apache.httpcomponents/httpasyncclient/${httpasyncclient-version}</bundle> - <bundle dependency='true'>wrap:mvn:co.elastic.clients/elasticsearch-java/8.12.1</bundle> - <bundle dependency='true'>wrap:mvn:org.elasticsearch.client/elasticsearch-rest-client/8.12.1</bundle> - <bundle dependency='true'>wrap:mvn:org.elasticsearch.client/elasticsearch-rest-client-sniffer/8.12.1</bundle> + <bundle dependency='true'>wrap:mvn:co.elastic.clients/elasticsearch-java/${elasticsearch-java-client-version}</bundle> + <bundle dependency='true'>wrap:mvn:org.elasticsearch.client/elasticsearch-rest-client/${elasticsearch-java-client-version}</bundle> + <bundle dependency='true'>wrap:mvn:org.elasticsearch.client/elasticsearch-rest-client-sniffer/${elasticsearch-java-client-sniffer-version}</bundle> <bundle>mvn:org.apache.camel.karaf/camel-elasticsearch/${project.version}</bundle> </feature> <feature name='camel-elasticsearch-rest-client' version='${project.version}' start-level='50'> @@ -842,8 +848,8 @@ <feature version='[2.16,2.17)'>jackson</feature> <feature version="[4,5)">http-client</feature> <bundle dependency='true'>wrap:mvn:org.apache.httpcomponents/httpasyncclient/${httpasyncclient-version}</bundle> - <bundle dependency='true'>wrap:mvn:org.elasticsearch.client/elasticsearch-rest-client/8.12.1</bundle> - <bundle dependency='true'>wrap:mvn:org.elasticsearch.client/elasticsearch-rest-client-sniffer/8.12.1</bundle> + <bundle dependency='true'>wrap:mvn:org.elasticsearch.client/elasticsearch-rest-client/${elasticsearch-java-client-version}</bundle> + <bundle dependency='true'>wrap:mvn:org.elasticsearch.client/elasticsearch-rest-client-sniffer/${elasticsearch-java-client-sniffer-version}</bundle> <bundle>mvn:org.apache.camel.karaf/camel-elasticsearch-rest-client/${project.version}</bundle> </feature> <feature name='camel-elytron' version='${project.version}' start-level='50'> diff --git a/tests/camel-integration-test/pom.xml b/tests/camel-integration-test/pom.xml index 36670d7d..caa119ee 100644 --- a/tests/camel-integration-test/pom.xml +++ b/tests/camel-integration-test/pom.xml @@ -71,15 +71,19 @@ <configuration> <instructions> <Export-Package> - org.apache.karaf.camel.itests;version=${camel.version} + org.apache.karaf.camel.itests;version=${project.version}, </Export-Package> <Import-Package> - org.apache.karaf.itests,org.ops4j.pax.exam,org.osgi.framework,org.junit, + org.apache.karaf.itests, + org.ops4j.pax.exam, + org.ops4j.pax.exam.options, + org.osgi.framework, + org.junit*, org.apache.camel*;${camel.osgi.import.camel.version}, org.apache.karaf.features, + org.ops4j.pax.swissbox.tracker, org.osgi.service.*, - org.awaitility, - org.ops4j.pax.swissbox.tracker + org.awaitility* </Import-Package> </instructions> <excludeDependencies>geronimo-atinject_1.0_spec</excludeDependencies> @@ -110,6 +114,11 @@ <groupId>org.ops4j.pax.exam</groupId> <artifactId>pax-exam-container-karaf</artifactId> </dependency> + <dependency> + <groupId>org.ops4j.pax.exam</groupId> + <artifactId>pax-exam-junit4</artifactId> + <scope>compile</scope> + </dependency> <dependency> <groupId>org.awaitility</groupId> <artifactId>awaitility</artifactId> @@ -167,5 +176,11 @@ <version>${project.version}</version> <scope>provided</scope> </dependency> + <dependency> + <groupId>org.testcontainers</groupId> + <artifactId>testcontainers</artifactId> + <version>${testcontainers-version}</version> + <scope>provided</scope> + </dependency> </dependencies> </project> \ No newline at end of file diff --git a/tests/camel-integration-test/src/main/java/org/apache/karaf/camel/itests/AbstractCamelComponent.java b/tests/camel-integration-test/src/main/java/org/apache/karaf/camel/itests/AbstractCamelComponent.java index 21bb7f75..35d2a591 100644 --- a/tests/camel-integration-test/src/main/java/org/apache/karaf/camel/itests/AbstractCamelComponent.java +++ b/tests/camel-integration-test/src/main/java/org/apache/karaf/camel/itests/AbstractCamelComponent.java @@ -60,6 +60,7 @@ public abstract class AbstractCamelComponent { return new RouteBuilder() { @Override public void configure() { + configureCamelContext(this.getCamelContext()); if (producerEnabled()) { configureProducer( this, from("timer:producer?repeatCount=1").routeId("producer-%s".formatted(getTestComponentName())) @@ -72,6 +73,10 @@ public abstract class AbstractCamelComponent { }; } + protected void configureCamelContext(CamelContext camelContext) { + //nothing by default + } + protected boolean producerEnabled() { return true; } @@ -94,8 +99,4 @@ public abstract class AbstractCamelComponent { } consumerRoute.routeId("consumer-%s".formatted(getTestComponentName())); } - - public int getNextAvailablePort() { - return AbstractCamelKarafITest.getAvailablePort(30000, 40000); - } } diff --git a/tests/camel-integration-test/src/main/java/org/apache/karaf/camel/itests/AbstractCamelKarafITest.java b/tests/camel-integration-test/src/main/java/org/apache/karaf/camel/itests/AbstractCamelKarafITest.java index c210924e..66a757a5 100644 --- a/tests/camel-integration-test/src/main/java/org/apache/karaf/camel/itests/AbstractCamelKarafITest.java +++ b/tests/camel-integration-test/src/main/java/org/apache/karaf/camel/itests/AbstractCamelKarafITest.java @@ -26,7 +26,6 @@ import org.osgi.framework.Bundle; import java.net.InetAddress; import java.net.InetSocketAddress; import java.net.ServerSocket; -import java.util.stream.Stream; import org.apache.karaf.itests.KarafTestSupport; import org.junit.Assert; @@ -39,6 +38,7 @@ import org.ops4j.pax.exam.karaf.options.KarafDistributionOption; import org.osgi.framework.BundleException; import static org.apache.karaf.camel.itests.Utils.toKebabCase; +import static org.ops4j.pax.exam.OptionUtils.combine; public abstract class AbstractCamelKarafITest extends KarafTestSupport { @@ -80,16 +80,19 @@ public abstract class AbstractCamelKarafITest extends KarafTestSupport { String sshPort = Integer.toString(getAvailablePort(Integer.parseInt(MIN_SSH_PORT), Integer.parseInt(MAX_SSH_PORT))); Option[] options = new Option[]{ - KarafDistributionOption.editConfigurationFilePut("etc/system.properties", "project.version", getVersion()), - KarafDistributionOption.editConfigurationFileExtend("etc/system.properties", "project.target", getBaseDir()), + CoreOptions.systemProperty("project.version").value(getVersion()), + CoreOptions.systemProperty("project.target").value(getBaseDir()), KarafDistributionOption.features("mvn:org.apache.camel.karaf/apache-camel/"+ getVersion() + "/xml/features", "scr","camel-core"), CoreOptions.mavenBundle().groupId("org.apache.camel.karaf").artifactId("camel-integration-test").versionAsInProject(), KarafDistributionOption.editConfigurationFilePut("etc/org.ops4j.pax.web.cfg", "org.osgi.service.http.port", httpPort), KarafDistributionOption.editConfigurationFilePut("etc/org.apache.karaf.management.cfg", "rmiRegistryPort", rmiRegistryPort), KarafDistributionOption.editConfigurationFilePut("etc/org.apache.karaf.management.cfg", "rmiServerPort", rmiServerPort), - KarafDistributionOption.editConfigurationFilePut("etc/org.apache.karaf.shell.cfg", "sshPort", sshPort), + KarafDistributionOption.editConfigurationFilePut("etc/org.apache.karaf.shell.cfg", "sshPort", sshPort) }; - return Stream.of(super.config(), options).flatMap(Stream::of).toArray(Option[]::new); + Option[] systemProperties = PaxExamWithExternalResource.systemProperties().entrySet().stream() + .map(e -> CoreOptions.systemProperty(e.getKey()).value(e.getValue())) + .toArray(Option[]::new); + return combine(combine(super.config(), options), systemProperties); } @Before diff --git a/tests/camel-integration-test/src/main/java/org/apache/karaf/camel/itests/ExternalResource.java b/tests/camel-integration-test/src/main/java/org/apache/karaf/camel/itests/ExternalResource.java new file mode 100644 index 00000000..858ee797 --- /dev/null +++ b/tests/camel-integration-test/src/main/java/org/apache/karaf/camel/itests/ExternalResource.java @@ -0,0 +1,30 @@ +package org.apache.karaf.camel.itests; + +import java.util.Map; + +/** + * An interface representing an external resource to set up before a test and guarantee to tear it down afterward. + * Compared to an {@link org.junit.rules.ExternalResource}, the methods {@link #before()} and {@link #after()} are + * executed outside Karaf container, so it can be used to set up external resources like a database, a message broker, + * etc. + * @see TemporaryFile + * @see GenericContainerResource + */ +public interface ExternalResource { + + /** + * Sets up the external resource. + */ + void before(); + + /** + * Tears down the external resource. + */ + void after(); + + /** + * Gives access to the properties of the external resource like a username, a password or a path, that will be + * provided to the Karaf instance as System properties. + */ + Map<String, String> properties(); +} diff --git a/tests/camel-integration-test/src/main/java/org/apache/karaf/camel/itests/GenericContainerResource.java b/tests/camel-integration-test/src/main/java/org/apache/karaf/camel/itests/GenericContainerResource.java new file mode 100644 index 00000000..8a188cee --- /dev/null +++ b/tests/camel-integration-test/src/main/java/org/apache/karaf/camel/itests/GenericContainerResource.java @@ -0,0 +1,97 @@ +/** + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You 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 + * <p> + * http://www.apache.org/licenses/LICENSE-2.0 + * <p> + * 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.apache.karaf.camel.itests; + +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.function.Consumer; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.testcontainers.containers.GenericContainer; + +/** + * A JUnit ExternalResource that starts and stops a TestContainer. + * + * @param <T> the type of the TestContainer + */ +public class GenericContainerResource<T extends GenericContainer<T>> implements ExternalResource { + + private static final Logger LOG = LoggerFactory.getLogger(GenericContainerResource.class); + private final T container; + private final Map<String, String> properties = new HashMap<>(); + private final List<ExternalResource> dependencies = new ArrayList<>(); + private final Consumer<GenericContainerResource<T>> onStarted; + + public GenericContainerResource(T container) { + this(container, t -> {}); + } + + /** + * Create a GenericContainerResource with the given TestContainer and a callback to be called when the container is + * started. + * @param container the TestContainer + * @param onStarted the callback to be called when the container is started + */ + public GenericContainerResource(T container, Consumer<GenericContainerResource<T>> onStarted) { + this.container = container; + this.onStarted = onStarted; + } + + @Override + public void before() { + container.start(); + onStarted.accept(this); + for (ExternalResource dependency : dependencies) { + dependency.properties().forEach(this::setProperty); + } + LOG.info("Container {} started", container.getDockerImageName()); + } + + @Override + public void after() { + container.stop(); + for (ExternalResource dependency : dependencies) { + try { + dependency.after(); + } catch (Exception e) { + LOG.warn("Error cleaning dependency: {}", dependency.getClass().getName(), e); + } + } + LOG.info("Container {} stopped", container.getDockerImageName()); + } + + @Override + public Map<String, String> properties() { + return properties; + } + + public T getContainer() { + return container; + } + + public void setProperty(String key, String value) { + properties.put(key, value); + } + + public void addDependency(ExternalResource dependency) { + dependencies.add(dependency); + } +} diff --git a/tests/camel-integration-test/src/main/java/org/apache/karaf/camel/itests/PaxExamWithExternalResource.java b/tests/camel-integration-test/src/main/java/org/apache/karaf/camel/itests/PaxExamWithExternalResource.java new file mode 100644 index 00000000..4bd00056 --- /dev/null +++ b/tests/camel-integration-test/src/main/java/org/apache/karaf/camel/itests/PaxExamWithExternalResource.java @@ -0,0 +1,138 @@ +/** + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You 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 + * <p> + * http://www.apache.org/licenses/LICENSE-2.0 + * <p> + * 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.apache.karaf.camel.itests; + +import java.lang.reflect.InvocationTargetException; +import java.lang.reflect.Method; +import java.lang.reflect.Modifier; +import java.util.ArrayList; +import java.util.List; +import java.util.Map; +import java.util.Set; +import java.util.stream.Collectors; + +import org.junit.runner.Description; +import org.junit.runner.Runner; +import org.junit.runner.manipulation.Filter; +import org.junit.runner.manipulation.Filterable; +import org.junit.runner.manipulation.NoTestsRemainException; +import org.junit.runner.manipulation.Sortable; +import org.junit.runner.manipulation.Sorter; +import org.junit.runner.notification.RunNotifier; +import org.junit.runners.ParentRunner; +import org.junit.runners.model.InitializationError; +import org.ops4j.pax.exam.junit.PaxExam; +import org.ops4j.pax.exam.junit.impl.ProbeRunner; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +/** + * A fork of {@link PaxExam} that supports external resources which can be created and destroyed outside Karaf. + * <p> + * This runner is intended to be used with {@link UseExternalResourceProvider} annotation in order to create the + * external resources that are needed by the test. Please note that due to the way PaxExam works, the class cannot be + * the same as the test class, but it can be a static inner class of it otherwise the class will need to be resolved + * within Karaf which is what we want to avoid. + * + * @see UseExternalResourceProvider + */ +public class PaxExamWithExternalResource extends Runner implements Filterable, Sortable { + private static final Logger LOG = LoggerFactory.getLogger(PaxExamWithExternalResource.class); + private static final ThreadLocal<PaxExamWithExternalResource> current = new ThreadLocal<>(); + private final ParentRunner<?> delegate; + private final List<ExternalResource> externalResources; + + public PaxExamWithExternalResource(Class<?> testClass) throws InitializationError, InvocationTargetException, + IllegalAccessException { + this.externalResources = beforeAll(testClass); + try { + current.set(this); + this.delegate = new ProbeRunner(testClass); + } finally { + current.remove(); + } + } + + private List<ExternalResource> beforeAll(Class<?> testClass) throws InvocationTargetException, IllegalAccessException { + UseExternalResourceProvider annotation = testClass.getAnnotation(UseExternalResourceProvider.class); + if (annotation != null) { + List<ExternalResource> result = new ArrayList<>(); + for (Method m : annotation.value().getMethods()) { + if (isExternalResourceSupplier(m)) { + ExternalResource externalResource = (ExternalResource) m.invoke(null); + externalResource.before(); + result.add(externalResource); + } + } + return result; + } + LOG.warn("Class {} is not annotated with @UseExternalResourceProvider", testClass.getName()); + return List.of(); + } + + private boolean isExternalResourceSupplier(Method m) { + return ExternalResource.class.isAssignableFrom(m.getReturnType()) && m.getParameterTypes().length == 0 + && Modifier.isStatic(m.getModifiers()); + } + + @Override + public Description getDescription() { + return delegate.getDescription(); + } + + @Override + public void run(RunNotifier notifier) { + try { + delegate.run(notifier); + } finally { + afterAll(); + } + } + + private void afterAll() { + for (int i = externalResources.size() - 1; i >= 0; i--) { + try { + externalResources.get(i).after(); + } catch (Exception e) { + LOG.warn("Error while cleaning up external resource", e); + } + } + } + + @Override + public void filter(Filter filter) throws NoTestsRemainException { + delegate.filter(filter); + } + + @Override + public void sort(Sorter sorter) { + delegate.sort(sorter); + } + + static Map<String, String> systemProperties() { + PaxExamWithExternalResource value = current.get(); + if (value == null) { + return Map.of(); + } + return value.externalResources.stream() + .map(ExternalResource::properties) + .map(Map::entrySet) + .flatMap(Set::stream) + .collect(Collectors.toUnmodifiableMap(Map.Entry::getKey, Map.Entry::getValue)); + } +} diff --git a/tests/camel-integration-test/src/main/java/org/apache/karaf/camel/itests/TemporaryFile.java b/tests/camel-integration-test/src/main/java/org/apache/karaf/camel/itests/TemporaryFile.java new file mode 100644 index 00000000..9e609402 --- /dev/null +++ b/tests/camel-integration-test/src/main/java/org/apache/karaf/camel/itests/TemporaryFile.java @@ -0,0 +1,74 @@ +/** + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You 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 + * <p> + * http://www.apache.org/licenses/LICENSE-2.0 + * <p> + * 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.apache.karaf.camel.itests; + +import java.io.IOException; +import java.io.UncheckedIOException; +import java.nio.file.Files; +import java.nio.file.Path; +import java.util.Map; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +/** + * A temporary file that is deleted when the test is finished. + */ +public class TemporaryFile implements ExternalResource { + private static final Logger LOG = LoggerFactory.getLogger(TemporaryFile.class); + private final String key; + private final Path path; + + /** + * Create a temporary file with the given key. + * + * @param key the key to store the path of the temporary file + * @param prefix the prefix of the temporary file + * @param suffix the suffix of the temporary file + * @throws IOException if an I/O error occurs + */ + public TemporaryFile(String key, String prefix, String suffix) throws IOException { + this.key = key; + this.path = Files.createTempFile(prefix, suffix); + } + + @Override + public void before() { + // Do nothing + } + + @Override + public void after() { + try { + if (Files.deleteIfExists(path)) { + LOG.debug("Deleted temporary file: {}", path); + } + } catch (IOException e) { + LOG.warn("Failed to delete temporary file: {}", path, e); + } + } + + @Override + public Map<String, String> properties() { + return Map.of(key, path.toString()); + } + + public Path getPath() { + return path; + } +} diff --git a/tests/camel-integration-test/src/main/java/org/apache/karaf/camel/itests/UseExternalResourceProvider.java b/tests/camel-integration-test/src/main/java/org/apache/karaf/camel/itests/UseExternalResourceProvider.java new file mode 100644 index 00000000..b9795bb0 --- /dev/null +++ b/tests/camel-integration-test/src/main/java/org/apache/karaf/camel/itests/UseExternalResourceProvider.java @@ -0,0 +1,26 @@ +package org.apache.karaf.camel.itests; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Inherited; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +/** + * Annotation to specify the class that provides the methods to create all the external resources required by the test. + * In the provider class, each public static method that returns an instance of a subtype of {@link ExternalResource} + * with no parameters is considered as an {@link ExternalResource} supplier, so it will be invoked before executing + * the test and {@code PaxExamWithExternalResource} will take care of its lifecycle making sure that it is created and + * destroyed outside Karaf. + * + * @see PaxExamWithExternalResource + */ +@Retention(RetentionPolicy.RUNTIME) +@Target(ElementType.TYPE) +@Inherited +public @interface UseExternalResourceProvider { + /** + * The external resource provider class. + */ + Class<?> value(); +} diff --git a/tests/camel-integration-test/src/main/java/org/apache/karaf/camel/itests/Utils.java b/tests/camel-integration-test/src/main/java/org/apache/karaf/camel/itests/Utils.java index 67390589..d94cb448 100644 --- a/tests/camel-integration-test/src/main/java/org/apache/karaf/camel/itests/Utils.java +++ b/tests/camel-integration-test/src/main/java/org/apache/karaf/camel/itests/Utils.java @@ -16,7 +16,7 @@ */ package org.apache.karaf.camel.itests; -final class Utils { +public final class Utils { private Utils() { } @@ -24,4 +24,8 @@ final class Utils { static String toKebabCase(String name) { return name.replaceAll("([a-z0-9])([A-Z])", "$1-$2").toLowerCase(); } + + public static int getNextAvailablePort() { + return AbstractCamelKarafITest.getAvailablePort(30000, 40000); + } } diff --git a/tests/components/camel-elasticsearch/pom.xml b/tests/components/camel-elasticsearch/pom.xml new file mode 100644 index 00000000..10fcaf35 --- /dev/null +++ b/tests/components/camel-elasticsearch/pom.xml @@ -0,0 +1,49 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- + + Licensed to the Apache Software Foundation (ASF) under one or more + contributor license agreements. See the NOTICE file distributed with + this work for additional information regarding copyright ownership. + The ASF licenses this file to You 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. + +--> +<project xmlns="http://maven.apache.org/POM/4.0.0" + xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> + <modelVersion>4.0.0</modelVersion> + <parent> + <groupId>org.apache.camel.karaf</groupId> + <artifactId>camel-karaf-components-test</artifactId> + <version>4.5.0-SNAPSHOT</version> + <relativePath>../pom.xml</relativePath> + </parent> + + <artifactId>camel-elasticsearch-test</artifactId> + <name>Apache Camel :: Karaf :: Tests :: Components :: ElasticSearch Java API Client</name> + + <dependencies> + <dependency> + <groupId>org.apache.camel</groupId> + <artifactId>camel-elasticsearch</artifactId> + <version>${camel.version}</version> + </dependency> + <dependency> + <groupId>org.testcontainers</groupId> + <artifactId>elasticsearch</artifactId> + <version>${testcontainers-version}</version> + <scope>test</scope> + </dependency> + </dependencies> + + +</project> \ No newline at end of file diff --git a/tests/components/camel-elasticsearch/src/main/java/org/apache/karaf/camel/test/CamelElasticsearchComponent.java b/tests/components/camel-elasticsearch/src/main/java/org/apache/karaf/camel/test/CamelElasticsearchComponent.java new file mode 100644 index 00000000..6780894b --- /dev/null +++ b/tests/components/camel-elasticsearch/src/main/java/org/apache/karaf/camel/test/CamelElasticsearchComponent.java @@ -0,0 +1,79 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You 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.apache.karaf.camel.test; + +import org.apache.camel.CamelContext; +import org.apache.camel.builder.RouteBuilder; +import org.apache.camel.component.es.ElasticsearchComponent; +import org.apache.camel.model.RouteDefinition; +import org.apache.karaf.camel.itests.AbstractCamelComponentResultMockBased; +import org.osgi.service.component.annotations.Component; + +@Component(name = "karaf-camel-elasticsearch-test", immediate = true) +public class CamelElasticsearchComponent extends AbstractCamelComponentResultMockBased { + + private static final String INDEX_NAME = "testindex"; + + @Override + protected void configureCamelContext(CamelContext camelContext) { + final ElasticsearchComponent elasticsearchComponent = new ElasticsearchComponent(); + elasticsearchComponent.setEnableSSL(true); + elasticsearchComponent.setHostAddresses( + "%s:%s".formatted(System.getProperty("elasticsearch.host"), System.getProperty("elasticsearch.port")) + ); + elasticsearchComponent.setUser(System.getProperty("elasticsearch.username")); + elasticsearchComponent.setPassword(System.getProperty("elasticsearch.password")); + elasticsearchComponent.setCertificatePath("file:%s".formatted(System.getProperty("elasticsearch.cafile"))); + + camelContext.addComponent("elasticsearch",elasticsearchComponent); + } + + @Override + protected void configureProducer(RouteBuilder builder, RouteDefinition producerRoute) { + //to add the mock endpoint at the end of the route, call configureConsumer + configureConsumer( + producerRoute.toF("elasticsearch://elasticsearch?operation=Exists&indexName=%s", INDEX_NAME) + .log("Index exist: ${body}") + .setBody(builder.simple(""" + {"date": "${header.CamelTimerFiredTime}", "someKey": "someValue"} + """)) + .toF("elasticsearch://elasticsearch?operation=Index&indexName=%s", INDEX_NAME) + .log("Index doc : ${body}") + .setHeader("_ID", builder.simple("${body}")) + .toF("elasticsearch://elasticsearch?operation=GetById&indexName=%s", INDEX_NAME) + .log("Get doc: ${body}") + .setHeader("indexId", builder.simple("${header._ID}")) + .setBody(builder.constant(""" + {"doc": {"someKey": "someValue2"}} + """)) + .toF("elasticsearch://elasticsearch?operation=Update&indexName=%s", INDEX_NAME) + .log("Update doc: ${body} ") + .setBody(builder.simple("${header._ID}")) + .toF("elasticsearch://elasticsearch?operation=GetById&indexName=%s", INDEX_NAME) + .log("Get doc: ${body}") + .setBody(builder.simple("${header._ID}")) + .toF("elasticsearch://elasticsearch?operation=Delete&indexName=%s", INDEX_NAME) + .log("Delete doc: ${body}") + .setBody(builder.constant("OK")) + ); + + } + + @Override + protected boolean consumerEnabled() { + return false; + } +} \ No newline at end of file diff --git a/tests/components/camel-elasticsearch/src/test/java/org/apache/karaf/camel/itests/CamelElasticsearchITest.java b/tests/components/camel-elasticsearch/src/test/java/org/apache/karaf/camel/itests/CamelElasticsearchITest.java new file mode 100644 index 00000000..dc349c7a --- /dev/null +++ b/tests/components/camel-elasticsearch/src/test/java/org/apache/karaf/camel/itests/CamelElasticsearchITest.java @@ -0,0 +1,77 @@ +/* + * 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.apache.karaf.camel.itests; + +import java.io.IOException; +import java.nio.file.Files; +import java.time.Duration; + +import org.apache.camel.component.mock.MockEndpoint; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.ops4j.pax.exam.spi.reactors.ExamReactorStrategy; +import org.ops4j.pax.exam.spi.reactors.PerClass; +import org.testcontainers.containers.wait.strategy.LogMessageWaitStrategy; +import org.testcontainers.elasticsearch.ElasticsearchContainer; + +@UseExternalResourceProvider(CamelElasticsearchITest.ExternalResourceProviders.class) +@RunWith(PaxExamWithExternalResource.class) +@ExamReactorStrategy(PerClass.class) +public class CamelElasticsearchITest extends AbstractCamelKarafResultMockBasedITest { + + @Override + protected void configureMock(MockEndpoint mock) { + mock.expectedBodiesReceived("OK"); + } + + @Test + public void testResultMock() throws Exception { + assertMockEndpointsSatisfied(); + } + + public static final class ExternalResourceProviders { + + private static final String USER_NAME = "elastic"; + private static final String PASSWORD = "s3cret"; + private static final int ELASTIC_SEARCH_PORT = 9200; + + public static GenericContainerResource<ElasticsearchContainer> createElasticsearchContainer() { + final ElasticsearchContainer elasticsearchContainer = + new ElasticsearchContainer("docker.elastic.co/elasticsearch/elasticsearch:8.11.1").withPassword(PASSWORD); + // Increase the timeout from 60 seconds to 90 seconds to ensure that it will be long enough + // on the build pipeline + elasticsearchContainer.setWaitStrategy( + new LogMessageWaitStrategy() + .withRegEx(".*(\"message\":\\s?\"started[\\s?|\"].*|] started\n$)") + .withStartupTimeout(Duration.ofSeconds(90))); + return new GenericContainerResource<>(elasticsearchContainer, + resource -> { + resource.getContainer().caCertAsBytes().ifPresent(content -> { + try { + TemporaryFile tempFile = new TemporaryFile("elasticsearch.cafile", "http_ca", ".crt"); + Files.write(tempFile.getPath(), content); + resource.addDependency(tempFile); + } catch (IOException e) { + throw new RuntimeException(e); + } + }); + resource.setProperty("elasticsearch.host", elasticsearchContainer.getHost()); + resource.setProperty("elasticsearch.port", Integer.toString(elasticsearchContainer.getMappedPort(ELASTIC_SEARCH_PORT))); + resource.setProperty("elasticsearch.username", USER_NAME); + resource.setProperty("elasticsearch.password", PASSWORD); + } + ); + } + } +} \ No newline at end of file diff --git a/tests/components/camel-jetty/src/main/java/org/apache/karaf/camel/test/CamelJettyComponent.java b/tests/components/camel-jetty/src/main/java/org/apache/karaf/camel/test/CamelJettyComponent.java index 0bd93e9a..a9c2ff0a 100644 --- a/tests/components/camel-jetty/src/main/java/org/apache/karaf/camel/test/CamelJettyComponent.java +++ b/tests/components/camel-jetty/src/main/java/org/apache/karaf/camel/test/CamelJettyComponent.java @@ -26,6 +26,7 @@ import org.apache.camel.Processor; import org.apache.camel.builder.RouteBuilder; import org.apache.camel.model.RouteDefinition; import org.apache.karaf.camel.itests.AbstractCamelComponentResultMockBased; +import org.apache.karaf.camel.itests.Utils; import org.osgi.service.component.annotations.Component; @Component( @@ -34,7 +35,7 @@ import org.osgi.service.component.annotations.Component; ) public class CamelJettyComponent extends AbstractCamelComponentResultMockBased { - private final int port = getNextAvailablePort(); + private final int port = Utils.getNextAvailablePort(); @Override protected Function<RouteBuilder, RouteDefinition> consumerRoute() { diff --git a/tests/components/pom.xml b/tests/components/pom.xml index e26df75d..c3fdae23 100644 --- a/tests/components/pom.xml +++ b/tests/components/pom.xml @@ -39,6 +39,7 @@ <modules> <module>camel-file</module> <module>camel-seda</module> + <module>camel-elasticsearch</module> <module>camel-jetty</module> </modules> @@ -182,7 +183,7 @@ <configuration> <instructions> <Export-Package> - org.apache.karaf.camel.test;version=${camel.version} + org.apache.karaf.camel.test;version=${project.version} </Export-Package> <Import-Package> org.apache.camel*;${camel.osgi.import.camel.version}, @@ -233,7 +234,7 @@ <project.version>${project.version}</project.version> <project.target>${project.build.directory}</project.target> <integration.test.project.resources>${integration.test.project.resources}</integration.test.project.resources> - <org.ops4j.pax.logging.DefaultServiceLog.level>ERROR</org.ops4j.pax.logging.DefaultServiceLog.level> + <org.ops4j.pax.logging.DefaultServiceLog.level>WARN</org.ops4j.pax.logging.DefaultServiceLog.level> </systemPropertyVariables> <forkedProcessExitTimeoutInSeconds>5</forkedProcessExitTimeoutInSeconds> </configuration>