From c927df33aca97744ede41b76e726d4ecef8d3563 Mon Sep 17 00:00:00 2001 From: lburgazzoli Date: Tue, 29 May 2018 17:08:00 +0200 Subject: [PATCH 01/20] CAMEL-12485: camel cloud : create camel-service component --- apache-camel/pom.xml | 9 + .../src/main/descriptors/common-bin.xml | 2 + bom/camel-bom/pom.xml | 10 + camel-core/pom.xml | 2 +- .../camel/cloud/DiscoverableService.java | 24 ++ .../apache/camel/cloud/ServiceDefinition.java | 26 ++ .../org/apache/camel/cloud/ServiceHealth.java | 18 +- .../apache/camel/cloud/ServiceRegistry.java | 68 ++++ .../camel/cluster/CamelClusterService.java | 1 + .../impl/cloud/AbstractServiceRegistry.java | 91 +++++ .../impl/cloud/BlacklistServiceFilter.java | 47 +-- .../impl/cloud/DefaultServiceDefinition.java | 208 ++++++++-- .../impl/cloud/DefaultServiceHealth.java | 5 +- .../impl/cloud/ServiceDefinitionHelper.java | 23 ++ .../impl/cloud/ServiceRegistryHelper.java | 54 +++ .../impl/cloud/ServiceRegistrySelectors.java | 153 +++++++ .../impl/cloud/StaticServiceDiscovery.java | 60 +-- .../org/apache/camel/util/AntPathMatcher.java | 1 + .../apache/camel/util/CollectionHelper.java | 15 + .../org/apache/camel/util/StringHelper.java | 40 ++ .../camel/impl/cloud/LoadBalancerTest.java | 8 +- .../cloud/ServiceCallConfigurationTest.java | 40 +- .../impl/cloud/ServiceDefinitionTest.java | 110 +++++ components/camel-consul/pom.xml | 110 ++--- .../consul/cloud/ConsulServiceDiscovery.java | 6 +- .../consul/cloud/ConsulServiceRegistry.java | 375 ++++++++++++++++++ .../ConsulServiceRegistryConfiguration.java | 119 ++++++ .../consul/endpoint/ConsulEventConsumer.java | 8 +- .../component/consul/ConsulCatalogTest.java | 1 + .../consul/ConsulCoordinatesTest.java | 5 +- .../component/consul/ConsulEventTest.java | 1 + .../consul/ConsulEventWatchTest.java | 5 +- .../component/consul/ConsulHealthTest.java | 1 + .../component/consul/ConsulKeyValueTest.java | 1 + .../consul/ConsulKeyValueWatchTest.java | 1 + .../component/consul/ConsulRegistryTest.java | 18 +- .../component/consul/ConsulSessionTest.java | 1 + .../ConsulDefaultServiceCallRouteTest.java | 3 +- .../ConsulRibbonServiceCallRouteTest.java | 3 +- .../cloud/ConsulServiceDiscoveryTest.java | 40 +- .../cloud/ConsulServiceRegistrationTest.java | 108 +++++ .../cloud/ConsulServiceRegistryTest.java | 75 ++++ .../SpringConsulServiceCallRouteTest.java | 21 +- ...onsulClusteredRoutePolicyFactoryTest.java} | 15 +- ...va => ConsulClusteredRoutePolicyTest.java} | 15 +- ...sulMasterIT.java => ConsulMasterTest.java} | 15 +- .../consul/support/ConsulContainerLogger.java | 31 ++ .../support/ConsulContainerSupport.java | 46 +++ .../support/ConsulContainerWaitStrategy.java | 48 +++ .../{ => support}/ConsulTestSupport.java | 19 +- .../src/test/resources/log4j2.properties | 18 +- ...pringConsulDefaultServiceCallRouteTest.xml | 2 +- ...ngConsulExpressionServiceCallRouteTest.xml | 2 +- ...SpringConsulRibbonServiceCallRouteTest.xml | 2 +- .../xml/AbstractCamelContextFactoryBean.java | 12 +- .../etcd/cloud/EtcdServiceDefinition.java | 3 +- .../camel/http/common/HttpCommonEndpoint.java | 18 +- .../component/master/MasterEndpoint.java | 5 +- .../RibbonServiceCallRegistryRouteTest.java | 4 +- .../RibbonServiceCallRouteMetadataTest.java | 7 +- .../cloud/RibbonServiceCallRouteTest.java | 4 +- .../RibbonServiceCallUpdateRouteTest.java | 7 +- components/camel-service/pom.xml | 89 +++++ .../src/main/docs/service-component.adoc | 85 ++++ .../component/service/ServiceComponent.java | 133 +++++++ .../component/service/ServiceConsumer.java | 121 ++++++ .../component/service/ServiceEndpoint.java | 119 ++++++ .../component/service/ServiceParameters.java | 114 ++++++ .../src/main/resources/META-INF/LICENSE.txt | 203 ++++++++++ .../src/main/resources/META-INF/NOTICE.txt | 11 + .../org/apache/camel/component/service | 17 + .../spring/boot/CamelAutoConfiguration.java | 10 + ...elCloudServiceFilterAutoConfiguration.java | 9 +- components/pom.xml | 1 + components/readme.adoc | 3 + docs/user-manual/en/SUMMARY.md | 1 + parent/pom.xml | 11 + .../JsonPathLanguageConfiguration.java | 2 +- .../camel-service-starter/pom.xml | 53 +++ .../ServiceComponentAutoConfiguration.java | 128 ++++++ .../ServiceComponentConfiguration.java | 79 ++++ .../src/main/resources/META-INF/LICENSE.txt | 203 ++++++++++ .../src/main/resources/META-INF/NOTICE.txt | 11 + .../main/resources/META-INF/spring.factories | 19 + .../main/resources/META-INF/spring.provides | 17 + .../spring-boot/components-starter/pom.xml | 1 + .../camel-spring-boot-dependencies/pom.xml | 10 + .../camel/itest/karaf/CamelServiceTest.java | 33 ++ 88 files changed, 3365 insertions(+), 308 deletions(-) create mode 100644 camel-core/src/main/java/org/apache/camel/cloud/DiscoverableService.java create mode 100644 camel-core/src/main/java/org/apache/camel/cloud/ServiceRegistry.java create mode 100644 camel-core/src/main/java/org/apache/camel/impl/cloud/AbstractServiceRegistry.java create mode 100644 camel-core/src/main/java/org/apache/camel/impl/cloud/ServiceDefinitionHelper.java create mode 100644 camel-core/src/main/java/org/apache/camel/impl/cloud/ServiceRegistryHelper.java create mode 100644 camel-core/src/main/java/org/apache/camel/impl/cloud/ServiceRegistrySelectors.java create mode 100644 camel-core/src/test/java/org/apache/camel/impl/cloud/ServiceDefinitionTest.java create mode 100644 components/camel-consul/src/main/java/org/apache/camel/component/consul/cloud/ConsulServiceRegistry.java create mode 100644 components/camel-consul/src/main/java/org/apache/camel/component/consul/cloud/ConsulServiceRegistryConfiguration.java create mode 100644 components/camel-consul/src/test/java/org/apache/camel/component/consul/cloud/ConsulServiceRegistrationTest.java create mode 100644 components/camel-consul/src/test/java/org/apache/camel/component/consul/cloud/ConsulServiceRegistryTest.java rename components/camel-consul/src/test/java/org/apache/camel/component/consul/cluster/{ConsulClusteredRoutePolicyFactoryIT.java => ConsulClusteredRoutePolicyFactoryTest.java} (90%) rename components/camel-consul/src/test/java/org/apache/camel/component/consul/cluster/{ConsulClusteredRoutePolicyIT.java => ConsulClusteredRoutePolicyTest.java} (90%) rename components/camel-consul/src/test/java/org/apache/camel/component/consul/cluster/{ConsulMasterIT.java => ConsulMasterTest.java} (90%) create mode 100644 components/camel-consul/src/test/java/org/apache/camel/component/consul/support/ConsulContainerLogger.java create mode 100644 components/camel-consul/src/test/java/org/apache/camel/component/consul/support/ConsulContainerSupport.java create mode 100644 components/camel-consul/src/test/java/org/apache/camel/component/consul/support/ConsulContainerWaitStrategy.java rename components/camel-consul/src/test/java/org/apache/camel/component/consul/{ => support}/ConsulTestSupport.java (80%) create mode 100644 components/camel-service/pom.xml create mode 100644 components/camel-service/src/main/docs/service-component.adoc create mode 100644 components/camel-service/src/main/java/org/apache/camel/component/service/ServiceComponent.java create mode 100644 components/camel-service/src/main/java/org/apache/camel/component/service/ServiceConsumer.java create mode 100644 components/camel-service/src/main/java/org/apache/camel/component/service/ServiceEndpoint.java create mode 100644 components/camel-service/src/main/java/org/apache/camel/component/service/ServiceParameters.java create mode 100644 components/camel-service/src/main/resources/META-INF/LICENSE.txt create mode 100644 components/camel-service/src/main/resources/META-INF/NOTICE.txt create mode 100644 components/camel-service/src/main/resources/META-INF/services/org/apache/camel/component/service create mode 100644 platforms/spring-boot/components-starter/camel-service-starter/pom.xml create mode 100644 platforms/spring-boot/components-starter/camel-service-starter/src/main/java/org/apache/camel/component/service/springboot/ServiceComponentAutoConfiguration.java create mode 100644 platforms/spring-boot/components-starter/camel-service-starter/src/main/java/org/apache/camel/component/service/springboot/ServiceComponentConfiguration.java create mode 100644 platforms/spring-boot/components-starter/camel-service-starter/src/main/resources/META-INF/LICENSE.txt create mode 100644 platforms/spring-boot/components-starter/camel-service-starter/src/main/resources/META-INF/NOTICE.txt create mode 100644 platforms/spring-boot/components-starter/camel-service-starter/src/main/resources/META-INF/spring.factories create mode 100644 platforms/spring-boot/components-starter/camel-service-starter/src/main/resources/META-INF/spring.provides create mode 100644 tests/camel-itest-karaf/src/test/java/org/apache/camel/itest/karaf/CamelServiceTest.java diff --git a/apache-camel/pom.xml b/apache-camel/pom.xml index 6ac762f768316..76da53b6fb13f 100644 --- a/apache-camel/pom.xml +++ b/apache-camel/pom.xml @@ -909,6 +909,10 @@ org.apache.camel camel-script + + org.apache.camel + camel-service + org.apache.camel camel-servicenow @@ -2183,6 +2187,11 @@ camel-script-starter ${project.version} + + org.apache.camel + camel-service-starter + ${project.version} + org.apache.camel camel-servicenow-starter diff --git a/apache-camel/src/main/descriptors/common-bin.xml b/apache-camel/src/main/descriptors/common-bin.xml index deda467549f75..d48a9f5e852ac 100644 --- a/apache-camel/src/main/descriptors/common-bin.xml +++ b/apache-camel/src/main/descriptors/common-bin.xml @@ -237,6 +237,7 @@ org.apache.camel:camel-schematron org.apache.camel:camel-scr org.apache.camel:camel-script + org.apache.camel:camel-service org.apache.camel:camel-servicenow org.apache.camel:camel-servlet org.apache.camel:camel-servletlistener @@ -545,6 +546,7 @@ org.apache.camel:camel-saxon-starter org.apache.camel:camel-schematron-starter org.apache.camel:camel-script-starter + org.apache.camel:camel-service-starter org.apache.camel:camel-servicenow-starter org.apache.camel:camel-servletlistener-starter org.apache.camel:camel-servlet-starter diff --git a/bom/camel-bom/pom.xml b/bom/camel-bom/pom.xml index 0aad870970d4e..f75a25b291457 100644 --- a/bom/camel-bom/pom.xml +++ b/bom/camel-bom/pom.xml @@ -2188,6 +2188,16 @@ camel-script-starter ${project.version} + + org.apache.camel + camel-service + ${project.version} + + + org.apache.camel + camel-service-starter + ${project.version} + org.apache.camel camel-servicenow diff --git a/camel-core/pom.xml b/camel-core/pom.xml index 3f7a989333a1c..22aa6969c27d9 100644 --- a/camel-core/pom.xml +++ b/camel-core/pom.xml @@ -136,7 +136,7 @@ com.github.ben-manes.caffeine caffeine - + org.apache.camel json-simple-ordered diff --git a/camel-core/src/main/java/org/apache/camel/cloud/DiscoverableService.java b/camel-core/src/main/java/org/apache/camel/cloud/DiscoverableService.java new file mode 100644 index 0000000000000..90710ad7e2d39 --- /dev/null +++ b/camel-core/src/main/java/org/apache/camel/cloud/DiscoverableService.java @@ -0,0 +1,24 @@ +/** + * 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.camel.cloud; + +public interface DiscoverableService { + /** + * Get the service definition. + */ + ServiceDefinition getServiceDefinition(); +} diff --git a/camel-core/src/main/java/org/apache/camel/cloud/ServiceDefinition.java b/camel-core/src/main/java/org/apache/camel/cloud/ServiceDefinition.java index 86d71763d340a..ee99441b78acb 100644 --- a/camel-core/src/main/java/org/apache/camel/cloud/ServiceDefinition.java +++ b/camel-core/src/main/java/org/apache/camel/cloud/ServiceDefinition.java @@ -18,6 +18,9 @@ import java.util.Map; +import org.apache.camel.util.StringHelper; + + /** * Represents a Service. * @@ -25,6 +28,15 @@ * @see ServiceDiscovery */ public interface ServiceDefinition { + // default service meta-data keys + String SERVICE_META_PORT = "service.port"; + String SERVICE_META_PROTOCOL= "service.protocol"; + String SERVICE_META_PATH = "service.path"; + + /** + * Gets the service id. + */ + String getId(); /** * Gets the service name. @@ -50,4 +62,18 @@ public interface ServiceDefinition { * Gets a key/value metadata associated with the service. */ Map getMetadata(); + + /** + * Check if a service definition matches. + */ + default boolean matches(ServiceDefinition other) { + if (this.equals(other)) { + return true; + } + + return getPort() == other.getPort() + && StringHelper.matches(getName(), other.getName()) + && StringHelper.matches(getId(), other.getId()) + && StringHelper.matches(getHost(), other.getHost()); + } } diff --git a/camel-core/src/main/java/org/apache/camel/cloud/ServiceHealth.java b/camel-core/src/main/java/org/apache/camel/cloud/ServiceHealth.java index 56257c62383d6..5900396c4a3e2 100644 --- a/camel-core/src/main/java/org/apache/camel/cloud/ServiceHealth.java +++ b/camel-core/src/main/java/org/apache/camel/cloud/ServiceHealth.java @@ -16,17 +16,31 @@ */ package org.apache.camel.cloud; +import java.net.URI; +import java.util.Collections; import java.util.Map; +import java.util.Optional; public interface ServiceHealth { /** * Gets a key/value metadata associated with the service. */ - Map getMetadata(); + default Map getMetadata() { + return Collections.EMPTY_MAP; + } /** * States if the service is healthy or not */ - boolean isHealthy(); + default boolean isHealthy() { + return true; + } + + /** + * The health enpoint exposed by the service. + */ + default Optional getEndpoint() { + return Optional.empty(); + } } diff --git a/camel-core/src/main/java/org/apache/camel/cloud/ServiceRegistry.java b/camel-core/src/main/java/org/apache/camel/cloud/ServiceRegistry.java new file mode 100644 index 0000000000000..96099c35da4d6 --- /dev/null +++ b/camel-core/src/main/java/org/apache/camel/cloud/ServiceRegistry.java @@ -0,0 +1,68 @@ +/** + * 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.camel.cloud; + +import java.util.Collection; +import java.util.Collections; +import java.util.Map; +import java.util.Optional; + +import org.apache.camel.CamelContextAware; +import org.apache.camel.Ordered; +import org.apache.camel.Service; +import org.apache.camel.spi.IdAware; + +public interface ServiceRegistry extends Service, CamelContextAware, IdAware, Ordered { + + @Override + default int getOrder() { + return Ordered.LOWEST; + } + + /** + * Attributes associated to the service. + */ + default Map getAttributes() { + return Collections.emptyMap(); + } + + /** + * Register the service definition. + * + * @param definition the service definition + */ + void register(ServiceDefinition definition); + + + /** + * Remove the service definition. + * + * @param definition the service definition + */ + void deregister(ServiceDefinition definition); + + /** + * A selector used to pick up a service among a list. + */ + @FunctionalInterface + interface Selector { + /** + * Select a specific ServiceRegistry instance among a collection. + */ + Optional select(Collection services); + } +} diff --git a/camel-core/src/main/java/org/apache/camel/cluster/CamelClusterService.java b/camel-core/src/main/java/org/apache/camel/cluster/CamelClusterService.java index 99e6001f4fd55..fbd9b4d54a217 100644 --- a/camel-core/src/main/java/org/apache/camel/cluster/CamelClusterService.java +++ b/camel-core/src/main/java/org/apache/camel/cluster/CamelClusterService.java @@ -100,6 +100,7 @@ default T unwrap(Class clazz) { ); } + @FunctionalInterface interface Selector { /** * Select a specific CamelClusterService instance among a collection. diff --git a/camel-core/src/main/java/org/apache/camel/impl/cloud/AbstractServiceRegistry.java b/camel-core/src/main/java/org/apache/camel/impl/cloud/AbstractServiceRegistry.java new file mode 100644 index 0000000000000..678ed6562a61a --- /dev/null +++ b/camel-core/src/main/java/org/apache/camel/impl/cloud/AbstractServiceRegistry.java @@ -0,0 +1,91 @@ +/** + * 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.camel.impl.cloud; + +import java.util.Collections; +import java.util.HashMap; +import java.util.Map; + +import org.apache.camel.CamelContext; +import org.apache.camel.Ordered; +import org.apache.camel.cloud.ServiceRegistry; +import org.apache.camel.support.ServiceSupport; + +public abstract class AbstractServiceRegistry extends ServiceSupport implements ServiceRegistry { + private final Map attributes; + private int order; + private String id; + private CamelContext camelContext; + + protected AbstractServiceRegistry() { + this(null, null); + } + + protected AbstractServiceRegistry(String id) { + this(id, null); + } + + protected AbstractServiceRegistry(String id, CamelContext camelContext) { + this.order = Ordered.LOWEST; + this.id = id; + this.camelContext = camelContext; + this.attributes = new HashMap<>(); + } + + @Override + public int getOrder() { + return order; + } + + public void setOrder(int order) { + this.order = order; + } + + @Override + public void setId(String id) { + this.id = id; + } + + @Override + public String getId() { + return id; + } + + @Override + public void setCamelContext(CamelContext camelContext) { + this.camelContext = camelContext; + } + + @Override + public CamelContext getCamelContext() { + return camelContext; + } + + public void setAttributes(Map attributes) { + this.attributes.clear(); + this.attributes.putAll(attributes); + } + + public void setAttribute(String key, Object value) { + this.attributes.put(key, value); + } + + @Override + public Map getAttributes() { + return Collections.unmodifiableMap(attributes); + } +} diff --git a/camel-core/src/main/java/org/apache/camel/impl/cloud/BlacklistServiceFilter.java b/camel-core/src/main/java/org/apache/camel/impl/cloud/BlacklistServiceFilter.java index 1df142c539931..95e9f4dc38fa4 100644 --- a/camel-core/src/main/java/org/apache/camel/impl/cloud/BlacklistServiceFilter.java +++ b/camel-core/src/main/java/org/apache/camel/impl/cloud/BlacklistServiceFilter.java @@ -20,12 +20,11 @@ import java.util.Collection; import java.util.Collections; import java.util.List; -import java.util.Map; +import java.util.function.Predicate; import java.util.stream.Collectors; import org.apache.camel.cloud.ServiceDefinition; import org.apache.camel.cloud.ServiceFilter; -import org.apache.camel.util.StringHelper; public class BlacklistServiceFilter implements ServiceFilter { private List services; @@ -39,7 +38,7 @@ public BlacklistServiceFilter(List blacklist) { } /** - * Set the servers. + * Set the servers to blacklist. * * @param servers server in the format: [service@]host:port. */ @@ -49,7 +48,7 @@ public void setServers(List servers) { } /** - * Set the servers. + * Set the servers to blacklist. * * @param servers servers separated by comma in the format: [service@]host:port,[service@]host2:port,[service@]host3:port and so on. */ @@ -66,47 +65,27 @@ public void addServer(ServiceDefinition server) { } /** - * Add a server to the known list of servers. + * Add a server to the known list of servers to blacklist. * @param serverString servers separated by comma in the format: [service@]host:port,[service@]host2:port,[service@]host3:port and so on. */ public void addServer(String serverString) { - String[] parts = serverString.split(","); - for (String part : parts) { - String service = StringHelper.before(part, "@"); - if (service != null) { - part = StringHelper.after(part, "@"); - } - String host = StringHelper.before(part, ":"); - String port = StringHelper.after(part, ":"); - - addServer(service, host, Integer.valueOf(port)); - } - } - - /** - * Add a server to the known list of servers. - */ - public void addServer(String host, int port) { - addServer(null, host, port, null); + DefaultServiceDefinition.parse(serverString).forEach(this::addServer); } /** - * Add a server to the known list of servers. - */ - public void addServer(String name, String host, int port) { - addServer(name, host, port, null); - } - - /** - * Add a server to the known list of servers. + * Remove an existing server from the list of known servers. */ - public void addServer(String name, String host, int port, Map meta) { - services.add(new DefaultServiceDefinition(name, host, port, meta)); + public void removeServer(Predicate condition) { + services.removeIf(condition); } @Override public List apply(List services) { - return services.stream().filter(s -> !this.services.contains(s)).collect(Collectors.toList()); + return services.stream().filter( + s -> this.services.stream().noneMatch(b -> b.matches(s)) + ).collect( + Collectors.toList() + ); } List getBlacklistedServices() { diff --git a/camel-core/src/main/java/org/apache/camel/impl/cloud/DefaultServiceDefinition.java b/camel-core/src/main/java/org/apache/camel/impl/cloud/DefaultServiceDefinition.java index beed3cf98ffde..ba4708c15b891 100644 --- a/camel-core/src/main/java/org/apache/camel/impl/cloud/DefaultServiceDefinition.java +++ b/camel-core/src/main/java/org/apache/camel/impl/cloud/DefaultServiceDefinition.java @@ -19,13 +19,19 @@ import java.util.Collections; import java.util.HashMap; import java.util.Map; +import java.util.Objects; +import java.util.stream.Stream; import org.apache.camel.cloud.ServiceDefinition; import org.apache.camel.cloud.ServiceHealth; +import org.apache.camel.util.CollectionHelper; +import org.apache.camel.util.ObjectHelper; +import org.apache.camel.util.StringHelper; public class DefaultServiceDefinition implements ServiceDefinition { private static final ServiceHealth DEFAULT_SERVICE_HEALTH = new DefaultServiceHealth(); + private final String id; private final String name; private final String host; private final int port; @@ -33,35 +39,45 @@ public class DefaultServiceDefinition implements ServiceDefinition { private final ServiceHealth health; public DefaultServiceDefinition(String name, String host, int port) { - this.name = name; - this.host = host; - this.port = port; - this.meta = Collections.emptyMap(); - this.health = DEFAULT_SERVICE_HEALTH; + this(null, name, host, port, Collections.emptyMap(), DEFAULT_SERVICE_HEALTH); + } + + public DefaultServiceDefinition(String id, String name, String host, int port) { + this(id, name, host, port, Collections.emptyMap(), DEFAULT_SERVICE_HEALTH); } public DefaultServiceDefinition(String name, String host, int port, ServiceHealth health) { - this.name = name; - this.host = host; - this.port = port; - this.meta = Collections.emptyMap(); - this.health = health; + this(null, name, host, port, Collections.emptyMap(), health); + } + + public DefaultServiceDefinition(String id, String name, String host, int port, ServiceHealth health) { + this(id, name, host, port, Collections.emptyMap(), health); } public DefaultServiceDefinition(String name, String host, int port, Map meta) { - this.name = name; - this.host = host; - this.port = port; - this.meta = meta != null ? Collections.unmodifiableMap(new HashMap<>(meta)) : Collections.emptyMap(); - this.health = DEFAULT_SERVICE_HEALTH; + this(null, name, host, port, meta, DEFAULT_SERVICE_HEALTH); + } + + public DefaultServiceDefinition(String id, String name, String host, int port, Map meta) { + this(id, name, host, port, meta, DEFAULT_SERVICE_HEALTH); } public DefaultServiceDefinition(String name, String host, int port, Map meta, ServiceHealth health) { + this(null, name, host, port, meta, health); + } + + public DefaultServiceDefinition(String id, String name, String host, int port, Map meta, ServiceHealth health) { + this.id = id; this.name = name; this.host = host; this.port = port; - this.meta = meta != null ? Collections.unmodifiableMap(new HashMap<>(meta)) : Collections.emptyMap(); - this.health = health; + this.meta = CollectionHelper.unmodifiableMap(meta); + this.health = ObjectHelper.supplyIfEmpty(health, () -> DEFAULT_SERVICE_HEALTH); + } + + @Override + public String getId() { + return id; } @Override @@ -91,7 +107,7 @@ public Map getMetadata() { @Override public String toString() { - return "DefaultServiceCallService[" + name + "@" + host + ":" + port + "]"; + return "DefaultServiceDefinition[" + id + "]"; } @Override @@ -104,21 +120,153 @@ public boolean equals(Object o) { } DefaultServiceDefinition that = (DefaultServiceDefinition) o; - - if (port != that.port) { - return false; - } - if (name != null ? !name.equals(that.name) : that.name != null) { - return false; - } - return host != null ? host.equals(that.host) : that.host == null; + return getPort() == that.getPort() + && Objects.equals(getId(), that.getId()) + && Objects.equals(getName(), that.getName()) + && Objects.equals(getHost(), that.getHost()); } @Override public int hashCode() { - int result = name != null ? name.hashCode() : 0; - result = 31 * result + (host != null ? host.hashCode() : 0); - result = 31 * result + port; - return result; + return Objects.hash(getId(), getName(), getHost(), getPort()); + } + + // *************************** + // Builder + // *************************** + + public static Stream parse(String serverString) { + return Stream.of(serverString.split(",")) + .map(part -> { + String serviceId = null; + String serviceName = StringHelper.before(part, "@"); + + if (serviceName != null) { + serviceId = StringHelper.before(serviceName, "/"); + serviceName = StringHelper.after(serviceName, "/"); + + if (serviceName == null) { + serviceName = StringHelper.before(part, "@"); + } + + part = StringHelper.after(part, "@"); + } + + String serviceHost = StringHelper.before(part, ":"); + String servicePort = StringHelper.after(part, ":"); + + if (ObjectHelper.isNotEmpty(serviceHost) && ObjectHelper.isNotEmpty(servicePort)) { + return new DefaultServiceDefinition(serviceId, serviceName, serviceHost, Integer.valueOf(servicePort)); + } + + return null; + } + ).filter(Objects::nonNull); + } + + public static Builder builder() { + return new Builder(); + } + + /** + * Fluent builder to contruct ServiceDefinition. + */ + public static class Builder { + private String id; + private String name; + private String host; + private int port; + private Map meta; + private ServiceHealth health; + + public Builder from(ServiceDefinition source) { + withId(source.getId()); + withName(source.getName()); + withHost(source.getHost()); + withPort(source.getPort()); + withMeta(source.getMetadata()); + withHealth(source.getHealth()); + + return this; + } + + public Builder withId(String id) { + this.id = id; + return this; + } + + public String id() { + return id; + } + + public Builder withName(String name) { + this.name = name; + return this; + } + + public String name() { + return name; + } + + public Builder withHost(String host) { + this.host = host; + return this; + } + + public String host() { + return host; + } + + public Builder withPort(int port) { + this.port = port; + return this; + } + + public int port() { + return port; + } + + public Builder withMeta(Map meta) { + this.meta = new HashMap<>(meta); + return this; + } + + public Builder addMeta(String key, String val) { + if (this.meta == null) { + this.meta = new HashMap<>(); + } + + this.meta.put(key, val); + + return this; + } + + public Builder addAllMeta(Map meta) { + if (this.meta == null) { + this.meta = new HashMap<>(); + } + + this.meta.putAll(meta); + + return this; + } + + public Map meta() { + return meta; + } + + public Builder withHealth(ServiceHealth health) { + this.health = health; + + return this; + } + + public ServiceHealth health() { + return health; + } + + public ServiceDefinition build() { + return new DefaultServiceDefinition(id, name, host, port, meta, health); + } } } \ No newline at end of file diff --git a/camel-core/src/main/java/org/apache/camel/impl/cloud/DefaultServiceHealth.java b/camel-core/src/main/java/org/apache/camel/impl/cloud/DefaultServiceHealth.java index 9a55b15da8a96..a42ef2ac19bb3 100644 --- a/camel-core/src/main/java/org/apache/camel/impl/cloud/DefaultServiceHealth.java +++ b/camel-core/src/main/java/org/apache/camel/impl/cloud/DefaultServiceHealth.java @@ -16,11 +16,10 @@ */ package org.apache.camel.impl.cloud; -import java.util.Collections; -import java.util.HashMap; import java.util.Map; import org.apache.camel.cloud.ServiceHealth; +import org.apache.camel.util.CollectionHelper; public class DefaultServiceHealth implements ServiceHealth { private final boolean healthy; @@ -40,7 +39,7 @@ public DefaultServiceHealth(Map meta) { public DefaultServiceHealth(boolean healthy, Map meta) { this.healthy = healthy; - this.meta = meta != null ? Collections.unmodifiableMap(new HashMap<>(meta)) : Collections.emptyMap(); + this.meta = CollectionHelper.unmodifiableMap(meta); } @Override diff --git a/camel-core/src/main/java/org/apache/camel/impl/cloud/ServiceDefinitionHelper.java b/camel-core/src/main/java/org/apache/camel/impl/cloud/ServiceDefinitionHelper.java new file mode 100644 index 0000000000000..6bc252da01e93 --- /dev/null +++ b/camel-core/src/main/java/org/apache/camel/impl/cloud/ServiceDefinitionHelper.java @@ -0,0 +1,23 @@ +/** + * 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.camel.impl.cloud; + +public final class ServiceDefinitionHelper { + + private ServiceDefinitionHelper() { + } +} diff --git a/camel-core/src/main/java/org/apache/camel/impl/cloud/ServiceRegistryHelper.java b/camel-core/src/main/java/org/apache/camel/impl/cloud/ServiceRegistryHelper.java new file mode 100644 index 0000000000000..d6f1468beecdf --- /dev/null +++ b/camel-core/src/main/java/org/apache/camel/impl/cloud/ServiceRegistryHelper.java @@ -0,0 +1,54 @@ +/** + * 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.camel.impl.cloud; + +import java.util.Optional; +import java.util.Set; + +import org.apache.camel.CamelContext; +import org.apache.camel.cloud.ServiceRegistry; +import org.apache.camel.util.ObjectHelper; + +public final class ServiceRegistryHelper { + private ServiceRegistryHelper() { + } + + public static Optional lookupService(CamelContext context) { + return lookupService(context, ServiceRegistrySelectors.DEFAULT_SELECTOR); + } + + public static Optional lookupService(CamelContext context, ServiceRegistry.Selector selector) { + ObjectHelper.notNull(context, "Camel Context"); + ObjectHelper.notNull(selector, "ServiceRegistry selector"); + + Set services = context.hasServices(ServiceRegistry.class); + + if (ObjectHelper.isNotEmpty(services)) { + return selector.select(services); + } + + return Optional.empty(); + } + + public static ServiceRegistry mandatoryLookupService(CamelContext context) { + return lookupService(context).orElseThrow(() -> new IllegalStateException("ServiceRegistry service not found")); + } + + public static ServiceRegistry mandatoryLookupService(CamelContext context, ServiceRegistry.Selector selector) { + return lookupService(context, selector).orElseThrow(() -> new IllegalStateException("ServiceRegistry service not found")); + } +} diff --git a/camel-core/src/main/java/org/apache/camel/impl/cloud/ServiceRegistrySelectors.java b/camel-core/src/main/java/org/apache/camel/impl/cloud/ServiceRegistrySelectors.java new file mode 100644 index 0000000000000..f81cfdb8c6e4f --- /dev/null +++ b/camel-core/src/main/java/org/apache/camel/impl/cloud/ServiceRegistrySelectors.java @@ -0,0 +1,153 @@ +/** + * 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.camel.impl.cloud; + +import java.util.Collection; +import java.util.Comparator; +import java.util.List; +import java.util.Map; +import java.util.Optional; +import java.util.stream.Collectors; + +import org.apache.camel.cloud.ServiceRegistry; +import org.apache.camel.util.ObjectHelper; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +public final class ServiceRegistrySelectors { + public static final ServiceRegistry.Selector DEFAULT_SELECTOR = new SelectSingle(); + private static final Logger LOGGER = LoggerFactory.getLogger(ServiceRegistrySelectors.class); + + private ServiceRegistrySelectors() { + } + + public static final class SelectSingle implements ServiceRegistry.Selector { + @Override + public Optional select(Collection services) { + if (services != null && services.size() == 1) { + return Optional.of(services.iterator().next()); + } else { + LOGGER.warn("Multiple ServiceRegistry instances available (items={})", services); + } + + return Optional.empty(); + } + } + + public static final class SelectFirst implements ServiceRegistry.Selector { + @Override + public Optional select(Collection services) { + return ObjectHelper.isNotEmpty(services) + ? Optional.of(services.iterator().next()) + : Optional.empty(); + } + } + + public static final class SelectByOrder implements ServiceRegistry.Selector { + @Override + public Optional select(Collection services) { + Optional>> highPriorityServices = services.stream() + .collect(Collectors.groupingBy(ServiceRegistry::getOrder)) + .entrySet().stream() + .min(Comparator.comparingInt(Map.Entry::getKey)); + + + if (highPriorityServices.isPresent()) { + if (highPriorityServices.get().getValue().size() == 1) { + return Optional.of(highPriorityServices.get().getValue().iterator().next()); + } else { + LOGGER.warn("Multiple ServiceRegistry instances available for highest priority (order={}, items={})", + highPriorityServices.get().getKey(), + highPriorityServices.get().getValue() + ); + } + } + + return Optional.empty(); + } + } + + public static final class SelectByType implements ServiceRegistry.Selector { + private final Class type; + + public SelectByType(Class type) { + this.type = type; + } + + @Override + public Optional select(Collection services) { + for (ServiceRegistry service : services) { + if (type.isInstance(service)) { + return Optional.of(service); + } + } + + return Optional.empty(); + } + } + + public static final class SelectByAttribute implements ServiceRegistry.Selector { + private final String key; + private final Object value; + + public SelectByAttribute(String key, Object value) { + this.key = key; + this.value = value; + } + + @Override + public Optional select(Collection services) { + for (ServiceRegistry service : services) { + Map attributes = service.getAttributes(); + + if (ObjectHelper.equal(attributes.get(key), value)) { + return Optional.of(service); + } + } + + return Optional.empty(); + } + } + + // ********************************** + // Helpers + // ********************************** + + public static ServiceRegistry.Selector defaultSelector() { + return DEFAULT_SELECTOR; + } + + public static ServiceRegistry.Selector single() { + return new SelectSingle(); + } + + public static ServiceRegistry.Selector first() { + return new SelectFirst(); + } + + public static ServiceRegistry.Selector order() { + return new SelectByOrder(); + } + + public static ServiceRegistry.Selector type(Class type) { + return new SelectByType(type); + } + + public static ServiceRegistry.Selector attribute(String key, Object value) { + return new SelectByAttribute(key, value); + } +} diff --git a/camel-core/src/main/java/org/apache/camel/impl/cloud/StaticServiceDiscovery.java b/camel-core/src/main/java/org/apache/camel/impl/cloud/StaticServiceDiscovery.java index 661823d2bd85f..f03de3fbadee9 100644 --- a/camel-core/src/main/java/org/apache/camel/impl/cloud/StaticServiceDiscovery.java +++ b/camel-core/src/main/java/org/apache/camel/impl/cloud/StaticServiceDiscovery.java @@ -20,8 +20,8 @@ import java.util.Collection; import java.util.Collections; import java.util.List; -import java.util.Map; import java.util.Objects; +import java.util.function.Predicate; import java.util.stream.Collectors; import org.apache.camel.cloud.ServiceDefinition; @@ -58,7 +58,13 @@ public void addServers(String serviceName, List servers) { String port = StringHelper.after(server, ":"); if (ObjectHelper.isNotEmpty(host) && ObjectHelper.isNotEmpty(port)) { - addServer(serviceName, host, Integer.valueOf(port)); + addServer( + DefaultServiceDefinition.builder() + .withName(serviceName) + .withHost(host) + .withPort(Integer.parseInt(port)) + .build() + ); } } } @@ -85,58 +91,14 @@ public void addServer(ServiceDefinition server) { * @param serverString servers separated by comma in the format: [service@]host:port,[service@]host2:port,[service@]host3:port and so on. */ public void addServer(String serverString) { - String[] parts = serverString.split(","); - for (String part : parts) { - String service = StringHelper.before(part, "@"); - if (service != null) { - part = StringHelper.after(part, "@"); - } - String host = StringHelper.before(part, ":"); - String port = StringHelper.after(part, ":"); - - if (ObjectHelper.isNotEmpty(host) && ObjectHelper.isNotEmpty(port)) { - addServer(service, host, Integer.valueOf(port)); - } - } - } - - /** - * Add a server to the known list of servers. - */ - public void addServer(String host, int port) { - addServer(null, host, port, null); - } - - /** - * Add a server to the known list of servers. - */ - public void addServer(String name, String host, int port) { - addServer(name, host, port, null); - } - - /** - * Add a server to the known list of servers. - */ - public void addServer(String name, String host, int port, Map meta) { - services.add(new DefaultServiceDefinition(name, host, port, meta)); + DefaultServiceDefinition.parse(serverString).forEach(this::addServer); } /** * Remove an existing server from the list of known servers. */ - public void removeServer(String host, int port) { - services.removeIf( - s -> Objects.equals(host, s.getHost()) && port == s.getPort() - ); - } - - /** - * Remove an existing server from the list of known servers. - */ - public void removeServer(String name, String host, int port) { - services.removeIf( - s -> Objects.equals(name, s.getName()) && Objects.equals(host, s.getHost()) && port == s.getPort() - ); + public void removeServer(Predicate condition) { + services.removeIf(condition); } @Override diff --git a/camel-core/src/main/java/org/apache/camel/util/AntPathMatcher.java b/camel-core/src/main/java/org/apache/camel/util/AntPathMatcher.java index 114a1aae11375..0e0d74ec04f8d 100644 --- a/camel-core/src/main/java/org/apache/camel/util/AntPathMatcher.java +++ b/camel-core/src/main/java/org/apache/camel/util/AntPathMatcher.java @@ -55,6 +55,7 @@ * */ public class AntPathMatcher { + public static final AntPathMatcher INSTANCE = new AntPathMatcher(); /** Default path separator: "/" */ public static final String DEFAULT_PATH_SEPARATOR = "/"; diff --git a/camel-core/src/main/java/org/apache/camel/util/CollectionHelper.java b/camel-core/src/main/java/org/apache/camel/util/CollectionHelper.java index dc79b6ff95c27..168e5ad86c1f2 100644 --- a/camel-core/src/main/java/org/apache/camel/util/CollectionHelper.java +++ b/camel-core/src/main/java/org/apache/camel/util/CollectionHelper.java @@ -20,6 +20,8 @@ import java.util.ArrayList; import java.util.Arrays; import java.util.Collection; +import java.util.Collections; +import java.util.HashMap; import java.util.HashSet; import java.util.Iterator; import java.util.LinkedHashMap; @@ -155,4 +157,17 @@ private static void doFlatternKeysInMap(Map source, String prefi } } } + + /** + * Build an unmodifiable map on top of a given map. Note tha thew given map is + * copied if not null. + * + * @param map a map + * @return an unmodifiable map. + */ + public static Map unmodifiableMap(Map map) { + return map == null + ? Collections.emptyMap() + : Collections.unmodifiableMap(new HashMap<>(map)); + } } diff --git a/camel-core/src/main/java/org/apache/camel/util/StringHelper.java b/camel-core/src/main/java/org/apache/camel/util/StringHelper.java index 6a68a8f1aed23..0f7e0b4d0564b 100644 --- a/camel-core/src/main/java/org/apache/camel/util/StringHelper.java +++ b/camel-core/src/main/java/org/apache/camel/util/StringHelper.java @@ -20,8 +20,11 @@ import java.util.Collections; import java.util.List; import java.util.Locale; +import java.util.Objects; import java.util.Optional; import java.util.function.Function; +import java.util.regex.Matcher; +import java.util.regex.Pattern; import static org.apache.camel.util.StringQuoteHelper.doubleQuote; @@ -739,4 +742,41 @@ public static String humanReadableBytes(long bytes) { return humanReadableBytes(Locale.getDefault(), bytes); } + /** + * Check for string pattern matching with a number of strategies in the + * following order: + * + * - equals + * - null pattern always matches + * - * always matches + * - Ant style matching + * - Regexp + * + * @param patter the pattern + * @param target the string to test + * @return true if target matches the pattern + */ + public static boolean matches(String patter, String target) { + if (Objects.equals(patter, target)) { + return true; + } + + if (Objects.isNull(patter)) { + return true; + } + + if (Objects.equals("*", patter)) { + return true; + } + + if (AntPathMatcher.INSTANCE.match(patter, target)) { + return true; + } + + Pattern p = Pattern.compile(patter); + Matcher m = p.matcher(target); + + return m.matches(); + } + } diff --git a/camel-core/src/test/java/org/apache/camel/impl/cloud/LoadBalancerTest.java b/camel-core/src/test/java/org/apache/camel/impl/cloud/LoadBalancerTest.java index 219f7ae411ed9..316ecb0a92a14 100644 --- a/camel-core/src/test/java/org/apache/camel/impl/cloud/LoadBalancerTest.java +++ b/camel-core/src/test/java/org/apache/camel/impl/cloud/LoadBalancerTest.java @@ -31,10 +31,10 @@ public class LoadBalancerTest { @BeforeClass public static void setUp() { - serviceDiscovery.addServer("no-name", "127.0.0.1", 2001); - serviceDiscovery.addServer("no-name", "127.0.0.1", 2002); - serviceDiscovery.addServer("no-name", "127.0.0.1", 1001); - serviceDiscovery.addServer("no-name", "127.0.0.1", 1002); + serviceDiscovery.addServer("no-name@127.0.0.1:2001"); + serviceDiscovery.addServer("no-name@127.0.0.1:2002"); + serviceDiscovery.addServer("no-name@127.0.0.1:1001"); + serviceDiscovery.addServer("no-name@127.0.0.1:1002"); } @Test diff --git a/camel-core/src/test/java/org/apache/camel/impl/cloud/ServiceCallConfigurationTest.java b/camel-core/src/test/java/org/apache/camel/impl/cloud/ServiceCallConfigurationTest.java index fd84be670919c..d2640d05c1798 100644 --- a/camel-core/src/test/java/org/apache/camel/impl/cloud/ServiceCallConfigurationTest.java +++ b/camel-core/src/test/java/org/apache/camel/impl/cloud/ServiceCallConfigurationTest.java @@ -43,8 +43,8 @@ public class ServiceCallConfigurationTest { @Test public void testDynamicUri() throws Exception { StaticServiceDiscovery sd = new StaticServiceDiscovery(); - sd.addServer("scall", "127.0.0.1", 8080); - sd.addServer("scall", "127.0.0.1", 8081); + sd.addServer("scall@127.0.0.1:8080"); + sd.addServer("scall@127.0.0.1:8081"); ServiceCallConfigurationDefinition conf = new ServiceCallConfigurationDefinition(); conf.setServiceDiscovery(sd); @@ -86,11 +86,11 @@ public void configure() throws Exception { @Test public void testDefaultConfigurationFromCamelContext() throws Exception { StaticServiceDiscovery sd = new StaticServiceDiscovery(); - sd.addServer("127.0.0.1:8080"); - sd.addServer("127.0.0.1:8081"); + sd.addServer("service@127.0.0.1:8080"); + sd.addServer("service@127.0.0.1:8081"); BlacklistServiceFilter sf = new BlacklistServiceFilter(); - sf.addServer("127.0.0.1:8080"); + sf.addServer("*@127.0.0.1:8080"); ServiceCallConfigurationDefinition conf = new ServiceCallConfigurationDefinition(); conf.setServiceDiscovery(sd); @@ -127,11 +127,11 @@ public void configure() throws Exception { @Test public void testDefaultConfigurationFromRegistryWithDefaultName() throws Exception { StaticServiceDiscovery sd = new StaticServiceDiscovery(); - sd.addServer("127.0.0.1:8080"); - sd.addServer("127.0.0.1:8081"); + sd.addServer("service@127.0.0.1:8080"); + sd.addServer("service@127.0.0.1:8081"); BlacklistServiceFilter sf = new BlacklistServiceFilter(); - sf.addServer("127.0.0.1:8080"); + sf.addServer("*@127.0.0.1:8080"); ServiceCallConfigurationDefinition conf = new ServiceCallConfigurationDefinition(); conf.setServiceDiscovery(sd); @@ -171,11 +171,11 @@ public void configure() throws Exception { @Test public void testDefaultConfigurationFromRegistryWithNonDefaultName() throws Exception { StaticServiceDiscovery sd = new StaticServiceDiscovery(); - sd.addServer("127.0.0.1:8080"); - sd.addServer("127.0.0.1:8081"); + sd.addServer("service@127.0.0.1:8080"); + sd.addServer("service@127.0.0.1:8081"); BlacklistServiceFilter sf = new BlacklistServiceFilter(); - sf.addServer("127.0.0.1:8080"); + sf.addServer("*@127.0.0.1:8080"); ServiceCallConfigurationDefinition conf = new ServiceCallConfigurationDefinition(); conf.setServiceDiscovery(sd); @@ -219,12 +219,12 @@ public void configure() throws Exception { public void testMixedConfiguration() throws Exception { // Default StaticServiceDiscovery defaultServiceDiscovery = new StaticServiceDiscovery(); - defaultServiceDiscovery.addServer("127.0.0.1:8080"); - defaultServiceDiscovery.addServer("127.0.0.1:8081"); - defaultServiceDiscovery.addServer("127.0.0.1:8082"); + defaultServiceDiscovery.addServer("service@127.0.0.1:8080"); + defaultServiceDiscovery.addServer("service@127.0.0.1:8081"); + defaultServiceDiscovery.addServer("service@127.0.0.1:8082"); BlacklistServiceFilter defaultServiceFilter = new BlacklistServiceFilter(); - defaultServiceFilter.addServer("127.0.0.1:8080"); + defaultServiceFilter.addServer("*@127.0.0.1:8080"); ServiceCallConfigurationDefinition defaultConfiguration = new ServiceCallConfigurationDefinition(); defaultConfiguration.setServiceDiscovery(defaultServiceDiscovery); @@ -232,17 +232,17 @@ public void testMixedConfiguration() throws Exception { // Named BlacklistServiceFilter namedServiceFilter = new BlacklistServiceFilter(); - namedServiceFilter.addServer("127.0.0.1:8081"); + namedServiceFilter.addServer("*@127.0.0.1:8081"); ServiceCallConfigurationDefinition namedConfiguration = new ServiceCallConfigurationDefinition(); namedConfiguration.serviceFilter(namedServiceFilter); // Local StaticServiceDiscovery localServiceDiscovery = new StaticServiceDiscovery(); - localServiceDiscovery.addServer("127.0.0.1:8080"); - localServiceDiscovery.addServer("127.0.0.1:8081"); - localServiceDiscovery.addServer("127.0.0.1:8082"); - localServiceDiscovery.addServer("127.0.0.1:8084"); + localServiceDiscovery.addServer("service@127.0.0.1:8080"); + localServiceDiscovery.addServer("service@127.0.0.1:8081"); + localServiceDiscovery.addServer("service@127.0.0.1:8082"); + localServiceDiscovery.addServer("service@127.0.0.1:8084"); // Camel context diff --git a/camel-core/src/test/java/org/apache/camel/impl/cloud/ServiceDefinitionTest.java b/camel-core/src/test/java/org/apache/camel/impl/cloud/ServiceDefinitionTest.java new file mode 100644 index 0000000000000..6c32597a18b38 --- /dev/null +++ b/camel-core/src/test/java/org/apache/camel/impl/cloud/ServiceDefinitionTest.java @@ -0,0 +1,110 @@ +/** + * 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.camel.impl.cloud; + +import java.util.ArrayList; +import java.util.List; +import java.util.stream.Collectors; + +import org.apache.camel.cloud.ServiceDefinition; +import org.junit.Test; + +import static junit.framework.TestCase.assertEquals; +import static junit.framework.TestCase.assertFalse; +import static junit.framework.TestCase.assertNull; +import static junit.framework.TestCase.assertTrue; + +public class ServiceDefinitionTest { + @Test + public void testParse() { + List definitions = DefaultServiceDefinition.parse( + "svc1@host:2001,myId/svc1@host:2001" + ).collect(Collectors.toList()); + + assertEquals(2, definitions.size()); + + assertNull(definitions.get(0).getId()); + assertEquals("svc1", definitions.get(0).getName()); + assertEquals("host", definitions.get(0).getHost()); + assertEquals(2001, definitions.get(0).getPort()); + + assertEquals("myId", definitions.get(1).getId()); + assertEquals("svc1", definitions.get(1).getName()); + assertEquals("host", definitions.get(1).getHost()); + assertEquals(2001, definitions.get(1).getPort()); + } + + @Test + public void testMatch() { + List definitions = new ArrayList<>(); + definitions.add(DefaultServiceDefinition.builder().withName("service-1").withHost("host-1.domain1.com").withPort(2001).build()); + definitions.add(DefaultServiceDefinition.builder().withName("service-2").withHost("host-2.domain1.com").withPort(2001).build()); + definitions.add(DefaultServiceDefinition.builder().withName("service-3").withHost("host-3.domain1.com").withPort(2001).build()); + definitions.add(DefaultServiceDefinition.builder().withName("service-4").withHost("host-3.domain2.com").withPort(2001).build()); + + assertTrue( + DefaultServiceDefinition.builder().withName("*").withHost(".*\\.domain1\\.com").withPort(2001).build().matches( + definitions.get(0) + ) + ); + assertTrue( + DefaultServiceDefinition.builder().withName("service-1").withHost("host-1.domain1.com").withPort(2001).build().matches( + definitions.get(0) + ) + ); + assertTrue( + DefaultServiceDefinition.builder().withName("service-.*").withHost("host-1.domain1.com").withPort(2001).build().matches( + definitions.get(0) + ) + ); + assertTrue( + DefaultServiceDefinition.builder().withName("service-.*").withHost("host-.*\\.domain.*\\.com").withPort(2001).build().matches( + definitions.get(0) + ) + ); + assertTrue( + DefaultServiceDefinition.builder().withName("service-.*").withHost("host-.*\\.domain.*\\.com").withPort(2001).build().matches( + definitions.get(1) + ) + ); + assertTrue( + DefaultServiceDefinition.builder().withName("service-.*").withHost("host-.*\\.domain.*\\.com").withPort(2001).build().matches( + definitions.get(2) + ) + ); + assertTrue( + DefaultServiceDefinition.builder().withName("service-.*").withHost("host-.*\\.domain.*\\.com").withPort(2001).build().matches( + definitions.get(3) + ) + ); + assertFalse( + DefaultServiceDefinition.builder().withName("service-.*").withHost("host-1.domain1.com").withPort(2001).build().matches( + definitions.get(3) + ) + ); + assertFalse( + DefaultServiceDefinition.builder().withName("*").withHost(".*\\.domain1\\.com").withPort(2001).build().matches( + definitions.get(3) + ) + ); + assertFalse( + DefaultServiceDefinition.builder().withName("*").withHost(".*\\.domain1\\.com").withPort(2002).build().matches( + definitions.get(3) + ) + ); + } +} diff --git a/components/camel-consul/pom.xml b/components/camel-consul/pom.xml index e57a91e9235ad..4d735c7f6c858 100644 --- a/components/camel-consul/pom.xml +++ b/components/camel-consul/pom.xml @@ -74,6 +74,11 @@ camel-master test + + org.apache.camel + camel-service + test + org.apache.camel camel-ribbon @@ -94,12 +99,18 @@ log4j-slf4j-impl test - + + + org.testcontainers + testcontainers + ${testcontainers-version} + test + + - consul-skip-tests @@ -116,10 +127,14 @@ + + - consul-tests + consul-tests-docker-file - false + + /var/run/docker.sock + @@ -127,100 +142,37 @@ maven-surefire-plugin false + + true + + - docker + consul-tests-docker-env - false + + env.DOCKER_HOST + - - io.fabric8 - docker-maven-plugin - ${docker-maven-plugin-version} - - - start - pre-integration-test - - start - - - false - - - consul:1.0.0 - consul - - - consul.port:8500 - - - agent: Synced node info - - - - - agent - -dev - -server - -bootstrap - -client - 0.0.0.0 - -log-level - trace - - - - - - - - - stop - post-integration-test - - stop - - - - - maven-surefire-plugin - true - - - - - org.apache.maven.plugins - maven-failsafe-plugin - - - - integration-test - verify - - - - - - ${consul.port} - 127.0.0.1 - + false + + true + - diff --git a/components/camel-consul/src/main/java/org/apache/camel/component/consul/cloud/ConsulServiceDiscovery.java b/components/camel-consul/src/main/java/org/apache/camel/component/consul/cloud/ConsulServiceDiscovery.java index 74b7c006c35e3..4cb1114f0a340 100644 --- a/components/camel-consul/src/main/java/org/apache/camel/component/consul/cloud/ConsulServiceDiscovery.java +++ b/components/camel-consul/src/main/java/org/apache/camel/component/consul/cloud/ConsulServiceDiscovery.java @@ -100,9 +100,9 @@ private ServiceDefinition newService(String serviceName, CatalogService service, service.getServicePort(), meta, new DefaultServiceHealth( - serviceHealthList.stream() - .filter(h -> ObjectHelper.equal(h.getService().getId(), service.getServiceId())) - .allMatch(this::isHealthy) + serviceHealthList.stream() + .filter(h -> ObjectHelper.equal(h.getService().getId(), service.getServiceId())) + .allMatch(this::isHealthy) ) ); } diff --git a/components/camel-consul/src/main/java/org/apache/camel/component/consul/cloud/ConsulServiceRegistry.java b/components/camel-consul/src/main/java/org/apache/camel/component/consul/cloud/ConsulServiceRegistry.java new file mode 100644 index 0000000000000..2028936de5971 --- /dev/null +++ b/components/camel-consul/src/main/java/org/apache/camel/component/consul/cloud/ConsulServiceRegistry.java @@ -0,0 +1,375 @@ +/** + * 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.camel.component.consul.cloud; + +import java.util.Objects; +import java.util.Optional; +import java.util.Set; +import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.ScheduledExecutorService; +import java.util.concurrent.TimeUnit; +import java.util.stream.Collectors; + +import com.orbitz.consul.Consul; +import com.orbitz.consul.NotRegisteredException; +import com.orbitz.consul.model.agent.ImmutableRegCheck; +import com.orbitz.consul.model.agent.ImmutableRegistration; +import com.orbitz.consul.model.agent.Registration; +import com.orbitz.consul.model.health.Service; +import org.apache.camel.cloud.ServiceDefinition; +import org.apache.camel.impl.cloud.AbstractServiceRegistry; +import org.apache.camel.util.ObjectHelper; +import org.apache.camel.util.jsse.SSLContextParameters; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +public class ConsulServiceRegistry extends AbstractServiceRegistry { + private static final Logger LOGGER = LoggerFactory.getLogger(ConsulServiceRegistry.class); + + private final Set serviceList; + private ConsulServiceRegistryConfiguration configuration; + private Consul client; + private ScheduledExecutorService scheduler; + + public ConsulServiceRegistry() { + this.serviceList = ConcurrentHashMap.newKeySet(); + this.configuration = new ConsulServiceRegistryConfiguration(); + } + + public ConsulServiceRegistry(ConsulServiceRegistryConfiguration configuration) { + this.serviceList = ConcurrentHashMap.newKeySet(); + this.configuration = configuration.copy(); + } + + // **************** + // Properties + // **************** + + public ConsulServiceRegistryConfiguration getConfiguration() { + return configuration; + } + + public void setConfiguration(ConsulServiceRegistryConfiguration configuration) { + this.configuration = configuration.copy(); + } + + public String getUrl() { + return configuration.getUrl(); + } + + public void setUrl(String url) { + configuration.setUrl(url); + } + + public String getDatacenter() { + return configuration.getDatacenter(); + } + + public void setDatacenter(String datacenter) { + configuration.setDatacenter(datacenter); + } + + public SSLContextParameters getSslContextParameters() { + return configuration.getSslContextParameters(); + } + + public void setSslContextParameters(SSLContextParameters sslContextParameters) { + configuration.setSslContextParameters(sslContextParameters); + } + + public String getAclToken() { + return configuration.getAclToken(); + } + + public void setAclToken(String aclToken) { + configuration.setAclToken(aclToken); + } + + public String getUserName() { + return configuration.getUserName(); + } + + public void setUserName(String userName) { + configuration.setUserName(userName); + } + + public String getPassword() { + return configuration.getPassword(); + } + + public void setPassword(String password) { + configuration.setPassword(password); + } + + public Long getConnectTimeoutMillis() { + return configuration.getConnectTimeoutMillis(); + } + + public void setConnectTimeoutMillis(Long connectTimeoutMillis) { + configuration.setConnectTimeoutMillis(connectTimeoutMillis); + } + + public Long getReadTimeoutMillis() { + return configuration.getReadTimeoutMillis(); + } + + public void setReadTimeoutMillis(Long readTimeoutMillis) { + configuration.setReadTimeoutMillis(readTimeoutMillis); + } + + public Long getWriteTimeoutMillis() { + return configuration.getWriteTimeoutMillis(); + } + + public void setWriteTimeoutMillis(Long writeTimeoutMillis) { + configuration.setWriteTimeoutMillis(writeTimeoutMillis); + } + + public Integer getBlockSeconds() { + return configuration.getBlockSeconds(); + } + + public void setBlockSeconds(Integer blockSeconds) { + configuration.setBlockSeconds(blockSeconds); + } + + public boolean isOverrideServiceHost() { + return configuration.isOverrideServiceHost(); + } + + public void setOverrideServiceHost(boolean overrideServiceHost) { + configuration.setOverrideServiceHost(overrideServiceHost); + } + + public String getServiceHost() { + return configuration.getServiceHost(); + } + + public void setServiceHost(String serviceHost) { + configuration.setServiceHost(serviceHost); + } + + public int getCheckTtl() { + return configuration.getCheckTtl(); + } + + public void setCheckTtl(int checkTtl) { + configuration.setCheckTtl(checkTtl); + } + + public int getCheckInterval() { + return configuration.getCheckInterval(); + } + + public void setCheckInterval(int checkInterval) { + configuration.setCheckInterval(checkInterval); + } + + public int getDeregisterAfter() { + return configuration.getDeregisterAfter(); + } + + public void setDeregisterAfter(int deregisterAfter) { + configuration.setDeregisterAfter(deregisterAfter); + } + + public boolean isDeregisterServicesOnStop() { + return configuration.isDeregisterServicesOnStop(); + } + + public void setDeregisterServicesOnStop(boolean deregisterServicesOnStop) { + configuration.setDeregisterServicesOnStop(deregisterServicesOnStop); + } + + // **************** + // Lifecycle + // **************** + + @Override + protected void doStart() throws Exception { + client = this.configuration.createConsulClient(getCamelContext()); + scheduler = getCamelContext().getExecutorServiceManager().newSingleThreadScheduledExecutor(this, "ConsulServiceRegistry"); + } + + @Override + protected void doStop() throws Exception { + if (scheduler != null) { + getCamelContext().getExecutorServiceManager().shutdownNow(scheduler); + scheduler = null; + } + + if (configuration.isDeregisterServicesOnStop()) { + for (Service service: client.agentClient().getServices().values()) { + try { + if (serviceList.contains(service.getId())) { + client.agentClient().deregister(service.getId()); + serviceList.remove(service.getId()); + } + } catch (Exception e) { + LOGGER.warn("Error de-registering service: " + service, e); + } + } + + client = null; + } + } + + // ********************************************* + // + // ********************************************* + + @Override + public void register(ServiceDefinition definition) { + if (definition.getId() == null) { + throw new IllegalArgumentException("Service ID must be defined (definition=" + definition + ")"); + } + if (definition.getName() == null) { + throw new IllegalArgumentException("Service Name must be defined (definition=" + definition + ")"); + } + + Registration registration = ImmutableRegistration.builder() + .address(computeServiceHost(definition)) + .port(definition.getPort()) + .name(definition.getName()) + .id(definition.getId()) + .check( + computeCheck(definition) + ) + .tags(definition.getMetadata().entrySet().stream() + .filter(e -> e.getValue() != null) + .map(e -> e.getKey() + "=" + e.getValue()) + .collect(Collectors.toList()) + ) + .addTags( + "_consul.service.registry.id=" + getId() + ) + .build(); + + // perform service registration against consul + client.agentClient().register(registration); + + try { + // mark the service as healthy + client.agentClient().pass(definition.getId()); + + // If the service has TTL enabled + registration.getCheck().flatMap(Registration.RegCheck::getTtl).ifPresent( + ignored -> { + LOGGER.debug("Configure service pass for: {}", definition); + + scheduler.scheduleAtFixedRate( + () -> { + try { + if (serviceList.contains(definition.getId())) { + client.agentClient().pass(definition.getId()); + } + } catch (NotRegisteredException e) { + LOGGER.warn("Service with id: {} is not more registered", definition.getId()); + serviceList.remove(definition.getId()); + } + }, + configuration.getCheckInterval() / 2, + configuration.getCheckInterval(), + TimeUnit.SECONDS + ); + } + ); + } catch (NotRegisteredException e) { + LOGGER.warn("There was an issue registering service: {}", definition.getId()); + } + + // add the serviceId to the list of known server + serviceList.add(definition.getId()); + } + + @Override + public void deregister(ServiceDefinition definition) { + if (definition.getId() == null) { + throw new IllegalArgumentException("ServiceID must be defined (definition=" + definition + ")"); + } + + client.agentClient().deregister(definition.getId()); + } + + private String computeServiceHost(ServiceDefinition definition) { + String host = definition.getHost(); + + if (configuration.isOverrideServiceHost() && configuration.getServiceHost() != null) { + host = configuration.getServiceHost(); + } + + return ObjectHelper.notNull(host, "service host"); + } + + // TODO: this need to be improved + private Registration.RegCheck computeCheck(ServiceDefinition definition) { + if (definition.getHealth() == null) { + return ImmutableRegCheck.builder() + .ttl(String.format("%ss", configuration.getCheckInterval())) + .deregisterCriticalServiceAfter(String.format("%ss", configuration.getDeregisterAfter())) + .build(); + } + + return definition.getHealth().getEndpoint().flatMap( + uri -> { + if (Objects.equals("http", uri.getScheme())) { + return Optional.of( + ImmutableRegCheck.builder() + .http(uri.toASCIIString()) + .interval(String.format("%ss", configuration.getCheckInterval())) + .deregisterCriticalServiceAfter(String.format("%ss", configuration.getDeregisterAfter())) + .build() + ); + } + if (Objects.equals("https", uri.getScheme())) { + return Optional.of( + ImmutableRegCheck.builder() + .http(uri.toASCIIString()) + .interval(String.format("%ss", configuration.getCheckInterval())) + .deregisterCriticalServiceAfter(String.format("%ss", configuration.getDeregisterAfter())) + .build() + ); + } + if (Objects.equals("tcp", uri.getScheme())) { + return Optional.of( + ImmutableRegCheck.builder() + .tcp(uri.getHost()) + .interval(String.format("%ss", configuration.getCheckInterval())) + .deregisterCriticalServiceAfter(String.format("%ss", configuration.getDeregisterAfter())) + .build() + ); + } + if (Objects.equals("grpc", uri.getScheme())) { + return Optional.of( + ImmutableRegCheck.builder() + .grpc(uri.getHost()) + .interval(String.format("%ss", configuration.getCheckInterval())) + .deregisterCriticalServiceAfter(String.format("%ss", configuration.getDeregisterAfter())) + .build() + ); + } + + return Optional.empty(); + } + ).orElseGet( + () -> ImmutableRegCheck.builder() + .ttl(String.format("%ss", configuration.getCheckInterval())) + .deregisterCriticalServiceAfter(String.format("%ss", configuration.getDeregisterAfter())) + .build() + ); + } +} diff --git a/components/camel-consul/src/main/java/org/apache/camel/component/consul/cloud/ConsulServiceRegistryConfiguration.java b/components/camel-consul/src/main/java/org/apache/camel/component/consul/cloud/ConsulServiceRegistryConfiguration.java new file mode 100644 index 0000000000000..2950f0323e5d3 --- /dev/null +++ b/components/camel-consul/src/main/java/org/apache/camel/component/consul/cloud/ConsulServiceRegistryConfiguration.java @@ -0,0 +1,119 @@ +/** + * 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.camel.component.consul.cloud; + +import org.apache.camel.RuntimeCamelException; +import org.apache.camel.component.consul.ConsulClientConfiguration; + +public class ConsulServiceRegistryConfiguration extends ConsulClientConfiguration { + /** + * Should we remove all the registered services know by this registry on stop? + */ + private boolean deregisterServicesOnStop = true; + + /** + * Should we override the service host if given ? + */ + private boolean overrideServiceHost = true; + + /** + * Service host. + */ + private String serviceHost; + + /** + * The time (in seconds) to live for TTL checks. Default is 1 minute. + */ + private int checkTtl = 60; + + /** + * How often (in seconds) a service has to be marked as healthy if its check + * is TTL or how often the check should run. Default is 5 seconds. + */ + private int checkInterval = 5; + + /** + * How long (in seconds) to wait to deregister a service in case of unclean + * shutdown. Default is 1 hour. + */ + private int deregisterAfter = 60 * 60; + + // *********************************************** + // Properties + // *********************************************** + + public boolean isDeregisterServicesOnStop() { + return deregisterServicesOnStop; + } + + public void setDeregisterServicesOnStop(boolean deregisterServicesOnStop) { + this.deregisterServicesOnStop = deregisterServicesOnStop; + } + + public boolean isOverrideServiceHost() { + return overrideServiceHost; + } + + public void setOverrideServiceHost(boolean overrideServiceHost) { + this.overrideServiceHost = overrideServiceHost; + } + + public String getServiceHost() { + return serviceHost; + } + + public void setServiceHost(String serviceHost) { + this.serviceHost = serviceHost; + } + + public int getCheckTtl() { + return checkTtl; + } + + public void setCheckTtl(int checkTtl) { + this.checkTtl = checkTtl; + } + + public int getCheckInterval() { + return checkInterval; + } + + public void setCheckInterval(int checkInterval) { + this.checkInterval = checkInterval; + } + + public int getDeregisterAfter() { + return deregisterAfter; + } + + public void setDeregisterAfter(int deregisterAfter) { + this.deregisterAfter = deregisterAfter; + } + + // *********************************************** + // + // *********************************************** + + @Override + public ConsulServiceRegistryConfiguration copy() { + try { + return (ConsulServiceRegistryConfiguration)super.clone(); + } catch (CloneNotSupportedException e) { + throw new RuntimeCamelException(e); + } + } +} diff --git a/components/camel-consul/src/main/java/org/apache/camel/component/consul/endpoint/ConsulEventConsumer.java b/components/camel-consul/src/main/java/org/apache/camel/component/consul/endpoint/ConsulEventConsumer.java index 13753c230035f..85ecc1504c482 100644 --- a/components/camel-consul/src/main/java/org/apache/camel/component/consul/endpoint/ConsulEventConsumer.java +++ b/components/camel-consul/src/main/java/org/apache/camel/component/consul/endpoint/ConsulEventConsumer.java @@ -34,6 +34,7 @@ import org.apache.camel.component.consul.ConsulConstants; import org.apache.camel.component.consul.ConsulEndpoint; import org.apache.camel.spi.ExecutorServiceManager; +import org.slf4j.LoggerFactory; public final class ConsulEventConsumer extends AbstractConsulConsumer { private final ExecutorServiceManager executorServiceManager; @@ -57,7 +58,10 @@ protected void doStart() throws Exception { @Override protected void doStop() throws Exception { - executorServiceManager.shutdownGraceful(scheduledExecutorService); + if (this.scheduledExecutorService != null) { + this.executorServiceManager.shutdownNow(scheduledExecutorService); + } + super.doStop(); } @@ -103,6 +107,8 @@ public void onFailure(Throwable throwable) { } private void onEvent(Event event) { + LoggerFactory.getLogger(ConsulEventConsumer.this.getClass()).info("{}", event); + final Exchange exchange = endpoint.createExchange(); final Message message = exchange.getIn(); diff --git a/components/camel-consul/src/test/java/org/apache/camel/component/consul/ConsulCatalogTest.java b/components/camel-consul/src/test/java/org/apache/camel/component/consul/ConsulCatalogTest.java index fa09d1f1cdc05..8986048009e17 100644 --- a/components/camel-consul/src/test/java/org/apache/camel/component/consul/ConsulCatalogTest.java +++ b/components/camel-consul/src/test/java/org/apache/camel/component/consul/ConsulCatalogTest.java @@ -21,6 +21,7 @@ import com.orbitz.consul.model.health.Node; import org.apache.camel.builder.RouteBuilder; import org.apache.camel.component.consul.endpoint.ConsulCatalogActions; +import org.apache.camel.component.consul.support.ConsulTestSupport; import org.junit.Assert; import org.junit.Test; diff --git a/components/camel-consul/src/test/java/org/apache/camel/component/consul/ConsulCoordinatesTest.java b/components/camel-consul/src/test/java/org/apache/camel/component/consul/ConsulCoordinatesTest.java index e8b3218f08ab5..e2a0bef97dba9 100644 --- a/components/camel-consul/src/test/java/org/apache/camel/component/consul/ConsulCoordinatesTest.java +++ b/components/camel-consul/src/test/java/org/apache/camel/component/consul/ConsulCoordinatesTest.java @@ -22,7 +22,9 @@ import com.orbitz.consul.model.coordinate.Datacenter; import org.apache.camel.builder.RouteBuilder; import org.apache.camel.component.consul.endpoint.ConsulCoordinatesActions; +import org.apache.camel.component.consul.support.ConsulTestSupport; import org.junit.Assert; +import org.junit.Ignore; import org.junit.Test; public class ConsulCoordinatesTest extends ConsulTestSupport { @@ -39,7 +41,8 @@ public void testDatacenters() throws Exception { Assert.assertFalse(res.isEmpty()); Assert.assertEquals(ref, res); } - + + @Ignore("Disabled as it seems that nodes is always 0") @Test public void testNodes() throws Exception { List ref = getConsul().coordinateClient().getNodes(); diff --git a/components/camel-consul/src/test/java/org/apache/camel/component/consul/ConsulEventTest.java b/components/camel-consul/src/test/java/org/apache/camel/component/consul/ConsulEventTest.java index 68e30cf56be38..82c1ca18f8758 100644 --- a/components/camel-consul/src/test/java/org/apache/camel/component/consul/ConsulEventTest.java +++ b/components/camel-consul/src/test/java/org/apache/camel/component/consul/ConsulEventTest.java @@ -22,6 +22,7 @@ import com.orbitz.consul.model.event.Event; import org.apache.camel.builder.RouteBuilder; import org.apache.camel.component.consul.endpoint.ConsulEventActions; +import org.apache.camel.component.consul.support.ConsulTestSupport; import org.apache.camel.component.mock.MockEndpoint; import org.junit.Test; diff --git a/components/camel-consul/src/test/java/org/apache/camel/component/consul/ConsulEventWatchTest.java b/components/camel-consul/src/test/java/org/apache/camel/component/consul/ConsulEventWatchTest.java index 591c79c7d9874..368d0f4041212 100644 --- a/components/camel-consul/src/test/java/org/apache/camel/component/consul/ConsulEventWatchTest.java +++ b/components/camel-consul/src/test/java/org/apache/camel/component/consul/ConsulEventWatchTest.java @@ -20,6 +20,7 @@ import com.orbitz.consul.EventClient; import org.apache.camel.builder.RouteBuilder; +import org.apache.camel.component.consul.support.ConsulTestSupport; import org.apache.camel.component.mock.MockEndpoint; import org.junit.Test; @@ -51,9 +52,9 @@ public void testWatchEvent() throws Exception { protected RouteBuilder createRouteBuilder() throws Exception { return new RouteBuilder() { public void configure() { - fromF("consul:event?key=%s", key) + fromF("consul:event?key=%s&blockSeconds=1", key) .to("log:org.apache.camel.component.consul?level=INFO&showAll=true") - .to("mock:event-watch"); + .to("mock:event-watch"); } }; } diff --git a/components/camel-consul/src/test/java/org/apache/camel/component/consul/ConsulHealthTest.java b/components/camel-consul/src/test/java/org/apache/camel/component/consul/ConsulHealthTest.java index e9acafd328543..d9e48aef8aeea 100644 --- a/components/camel-consul/src/test/java/org/apache/camel/component/consul/ConsulHealthTest.java +++ b/components/camel-consul/src/test/java/org/apache/camel/component/consul/ConsulHealthTest.java @@ -27,6 +27,7 @@ import com.orbitz.consul.model.health.ServiceHealth; import org.apache.camel.builder.RouteBuilder; import org.apache.camel.component.consul.endpoint.ConsulHealthActions; +import org.apache.camel.component.consul.support.ConsulTestSupport; import org.junit.Assert; import org.junit.Test; diff --git a/components/camel-consul/src/test/java/org/apache/camel/component/consul/ConsulKeyValueTest.java b/components/camel-consul/src/test/java/org/apache/camel/component/consul/ConsulKeyValueTest.java index a0e4f05607361..c8703119c45c5 100644 --- a/components/camel-consul/src/test/java/org/apache/camel/component/consul/ConsulKeyValueTest.java +++ b/components/camel-consul/src/test/java/org/apache/camel/component/consul/ConsulKeyValueTest.java @@ -20,6 +20,7 @@ import org.apache.camel.builder.RouteBuilder; import org.apache.camel.component.consul.endpoint.ConsulKeyValueActions; +import org.apache.camel.component.consul.support.ConsulTestSupport; import org.apache.camel.component.mock.MockEndpoint; import org.junit.Test; diff --git a/components/camel-consul/src/test/java/org/apache/camel/component/consul/ConsulKeyValueWatchTest.java b/components/camel-consul/src/test/java/org/apache/camel/component/consul/ConsulKeyValueWatchTest.java index 71dfbf5b526c4..8fbebe5ab26ef 100644 --- a/components/camel-consul/src/test/java/org/apache/camel/component/consul/ConsulKeyValueWatchTest.java +++ b/components/camel-consul/src/test/java/org/apache/camel/component/consul/ConsulKeyValueWatchTest.java @@ -21,6 +21,7 @@ import com.orbitz.consul.KeyValueClient; import org.apache.camel.builder.RouteBuilder; +import org.apache.camel.component.consul.support.ConsulTestSupport; import org.apache.camel.component.mock.MockEndpoint; import org.junit.Test; diff --git a/components/camel-consul/src/test/java/org/apache/camel/component/consul/ConsulRegistryTest.java b/components/camel-consul/src/test/java/org/apache/camel/component/consul/ConsulRegistryTest.java index 4cd215f7e85c7..7c04165f9d5e9 100644 --- a/components/camel-consul/src/test/java/org/apache/camel/component/consul/ConsulRegistryTest.java +++ b/components/camel-consul/src/test/java/org/apache/camel/component/consul/ConsulRegistryTest.java @@ -22,11 +22,13 @@ import java.util.Map; import java.util.Set; +import com.orbitz.consul.Consul; import org.apache.camel.NoSuchBeanException; +import org.apache.camel.component.consul.support.ConsulContainerSupport; +import org.junit.AfterClass; import org.junit.BeforeClass; import org.junit.Test; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; +import org.testcontainers.containers.GenericContainer; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertNotNull; @@ -39,7 +41,7 @@ public class ConsulRegistryTest implements Serializable { private static final long serialVersionUID = -3482971969351609265L; private static ConsulRegistry registry; - private static final Logger LOGGER = LoggerFactory.getLogger(ConsulRegistryTest.class); + private static GenericContainer container; public class ConsulTestClass implements Serializable { private static final long serialVersionUID = -4815945688487114891L; @@ -51,7 +53,15 @@ public String hello(String name) { @BeforeClass public static void setUp() { - registry = new ConsulRegistry.Builder("localhost").build(); + container = ConsulContainerSupport.consulContainer(); + container.start(); + + registry = new ConsulRegistry(container.getContainerIpAddress(), container.getMappedPort(Consul.DEFAULT_HTTP_PORT)); + } + + @AfterClass + public static void tearDown() { + container.stop(); } @Test diff --git a/components/camel-consul/src/test/java/org/apache/camel/component/consul/ConsulSessionTest.java b/components/camel-consul/src/test/java/org/apache/camel/component/consul/ConsulSessionTest.java index 1e3b7ab8808ff..30ff056085e9b 100644 --- a/components/camel-consul/src/test/java/org/apache/camel/component/consul/ConsulSessionTest.java +++ b/components/camel-consul/src/test/java/org/apache/camel/component/consul/ConsulSessionTest.java @@ -24,6 +24,7 @@ import com.orbitz.consul.model.session.SessionInfo; import org.apache.camel.builder.RouteBuilder; import org.apache.camel.component.consul.endpoint.ConsulSessionActions; +import org.apache.camel.component.consul.support.ConsulTestSupport; import org.junit.Assert; import org.junit.Test; diff --git a/components/camel-consul/src/test/java/org/apache/camel/component/consul/cloud/ConsulDefaultServiceCallRouteTest.java b/components/camel-consul/src/test/java/org/apache/camel/component/consul/cloud/ConsulDefaultServiceCallRouteTest.java index 00586db3d84c2..aee8c8df7f736 100644 --- a/components/camel-consul/src/test/java/org/apache/camel/component/consul/cloud/ConsulDefaultServiceCallRouteTest.java +++ b/components/camel-consul/src/test/java/org/apache/camel/component/consul/cloud/ConsulDefaultServiceCallRouteTest.java @@ -25,7 +25,7 @@ import com.orbitz.consul.model.agent.Registration; import org.apache.camel.RoutesBuilder; import org.apache.camel.builder.RouteBuilder; -import org.apache.camel.component.consul.ConsulTestSupport; +import org.apache.camel.component.consul.support.ConsulTestSupport; import org.junit.Test; public class ConsulDefaultServiceCallRouteTest extends ConsulTestSupport { @@ -99,6 +99,7 @@ public void configure() throws Exception { .component("jetty") .defaultLoadBalancer() .consulServiceDiscovery() + .url(consulUrl()) .endParent() .to("log:org.apache.camel.component.consul.cloud?level=INFO&showAll=true&multiline=true") .to("mock:result"); diff --git a/components/camel-consul/src/test/java/org/apache/camel/component/consul/cloud/ConsulRibbonServiceCallRouteTest.java b/components/camel-consul/src/test/java/org/apache/camel/component/consul/cloud/ConsulRibbonServiceCallRouteTest.java index 542e6cbfce50f..f530e314d8a27 100644 --- a/components/camel-consul/src/test/java/org/apache/camel/component/consul/cloud/ConsulRibbonServiceCallRouteTest.java +++ b/components/camel-consul/src/test/java/org/apache/camel/component/consul/cloud/ConsulRibbonServiceCallRouteTest.java @@ -25,7 +25,7 @@ import com.orbitz.consul.model.agent.Registration; import org.apache.camel.RoutesBuilder; import org.apache.camel.builder.RouteBuilder; -import org.apache.camel.component.consul.ConsulTestSupport; +import org.apache.camel.component.consul.support.ConsulTestSupport; import org.junit.Test; public class ConsulRibbonServiceCallRouteTest extends ConsulTestSupport { @@ -99,6 +99,7 @@ public void configure() throws Exception { .name(SERVICE_NAME) .component("jetty") .consulServiceDiscovery() + .url(consulUrl()) .endParent() .to("log:org.apache.camel.component.consul.processor.service?level=INFO&showAll=true&multiline=true") .to("mock:result"); diff --git a/components/camel-consul/src/test/java/org/apache/camel/component/consul/cloud/ConsulServiceDiscoveryTest.java b/components/camel-consul/src/test/java/org/apache/camel/component/consul/cloud/ConsulServiceDiscoveryTest.java index 119e85b68fde9..b7a6945d07b21 100644 --- a/components/camel-consul/src/test/java/org/apache/camel/component/consul/cloud/ConsulServiceDiscoveryTest.java +++ b/components/camel-consul/src/test/java/org/apache/camel/component/consul/cloud/ConsulServiceDiscoveryTest.java @@ -18,37 +18,40 @@ import java.util.ArrayList; import java.util.List; +import java.util.concurrent.ThreadLocalRandom; import com.orbitz.consul.AgentClient; -import com.orbitz.consul.Consul; import com.orbitz.consul.model.agent.ImmutableRegCheck; import com.orbitz.consul.model.agent.ImmutableRegistration; import com.orbitz.consul.model.agent.Registration; import org.apache.camel.cloud.ServiceDefinition; import org.apache.camel.cloud.ServiceDiscovery; import org.apache.camel.component.consul.ConsulConfiguration; -import org.junit.After; -import org.junit.Before; +import org.apache.camel.component.consul.support.ConsulTestSupport; import org.junit.Test; +import org.springframework.util.SocketUtils; -import static org.junit.Assert.assertEquals; -import static org.junit.Assert.assertFalse; -import static org.junit.Assert.assertNotNull; -import static org.junit.Assert.assertTrue; - -public class ConsulServiceDiscoveryTest { +public class ConsulServiceDiscoveryTest extends ConsulTestSupport { private AgentClient client; private List registrations; - @Before - public void setUp() throws Exception { - client = Consul.builder().build().agentClient(); + @Override + public boolean isUseRouteBuilder() { + return false; + } + + @Override + protected void doPreSetup() throws Exception { + client = getConsul().agentClient(); registrations = new ArrayList<>(3); for (int i = 0; i < 6; i++) { + final boolean healty = ThreadLocalRandom.current().nextBoolean(); + final int port = SocketUtils.findAvailableTcpPort(); + Registration.RegCheck c = ImmutableRegCheck.builder() .ttl("1m") - .status(i < 3 ? "passing" : "critical") + .status(healty ? "passing" : "critical") .build(); Registration r = ImmutableRegistration.builder() @@ -58,7 +61,8 @@ public void setUp() throws Exception { .addTags("a-tag") .addTags("key1=value1") .addTags("key2=value2") - .port(9000 + i) + .addTags("healthy=" + healty) + .port(port) .check(c) .build(); @@ -67,8 +71,10 @@ public void setUp() throws Exception { } } - @After + @Override public void tearDown() throws Exception { + super.tearDown(); + registrations.forEach(r -> client.deregister(r.getId())); } @@ -79,6 +85,8 @@ public void tearDown() throws Exception { @Test public void testServiceDiscovery() throws Exception { ConsulConfiguration configuration = new ConsulConfiguration(); + configuration.setUrl(consulUrl()); + ServiceDiscovery discovery = new ConsulServiceDiscovery(configuration); List services = discovery.getServices("my-service"); @@ -92,7 +100,7 @@ public void testServiceDiscovery() throws Exception { assertTrue(service.getMetadata().containsKey("a-tag")); assertTrue(service.getMetadata().containsKey("key1")); assertTrue(service.getMetadata().containsKey("key2")); - assertTrue(service.getPort() < 9003 ? service.getHealth().isHealthy() : !service.getHealth().isHealthy()); + assertEquals("" + service.getHealth().isHealthy() , service.getMetadata().get("healthy")); } } } diff --git a/components/camel-consul/src/test/java/org/apache/camel/component/consul/cloud/ConsulServiceRegistrationTest.java b/components/camel-consul/src/test/java/org/apache/camel/component/consul/cloud/ConsulServiceRegistrationTest.java new file mode 100644 index 0000000000000..be189cbcfa1dd --- /dev/null +++ b/components/camel-consul/src/test/java/org/apache/camel/component/consul/cloud/ConsulServiceRegistrationTest.java @@ -0,0 +1,108 @@ +/** + * 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.camel.component.consul.cloud; + +import java.util.List; + +import com.orbitz.consul.CatalogClient; +import com.orbitz.consul.HealthClient; +import com.orbitz.consul.model.catalog.CatalogService; +import com.orbitz.consul.model.health.ServiceHealth; +import org.apache.camel.CamelContext; +import org.apache.camel.RoutesBuilder; +import org.apache.camel.builder.RouteBuilder; +import org.apache.camel.component.consul.support.ConsulTestSupport; +import org.apache.camel.component.service.ServiceComponent; +import org.apache.camel.impl.JndiRegistry; +import org.junit.Test; +import org.springframework.util.SocketUtils; + +public class ConsulServiceRegistrationTest extends ConsulTestSupport { + private final static String SERVICE_NAME = "my-service"; + private final static String SERVICE_HOST = "localhost"; + private final static int SERVICE_PORT = SocketUtils.findAvailableTcpPort(); + + @Override + protected JndiRegistry createRegistry() throws Exception { + JndiRegistry registry = super.createRegistry(); + registry.bind("service", new ServiceComponent()); + + return registry; + } + + @Override + protected CamelContext createCamelContext() throws Exception { + final CamelContext context = super.createCamelContext(); + + ConsulServiceRegistry registry = new ConsulServiceRegistry(); + registry.setId(context.getUuidGenerator().generateUuid()); + registry.setCamelContext(context()); + registry.setUrl(consulUrl()); + registry.setServiceHost(SERVICE_HOST); + registry.setOverrideServiceHost(true); + + context.addService(registry, true, false); + + return context; + } + + @Override + protected RoutesBuilder createRouteBuilder() throws Exception { + return new RouteBuilder() { + @Override + public void configure() throws Exception { + fromF("service:%s:jetty:http://0.0.0.0:%d?serviceMeta.type=consul", SERVICE_NAME, SERVICE_PORT) + .routeId("exposed") + .noAutoStartup() + .to("log:service-registry?level=INFO"); + } + }; + } + + @Test + public void testRegistrationFromRoute() throws Exception { + final CatalogClient catalog = getConsul().catalogClient(); + final HealthClient health = getConsul().healthClient(); + + // the service should not be registered as the route is not running + assertTrue(catalog.getService(SERVICE_NAME).getResponse().isEmpty()); + + // let start the route + context().startRoute("exposed"); + + // check that service has been registered + List services = catalog.getService(SERVICE_NAME).getResponse(); + assertEquals(1, services.size()); + assertEquals(SERVICE_PORT, services.get(0).getServicePort()); + assertEquals("localhost", services.get(0).getServiceAddress()); + assertTrue(services.get(0).getServiceTags().contains("type=consul")); + assertTrue(services.get(0).getServiceTags().contains("service.protocol=http")); + assertTrue(services.get(0).getServiceTags().contains("service.path=/")); + assertTrue(services.get(0).getServiceTags().contains("service.port=" + SERVICE_PORT)); + + List checks = health.getHealthyServiceInstances(SERVICE_NAME).getResponse(); + assertEquals(1, checks.size()); + assertEquals(SERVICE_PORT, checks.get(0).getService().getPort()); + assertEquals("localhost", checks.get(0).getService().getAddress()); + + // let stop the route + context().stopRoute("exposed"); + + // the service should be removed once the route is stopped + assertTrue(catalog.getService(SERVICE_NAME).getResponse().isEmpty()); + } +} diff --git a/components/camel-consul/src/test/java/org/apache/camel/component/consul/cloud/ConsulServiceRegistryTest.java b/components/camel-consul/src/test/java/org/apache/camel/component/consul/cloud/ConsulServiceRegistryTest.java new file mode 100644 index 0000000000000..5c5ace32ed66c --- /dev/null +++ b/components/camel-consul/src/test/java/org/apache/camel/component/consul/cloud/ConsulServiceRegistryTest.java @@ -0,0 +1,75 @@ +/** + * 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.camel.component.consul.cloud; + +import java.util.List; + +import com.orbitz.consul.CatalogClient; +import com.orbitz.consul.HealthClient; +import com.orbitz.consul.model.catalog.CatalogService; +import com.orbitz.consul.model.health.ServiceHealth; +import org.apache.camel.component.consul.support.ConsulTestSupport; +import org.apache.camel.impl.cloud.DefaultServiceDefinition; +import org.junit.Test; + +public class ConsulServiceRegistryTest extends ConsulTestSupport { + @Override + public boolean isUseRouteBuilder() { + return false; + } + + @Test + public void testSimpleServiceRegistration() throws Exception { + ConsulServiceRegistry registry = new ConsulServiceRegistry(); + registry.setCamelContext(context()); + registry.setUrl(consulUrl()); + registry.setServiceHost("service-host"); + registry.setOverrideServiceHost(true); + registry.start(); + + registry.register( + DefaultServiceDefinition.builder() + .withId("my-id") + .withName("service-name") + .withHost("my-host") + .withPort(9091) + .build() + ); + + final CatalogClient catalog = getConsul().catalogClient(); + final HealthClient health = getConsul().healthClient(); + + // check that service has been registered + List services = catalog.getService("service-name").getResponse(); + assertEquals(1, services.size()); + assertEquals(9091, services.get(0).getServicePort()); + assertEquals("service-host", services.get(0).getServiceAddress()); + assertEquals("my-id", services.get(0).getServiceId()); + + List checks = health.getHealthyServiceInstances("service-name").getResponse(); + assertEquals(1, checks.size()); + assertEquals(9091, checks.get(0).getService().getPort()); + assertEquals("service-host", checks.get(0).getService().getAddress()); + assertEquals("my-id", checks.get(0).getService().getId()); + + registry.stop(); + + // check that service has been de registered on service registry shutdown + assertEquals(0, catalog.getService("service-name").getResponse().size()); + assertEquals(0, health.getServiceChecks("service-name").getResponse().size()); + } +} diff --git a/components/camel-consul/src/test/java/org/apache/camel/component/consul/cloud/SpringConsulServiceCallRouteTest.java b/components/camel-consul/src/test/java/org/apache/camel/component/consul/cloud/SpringConsulServiceCallRouteTest.java index 335c61e1949f8..6d8d6d6e4036a 100644 --- a/components/camel-consul/src/test/java/org/apache/camel/component/consul/cloud/SpringConsulServiceCallRouteTest.java +++ b/components/camel-consul/src/test/java/org/apache/camel/component/consul/cloud/SpringConsulServiceCallRouteTest.java @@ -20,6 +20,7 @@ import java.util.ArrayList; import java.util.Arrays; import java.util.List; +import java.util.Properties; import com.orbitz.consul.AgentClient; import com.orbitz.consul.Consul; @@ -28,24 +29,42 @@ import org.apache.camel.Navigate; import org.apache.camel.Processor; import org.apache.camel.Route; +import org.apache.camel.component.consul.support.ConsulContainerSupport; import org.apache.camel.impl.cloud.DefaultServiceCallProcessor; import org.apache.camel.processor.ChoiceProcessor; import org.apache.camel.processor.FilterProcessor; import org.apache.camel.test.spring.CamelSpringTestSupport; import org.junit.Assert; +import org.junit.Rule; import org.junit.Test; +import org.testcontainers.containers.GenericContainer; public abstract class SpringConsulServiceCallRouteTest extends CamelSpringTestSupport { private AgentClient client; private List registrations; + @Rule + public GenericContainer container = ConsulContainerSupport.consulContainer(); + // ************************************************************************* // Setup / tear down // ************************************************************************* + @Override + protected Properties useOverridePropertiesWithPropertiesComponent() { + Properties properties = new Properties(); + properties.put("consul.url", ConsulContainerSupport.consulUrl(container)); + + return properties; + } + @Override public void doPreSetup() throws Exception { - this.client = Consul.builder().build().agentClient(); + this.client = Consul.builder() + .withUrl(ConsulContainerSupport.consulUrl(container)) + .build() + .agentClient(); + this.registrations = Arrays.asList( ImmutableRegistration.builder() .id("service-1-1") diff --git a/components/camel-consul/src/test/java/org/apache/camel/component/consul/cluster/ConsulClusteredRoutePolicyFactoryIT.java b/components/camel-consul/src/test/java/org/apache/camel/component/consul/cluster/ConsulClusteredRoutePolicyFactoryTest.java similarity index 90% rename from components/camel-consul/src/test/java/org/apache/camel/component/consul/cluster/ConsulClusteredRoutePolicyFactoryIT.java rename to components/camel-consul/src/test/java/org/apache/camel/component/consul/cluster/ConsulClusteredRoutePolicyFactoryTest.java index 668cb97516489..c87efb1fa3485 100644 --- a/components/camel-consul/src/test/java/org/apache/camel/component/consul/cluster/ConsulClusteredRoutePolicyFactoryIT.java +++ b/components/camel-consul/src/test/java/org/apache/camel/component/consul/cluster/ConsulClusteredRoutePolicyFactoryTest.java @@ -26,23 +26,26 @@ import java.util.stream.Collectors; import java.util.stream.IntStream; -import com.orbitz.consul.Consul; import org.apache.camel.builder.RouteBuilder; +import org.apache.camel.component.consul.support.ConsulContainerSupport; import org.apache.camel.impl.DefaultCamelContext; import org.apache.camel.impl.cluster.ClusteredRoutePolicyFactory; import org.junit.Assert; +import org.junit.ClassRule; import org.junit.Test; import org.slf4j.Logger; import org.slf4j.LoggerFactory; +import org.testcontainers.containers.GenericContainer; -public class ConsulClusteredRoutePolicyFactoryIT { - private static final Logger LOGGER = LoggerFactory.getLogger(ConsulClusteredRoutePolicyFactoryIT.class); +public class ConsulClusteredRoutePolicyFactoryTest { + private static final Logger LOGGER = LoggerFactory.getLogger(ConsulClusteredRoutePolicyFactoryTest.class); private static final List CLIENTS = IntStream.range(0, 3).mapToObj(Integer::toString).collect(Collectors.toList()); private static final List RESULTS = new ArrayList<>(); private static final ScheduledExecutorService SCHEDULER = Executors.newScheduledThreadPool(CLIENTS.size() * 2); private static final CountDownLatch LATCH = new CountDownLatch(CLIENTS.size()); - private static final String CONSUL_HOST = System.getProperty("camel.consul.host", Consul.DEFAULT_HTTP_HOST); - private static final int CONSUL_PORT = Integer.getInteger("camel.consul.port", Consul.DEFAULT_HTTP_PORT); + + @ClassRule + public static GenericContainer container = ConsulContainerSupport.consulContainer(); // ************************************ // Test @@ -72,7 +75,7 @@ private static void run(String id) { ConsulClusterService service = new ConsulClusterService(); service.setId("node-" + id); - service.setUrl(String.format("http://%s:%d", CONSUL_HOST, CONSUL_PORT)); + service.setUrl(ConsulContainerSupport.consulUrl(container)); LOGGER.info("Consul URL {}", service.getUrl()); diff --git a/components/camel-consul/src/test/java/org/apache/camel/component/consul/cluster/ConsulClusteredRoutePolicyIT.java b/components/camel-consul/src/test/java/org/apache/camel/component/consul/cluster/ConsulClusteredRoutePolicyTest.java similarity index 90% rename from components/camel-consul/src/test/java/org/apache/camel/component/consul/cluster/ConsulClusteredRoutePolicyIT.java rename to components/camel-consul/src/test/java/org/apache/camel/component/consul/cluster/ConsulClusteredRoutePolicyTest.java index f625a06736f07..45aea5f6de5a8 100644 --- a/components/camel-consul/src/test/java/org/apache/camel/component/consul/cluster/ConsulClusteredRoutePolicyIT.java +++ b/components/camel-consul/src/test/java/org/apache/camel/component/consul/cluster/ConsulClusteredRoutePolicyTest.java @@ -26,23 +26,26 @@ import java.util.stream.Collectors; import java.util.stream.IntStream; -import com.orbitz.consul.Consul; import org.apache.camel.builder.RouteBuilder; +import org.apache.camel.component.consul.support.ConsulContainerSupport; import org.apache.camel.impl.DefaultCamelContext; import org.apache.camel.impl.cluster.ClusteredRoutePolicy; import org.junit.Assert; +import org.junit.ClassRule; import org.junit.Test; import org.slf4j.Logger; import org.slf4j.LoggerFactory; +import org.testcontainers.containers.GenericContainer; -public class ConsulClusteredRoutePolicyIT { - private static final Logger LOGGER = LoggerFactory.getLogger(ConsulClusteredRoutePolicyIT.class); +public class ConsulClusteredRoutePolicyTest { + private static final Logger LOGGER = LoggerFactory.getLogger(ConsulClusteredRoutePolicyTest.class); private static final List CLIENTS = IntStream.range(0, 3).mapToObj(Integer::toString).collect(Collectors.toList()); private static final List RESULTS = new ArrayList<>(); private static final ScheduledExecutorService SCHEDULER = Executors.newScheduledThreadPool(CLIENTS.size() * 2); private static final CountDownLatch LATCH = new CountDownLatch(CLIENTS.size()); - private static final String CONSUL_HOST = System.getProperty("camel.consul.host", Consul.DEFAULT_HTTP_HOST); - private static final int CONSUL_PORT = Integer.getInteger("camel.consul.port", Consul.DEFAULT_HTTP_PORT); + + @ClassRule + public static GenericContainer container = ConsulContainerSupport.consulContainer(); // ************************************ // Test @@ -72,7 +75,7 @@ private static void run(String id) { ConsulClusterService service = new ConsulClusterService(); service.setId("node-" + id); - service.setUrl(String.format("http://%s:%d", CONSUL_HOST, CONSUL_PORT)); + service.setUrl(ConsulContainerSupport.consulUrl(container)); LOGGER.info("Consul URL {}", service.getUrl()); diff --git a/components/camel-consul/src/test/java/org/apache/camel/component/consul/cluster/ConsulMasterIT.java b/components/camel-consul/src/test/java/org/apache/camel/component/consul/cluster/ConsulMasterTest.java similarity index 90% rename from components/camel-consul/src/test/java/org/apache/camel/component/consul/cluster/ConsulMasterIT.java rename to components/camel-consul/src/test/java/org/apache/camel/component/consul/cluster/ConsulMasterTest.java index 3b935771197bc..b223afb73264a 100644 --- a/components/camel-consul/src/test/java/org/apache/camel/component/consul/cluster/ConsulMasterIT.java +++ b/components/camel-consul/src/test/java/org/apache/camel/component/consul/cluster/ConsulMasterTest.java @@ -26,22 +26,25 @@ import java.util.stream.Collectors; import java.util.stream.IntStream; -import com.orbitz.consul.Consul; import org.apache.camel.builder.RouteBuilder; +import org.apache.camel.component.consul.support.ConsulContainerSupport; import org.apache.camel.impl.DefaultCamelContext; import org.junit.Assert; +import org.junit.ClassRule; import org.junit.Test; import org.slf4j.Logger; import org.slf4j.LoggerFactory; +import org.testcontainers.containers.GenericContainer; -public class ConsulMasterIT { - private static final Logger LOGGER = LoggerFactory.getLogger(ConsulMasterIT.class); +public class ConsulMasterTest { + private static final Logger LOGGER = LoggerFactory.getLogger(ConsulMasterTest.class); private static final List CLIENTS = IntStream.range(0, 3).mapToObj(Integer::toString).collect(Collectors.toList()); private static final List RESULTS = new ArrayList<>(); private static final ScheduledExecutorService SCHEDULER = Executors.newScheduledThreadPool(CLIENTS.size() * 2); private static final CountDownLatch LATCH = new CountDownLatch(CLIENTS.size()); - private static final String CONSUL_HOST = System.getProperty("camel.consul.host", Consul.DEFAULT_HTTP_HOST); - private static final int CONSUL_PORT = Integer.getInteger("camel.consul.port", Consul.DEFAULT_HTTP_PORT); + + @ClassRule + public static GenericContainer container = ConsulContainerSupport.consulContainer(); // ************************************ // Test @@ -71,7 +74,7 @@ private static void run(String id) { ConsulClusterService service = new ConsulClusterService(); service.setId("node-" + id); - service.setUrl(String.format("http://%s:%d", CONSUL_HOST, CONSUL_PORT)); + service.setUrl(ConsulContainerSupport.consulUrl(container)); LOGGER.info("Consul URL {}", service.getUrl()); diff --git a/components/camel-consul/src/test/java/org/apache/camel/component/consul/support/ConsulContainerLogger.java b/components/camel-consul/src/test/java/org/apache/camel/component/consul/support/ConsulContainerLogger.java new file mode 100644 index 0000000000000..3f80181aad5c0 --- /dev/null +++ b/components/camel-consul/src/test/java/org/apache/camel/component/consul/support/ConsulContainerLogger.java @@ -0,0 +1,31 @@ +/** + * 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.camel.component.consul.support; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.testcontainers.containers.output.Slf4jLogConsumer; + +public final class ConsulContainerLogger extends Slf4jLogConsumer { + private static final Logger LOGGER = LoggerFactory.getLogger(ConsulContainerLogger.class); + + public ConsulContainerLogger() { + super(LOGGER); + + withPrefix("consul"); + } +} diff --git a/components/camel-consul/src/test/java/org/apache/camel/component/consul/support/ConsulContainerSupport.java b/components/camel-consul/src/test/java/org/apache/camel/component/consul/support/ConsulContainerSupport.java new file mode 100644 index 0000000000000..0602f444ebcf3 --- /dev/null +++ b/components/camel-consul/src/test/java/org/apache/camel/component/consul/support/ConsulContainerSupport.java @@ -0,0 +1,46 @@ +/** + * 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.camel.component.consul.support; + +import com.orbitz.consul.Consul; +import org.testcontainers.containers.GenericContainer; + +public final class ConsulContainerSupport { + private ConsulContainerSupport() { + } + + public static GenericContainer consulContainer() { + return new GenericContainer("consul:1.0.0") + .withExposedPorts(Consul.DEFAULT_HTTP_PORT) + .waitingFor(new ConsulContainerWaitStrategy()) + .withLogConsumer(new ConsulContainerLogger()) + .withCommand( + "agent", + "-dev", + "-server", + "-bootstrap", + "-client", + "0.0.0.0", + "-log-level", + "trace" + ); + } + + public static String consulUrl(GenericContainer container) { + return String.format("http://%s:%d", container.getContainerIpAddress(), container.getMappedPort(Consul.DEFAULT_HTTP_PORT)); + } +} diff --git a/components/camel-consul/src/test/java/org/apache/camel/component/consul/support/ConsulContainerWaitStrategy.java b/components/camel-consul/src/test/java/org/apache/camel/component/consul/support/ConsulContainerWaitStrategy.java new file mode 100644 index 0000000000000..c17cb7c426318 --- /dev/null +++ b/components/camel-consul/src/test/java/org/apache/camel/component/consul/support/ConsulContainerWaitStrategy.java @@ -0,0 +1,48 @@ +/** + * 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.camel.component.consul.support; + +import java.util.concurrent.TimeUnit; +import java.util.concurrent.TimeoutException; + +import com.github.dockerjava.api.DockerClient; +import org.testcontainers.DockerClientFactory; +import org.testcontainers.containers.ContainerLaunchException; +import org.testcontainers.containers.output.WaitingConsumer; +import org.testcontainers.containers.wait.strategy.AbstractWaitStrategy; +import org.testcontainers.utility.LogUtils; + +public final class ConsulContainerWaitStrategy extends AbstractWaitStrategy { + @Override + protected void waitUntilReady() { + final DockerClient client = DockerClientFactory.instance().client(); + final WaitingConsumer waitingConsumer = new WaitingConsumer(); + + LogUtils.followOutput(client, waitStrategyTarget.getContainerId(), waitingConsumer); + + try { + waitingConsumer.waitUntil( + f -> f.getUtf8String().contains("Synced node info"), + startupTimeout.getSeconds(), + TimeUnit.SECONDS, + 1 + ); + } catch (TimeoutException e) { + throw new ContainerLaunchException("Timed out"); + } + } +} \ No newline at end of file diff --git a/components/camel-consul/src/test/java/org/apache/camel/component/consul/ConsulTestSupport.java b/components/camel-consul/src/test/java/org/apache/camel/component/consul/support/ConsulTestSupport.java similarity index 80% rename from components/camel-consul/src/test/java/org/apache/camel/component/consul/ConsulTestSupport.java rename to components/camel-consul/src/test/java/org/apache/camel/component/consul/support/ConsulTestSupport.java index d917933f59a9d..890b98f1ff2d5 100644 --- a/components/camel-consul/src/test/java/org/apache/camel/component/consul/ConsulTestSupport.java +++ b/components/camel-consul/src/test/java/org/apache/camel/component/consul/support/ConsulTestSupport.java @@ -14,7 +14,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package org.apache.camel.component.consul; +package org.apache.camel.component.consul.support; import java.util.Arrays; import java.util.List; @@ -22,26 +22,29 @@ import com.orbitz.consul.Consul; import com.orbitz.consul.KeyValueClient; +import org.apache.camel.component.consul.ConsulComponent; +import org.apache.camel.component.consul.support.ConsulContainerSupport; import org.apache.camel.impl.JndiRegistry; import org.apache.camel.test.junit4.CamelTestSupport; import org.junit.Rule; import org.junit.rules.TestName; +import org.testcontainers.containers.GenericContainer; public class ConsulTestSupport extends CamelTestSupport { - public static final String CONSUL_HOST = System.getProperty("camel.consul.host", Consul.DEFAULT_HTTP_HOST); - public static final int CONSUL_PORT = Integer.getInteger("camel.consul.port", Consul.DEFAULT_HTTP_PORT); - public static final String CONSUL_URL = String.format("http://%s:%d", CONSUL_HOST, CONSUL_PORT); public static final String KV_PREFIX = "/camel"; @Rule public final TestName testName = new TestName(); + @Rule + public GenericContainer container = ConsulContainerSupport.consulContainer(); + @Override protected JndiRegistry createRegistry() throws Exception { JndiRegistry registry = super.createRegistry(); ConsulComponent component = new ConsulComponent(); - component.setUrl(CONSUL_URL); + component.setUrl(consulUrl()); registry.bind("consul", component); @@ -49,7 +52,7 @@ protected JndiRegistry createRegistry() throws Exception { } protected Consul getConsul() { - return Consul.builder().withUrl(CONSUL_URL).build(); + return Consul.builder().withUrl(consulUrl()).build(); } protected KeyValueClient getKeyValueClient() { @@ -74,4 +77,8 @@ protected List generateRandomListOfStrings(int size) { protected String generateKey() { return KV_PREFIX + "/" + testName.getMethodName() + "/" + generateRandomString(); } + + protected String consulUrl() { + return ConsulContainerSupport.consulUrl(container); + } } diff --git a/components/camel-consul/src/test/resources/log4j2.properties b/components/camel-consul/src/test/resources/log4j2.properties index f08fa7591c77c..d0c9eabb4ee4d 100644 --- a/components/camel-consul/src/test/resources/log4j2.properties +++ b/components/camel-consul/src/test/resources/log4j2.properties @@ -25,17 +25,14 @@ appender.out.name = out appender.out.layout.type = PatternLayout appender.out.layout.pattern = [%30.30t] %-30.30c{1} %-5p %m%n -logger.atomix.name = io.atomix -logger.atomix.level = INFO -logger.atomix-copycat.name = io.atomix.copycat -logger.atomix-copycat.level = WARN -logger.atomix-catalyst.name = io.atomix.catalyst -logger.atomix-catalyst.level = WARN - logger.camel.name = org.apache.camel logger.camel.level = INFO + logger.camel-consul.name = org.apache.camel.component.consul logger.camel-consul.level = DEBUG +logger.camel-consul-support.name = org.apache.camel.component.consul.support +logger.camel-consul-support.level = INFO + logger.camel-cluster.name = org.apache.camel.cluster logger.camel-cluster.level = DEBUG logger.camel-impl-cluster.name = org.apache.camel.impl.cluster @@ -43,7 +40,10 @@ logger.camel-impl-cluster.level = DEBUG logger.camel-master.name = org.apache.camel.component.master logger.camel-master.level = DEBUG + +logger.testcontainers.name = org.testcontainers +logger.testcontainers.level = INFO + rootLogger.level = INFO #rootLogger.appenderRef.stdout.ref = out -rootLogger.appenderRef.out.ref = file - +rootLogger.appenderRef.out.ref = file \ No newline at end of file diff --git a/components/camel-consul/src/test/resources/org/apache/camel/component/consul/cloud/SpringConsulDefaultServiceCallRouteTest.xml b/components/camel-consul/src/test/resources/org/apache/camel/component/consul/cloud/SpringConsulDefaultServiceCallRouteTest.xml index 576203efdbf99..607d6bdd0fd9a 100644 --- a/components/camel-consul/src/test/resources/org/apache/camel/component/consul/cloud/SpringConsulDefaultServiceCallRouteTest.xml +++ b/components/camel-consul/src/test/resources/org/apache/camel/component/consul/cloud/SpringConsulDefaultServiceCallRouteTest.xml @@ -33,7 +33,7 @@ - + diff --git a/components/camel-consul/src/test/resources/org/apache/camel/component/consul/cloud/SpringConsulExpressionServiceCallRouteTest.xml b/components/camel-consul/src/test/resources/org/apache/camel/component/consul/cloud/SpringConsulExpressionServiceCallRouteTest.xml index bea457f5a57a1..b55331404f811 100644 --- a/components/camel-consul/src/test/resources/org/apache/camel/component/consul/cloud/SpringConsulExpressionServiceCallRouteTest.xml +++ b/components/camel-consul/src/test/resources/org/apache/camel/component/consul/cloud/SpringConsulExpressionServiceCallRouteTest.xml @@ -33,7 +33,7 @@ - + diff --git a/components/camel-consul/src/test/resources/org/apache/camel/component/consul/cloud/SpringConsulRibbonServiceCallRouteTest.xml b/components/camel-consul/src/test/resources/org/apache/camel/component/consul/cloud/SpringConsulRibbonServiceCallRouteTest.xml index a72b49143ff0f..78bb6295105b3 100644 --- a/components/camel-consul/src/test/resources/org/apache/camel/component/consul/cloud/SpringConsulRibbonServiceCallRouteTest.xml +++ b/components/camel-consul/src/test/resources/org/apache/camel/component/consul/cloud/SpringConsulRibbonServiceCallRouteTest.xml @@ -33,7 +33,7 @@ - + diff --git a/components/camel-core-xml/src/main/java/org/apache/camel/core/xml/AbstractCamelContextFactoryBean.java b/components/camel-core-xml/src/main/java/org/apache/camel/core/xml/AbstractCamelContextFactoryBean.java index 521188ffb83b6..de873c89db7a6 100644 --- a/components/camel-core-xml/src/main/java/org/apache/camel/core/xml/AbstractCamelContextFactoryBean.java +++ b/components/camel-core-xml/src/main/java/org/apache/camel/core/xml/AbstractCamelContextFactoryBean.java @@ -40,6 +40,7 @@ import org.apache.camel.TypeConverters; import org.apache.camel.builder.ErrorHandlerBuilderRef; import org.apache.camel.builder.RouteBuilder; +import org.apache.camel.cloud.ServiceRegistry; import org.apache.camel.cluster.CamelClusterService; import org.apache.camel.component.properties.PropertiesComponent; import org.apache.camel.component.properties.PropertiesFunction; @@ -344,6 +345,15 @@ public void afterPropertiesSet() throws Exception { getContext().addService(service); } } + // service registry + Map serviceRegistries = getContext().getRegistry().findByTypeWithName(ServiceRegistry.class); + if (serviceRegistries != null && !serviceRegistries.isEmpty()) { + for (Map.Entry entry : serviceRegistries.entrySet()) { + ServiceRegistry service = entry.getValue(); + LOG.info("Using ServiceRegistry with id: {} and implementation: {}", service.getId(), service); + getContext().addService(service); + } + } // add route policy factories Map routePolicyFactories = getContext().getRegistry().findByTypeWithName(RoutePolicyFactory.class); if (routePolicyFactories != null && !routePolicyFactories.isEmpty()) { @@ -691,7 +701,7 @@ protected void initPropertyPlaceholder() throws Exception { pc.setPropertiesResolver(resolver); } - // if using a custom parser + // if using a custom parse if (ObjectHelper.isNotEmpty(def.getPropertiesParserRef())) { PropertiesParser parser = CamelContextHelper.mandatoryLookup(getContext(), def.getPropertiesParserRef(), PropertiesParser.class); diff --git a/components/camel-etcd/src/main/java/org/apache/camel/component/etcd/cloud/EtcdServiceDefinition.java b/components/camel-etcd/src/main/java/org/apache/camel/component/etcd/cloud/EtcdServiceDefinition.java index 8a9d2768f7567..b5ac22e657d56 100644 --- a/components/camel-etcd/src/main/java/org/apache/camel/component/etcd/cloud/EtcdServiceDefinition.java +++ b/components/camel-etcd/src/main/java/org/apache/camel/component/etcd/cloud/EtcdServiceDefinition.java @@ -29,11 +29,12 @@ public class EtcdServiceDefinition extends DefaultServiceDefinition { @JsonCreator public EtcdServiceDefinition( + @JsonProperty("id") final String id, @JsonProperty("name") final String name, @JsonProperty("address") final String address, @JsonProperty("port") final Integer port, @JsonProperty("tags") final Map tags) { - super(name, address, port, tags); + super(id, name, address, port, tags); } public static Comparator comparator() { diff --git a/components/camel-http-common/src/main/java/org/apache/camel/http/common/HttpCommonEndpoint.java b/components/camel-http-common/src/main/java/org/apache/camel/http/common/HttpCommonEndpoint.java index 58deced55b4b6..759dc50e3b09f 100644 --- a/components/camel-http-common/src/main/java/org/apache/camel/http/common/HttpCommonEndpoint.java +++ b/components/camel-http-common/src/main/java/org/apache/camel/http/common/HttpCommonEndpoint.java @@ -19,15 +19,18 @@ import java.net.URI; import java.net.URISyntaxException; +import org.apache.camel.cloud.DiscoverableService; +import org.apache.camel.cloud.ServiceDefinition; import org.apache.camel.http.common.cookie.CookieHandler; import org.apache.camel.impl.DefaultEndpoint; +import org.apache.camel.impl.cloud.DefaultServiceDefinition; import org.apache.camel.spi.HeaderFilterStrategy; import org.apache.camel.spi.HeaderFilterStrategyAware; import org.apache.camel.spi.Metadata; import org.apache.camel.spi.UriParam; import org.apache.camel.spi.UriPath; -public abstract class HttpCommonEndpoint extends DefaultEndpoint implements HeaderFilterStrategyAware { +public abstract class HttpCommonEndpoint extends DefaultEndpoint implements HeaderFilterStrategyAware, DiscoverableService { // Note: all options must be documented with description in annotations so extended components can access the documentation @@ -193,6 +196,19 @@ public boolean isSingleton() { return true; } + // Service Registration + //------------------------------------------------------------------------- + + @Override + public ServiceDefinition getServiceDefinition() { + // Returns a partial + return DefaultServiceDefinition.builder() + .withPort(getPort()) + .addMeta(ServiceDefinition.SERVICE_META_PORT, Integer.toString(getPort())) + .addMeta(ServiceDefinition.SERVICE_META_PATH, getPath()) + .addMeta(ServiceDefinition.SERVICE_META_PROTOCOL, getProtocol()) + .build(); + } // Properties //------------------------------------------------------------------------- diff --git a/components/camel-master/src/main/java/org/apache/camel/component/master/MasterEndpoint.java b/components/camel-master/src/main/java/org/apache/camel/component/master/MasterEndpoint.java index 79e9a9bc7eaf7..b2f2d104a6b5d 100644 --- a/components/camel-master/src/main/java/org/apache/camel/component/master/MasterEndpoint.java +++ b/components/camel-master/src/main/java/org/apache/camel/component/master/MasterEndpoint.java @@ -45,8 +45,8 @@ lenientProperties = true, label = "clustering") public class MasterEndpoint extends DefaultEndpoint implements DelegateEndpoint { - private final Endpoint delegateEndpoint; + private final CamelClusterService clusterService; @UriPath(description = "The name of the cluster namespace to use") @Metadata(required = "true") @@ -56,8 +56,6 @@ public class MasterEndpoint extends DefaultEndpoint implements DelegateEndpoint @Metadata(required = "true") private final String delegateUri; - private final CamelClusterService clusterService; - public MasterEndpoint(String uri, MasterComponent component, CamelClusterService clusterService, String namespace, String delegateUri) { super(uri, component); @@ -69,7 +67,6 @@ public MasterEndpoint(String uri, MasterComponent component, CamelClusterService @Override public Producer createProducer() throws Exception { - getComponent(); throw new UnsupportedOperationException("Cannot produce from this endpoint"); } diff --git a/components/camel-ribbon/src/test/java/org/apache/camel/component/ribbon/cloud/RibbonServiceCallRegistryRouteTest.java b/components/camel-ribbon/src/test/java/org/apache/camel/component/ribbon/cloud/RibbonServiceCallRegistryRouteTest.java index 635c53067998b..6e2412d2a1fa2 100644 --- a/components/camel-ribbon/src/test/java/org/apache/camel/component/ribbon/cloud/RibbonServiceCallRegistryRouteTest.java +++ b/components/camel-ribbon/src/test/java/org/apache/camel/component/ribbon/cloud/RibbonServiceCallRegistryRouteTest.java @@ -31,8 +31,8 @@ protected RoutesBuilder createRouteBuilder() throws Exception { public void configure() throws Exception { // setup a static ribbon server list with these 2 servers to start with StaticServiceDiscovery servers = new StaticServiceDiscovery(); - servers.addServer("localhost", 9090); - servers.addServer("localhost", 9091); + servers.addServer("myService@localhost:9090"); + servers.addServer("myService@localhost:9091"); RibbonConfiguration configuration = new RibbonConfiguration(); RibbonServiceLoadBalancer loadBalancer = new RibbonServiceLoadBalancer(configuration); diff --git a/components/camel-ribbon/src/test/java/org/apache/camel/component/ribbon/cloud/RibbonServiceCallRouteMetadataTest.java b/components/camel-ribbon/src/test/java/org/apache/camel/component/ribbon/cloud/RibbonServiceCallRouteMetadataTest.java index a0ac463e3f7f9..b586f3a780ac6 100644 --- a/components/camel-ribbon/src/test/java/org/apache/camel/component/ribbon/cloud/RibbonServiceCallRouteMetadataTest.java +++ b/components/camel-ribbon/src/test/java/org/apache/camel/component/ribbon/cloud/RibbonServiceCallRouteMetadataTest.java @@ -17,11 +17,10 @@ package org.apache.camel.component.ribbon.cloud; -import java.util.Collections; - import org.apache.camel.RoutesBuilder; import org.apache.camel.builder.RouteBuilder; import org.apache.camel.component.ribbon.RibbonConfiguration; +import org.apache.camel.impl.cloud.DefaultServiceDefinition; import org.apache.camel.impl.cloud.StaticServiceDiscovery; import org.apache.camel.test.junit4.CamelTestSupport; import org.junit.Test; @@ -48,8 +47,8 @@ protected RoutesBuilder createRouteBuilder() throws Exception { public void configure() throws Exception { // setup a static ribbon server list with these 2 servers to start with StaticServiceDiscovery servers = new StaticServiceDiscovery(); - servers.addServer("myService", "localhost", 9090, Collections.singletonMap("contextPath", "app1")); - servers.addServer("myService", "localhost", 9090, Collections.singletonMap("contextPath", "app2")); + servers.addServer(DefaultServiceDefinition.builder().withName("myService").withHost("localhost").withPort(9090).addMeta("contextPath", "app1").build()); + servers.addServer(DefaultServiceDefinition.builder().withName("myService").withHost("localhost").withPort(9090).addMeta("contextPath", "app2").build()); RibbonConfiguration configuration = new RibbonConfiguration(); RibbonServiceLoadBalancer loadBalancer = new RibbonServiceLoadBalancer(configuration); diff --git a/components/camel-ribbon/src/test/java/org/apache/camel/component/ribbon/cloud/RibbonServiceCallRouteTest.java b/components/camel-ribbon/src/test/java/org/apache/camel/component/ribbon/cloud/RibbonServiceCallRouteTest.java index c7990de9e96d7..267b48c27ef07 100644 --- a/components/camel-ribbon/src/test/java/org/apache/camel/component/ribbon/cloud/RibbonServiceCallRouteTest.java +++ b/components/camel-ribbon/src/test/java/org/apache/camel/component/ribbon/cloud/RibbonServiceCallRouteTest.java @@ -46,8 +46,8 @@ protected RoutesBuilder createRouteBuilder() throws Exception { public void configure() throws Exception { // setup a static ribbon server list with these 2 servers to start with StaticServiceDiscovery servers = new StaticServiceDiscovery(); - servers.addServer("localhost", 9090); - servers.addServer("localhost", 9091); + servers.addServer("myService@localhost:9090"); + servers.addServer("myService@localhost:9091"); RibbonConfiguration configuration = new RibbonConfiguration(); RibbonServiceLoadBalancer loadBalancer = new RibbonServiceLoadBalancer(configuration); diff --git a/components/camel-ribbon/src/test/java/org/apache/camel/component/ribbon/cloud/RibbonServiceCallUpdateRouteTest.java b/components/camel-ribbon/src/test/java/org/apache/camel/component/ribbon/cloud/RibbonServiceCallUpdateRouteTest.java index 867931f437c8d..99b9ad40e29a7 100644 --- a/components/camel-ribbon/src/test/java/org/apache/camel/component/ribbon/cloud/RibbonServiceCallUpdateRouteTest.java +++ b/components/camel-ribbon/src/test/java/org/apache/camel/component/ribbon/cloud/RibbonServiceCallUpdateRouteTest.java @@ -22,6 +22,7 @@ import org.apache.camel.component.ribbon.RibbonConfiguration; import org.apache.camel.impl.cloud.StaticServiceDiscovery; import org.apache.camel.test.junit4.CamelTestSupport; +import org.apache.camel.util.ObjectHelper; import org.junit.Test; public class RibbonServiceCallUpdateRouteTest extends CamelTestSupport { @@ -30,8 +31,8 @@ public class RibbonServiceCallUpdateRouteTest extends CamelTestSupport { @Override public void setUp() throws Exception { // setup a static ribbon server list with these 2 servers to start with - servers.addServer("localhost", 9090); - servers.addServer("localhost", 9091); + servers.addServer("myService@localhost:9090"); + servers.addServer("myService@localhost:9091"); super.setUp(); } @@ -51,7 +52,7 @@ public void testServiceCall() throws Exception { // stop the first server and remove it from the known list of servers context.stopRoute("9090"); - servers.removeServer("localhost", 9090); + servers.removeServer(s -> ObjectHelper.equal("localhost", s.getHost()) && 9090 == s.getPort()); // call the other active server String out3 = template.requestBody("direct:start", null, String.class); diff --git a/components/camel-service/pom.xml b/components/camel-service/pom.xml new file mode 100644 index 0000000000000..a08fb097461ef --- /dev/null +++ b/components/camel-service/pom.xml @@ -0,0 +1,89 @@ + + + + + 4.0.0 + + + org.apache.camel + components + 2.22.0-SNAPSHOT + + + camel-service + jar + Camel :: Service + Camel service Support + + + org.apache.camel.component.service + org.apache.camel.spi.ComponentResolver;component=service + + + + + + org.apache.camel + camel-core + + + + + org.apache.camel + camel-test + test + + + + org.apache.logging.log4j + log4j-api + test + + + org.apache.logging.log4j + log4j-core + test + + + org.apache.logging.log4j + log4j-slf4j-impl + test + + + + + + + + maven-surefire-plugin + + false + true + 1 + true + 600 + + + + + + + diff --git a/components/camel-service/src/main/docs/service-component.adoc b/components/camel-service/src/main/docs/service-component.adoc new file mode 100644 index 0000000000000..b1dece8a97909 --- /dev/null +++ b/components/camel-service/src/main/docs/service-component.adoc @@ -0,0 +1,85 @@ +[[service-component]] +== Service Component +== Master Component +*Available as of Camel version 2.22* + + +*Available as of Camel version 2.22* + + +### Using the master endpoint + + +### URI format + +[source] +---- +service:endpoint[?options] +---- + + +### Options + +// component options: START +The Service component supports 3 options which are listed below. + + + +[width="100%",cols="2,5,^1,2",options="header"] +|=== +| Name | Description | Default | Type +| *service* (advanced) | Inject the service to use. | | ServiceRegistry +| *serviceSelector* (advanced) | Inject the service selector used to lookup the ServiceRegistry to use. | | Selector +| *resolveProperty Placeholders* (advanced) | Whether the component should resolve property placeholders on itself when starting. Only properties which are of String type can use property placeholders. | true | boolean +|=== +// component options: END + +// endpoint options: START +The Service endpoint is configured using URI syntax: + +---- +service:serviceName:delegateUri +---- + +with the following path and query parameters: + +==== Path Parameters (1 parameters): + + +[width="100%",cols="2,5,^1,2",options="header"] +|=== +| Name | Description | Default | Type +| *delegateUri* | *Required* The endpoint uri to expose as service | | String +|=== + + +==== Query Parameters (4 parameters): + + +[width="100%",cols="2,5,^1,2",options="header"] +|=== +| Name | Description | Default | Type +| *bridgeErrorHandler* (consumer) | Allows for bridging the consumer to the Camel routing Error Handler, which mean any exceptions occurred while the consumer is trying to pickup incoming messages, or the likes, will now be processed as a message and handled by the routing Error Handler. By default the consumer will use the org.apache.camel.spi.ExceptionHandler to deal with exceptions, that will be logged at WARN or ERROR level and ignored. | false | boolean +| *exceptionHandler* (consumer) | To let the consumer use a custom ExceptionHandler. Notice if the option bridgeErrorHandler is enabled then this options is not in use. By default the consumer will deal with exceptions, that will be logged at WARN or ERROR level and ignored. | | ExceptionHandler +| *exchangePattern* (consumer) | Sets the exchange pattern when the consumer creates an exchange. | | ExchangePattern +| *synchronous* (advanced) | Sets whether synchronous processing should be strictly used, or Camel is allowed to use asynchronous processing (if supported). | false | boolean +|=== +// endpoint options: END + + +### Implementations + +Camel provide the following ServiceRegistry implementations: + +- camel-consul + +Camel provide the following ServiceCall implementations: + +- camel-consul + +### See Also + +* Configuring Camel +* Component +* Endpoint +* Getting Started diff --git a/components/camel-service/src/main/java/org/apache/camel/component/service/ServiceComponent.java b/components/camel-service/src/main/java/org/apache/camel/component/service/ServiceComponent.java new file mode 100644 index 0000000000000..0f8d748d7c925 --- /dev/null +++ b/components/camel-service/src/main/java/org/apache/camel/component/service/ServiceComponent.java @@ -0,0 +1,133 @@ +/** + * 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.camel.component.service; + + +import java.util.Map; + +import org.apache.camel.CamelContext; +import org.apache.camel.Endpoint; +import org.apache.camel.cloud.ServiceRegistry; +import org.apache.camel.impl.DefaultComponent; +import org.apache.camel.impl.cloud.ServiceRegistryHelper; +import org.apache.camel.impl.cloud.ServiceRegistrySelectors; +import org.apache.camel.spi.Metadata; +import org.apache.camel.util.IntrospectionSupport; +import org.apache.camel.util.ObjectHelper; +import org.apache.camel.util.StringHelper; +import org.apache.camel.util.URISupport; + +public class ServiceComponent extends DefaultComponent { + @Metadata(label = "advanced") + private ServiceRegistry service; + @Metadata(label = "advanced") + private ServiceRegistry.Selector serviceSelector; + + public ServiceComponent() { + this(null); + } + + public ServiceComponent(CamelContext context) { + super(context); + + this.serviceSelector = ServiceRegistrySelectors.DEFAULT_SELECTOR; + } + + @Override + protected Endpoint createEndpoint(String uri, String remaining, Map parameters) throws Exception { + final String serviceName = StringHelper.before(remaining, ":"); + final String delegateUri = StringHelper.after(remaining, ":"); + + ObjectHelper.notNull(serviceName, "Service Name"); + ObjectHelper.notNull(delegateUri, "Delegate URI"); + + // add service name to the parameters + parameters.put("serviceName", serviceName); + + // Lookup the service registry, this may be a static selected service + // or dynamically selected one through a ServiceRegistry.Selector + final ServiceRegistry service = getServiceRegistry(); + + // Compute service definition from parameters, this is used as default + // definition + final ServiceParameters params = computeServiceParameters(parameters); + + return new ServiceEndpoint( + uri, + this, + service, + params, + URISupport.appendParametersToURI(delegateUri, parameters) + ); + } + + public ServiceRegistry getService() { + return service; + } + + /** + * Inject the service to use. + */ + public void setService(ServiceRegistry service) { + this.service = service; + } + + public ServiceRegistry.Selector getServiceSelector() { + return serviceSelector; + } + + /** + * + * Inject the service selector used to lookup the {@link ServiceRegistry} to use. + */ + public void setServiceSelector(ServiceRegistry.Selector serviceSelector) { + this.serviceSelector = serviceSelector; + } + + // ***************** + // Helpers + // ***************** + + private ServiceRegistry getServiceRegistry() { + if (service == null) { + return ServiceRegistryHelper.lookupService(getCamelContext(), serviceSelector).orElseThrow( + () -> new IllegalStateException("No cluster service found") + ); + } + + return service; + } + + @SuppressWarnings("unchecked") + private ServiceParameters computeServiceParameters(Map parameters) { + // Extract service definition related parameter from uri + final String serviceId = getAndRemoveParameter(parameters, "serviceId", String.class); + final String serviceName = getAndRemoveParameter(parameters, "serviceName", String.class); + final String serviceHost = getAndRemoveParameter(parameters, "serviceHost", String.class); + final String servicePort = getAndRemoveParameter(parameters, "servicePort", String.class); + final Map serviceMeta = IntrospectionSupport.extractProperties(parameters, "serviceMeta.", true); + + ServiceParameters params = new ServiceParameters(); + ObjectHelper.ifNotEmpty(serviceId, params::setId); + ObjectHelper.ifNotEmpty(serviceName, params::setName); + ObjectHelper.ifNotEmpty(serviceHost, params::setHost); + ObjectHelper.ifNotEmpty(servicePort, params::setPort); + ObjectHelper.ifNotEmpty(serviceMeta, meta -> params.setMeta(Map.class.cast(meta))); + + return params; + } +} diff --git a/components/camel-service/src/main/java/org/apache/camel/component/service/ServiceConsumer.java b/components/camel-service/src/main/java/org/apache/camel/component/service/ServiceConsumer.java new file mode 100644 index 0000000000000..4d35aef0df852 --- /dev/null +++ b/components/camel-service/src/main/java/org/apache/camel/component/service/ServiceConsumer.java @@ -0,0 +1,121 @@ +/** + * 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.camel.component.service; + + +import org.apache.camel.Consumer; +import org.apache.camel.Endpoint; +import org.apache.camel.Processor; +import org.apache.camel.StartupListener; +import org.apache.camel.SuspendableService; +import org.apache.camel.api.management.ManagedResource; +import org.apache.camel.cloud.ServiceDefinition; +import org.apache.camel.cloud.ServiceRegistry; +import org.apache.camel.impl.DefaultConsumer; +import org.apache.camel.util.ServiceHelper; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +@ManagedResource(description = "Managed Service Consumer") +public class ServiceConsumer extends DefaultConsumer { + private static final transient Logger LOGGER = LoggerFactory.getLogger(ServiceConsumer.class); + + private final ServiceRegistry serviceRegistry; + private final Endpoint delegatedEndpoint; + private final Processor processor; + private Consumer delegatedConsumer; + + public ServiceConsumer(ServiceEndpoint serviceEndpoint, Processor processor, ServiceRegistry serviceRegistry) { + super(serviceEndpoint, processor); + + this.serviceRegistry = serviceRegistry; + this.delegatedEndpoint = serviceEndpoint.getEndpoint(); + this.processor = processor; + } + + @Override + protected void doStart() throws Exception { + super.doStart(); + + final ServiceEndpoint endpoint = (ServiceEndpoint)getEndpoint(); + final ServiceDefinition definition = endpoint.getServiceDefinition(); + + LOGGER.debug("Using ServiceRegistry instance {} (id={}, type={}) to register: {}", + serviceRegistry, + serviceRegistry.getId(), + serviceRegistry.getClass().getName(), + definition + ); + + // register service + serviceRegistry.register(definition); + + // start delegate + delegatedConsumer = delegatedEndpoint.createConsumer(processor); + if (delegatedConsumer instanceof StartupListener) { + getEndpoint().getCamelContext().addStartupListener((StartupListener) delegatedConsumer); + } + + ServiceHelper.startService(delegatedEndpoint); + ServiceHelper.startService(delegatedConsumer); + } + + @Override + protected void doStop() throws Exception { + super.doStop(); + + final ServiceEndpoint endpoint = (ServiceEndpoint)getEndpoint(); + final ServiceDefinition definition = endpoint.getServiceDefinition(); + + // de-register service + serviceRegistry.deregister(definition); + + // stop delegate + ServiceHelper.stopAndShutdownServices(delegatedConsumer); + ServiceHelper.stopAndShutdownServices(delegatedEndpoint); + + delegatedConsumer = null; + } + + @Override + protected void doResume() throws Exception { + if (delegatedConsumer instanceof SuspendableService) { + final ServiceEndpoint endpoint = (ServiceEndpoint)getEndpoint(); + final ServiceDefinition definition = endpoint.getServiceDefinition(); + + // register service + serviceRegistry.register(definition); + + ((SuspendableService)delegatedConsumer).resume(); + } + super.doResume(); + } + + @Override + protected void doSuspend() throws Exception { + if (delegatedConsumer instanceof SuspendableService) { + final ServiceEndpoint endpoint = (ServiceEndpoint)getEndpoint(); + final ServiceDefinition definition = endpoint.getServiceDefinition(); + + // de-register service + serviceRegistry.deregister(definition); + + ((SuspendableService)delegatedConsumer).suspend(); + } + super.doSuspend(); + } +} diff --git a/components/camel-service/src/main/java/org/apache/camel/component/service/ServiceEndpoint.java b/components/camel-service/src/main/java/org/apache/camel/component/service/ServiceEndpoint.java new file mode 100644 index 0000000000000..b99c490191038 --- /dev/null +++ b/components/camel-service/src/main/java/org/apache/camel/component/service/ServiceEndpoint.java @@ -0,0 +1,119 @@ +/** + * 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.camel.component.service; + +import org.apache.camel.CamelContext; +import org.apache.camel.Consumer; +import org.apache.camel.DelegateEndpoint; +import org.apache.camel.Endpoint; +import org.apache.camel.Processor; +import org.apache.camel.Producer; +import org.apache.camel.api.management.ManagedAttribute; +import org.apache.camel.api.management.ManagedResource; +import org.apache.camel.cloud.DiscoverableService; +import org.apache.camel.cloud.ServiceDefinition; +import org.apache.camel.cloud.ServiceRegistry; +import org.apache.camel.cluster.CamelClusterView; +import org.apache.camel.impl.DefaultEndpoint; +import org.apache.camel.spi.Metadata; +import org.apache.camel.spi.UriEndpoint; +import org.apache.camel.spi.UriPath; + +/** + * Represents an endpoint which only becomes active when the {@link CamelClusterView} + * has the leadership. + */ +@ManagedResource(description = "Managed Service Endpoint") +@UriEndpoint( + firstVersion = "2.22.0", + scheme = "service", + syntax = "service:serviceName:delegateUri", + consumerClass = ServiceConsumer.class, + consumerOnly = true, + title = "Service", + lenientProperties = true, + label = "cloud") +public class ServiceEndpoint extends DefaultEndpoint implements DelegateEndpoint { + private final Endpoint delegateEndpoint; + private final ServiceRegistry serviceRegistry; + private final ServiceParameters serviceParameters; + private final ServiceDefinition serviceDefinition; + + @UriPath(description = "The endpoint uri to expose as service") + @Metadata(required = "true") + private final String delegateUri; + + public ServiceEndpoint(String uri, ServiceComponent component, ServiceRegistry serviceRegistry, ServiceParameters serviceParameters, String delegateUri) { + super(uri, component); + + this.serviceRegistry = serviceRegistry; + this.serviceParameters = serviceParameters; + this.delegateUri = delegateUri; + this.delegateEndpoint = getCamelContext().getEndpoint(delegateUri); + + // The service definition built from uri parameter may override parameter + // of service definition generated by an endpoint that implement DiscoverableService + this.serviceDefinition = computeServiceDefinition(component.getCamelContext(), delegateEndpoint); + + // Perform some basic validation, service id an name and port are mandatory, + if (serviceDefinition.getId() == null) { + throw new IllegalArgumentException("Service ID si required"); + } + if (serviceDefinition.getName() == null) { + throw new IllegalArgumentException("Service Name si required"); + } + if (serviceDefinition.getPort() <= 0) { + throw new IllegalArgumentException("Service Port should be greater than 0"); + } + } + + @ManagedAttribute(description = "The consumer endpoint to expose as a service", mask = true) + @Override + public Endpoint getEndpoint() { + return this.delegateEndpoint; + } + + @ManagedAttribute(description = "The service definition", mask = true) + public ServiceDefinition getServiceDefinition() { + return this.serviceDefinition; + } + + @Override + public Producer createProducer() throws Exception { + throw new UnsupportedOperationException("Not yet implemented"); + } + + @Override + public Consumer createConsumer(Processor processor) throws Exception { + return new ServiceConsumer(this, processor, serviceRegistry); + } + + @Override + public boolean isSingleton() { + return true; + } + + private ServiceDefinition computeServiceDefinition(CamelContext context, Endpoint delegateEndpoint) { + ServiceDefinition definition = null; + + if (delegateEndpoint instanceof DiscoverableService) { + definition = ((DiscoverableService)delegateEndpoint).getServiceDefinition(); + } + + return serviceParameters.enrich(context, definition); + } +} diff --git a/components/camel-service/src/main/java/org/apache/camel/component/service/ServiceParameters.java b/components/camel-service/src/main/java/org/apache/camel/component/service/ServiceParameters.java new file mode 100644 index 0000000000000..2bbaca4745797 --- /dev/null +++ b/components/camel-service/src/main/java/org/apache/camel/component/service/ServiceParameters.java @@ -0,0 +1,114 @@ +/** + * 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.camel.component.service; + +import java.util.HashMap; +import java.util.Map; + +import org.apache.camel.CamelContext; +import org.apache.camel.cloud.ServiceDefinition; +import org.apache.camel.impl.cloud.DefaultServiceDefinition; +import org.apache.camel.util.ObjectHelper; + +public class ServiceParameters { + private String id; + private String name; + private String host; + private int port; + private Map meta; + + public String getId() { + return id; + } + + public void setId(String id) { + this.id = id; + } + + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } + + public String getHost() { + return host; + } + + public void setHost(String host) { + this.host = host; + } + + public int getPort() { + return port; + } + + public void setPort(int port) { + this.port = port; + } + + public void setPort(String port) { + setPort(Integer.parseInt(port)); + } + + public Map getMeta() { + return meta; + } + + public void setMeta(Map meta) { + this.meta = meta; + } + + public void addMeta(String key, String value) { + if (this.meta == null) { + this.meta = new HashMap<>(); + } + + this.meta.put(key, value); + } + + public void addAllMeta(Map meta) { + if (this.meta == null) { + this.meta = new HashMap<>(); + } + + this.meta.putAll(meta); + } + + public ServiceDefinition enrich(CamelContext context, ServiceDefinition definition) { + final DefaultServiceDefinition.Builder builder = DefaultServiceDefinition.builder(); + + ObjectHelper.ifNotEmpty(definition, builder::from); + ObjectHelper.ifNotEmpty(id, builder::withId); + ObjectHelper.ifNotEmpty(name, builder::withName); + ObjectHelper.ifNotEmpty(meta, builder::addAllMeta); + ObjectHelper.ifNotEmpty(host, builder::withHost); + + if (port > 0) { + builder.withPort(port); + } + + // if the service does not have an id, we can auto-generate it + if (builder.id() == null) { + builder.withId(context.getUuidGenerator().generateUuid()); + } + + return builder.build(); + } +} diff --git a/components/camel-service/src/main/resources/META-INF/LICENSE.txt b/components/camel-service/src/main/resources/META-INF/LICENSE.txt new file mode 100644 index 0000000000000..6b0b1270ff0ca --- /dev/null +++ b/components/camel-service/src/main/resources/META-INF/LICENSE.txt @@ -0,0 +1,203 @@ + + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright [yyyy] [name of copyright owner] + + 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. + diff --git a/components/camel-service/src/main/resources/META-INF/NOTICE.txt b/components/camel-service/src/main/resources/META-INF/NOTICE.txt new file mode 100644 index 0000000000000..2e215bf2e6b1f --- /dev/null +++ b/components/camel-service/src/main/resources/META-INF/NOTICE.txt @@ -0,0 +1,11 @@ + ========================================================================= + == NOTICE file corresponding to the section 4 d of == + == the Apache License, Version 2.0, == + == in this case for the Apache Camel distribution. == + ========================================================================= + + This product includes software developed by + The Apache Software Foundation (http://www.apache.org/). + + Please read the different LICENSE files present in the licenses directory of + this distribution. diff --git a/components/camel-service/src/main/resources/META-INF/services/org/apache/camel/component/service b/components/camel-service/src/main/resources/META-INF/services/org/apache/camel/component/service new file mode 100644 index 0000000000000..9432e3c6ffa6b --- /dev/null +++ b/components/camel-service/src/main/resources/META-INF/services/org/apache/camel/component/service @@ -0,0 +1,17 @@ +# +# 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. +# +class=org.apache.camel.component.service.ServiceComponent diff --git a/components/camel-spring-boot/src/main/java/org/apache/camel/spring/boot/CamelAutoConfiguration.java b/components/camel-spring-boot/src/main/java/org/apache/camel/spring/boot/CamelAutoConfiguration.java index 78a8dbb24a70f..9e967dd65ed70 100644 --- a/components/camel-spring-boot/src/main/java/org/apache/camel/spring/boot/CamelAutoConfiguration.java +++ b/components/camel-spring-boot/src/main/java/org/apache/camel/spring/boot/CamelAutoConfiguration.java @@ -28,6 +28,7 @@ import org.apache.camel.FluentProducerTemplate; import org.apache.camel.ProducerTemplate; import org.apache.camel.TypeConverters; +import org.apache.camel.cloud.ServiceRegistry; import org.apache.camel.cluster.CamelClusterService; import org.apache.camel.component.properties.PropertiesComponent; import org.apache.camel.component.properties.PropertiesParser; @@ -474,6 +475,15 @@ static void afterPropertiesSet(ApplicationContext applicationContext, CamelConte camelContext.addService(service); } } + // service registry + Map serviceRegistries = applicationContext.getBeansOfType(ServiceRegistry.class); + if (serviceRegistries != null && !serviceRegistries.isEmpty()) { + for (Map.Entry entry : serviceRegistries.entrySet()) { + ServiceRegistry service = entry.getValue(); + LOG.info("Using ServiceRegistry with id: {} and implementation: {}", service.getId(), service); + camelContext.addService(service); + } + } // add route policy factories Map routePolicyFactories = applicationContext.getBeansOfType(RoutePolicyFactory.class); if (routePolicyFactories != null && !routePolicyFactories.isEmpty()) { diff --git a/components/camel-spring-boot/src/main/java/org/apache/camel/spring/boot/cloud/CamelCloudServiceFilterAutoConfiguration.java b/components/camel-spring-boot/src/main/java/org/apache/camel/spring/boot/cloud/CamelCloudServiceFilterAutoConfiguration.java index a0a33ed803bde..cf78a39ee4c7a 100644 --- a/components/camel-spring-boot/src/main/java/org/apache/camel/spring/boot/cloud/CamelCloudServiceFilterAutoConfiguration.java +++ b/components/camel-spring-boot/src/main/java/org/apache/camel/spring/boot/cloud/CamelCloudServiceFilterAutoConfiguration.java @@ -24,6 +24,7 @@ import org.apache.camel.CamelContext; import org.apache.camel.impl.cloud.BlacklistServiceFilter; +import org.apache.camel.impl.cloud.DefaultServiceDefinition; import org.apache.camel.impl.cloud.HealthyServiceFilter; import org.apache.camel.spring.boot.util.GroupCondition; import org.apache.camel.util.ObjectHelper; @@ -114,7 +115,13 @@ private CamelCloudServiceFilter createServiceFilter(CamelCloudConfigurationPrope String port = StringHelper.after(part, ":"); if (ObjectHelper.isNotEmpty(host) && ObjectHelper.isNotEmpty(port)) { - blacklist.addServer(entry.getKey(), host, Integer.parseInt(port)); + blacklist.addServer( + DefaultServiceDefinition.builder() + .withName(entry.getKey()) + .withHost(host) + .withPort(Integer.parseInt(port)) + .build() + ); } } } diff --git a/components/pom.xml b/components/pom.xml index d74cd51b4d965..8651841f4582a 100644 --- a/components/pom.xml +++ b/components/pom.xml @@ -60,6 +60,7 @@ camel-cxf-transport camel-jms camel-master + camel-service camel-ribbon diff --git a/components/readme.adoc b/components/readme.adoc index ebd61baae9d32..d79a3e1974f0d 100644 --- a/components/readme.adoc +++ b/components/readme.adoc @@ -719,6 +719,9 @@ Number of Components: 294 in 201 JAR artifacts (20 deprecated) | link:../camel-core/src/main/docs/seda-component.adoc[SEDA] (camel-core) + `seda:name` | 1.1 | The seda component provides asynchronous call to another endpoint from any CamelContext in the same JVM. +| link:camel-service/src/main/docs/service-component.adoc[Service] (camel-service) + +`service:serviceName:delegateUri` | 2.22 | Represents an endpoint which only becomes active when the CamelClusterView has the leadership. + | link:camel-servicenow/camel-servicenow-component/src/main/docs/servicenow-component.adoc[ServiceNow] (camel-servicenow) + `servicenow:instanceName` | 2.18 | The servicenow component is used to integrate Camel with ServiceNow cloud services. diff --git a/docs/user-manual/en/SUMMARY.md b/docs/user-manual/en/SUMMARY.md index 9f47533214233..0d7ce31d7587c 100644 --- a/docs/user-manual/en/SUMMARY.md +++ b/docs/user-manual/en/SUMMARY.md @@ -340,6 +340,7 @@ * [SAP NetWeaver](sap-netweaver-component.adoc) * [Schematron](schematron-component.adoc) * [SCP](scp-component.adoc) + * [Service](service-component.adoc) * [ServiceNow](servicenow-component.adoc) * [Servlet](servlet-component.adoc) * [SFTP](sftp-component.adoc) diff --git a/parent/pom.xml b/parent/pom.xml index e8bb96885bfa2..b21be66b725e1 100644 --- a/parent/pom.xml +++ b/parent/pom.xml @@ -680,6 +680,7 @@ 4.0.8 1.2.1_1 1.2.1 + 1.7.2 6.14.2 2.1.1 1.18 @@ -1906,6 +1907,11 @@ camel-script ${project.version} + + org.apache.camel + camel-service + ${project.version} + org.apache.camel camel-servicenow @@ -3345,6 +3351,11 @@ camel-script-starter ${project.version} + + org.apache.camel + camel-service-starter + ${project.version} + org.apache.camel camel-servicenow-starter diff --git a/platforms/spring-boot/components-starter/camel-jsonpath-starter/src/main/java/org/apache/camel/jsonpath/springboot/JsonPathLanguageConfiguration.java b/platforms/spring-boot/components-starter/camel-jsonpath-starter/src/main/java/org/apache/camel/jsonpath/springboot/JsonPathLanguageConfiguration.java index ba22f22c8d4ba..848cd466f41ed 100644 --- a/platforms/spring-boot/components-starter/camel-jsonpath-starter/src/main/java/org/apache/camel/jsonpath/springboot/JsonPathLanguageConfiguration.java +++ b/platforms/spring-boot/components-starter/camel-jsonpath-starter/src/main/java/org/apache/camel/jsonpath/springboot/JsonPathLanguageConfiguration.java @@ -40,7 +40,7 @@ public class JsonPathLanguageConfiguration */ private Boolean allowSimple = true; /** - * Whether to allow using the easy predicate parser to pre-parse predicates. + * Whether to allow using the easy predicate parse to pre-parse predicates. */ private Boolean allowEasyPredicate = true; /** diff --git a/platforms/spring-boot/components-starter/camel-service-starter/pom.xml b/platforms/spring-boot/components-starter/camel-service-starter/pom.xml new file mode 100644 index 0000000000000..5c6e224b6a350 --- /dev/null +++ b/platforms/spring-boot/components-starter/camel-service-starter/pom.xml @@ -0,0 +1,53 @@ + + + + 4.0.0 + + org.apache.camel + components-starter + 2.22.0-SNAPSHOT + + camel-service-starter + jar + Spring-Boot Starter :: Camel :: Service + Spring-Boot Starter for Camel service Support + + + org.springframework.boot + spring-boot-starter + ${spring-boot-version} + + + org.apache.camel + camel-service + ${project.version} + + + + org.apache.camel + camel-core-starter + + + org.apache.camel + camel-spring-boot-starter + + + + diff --git a/platforms/spring-boot/components-starter/camel-service-starter/src/main/java/org/apache/camel/component/service/springboot/ServiceComponentAutoConfiguration.java b/platforms/spring-boot/components-starter/camel-service-starter/src/main/java/org/apache/camel/component/service/springboot/ServiceComponentAutoConfiguration.java new file mode 100644 index 0000000000000..ab1b85f83fd3d --- /dev/null +++ b/platforms/spring-boot/components-starter/camel-service-starter/src/main/java/org/apache/camel/component/service/springboot/ServiceComponentAutoConfiguration.java @@ -0,0 +1,128 @@ +/** + * 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.camel.component.service.springboot; + +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import javax.annotation.Generated; +import org.apache.camel.CamelContext; +import org.apache.camel.component.service.ServiceComponent; +import org.apache.camel.spi.ComponentCustomizer; +import org.apache.camel.spi.HasId; +import org.apache.camel.spring.boot.CamelAutoConfiguration; +import org.apache.camel.spring.boot.ComponentConfigurationProperties; +import org.apache.camel.spring.boot.util.CamelPropertiesHelper; +import org.apache.camel.spring.boot.util.ConditionalOnCamelContextAndAutoConfigurationBeans; +import org.apache.camel.spring.boot.util.GroupCondition; +import org.apache.camel.spring.boot.util.HierarchicalPropertiesEvaluator; +import org.apache.camel.util.IntrospectionSupport; +import org.apache.camel.util.ObjectHelper; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.autoconfigure.AutoConfigureAfter; +import org.springframework.boot.autoconfigure.condition.ConditionalOnBean; +import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean; +import org.springframework.boot.context.properties.EnableConfigurationProperties; +import org.springframework.context.ApplicationContext; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Conditional; +import org.springframework.context.annotation.Configuration; +import org.springframework.context.annotation.Lazy; + +/** + * Generated by camel-package-maven-plugin - do not edit this file! + */ +@Generated("org.apache.camel.maven.packaging.SpringBootAutoConfigurationMojo") +@Configuration +@Conditional({ConditionalOnCamelContextAndAutoConfigurationBeans.class, + ServiceComponentAutoConfiguration.GroupConditions.class}) +@AutoConfigureAfter(CamelAutoConfiguration.class) +@EnableConfigurationProperties({ComponentConfigurationProperties.class, + ServiceComponentConfiguration.class}) +public class ServiceComponentAutoConfiguration { + + private static final Logger LOGGER = LoggerFactory + .getLogger(ServiceComponentAutoConfiguration.class); + @Autowired + private ApplicationContext applicationContext; + @Autowired + private CamelContext camelContext; + @Autowired + private ServiceComponentConfiguration configuration; + @Autowired(required = false) + private List> customizers; + + static class GroupConditions extends GroupCondition { + public GroupConditions() { + super("camel.component", "camel.component.service"); + } + } + + @Lazy + @Bean(name = "service-component") + @ConditionalOnMissingBean(ServiceComponent.class) + public ServiceComponent configureServiceComponent() throws Exception { + ServiceComponent component = new ServiceComponent(); + component.setCamelContext(camelContext); + Map parameters = new HashMap<>(); + IntrospectionSupport.getProperties(configuration, parameters, null, + false); + for (Map.Entry entry : parameters.entrySet()) { + Object value = entry.getValue(); + Class paramClass = value.getClass(); + if (paramClass.getName().endsWith("NestedConfiguration")) { + Class nestedClass = null; + try { + nestedClass = (Class) paramClass.getDeclaredField( + "CAMEL_NESTED_CLASS").get(null); + HashMap nestedParameters = new HashMap<>(); + IntrospectionSupport.getProperties(value, nestedParameters, + null, false); + Object nestedProperty = nestedClass.newInstance(); + CamelPropertiesHelper.setCamelProperties(camelContext, + nestedProperty, nestedParameters, false); + entry.setValue(nestedProperty); + } catch (NoSuchFieldException e) { + } + } + } + CamelPropertiesHelper.setCamelProperties(camelContext, component, + parameters, false); + if (ObjectHelper.isNotEmpty(customizers)) { + for (ComponentCustomizer customizer : customizers) { + boolean useCustomizer = (customizer instanceof HasId) + ? HierarchicalPropertiesEvaluator.evaluate( + applicationContext.getEnvironment(), + "camel.component.customizer", + "camel.component.service.customizer", + ((HasId) customizer).getId()) + : HierarchicalPropertiesEvaluator.evaluate( + applicationContext.getEnvironment(), + "camel.component.customizer", + "camel.component.service.customizer"); + if (useCustomizer) { + LOGGER.debug("Configure component {}, with customizer {}", + component, customizer); + customizer.customize(component); + } + } + } + return component; + } +} \ No newline at end of file diff --git a/platforms/spring-boot/components-starter/camel-service-starter/src/main/java/org/apache/camel/component/service/springboot/ServiceComponentConfiguration.java b/platforms/spring-boot/components-starter/camel-service-starter/src/main/java/org/apache/camel/component/service/springboot/ServiceComponentConfiguration.java new file mode 100644 index 0000000000000..87f981483ab4d --- /dev/null +++ b/platforms/spring-boot/components-starter/camel-service-starter/src/main/java/org/apache/camel/component/service/springboot/ServiceComponentConfiguration.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.camel.component.service.springboot; + +import javax.annotation.Generated; +import org.apache.camel.cloud.ServiceRegistry; +import org.apache.camel.cloud.ServiceRegistry.Selector; +import org.apache.camel.spring.boot.ComponentConfigurationPropertiesCommon; +import org.springframework.boot.context.properties.ConfigurationProperties; +import org.springframework.boot.context.properties.NestedConfigurationProperty; + +/** + * Represents an endpoint which only becomes active when the CamelClusterView + * has the leadership. + * + * Generated by camel-package-maven-plugin - do not edit this file! + */ +@Generated("org.apache.camel.maven.packaging.SpringBootAutoConfigurationMojo") +@ConfigurationProperties(prefix = "camel.component.service") +public class ServiceComponentConfiguration + extends + ComponentConfigurationPropertiesCommon { + + /** + * Inject the service to use. + */ + @NestedConfigurationProperty + private ServiceRegistry service; + /** + * Inject the service selector used to lookup the ServiceRegistry to use. + */ + @NestedConfigurationProperty + private Selector serviceSelector; + /** + * Whether the component should resolve property placeholders on itself when + * starting. Only properties which are of String type can use property + * placeholders. + */ + private Boolean resolvePropertyPlaceholders = true; + + public ServiceRegistry getService() { + return service; + } + + public void setService(ServiceRegistry service) { + this.service = service; + } + + public Selector getServiceSelector() { + return serviceSelector; + } + + public void setServiceSelector(Selector serviceSelector) { + this.serviceSelector = serviceSelector; + } + + public Boolean getResolvePropertyPlaceholders() { + return resolvePropertyPlaceholders; + } + + public void setResolvePropertyPlaceholders( + Boolean resolvePropertyPlaceholders) { + this.resolvePropertyPlaceholders = resolvePropertyPlaceholders; + } +} \ No newline at end of file diff --git a/platforms/spring-boot/components-starter/camel-service-starter/src/main/resources/META-INF/LICENSE.txt b/platforms/spring-boot/components-starter/camel-service-starter/src/main/resources/META-INF/LICENSE.txt new file mode 100644 index 0000000000000..6b0b1270ff0ca --- /dev/null +++ b/platforms/spring-boot/components-starter/camel-service-starter/src/main/resources/META-INF/LICENSE.txt @@ -0,0 +1,203 @@ + + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright [yyyy] [name of copyright owner] + + 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. + diff --git a/platforms/spring-boot/components-starter/camel-service-starter/src/main/resources/META-INF/NOTICE.txt b/platforms/spring-boot/components-starter/camel-service-starter/src/main/resources/META-INF/NOTICE.txt new file mode 100644 index 0000000000000..2e215bf2e6b1f --- /dev/null +++ b/platforms/spring-boot/components-starter/camel-service-starter/src/main/resources/META-INF/NOTICE.txt @@ -0,0 +1,11 @@ + ========================================================================= + == NOTICE file corresponding to the section 4 d of == + == the Apache License, Version 2.0, == + == in this case for the Apache Camel distribution. == + ========================================================================= + + This product includes software developed by + The Apache Software Foundation (http://www.apache.org/). + + Please read the different LICENSE files present in the licenses directory of + this distribution. diff --git a/platforms/spring-boot/components-starter/camel-service-starter/src/main/resources/META-INF/spring.factories b/platforms/spring-boot/components-starter/camel-service-starter/src/main/resources/META-INF/spring.factories new file mode 100644 index 0000000000000..bfcf05da4d390 --- /dev/null +++ b/platforms/spring-boot/components-starter/camel-service-starter/src/main/resources/META-INF/spring.factories @@ -0,0 +1,19 @@ +## --------------------------------------------------------------------------- +## 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. +## --------------------------------------------------------------------------- + +org.springframework.boot.autoconfigure.EnableAutoConfiguration=\ +org.apache.camel.component.service.springboot.ServiceComponentAutoConfiguration diff --git a/platforms/spring-boot/components-starter/camel-service-starter/src/main/resources/META-INF/spring.provides b/platforms/spring-boot/components-starter/camel-service-starter/src/main/resources/META-INF/spring.provides new file mode 100644 index 0000000000000..ff9f1f8a10389 --- /dev/null +++ b/platforms/spring-boot/components-starter/camel-service-starter/src/main/resources/META-INF/spring.provides @@ -0,0 +1,17 @@ +## --------------------------------------------------------------------------- +## 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. +## --------------------------------------------------------------------------- +provides: camel-service diff --git a/platforms/spring-boot/components-starter/pom.xml b/platforms/spring-boot/components-starter/pom.xml index e31ad2f5fcab5..f0751895eeb12 100644 --- a/platforms/spring-boot/components-starter/pom.xml +++ b/platforms/spring-boot/components-starter/pom.xml @@ -274,6 +274,7 @@ camel-saxon-starter camel-schematron-starter camel-script-starter + camel-service-starter camel-servicenow-starter camel-servlet-starter camel-servletlistener-starter diff --git a/platforms/spring-boot/spring-boot-dm/camel-spring-boot-dependencies/pom.xml b/platforms/spring-boot/spring-boot-dm/camel-spring-boot-dependencies/pom.xml index 986b55526a48b..8f335eb852478 100644 --- a/platforms/spring-boot/spring-boot-dm/camel-spring-boot-dependencies/pom.xml +++ b/platforms/spring-boot/spring-boot-dm/camel-spring-boot-dependencies/pom.xml @@ -2384,6 +2384,16 @@ camel-script-starter ${project.version} + + org.apache.camel + camel-service + ${project.version} + + + org.apache.camel + camel-service-starter + ${project.version} + org.apache.camel camel-servicenow diff --git a/tests/camel-itest-karaf/src/test/java/org/apache/camel/itest/karaf/CamelServiceTest.java b/tests/camel-itest-karaf/src/test/java/org/apache/camel/itest/karaf/CamelServiceTest.java new file mode 100644 index 0000000000000..98df8354dda49 --- /dev/null +++ b/tests/camel-itest-karaf/src/test/java/org/apache/camel/itest/karaf/CamelServiceTest.java @@ -0,0 +1,33 @@ +/** + * 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.camel.itest.karaf; + +import org.junit.Test; +import org.junit.runner.RunWith; +import org.ops4j.pax.exam.junit.PaxExam; + +@RunWith(PaxExam.class) +public class CamelServiceTest extends BaseKarafTest { + + public static final String COMPONENT = extractName(CamelServiceTest.class); + + @Test + public void test() throws Exception { + testComponent(COMPONENT); + } + +} \ No newline at end of file From 349639992a22a5c67978c3620ac2af0f003c1d86 Mon Sep 17 00:00:00 2001 From: lburgazzoli Date: Thu, 10 May 2018 18:33:38 +0200 Subject: [PATCH 02/20] CAMEL-12502: camel cloud : create a service route policy --- .../apache/camel/cloud/ServiceDefinition.java | 4 + .../cloud/ServiceRegistrationRoutePolicy.java | 186 ++++++++++++++++++ ...ServiceRegistrationRoutePolicyFactory.java | 50 +++++ ...=> ConsulServiceRegistrationTestBase.java} | 47 ++--- ...egistrationWithRoutePolicyFactoryTest.java | 46 +++++ ...erviceRegistrationWithRoutePolicyTest.java | 38 ++++ ...eRegistrationWithServiceComponentTest.java | 46 +++++ 7 files changed, 382 insertions(+), 35 deletions(-) create mode 100644 camel-core/src/main/java/org/apache/camel/impl/cloud/ServiceRegistrationRoutePolicy.java create mode 100644 camel-core/src/main/java/org/apache/camel/impl/cloud/ServiceRegistrationRoutePolicyFactory.java rename components/camel-consul/src/test/java/org/apache/camel/component/consul/cloud/{ConsulServiceRegistrationTest.java => ConsulServiceRegistrationTestBase.java} (65%) create mode 100644 components/camel-consul/src/test/java/org/apache/camel/component/consul/cloud/ConsulServiceRegistrationWithRoutePolicyFactoryTest.java create mode 100644 components/camel-consul/src/test/java/org/apache/camel/component/consul/cloud/ConsulServiceRegistrationWithRoutePolicyTest.java create mode 100644 components/camel-consul/src/test/java/org/apache/camel/component/consul/cloud/ConsulServiceRegistrationWithServiceComponentTest.java diff --git a/camel-core/src/main/java/org/apache/camel/cloud/ServiceDefinition.java b/camel-core/src/main/java/org/apache/camel/cloud/ServiceDefinition.java index ee99441b78acb..3007e8c896269 100644 --- a/camel-core/src/main/java/org/apache/camel/cloud/ServiceDefinition.java +++ b/camel-core/src/main/java/org/apache/camel/cloud/ServiceDefinition.java @@ -28,7 +28,11 @@ * @see ServiceDiscovery */ public interface ServiceDefinition { + String SERVICE_META_PREFIX = "service."; + // default service meta-data keys + String SERVICE_META_ID = "service.id"; + String SERVICE_META_NAME = "service.name"; String SERVICE_META_PORT = "service.port"; String SERVICE_META_PROTOCOL= "service.protocol"; String SERVICE_META_PATH = "service.path"; diff --git a/camel-core/src/main/java/org/apache/camel/impl/cloud/ServiceRegistrationRoutePolicy.java b/camel-core/src/main/java/org/apache/camel/impl/cloud/ServiceRegistrationRoutePolicy.java new file mode 100644 index 0000000000000..ce05a0355607e --- /dev/null +++ b/camel-core/src/main/java/org/apache/camel/impl/cloud/ServiceRegistrationRoutePolicy.java @@ -0,0 +1,186 @@ +/** + * 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.camel.impl.cloud; + +import java.util.Map; +import java.util.Optional; + +import org.apache.camel.CamelContext; +import org.apache.camel.CamelContextAware; +import org.apache.camel.Endpoint; +import org.apache.camel.Route; +import org.apache.camel.api.management.ManagedResource; +import org.apache.camel.cloud.DiscoverableService; +import org.apache.camel.cloud.ServiceDefinition; +import org.apache.camel.cloud.ServiceRegistry; +import org.apache.camel.support.RoutePolicySupport; +import org.apache.camel.util.ObjectHelper; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +@ManagedResource(description = "Clustered Route policy using") +public class ServiceRegistrationRoutePolicy extends RoutePolicySupport implements CamelContextAware { + private static final Logger LOGGER = LoggerFactory.getLogger(ServiceRegistrationRoutePolicy.class); + + private final ServiceRegistry.Selector serviceRegistrySelector; + + private ServiceRegistry serviceRegistry; + private CamelContext camelContext; + + public ServiceRegistrationRoutePolicy() { + this(null, ServiceRegistrySelectors.DEFAULT_SELECTOR); + } + + public ServiceRegistrationRoutePolicy(ServiceRegistry.Selector serviceRegistrySelector) { + this(null, serviceRegistrySelector); + } + + public ServiceRegistrationRoutePolicy(ServiceRegistry serviceRegistry, ServiceRegistry.Selector serviceRegistrySelector) { + this.serviceRegistry = serviceRegistry; + this.serviceRegistrySelector = serviceRegistrySelector; + } + + @Override + public CamelContext getCamelContext() { + return camelContext; + } + + @Override + public void setCamelContext(CamelContext camelContext) { + this.camelContext = camelContext; + } + + // *********************** + // policy life-cycle + // *********************** + + @Override + public void doStart() throws Exception { + if (serviceRegistry == null) { + serviceRegistry = ServiceRegistryHelper.lookupService(camelContext, serviceRegistrySelector).orElseThrow( + () -> new IllegalStateException("ServiceRegistry service not found") + ); + } + + LOGGER.debug("ServiceRegistrationRoutePolicy {} is using ServiceRegistry instance {} (id={}, type={})", + this, + serviceRegistry, + serviceRegistry.getId(), + serviceRegistry.getClass().getName() + ); + } + + // *********************** + // route life-cycle + // *********************** + + @Override + public void onStart(Route route) { + register(route); + } + + @Override + public void onStop(Route route) { + deregister(route); + } + + @Override + public void onSuspend(Route route) { + deregister(route); + } + + @Override + public void onResume(Route route) { + register(route); + } + + // *********************** + // registration helpers + // *********************** + + private void register(Route route) { + computeServiceDefinition(route).ifPresent(serviceRegistry::register); + } + + private void deregister(Route route) { + computeServiceDefinition(route).ifPresent(serviceRegistry::deregister); + } + + private Optional computeServiceDefinition(Route route) { + Endpoint endpoint = route.getConsumer().getEndpoint(); + + if (endpoint instanceof DiscoverableService) { + final DiscoverableService service = (DiscoverableService)endpoint; + final ServiceDefinition definition = service.getServiceDefinition(); + + // try to get the service id from route properties + String serviceId = (String)route.getProperties().get(ServiceDefinition.SERVICE_META_ID); + if (serviceId == null) { + // if not check if the route id is custom and use it + if (route.getRouteContext().getRoute().hasCustomIdAssigned()) { + serviceId = route.getId(); + } + } + if (serviceId == null) { + // finally get the id from the DiscoverableService + serviceId = definition.getId(); + } + + // try to get the service name from route properties + String serviceName = (String)route.getProperties().get(ServiceDefinition.SERVICE_META_NAME); + if (serviceName == null) { + // if not check if the route group is defined use the route group + serviceName = route.getGroup(); + } + if (serviceName == null) { + // finally get the name from the DiscoverableService + serviceName = definition.getName(); + } + + ObjectHelper.notNull(serviceId, "Service ID"); + ObjectHelper.notNull(serviceName, "Service Name"); + + // Build the final resource definition from bits collected from the + // endpoint and the route. + DefaultServiceDefinition.Builder builder = DefaultServiceDefinition.builder() + .from(definition) + .withId(serviceId) + .withName(serviceName); + + // Add additional metadata from route properties whose name starts + // with ServiceDefinition.SERVICE_META_PREFIX. + // + // NOTE: At the moment it is not possible to add properties to a route + // with fluent DSL + for (Map.Entry entry: route.getProperties().entrySet()) { + if (!entry.getKey().startsWith(ServiceDefinition.SERVICE_META_PREFIX)) { + continue; + } + + final String key = entry.getKey().substring(ServiceDefinition.SERVICE_META_PREFIX.length()); + final String val = camelContext.getTypeConverter().convertTo(String.class, entry.getValue()); + + builder.addMeta(key, val); + } + + return Optional.of(builder.build()); + } + + return Optional.empty(); + } +} + diff --git a/camel-core/src/main/java/org/apache/camel/impl/cloud/ServiceRegistrationRoutePolicyFactory.java b/camel-core/src/main/java/org/apache/camel/impl/cloud/ServiceRegistrationRoutePolicyFactory.java new file mode 100644 index 0000000000000..54825fc6a41e8 --- /dev/null +++ b/camel-core/src/main/java/org/apache/camel/impl/cloud/ServiceRegistrationRoutePolicyFactory.java @@ -0,0 +1,50 @@ +/** + * 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.camel.impl.cloud; + +import org.apache.camel.CamelContext; +import org.apache.camel.cloud.ServiceRegistry; +import org.apache.camel.model.RouteDefinition; +import org.apache.camel.spi.RoutePolicy; +import org.apache.camel.spi.RoutePolicyFactory; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +public class ServiceRegistrationRoutePolicyFactory implements RoutePolicyFactory { + private static final Logger LOGGER = LoggerFactory.getLogger(ServiceRegistrationRoutePolicyFactory.class); + + private final ServiceRegistry.Selector serviceRegistrySelector; + private final ServiceRegistry serviceRegistry; + + public ServiceRegistrationRoutePolicyFactory() { + this(null, ServiceRegistrySelectors.DEFAULT_SELECTOR); + } + + public ServiceRegistrationRoutePolicyFactory(ServiceRegistry.Selector serviceRegistrySelector) { + this(null, serviceRegistrySelector); + } + + public ServiceRegistrationRoutePolicyFactory(ServiceRegistry serviceRegistry, ServiceRegistry.Selector serviceRegistrySelector) { + this.serviceRegistry = serviceRegistry; + this.serviceRegistrySelector = serviceRegistrySelector; + } + + @Override + public RoutePolicy createRoutePolicy(CamelContext camelContext, String routeId, RouteDefinition route) { + return new ServiceRegistrationRoutePolicy(serviceRegistry, serviceRegistrySelector); + } +} diff --git a/components/camel-consul/src/test/java/org/apache/camel/component/consul/cloud/ConsulServiceRegistrationTest.java b/components/camel-consul/src/test/java/org/apache/camel/component/consul/cloud/ConsulServiceRegistrationTestBase.java similarity index 65% rename from components/camel-consul/src/test/java/org/apache/camel/component/consul/cloud/ConsulServiceRegistrationTest.java rename to components/camel-consul/src/test/java/org/apache/camel/component/consul/cloud/ConsulServiceRegistrationTestBase.java index be189cbcfa1dd..a6ef5c945e547 100644 --- a/components/camel-consul/src/test/java/org/apache/camel/component/consul/cloud/ConsulServiceRegistrationTest.java +++ b/components/camel-consul/src/test/java/org/apache/camel/component/consul/cloud/ConsulServiceRegistrationTestBase.java @@ -17,32 +17,23 @@ package org.apache.camel.component.consul.cloud; import java.util.List; +import java.util.UUID; import com.orbitz.consul.CatalogClient; import com.orbitz.consul.HealthClient; import com.orbitz.consul.model.catalog.CatalogService; import com.orbitz.consul.model.health.ServiceHealth; import org.apache.camel.CamelContext; -import org.apache.camel.RoutesBuilder; -import org.apache.camel.builder.RouteBuilder; +import org.apache.camel.cloud.ServiceDefinition; import org.apache.camel.component.consul.support.ConsulTestSupport; -import org.apache.camel.component.service.ServiceComponent; -import org.apache.camel.impl.JndiRegistry; import org.junit.Test; import org.springframework.util.SocketUtils; -public class ConsulServiceRegistrationTest extends ConsulTestSupport { - private final static String SERVICE_NAME = "my-service"; - private final static String SERVICE_HOST = "localhost"; - private final static int SERVICE_PORT = SocketUtils.findAvailableTcpPort(); - - @Override - protected JndiRegistry createRegistry() throws Exception { - JndiRegistry registry = super.createRegistry(); - registry.bind("service", new ServiceComponent()); - - return registry; - } +public abstract class ConsulServiceRegistrationTestBase extends ConsulTestSupport { + protected final static String SERVICE_ID = UUID.randomUUID().toString(); + protected final static String SERVICE_NAME = "my-service"; + protected final static String SERVICE_HOST = "localhost"; + protected final static int SERVICE_PORT = SocketUtils.findAvailableTcpPort(); @Override protected CamelContext createCamelContext() throws Exception { @@ -60,19 +51,6 @@ protected CamelContext createCamelContext() throws Exception { return context; } - @Override - protected RoutesBuilder createRouteBuilder() throws Exception { - return new RouteBuilder() { - @Override - public void configure() throws Exception { - fromF("service:%s:jetty:http://0.0.0.0:%d?serviceMeta.type=consul", SERVICE_NAME, SERVICE_PORT) - .routeId("exposed") - .noAutoStartup() - .to("log:service-registry?level=INFO"); - } - }; - } - @Test public void testRegistrationFromRoute() throws Exception { final CatalogClient catalog = getConsul().catalogClient(); @@ -82,17 +60,16 @@ public void testRegistrationFromRoute() throws Exception { assertTrue(catalog.getService(SERVICE_NAME).getResponse().isEmpty()); // let start the route - context().startRoute("exposed"); + context().startRoute(SERVICE_ID); // check that service has been registered List services = catalog.getService(SERVICE_NAME).getResponse(); assertEquals(1, services.size()); assertEquals(SERVICE_PORT, services.get(0).getServicePort()); assertEquals("localhost", services.get(0).getServiceAddress()); - assertTrue(services.get(0).getServiceTags().contains("type=consul")); - assertTrue(services.get(0).getServiceTags().contains("service.protocol=http")); - assertTrue(services.get(0).getServiceTags().contains("service.path=/")); - assertTrue(services.get(0).getServiceTags().contains("service.port=" + SERVICE_PORT)); + assertTrue(services.get(0).getServiceTags().contains(ServiceDefinition.SERVICE_META_PROTOCOL + "=http")); + assertTrue(services.get(0).getServiceTags().contains(ServiceDefinition.SERVICE_META_PATH + "=/service/endpoint/")); + assertTrue(services.get(0).getServiceTags().contains(ServiceDefinition.SERVICE_META_PORT + "=" + SERVICE_PORT)); List checks = health.getHealthyServiceInstances(SERVICE_NAME).getResponse(); assertEquals(1, checks.size()); @@ -100,7 +77,7 @@ public void testRegistrationFromRoute() throws Exception { assertEquals("localhost", checks.get(0).getService().getAddress()); // let stop the route - context().stopRoute("exposed"); + context().stopRoute(SERVICE_ID); // the service should be removed once the route is stopped assertTrue(catalog.getService(SERVICE_NAME).getResponse().isEmpty()); diff --git a/components/camel-consul/src/test/java/org/apache/camel/component/consul/cloud/ConsulServiceRegistrationWithRoutePolicyFactoryTest.java b/components/camel-consul/src/test/java/org/apache/camel/component/consul/cloud/ConsulServiceRegistrationWithRoutePolicyFactoryTest.java new file mode 100644 index 0000000000000..cb2c51aba1d53 --- /dev/null +++ b/components/camel-consul/src/test/java/org/apache/camel/component/consul/cloud/ConsulServiceRegistrationWithRoutePolicyFactoryTest.java @@ -0,0 +1,46 @@ +/** + * 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.camel.component.consul.cloud; + +import org.apache.camel.CamelContext; +import org.apache.camel.RoutesBuilder; +import org.apache.camel.builder.RouteBuilder; +import org.apache.camel.impl.cloud.ServiceRegistrationRoutePolicyFactory; + +public class ConsulServiceRegistrationWithRoutePolicyFactoryTest extends ConsulServiceRegistrationTestBase { + @Override + protected CamelContext createCamelContext() throws Exception { + CamelContext context = super.createCamelContext(); + context.addRoutePolicyFactory(new ServiceRegistrationRoutePolicyFactory()); + + return context; + } + + @Override + protected RoutesBuilder createRouteBuilder() throws Exception { + return new RouteBuilder() { + @Override + public void configure() throws Exception { + fromF("jetty:http://0.0.0.0:%d/service/endpoint/", SERVICE_PORT) + .routeId(SERVICE_ID) + .routeGroup(SERVICE_NAME) + .noAutoStartup() + .to("log:service-registry?level=INFO"); + } + }; + } +} diff --git a/components/camel-consul/src/test/java/org/apache/camel/component/consul/cloud/ConsulServiceRegistrationWithRoutePolicyTest.java b/components/camel-consul/src/test/java/org/apache/camel/component/consul/cloud/ConsulServiceRegistrationWithRoutePolicyTest.java new file mode 100644 index 0000000000000..d79c3065947fd --- /dev/null +++ b/components/camel-consul/src/test/java/org/apache/camel/component/consul/cloud/ConsulServiceRegistrationWithRoutePolicyTest.java @@ -0,0 +1,38 @@ +/** + * 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.camel.component.consul.cloud; + +import org.apache.camel.RoutesBuilder; +import org.apache.camel.builder.RouteBuilder; +import org.apache.camel.impl.cloud.ServiceRegistrationRoutePolicy; + +public class ConsulServiceRegistrationWithRoutePolicyTest extends ConsulServiceRegistrationTestBase { + @Override + protected RoutesBuilder createRouteBuilder() throws Exception { + return new RouteBuilder() { + @Override + public void configure() throws Exception { + fromF("jetty:http://0.0.0.0:%d/service/endpoint/", SERVICE_PORT) + .routeId(SERVICE_ID) + .routeGroup(SERVICE_NAME) + .routePolicy(new ServiceRegistrationRoutePolicy()) + .noAutoStartup() + .to("log:service-registry?level=INFO"); + } + }; + } +} diff --git a/components/camel-consul/src/test/java/org/apache/camel/component/consul/cloud/ConsulServiceRegistrationWithServiceComponentTest.java b/components/camel-consul/src/test/java/org/apache/camel/component/consul/cloud/ConsulServiceRegistrationWithServiceComponentTest.java new file mode 100644 index 0000000000000..6738c12a49f65 --- /dev/null +++ b/components/camel-consul/src/test/java/org/apache/camel/component/consul/cloud/ConsulServiceRegistrationWithServiceComponentTest.java @@ -0,0 +1,46 @@ +/** + * 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.camel.component.consul.cloud; + +import org.apache.camel.RoutesBuilder; +import org.apache.camel.builder.RouteBuilder; +import org.apache.camel.component.service.ServiceComponent; +import org.apache.camel.impl.JndiRegistry; + +public class ConsulServiceRegistrationWithServiceComponentTest extends ConsulServiceRegistrationTestBase { + @Override + protected JndiRegistry createRegistry() throws Exception { + JndiRegistry registry = super.createRegistry(); + registry.bind("service", new ServiceComponent()); + + return registry; + } + + @Override + protected RoutesBuilder createRouteBuilder() throws Exception { + return new RouteBuilder() { + @Override + public void configure() throws Exception { + fromF("service:%s:jetty:http://0.0.0.0:%d/service/endpoint/", SERVICE_NAME, SERVICE_PORT) + .routeId(SERVICE_ID) + .routeGroup(SERVICE_NAME) + .noAutoStartup() + .to("log:service-registry?level=INFO"); + } + }; + } +} From 95dbeb2794d23538a218a275896a51ad723db550 Mon Sep 17 00:00:00 2001 From: lburgazzoli Date: Thu, 10 May 2018 22:31:49 +0200 Subject: [PATCH 03/20] CAMEL-12485: camel cloud : create camel-service component --- .../camel/cloud/DiscoverableService.java | 6 +- .../apache/camel/cloud/ServiceDefinition.java | 2 + .../impl/cloud/DefaultServiceDefinition.java | 61 +++++++++- .../cloud/ServiceRegistrationRoutePolicy.java | 8 +- .../ConsulServiceRegistrationTestBase.java | 15 ++- ...egistrationWithRoutePolicyFactoryTest.java | 2 +- ...erviceRegistrationWithRoutePolicyTest.java | 2 +- ...eRegistrationWithServiceComponentTest.java | 13 +- .../camel/http/common/HttpCommonEndpoint.java | 17 ++- .../component/service/ServiceComponent.java | 37 +++--- .../component/service/ServiceEndpoint.java | 32 +++-- .../component/service/ServiceParameters.java | 114 ------------------ 12 files changed, 131 insertions(+), 178 deletions(-) delete mode 100644 components/camel-service/src/main/java/org/apache/camel/component/service/ServiceParameters.java diff --git a/camel-core/src/main/java/org/apache/camel/cloud/DiscoverableService.java b/camel-core/src/main/java/org/apache/camel/cloud/DiscoverableService.java index 90710ad7e2d39..6d1946c6501b6 100644 --- a/camel-core/src/main/java/org/apache/camel/cloud/DiscoverableService.java +++ b/camel-core/src/main/java/org/apache/camel/cloud/DiscoverableService.java @@ -16,9 +16,11 @@ */ package org.apache.camel.cloud; +import java.util.Map; + public interface DiscoverableService { /** - * Get the service definition. + * Get the service properties. */ - ServiceDefinition getServiceDefinition(); + Map getServiceProperties(); } diff --git a/camel-core/src/main/java/org/apache/camel/cloud/ServiceDefinition.java b/camel-core/src/main/java/org/apache/camel/cloud/ServiceDefinition.java index 3007e8c896269..8a0a3f66d7a10 100644 --- a/camel-core/src/main/java/org/apache/camel/cloud/ServiceDefinition.java +++ b/camel-core/src/main/java/org/apache/camel/cloud/ServiceDefinition.java @@ -33,7 +33,9 @@ public interface ServiceDefinition { // default service meta-data keys String SERVICE_META_ID = "service.id"; String SERVICE_META_NAME = "service.name"; + String SERVICE_META_HOST = "service.host"; String SERVICE_META_PORT = "service.port"; + String SERVICE_META_ZONE = "service.zone"; String SERVICE_META_PROTOCOL= "service.protocol"; String SERVICE_META_PATH = "service.path"; diff --git a/camel-core/src/main/java/org/apache/camel/impl/cloud/DefaultServiceDefinition.java b/camel-core/src/main/java/org/apache/camel/impl/cloud/DefaultServiceDefinition.java index ba4708c15b891..8deace605ec8a 100644 --- a/camel-core/src/main/java/org/apache/camel/impl/cloud/DefaultServiceDefinition.java +++ b/camel-core/src/main/java/org/apache/camel/impl/cloud/DefaultServiceDefinition.java @@ -175,7 +175,7 @@ public static class Builder { private String id; private String name; private String host; - private int port; + private Integer port; private Map meta; private ServiceHealth health; @@ -190,6 +190,51 @@ public Builder from(ServiceDefinition source) { return this; } + public Builder from(Map properties) { + Map options = new HashMap<>(properties); + Object val = null; + + val = options.remove(ServiceDefinition.SERVICE_META_ID); + if (val != null && val instanceof String) { + withId((String)val); + } + + val = options.remove(ServiceDefinition.SERVICE_META_NAME); + if (val != null && val instanceof String) { + withName((String)val); + } + + val = options.remove(ServiceDefinition.SERVICE_META_HOST); + if (val != null && val instanceof String) { + withHost((String)val); + } + + val = options.remove(ServiceDefinition.SERVICE_META_PORT); + if (val != null && val instanceof String) { + withPort((String)val); + } + if (val != null && val instanceof Integer) { + withPort((Integer)val); + } + + val = options.remove(ServiceDefinition.SERVICE_META_HOST); + if (val != null && val instanceof String) { + withHost((String)val); + } + + for (Map.Entry entry : options.entrySet()) { + if (!entry.getKey().startsWith(ServiceDefinition.SERVICE_META_PREFIX)) { + continue; + } + + if (entry.getValue() instanceof String) { + addMeta(entry.getKey(), (String)entry.getValue()); + } + } + + return this; + } + public Builder withId(String id) { this.id = id; return this; @@ -217,12 +262,20 @@ public String host() { return host; } - public Builder withPort(int port) { + public Builder withPort(Integer port) { this.port = port; return this; } - public int port() { + public Builder withPort(String port) { + if (port != null) { + withPort(Integer.parseInt(port)); + } + + return this; + } + + public Integer port() { return port; } @@ -266,7 +319,7 @@ public ServiceHealth health() { } public ServiceDefinition build() { - return new DefaultServiceDefinition(id, name, host, port, meta, health); + return new DefaultServiceDefinition(id, name, host, port != null ? port : -1, meta, health); } } } \ No newline at end of file diff --git a/camel-core/src/main/java/org/apache/camel/impl/cloud/ServiceRegistrationRoutePolicy.java b/camel-core/src/main/java/org/apache/camel/impl/cloud/ServiceRegistrationRoutePolicy.java index ce05a0355607e..c432fa7218233 100644 --- a/camel-core/src/main/java/org/apache/camel/impl/cloud/ServiceRegistrationRoutePolicy.java +++ b/camel-core/src/main/java/org/apache/camel/impl/cloud/ServiceRegistrationRoutePolicy.java @@ -125,7 +125,7 @@ private Optional computeServiceDefinition(Route route) { if (endpoint instanceof DiscoverableService) { final DiscoverableService service = (DiscoverableService)endpoint; - final ServiceDefinition definition = service.getServiceDefinition(); + final Map properties = service.getServiceProperties(); // try to get the service id from route properties String serviceId = (String)route.getProperties().get(ServiceDefinition.SERVICE_META_ID); @@ -137,7 +137,7 @@ private Optional computeServiceDefinition(Route route) { } if (serviceId == null) { // finally get the id from the DiscoverableService - serviceId = definition.getId(); + serviceId = (String)properties.get(ServiceDefinition.SERVICE_META_ID); } // try to get the service name from route properties @@ -148,7 +148,7 @@ private Optional computeServiceDefinition(Route route) { } if (serviceName == null) { // finally get the name from the DiscoverableService - serviceName = definition.getName(); + serviceName = (String)properties.get(ServiceDefinition.SERVICE_META_NAME); } ObjectHelper.notNull(serviceId, "Service ID"); @@ -157,7 +157,7 @@ private Optional computeServiceDefinition(Route route) { // Build the final resource definition from bits collected from the // endpoint and the route. DefaultServiceDefinition.Builder builder = DefaultServiceDefinition.builder() - .from(definition) + .from(properties) .withId(serviceId) .withName(serviceName); diff --git a/components/camel-consul/src/test/java/org/apache/camel/component/consul/cloud/ConsulServiceRegistrationTestBase.java b/components/camel-consul/src/test/java/org/apache/camel/component/consul/cloud/ConsulServiceRegistrationTestBase.java index a6ef5c945e547..c1a97e493f6ae 100644 --- a/components/camel-consul/src/test/java/org/apache/camel/component/consul/cloud/ConsulServiceRegistrationTestBase.java +++ b/components/camel-consul/src/test/java/org/apache/camel/component/consul/cloud/ConsulServiceRegistrationTestBase.java @@ -16,7 +16,9 @@ */ package org.apache.camel.component.consul.cloud; +import java.util.Collections; import java.util.List; +import java.util.Map; import java.util.UUID; import com.orbitz.consul.CatalogClient; @@ -35,6 +37,10 @@ public abstract class ConsulServiceRegistrationTestBase extends ConsulTestSuppor protected final static String SERVICE_HOST = "localhost"; protected final static int SERVICE_PORT = SocketUtils.findAvailableTcpPort(); + protected Map getMetadata() { + return Collections.emptyMap(); + } + @Override protected CamelContext createCamelContext() throws Exception { final CamelContext context = super.createCamelContext(); @@ -68,8 +74,13 @@ public void testRegistrationFromRoute() throws Exception { assertEquals(SERVICE_PORT, services.get(0).getServicePort()); assertEquals("localhost", services.get(0).getServiceAddress()); assertTrue(services.get(0).getServiceTags().contains(ServiceDefinition.SERVICE_META_PROTOCOL + "=http")); - assertTrue(services.get(0).getServiceTags().contains(ServiceDefinition.SERVICE_META_PATH + "=/service/endpoint/")); - assertTrue(services.get(0).getServiceTags().contains(ServiceDefinition.SERVICE_META_PORT + "=" + SERVICE_PORT)); + assertTrue(services.get(0).getServiceTags().contains(ServiceDefinition.SERVICE_META_PATH + "=/service/endpoint")); + + getMetadata().forEach( + (k, v) -> { + assertTrue(services.get(0).getServiceTags().contains(k + "=" + v)); + } + ); List checks = health.getHealthyServiceInstances(SERVICE_NAME).getResponse(); assertEquals(1, checks.size()); diff --git a/components/camel-consul/src/test/java/org/apache/camel/component/consul/cloud/ConsulServiceRegistrationWithRoutePolicyFactoryTest.java b/components/camel-consul/src/test/java/org/apache/camel/component/consul/cloud/ConsulServiceRegistrationWithRoutePolicyFactoryTest.java index cb2c51aba1d53..65981fce20598 100644 --- a/components/camel-consul/src/test/java/org/apache/camel/component/consul/cloud/ConsulServiceRegistrationWithRoutePolicyFactoryTest.java +++ b/components/camel-consul/src/test/java/org/apache/camel/component/consul/cloud/ConsulServiceRegistrationWithRoutePolicyFactoryTest.java @@ -35,7 +35,7 @@ protected RoutesBuilder createRouteBuilder() throws Exception { return new RouteBuilder() { @Override public void configure() throws Exception { - fromF("jetty:http://0.0.0.0:%d/service/endpoint/", SERVICE_PORT) + fromF("jetty:http://0.0.0.0:%d/service/endpoint", SERVICE_PORT) .routeId(SERVICE_ID) .routeGroup(SERVICE_NAME) .noAutoStartup() diff --git a/components/camel-consul/src/test/java/org/apache/camel/component/consul/cloud/ConsulServiceRegistrationWithRoutePolicyTest.java b/components/camel-consul/src/test/java/org/apache/camel/component/consul/cloud/ConsulServiceRegistrationWithRoutePolicyTest.java index d79c3065947fd..6e18de2963743 100644 --- a/components/camel-consul/src/test/java/org/apache/camel/component/consul/cloud/ConsulServiceRegistrationWithRoutePolicyTest.java +++ b/components/camel-consul/src/test/java/org/apache/camel/component/consul/cloud/ConsulServiceRegistrationWithRoutePolicyTest.java @@ -26,7 +26,7 @@ protected RoutesBuilder createRouteBuilder() throws Exception { return new RouteBuilder() { @Override public void configure() throws Exception { - fromF("jetty:http://0.0.0.0:%d/service/endpoint/", SERVICE_PORT) + fromF("jetty:http://0.0.0.0:%d/service/endpoint", SERVICE_PORT) .routeId(SERVICE_ID) .routeGroup(SERVICE_NAME) .routePolicy(new ServiceRegistrationRoutePolicy()) diff --git a/components/camel-consul/src/test/java/org/apache/camel/component/consul/cloud/ConsulServiceRegistrationWithServiceComponentTest.java b/components/camel-consul/src/test/java/org/apache/camel/component/consul/cloud/ConsulServiceRegistrationWithServiceComponentTest.java index 6738c12a49f65..d12dce31e3c44 100644 --- a/components/camel-consul/src/test/java/org/apache/camel/component/consul/cloud/ConsulServiceRegistrationWithServiceComponentTest.java +++ b/components/camel-consul/src/test/java/org/apache/camel/component/consul/cloud/ConsulServiceRegistrationWithServiceComponentTest.java @@ -16,12 +16,23 @@ */ package org.apache.camel.component.consul.cloud; +import java.util.HashMap; +import java.util.Map; + import org.apache.camel.RoutesBuilder; import org.apache.camel.builder.RouteBuilder; import org.apache.camel.component.service.ServiceComponent; import org.apache.camel.impl.JndiRegistry; public class ConsulServiceRegistrationWithServiceComponentTest extends ConsulServiceRegistrationTestBase { + + protected Map getMetadata() { + return new HashMap() {{ + put("service.type", "consul"); + put("service.zone", "US"); + }}; + } + @Override protected JndiRegistry createRegistry() throws Exception { JndiRegistry registry = super.createRegistry(); @@ -35,7 +46,7 @@ protected RoutesBuilder createRouteBuilder() throws Exception { return new RouteBuilder() { @Override public void configure() throws Exception { - fromF("service:%s:jetty:http://0.0.0.0:%d/service/endpoint/", SERVICE_NAME, SERVICE_PORT) + fromF("service:%s:jetty:http://0.0.0.0:%d/service/endpoint?service.type=consul&service.zone=US", SERVICE_NAME, SERVICE_PORT) .routeId(SERVICE_ID) .routeGroup(SERVICE_NAME) .noAutoStartup() diff --git a/components/camel-http-common/src/main/java/org/apache/camel/http/common/HttpCommonEndpoint.java b/components/camel-http-common/src/main/java/org/apache/camel/http/common/HttpCommonEndpoint.java index 759dc50e3b09f..9e9e196f77e0f 100644 --- a/components/camel-http-common/src/main/java/org/apache/camel/http/common/HttpCommonEndpoint.java +++ b/components/camel-http-common/src/main/java/org/apache/camel/http/common/HttpCommonEndpoint.java @@ -18,12 +18,13 @@ import java.net.URI; import java.net.URISyntaxException; +import java.util.HashMap; +import java.util.Map; import org.apache.camel.cloud.DiscoverableService; import org.apache.camel.cloud.ServiceDefinition; import org.apache.camel.http.common.cookie.CookieHandler; import org.apache.camel.impl.DefaultEndpoint; -import org.apache.camel.impl.cloud.DefaultServiceDefinition; import org.apache.camel.spi.HeaderFilterStrategy; import org.apache.camel.spi.HeaderFilterStrategyAware; import org.apache.camel.spi.Metadata; @@ -200,14 +201,12 @@ public boolean isSingleton() { //------------------------------------------------------------------------- @Override - public ServiceDefinition getServiceDefinition() { - // Returns a partial - return DefaultServiceDefinition.builder() - .withPort(getPort()) - .addMeta(ServiceDefinition.SERVICE_META_PORT, Integer.toString(getPort())) - .addMeta(ServiceDefinition.SERVICE_META_PATH, getPath()) - .addMeta(ServiceDefinition.SERVICE_META_PROTOCOL, getProtocol()) - .build(); + public Map getServiceProperties() { + return new HashMap() {{ + put(ServiceDefinition.SERVICE_META_PORT, getPort()); + put(ServiceDefinition.SERVICE_META_PATH, getPath()); + put(ServiceDefinition.SERVICE_META_PROTOCOL, getProtocol()); + }}; } // Properties diff --git a/components/camel-service/src/main/java/org/apache/camel/component/service/ServiceComponent.java b/components/camel-service/src/main/java/org/apache/camel/component/service/ServiceComponent.java index 0f8d748d7c925..b7e249be53e61 100644 --- a/components/camel-service/src/main/java/org/apache/camel/component/service/ServiceComponent.java +++ b/components/camel-service/src/main/java/org/apache/camel/component/service/ServiceComponent.java @@ -17,16 +17,17 @@ package org.apache.camel.component.service; +import java.util.HashMap; import java.util.Map; import org.apache.camel.CamelContext; import org.apache.camel.Endpoint; +import org.apache.camel.cloud.ServiceDefinition; import org.apache.camel.cloud.ServiceRegistry; import org.apache.camel.impl.DefaultComponent; import org.apache.camel.impl.cloud.ServiceRegistryHelper; import org.apache.camel.impl.cloud.ServiceRegistrySelectors; import org.apache.camel.spi.Metadata; -import org.apache.camel.util.IntrospectionSupport; import org.apache.camel.util.ObjectHelper; import org.apache.camel.util.StringHelper; import org.apache.camel.util.URISupport; @@ -56,7 +57,7 @@ protected Endpoint createEndpoint(String uri, String remaining, Map params = new HashMap<>(); + parameters.forEach( + (k, v) -> { + if (k.startsWith(ServiceDefinition.SERVICE_META_PREFIX)) { + params.put(k, v); + } + } + ); + + // remove all the service related options so the underlying component + // does not fail because of unknown parameters + parameters.keySet().removeAll(parameters.keySet()); return new ServiceEndpoint( uri, @@ -111,23 +123,4 @@ private ServiceRegistry getServiceRegistry() { return service; } - - @SuppressWarnings("unchecked") - private ServiceParameters computeServiceParameters(Map parameters) { - // Extract service definition related parameter from uri - final String serviceId = getAndRemoveParameter(parameters, "serviceId", String.class); - final String serviceName = getAndRemoveParameter(parameters, "serviceName", String.class); - final String serviceHost = getAndRemoveParameter(parameters, "serviceHost", String.class); - final String servicePort = getAndRemoveParameter(parameters, "servicePort", String.class); - final Map serviceMeta = IntrospectionSupport.extractProperties(parameters, "serviceMeta.", true); - - ServiceParameters params = new ServiceParameters(); - ObjectHelper.ifNotEmpty(serviceId, params::setId); - ObjectHelper.ifNotEmpty(serviceName, params::setName); - ObjectHelper.ifNotEmpty(serviceHost, params::setHost); - ObjectHelper.ifNotEmpty(servicePort, params::setPort); - ObjectHelper.ifNotEmpty(serviceMeta, meta -> params.setMeta(Map.class.cast(meta))); - - return params; - } } diff --git a/components/camel-service/src/main/java/org/apache/camel/component/service/ServiceEndpoint.java b/components/camel-service/src/main/java/org/apache/camel/component/service/ServiceEndpoint.java index b99c490191038..f4bcd55e32719 100644 --- a/components/camel-service/src/main/java/org/apache/camel/component/service/ServiceEndpoint.java +++ b/components/camel-service/src/main/java/org/apache/camel/component/service/ServiceEndpoint.java @@ -16,6 +16,9 @@ */ package org.apache.camel.component.service; +import java.util.HashMap; +import java.util.Map; + import org.apache.camel.CamelContext; import org.apache.camel.Consumer; import org.apache.camel.DelegateEndpoint; @@ -29,6 +32,7 @@ import org.apache.camel.cloud.ServiceRegistry; import org.apache.camel.cluster.CamelClusterView; import org.apache.camel.impl.DefaultEndpoint; +import org.apache.camel.impl.cloud.DefaultServiceDefinition; import org.apache.camel.spi.Metadata; import org.apache.camel.spi.UriEndpoint; import org.apache.camel.spi.UriPath; @@ -50,14 +54,14 @@ public class ServiceEndpoint extends DefaultEndpoint implements DelegateEndpoint { private final Endpoint delegateEndpoint; private final ServiceRegistry serviceRegistry; - private final ServiceParameters serviceParameters; + private final Map serviceParameters; private final ServiceDefinition serviceDefinition; @UriPath(description = "The endpoint uri to expose as service") @Metadata(required = "true") private final String delegateUri; - public ServiceEndpoint(String uri, ServiceComponent component, ServiceRegistry serviceRegistry, ServiceParameters serviceParameters, String delegateUri) { + public ServiceEndpoint(String uri, ServiceComponent component, ServiceRegistry serviceRegistry, Map serviceParameters, String delegateUri) { super(uri, component); this.serviceRegistry = serviceRegistry; @@ -65,20 +69,9 @@ public ServiceEndpoint(String uri, ServiceComponent component, ServiceRegistry s this.delegateUri = delegateUri; this.delegateEndpoint = getCamelContext().getEndpoint(delegateUri); - // The service definition built from uri parameter may override parameter - // of service definition generated by an endpoint that implement DiscoverableService + // The service properties set on uri override parameter provided by a + // an endpoint of type DiscoverableService. this.serviceDefinition = computeServiceDefinition(component.getCamelContext(), delegateEndpoint); - - // Perform some basic validation, service id an name and port are mandatory, - if (serviceDefinition.getId() == null) { - throw new IllegalArgumentException("Service ID si required"); - } - if (serviceDefinition.getName() == null) { - throw new IllegalArgumentException("Service Name si required"); - } - if (serviceDefinition.getPort() <= 0) { - throw new IllegalArgumentException("Service Port should be greater than 0"); - } } @ManagedAttribute(description = "The consumer endpoint to expose as a service", mask = true) @@ -108,12 +101,15 @@ public boolean isSingleton() { } private ServiceDefinition computeServiceDefinition(CamelContext context, Endpoint delegateEndpoint) { - ServiceDefinition definition = null; + Map parameters = new HashMap<>(); if (delegateEndpoint instanceof DiscoverableService) { - definition = ((DiscoverableService)delegateEndpoint).getServiceDefinition(); + parameters.putAll(((DiscoverableService)delegateEndpoint).getServiceProperties()); } - return serviceParameters.enrich(context, definition); + parameters.putAll(serviceParameters); + parameters.computeIfAbsent(ServiceDefinition.SERVICE_META_ID, k -> context.getUuidGenerator().generateUuid()); + + return DefaultServiceDefinition.builder().from(parameters).build(); } } diff --git a/components/camel-service/src/main/java/org/apache/camel/component/service/ServiceParameters.java b/components/camel-service/src/main/java/org/apache/camel/component/service/ServiceParameters.java deleted file mode 100644 index 2bbaca4745797..0000000000000 --- a/components/camel-service/src/main/java/org/apache/camel/component/service/ServiceParameters.java +++ /dev/null @@ -1,114 +0,0 @@ -/** - * 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.camel.component.service; - -import java.util.HashMap; -import java.util.Map; - -import org.apache.camel.CamelContext; -import org.apache.camel.cloud.ServiceDefinition; -import org.apache.camel.impl.cloud.DefaultServiceDefinition; -import org.apache.camel.util.ObjectHelper; - -public class ServiceParameters { - private String id; - private String name; - private String host; - private int port; - private Map meta; - - public String getId() { - return id; - } - - public void setId(String id) { - this.id = id; - } - - public String getName() { - return name; - } - - public void setName(String name) { - this.name = name; - } - - public String getHost() { - return host; - } - - public void setHost(String host) { - this.host = host; - } - - public int getPort() { - return port; - } - - public void setPort(int port) { - this.port = port; - } - - public void setPort(String port) { - setPort(Integer.parseInt(port)); - } - - public Map getMeta() { - return meta; - } - - public void setMeta(Map meta) { - this.meta = meta; - } - - public void addMeta(String key, String value) { - if (this.meta == null) { - this.meta = new HashMap<>(); - } - - this.meta.put(key, value); - } - - public void addAllMeta(Map meta) { - if (this.meta == null) { - this.meta = new HashMap<>(); - } - - this.meta.putAll(meta); - } - - public ServiceDefinition enrich(CamelContext context, ServiceDefinition definition) { - final DefaultServiceDefinition.Builder builder = DefaultServiceDefinition.builder(); - - ObjectHelper.ifNotEmpty(definition, builder::from); - ObjectHelper.ifNotEmpty(id, builder::withId); - ObjectHelper.ifNotEmpty(name, builder::withName); - ObjectHelper.ifNotEmpty(meta, builder::addAllMeta); - ObjectHelper.ifNotEmpty(host, builder::withHost); - - if (port > 0) { - builder.withPort(port); - } - - // if the service does not have an id, we can auto-generate it - if (builder.id() == null) { - builder.withId(context.getUuidGenerator().generateUuid()); - } - - return builder.build(); - } -} From dfb11b311717cf25068728703ce1542af087cd69 Mon Sep 17 00:00:00 2001 From: lburgazzoli Date: Fri, 11 May 2018 12:33:33 +0200 Subject: [PATCH 04/20] CAMEL-12505: service-call : include ServiceDefinition metatdata when computing the final URI --- .../cloud/DefaultServiceCallProcessor.java | 75 ++++++---- ...ConsulServiceCallWithRegistrationTest.java | 133 ++++++++++++++++++ ...trationWithRoutePolicyAndMetadataTest.java | 40 ++++++ 3 files changed, 218 insertions(+), 30 deletions(-) create mode 100644 components/camel-consul/src/test/java/org/apache/camel/component/consul/cloud/ConsulServiceCallWithRegistrationTest.java create mode 100644 components/camel-consul/src/test/java/org/apache/camel/component/consul/cloud/ConsulServiceRegistrationWithRoutePolicyAndMetadataTest.java diff --git a/camel-core/src/main/java/org/apache/camel/impl/cloud/DefaultServiceCallProcessor.java b/camel-core/src/main/java/org/apache/camel/impl/cloud/DefaultServiceCallProcessor.java index 78a900e4baa04..f6b5cbfc83b4b 100644 --- a/camel-core/src/main/java/org/apache/camel/impl/cloud/DefaultServiceCallProcessor.java +++ b/camel-core/src/main/java/org/apache/camel/impl/cloud/DefaultServiceCallProcessor.java @@ -16,6 +16,8 @@ */ package org.apache.camel.impl.cloud; +import java.util.Map; + import org.apache.camel.AsyncCallback; import org.apache.camel.AsyncProcessor; import org.apache.camel.CamelContext; @@ -154,31 +156,17 @@ public void process(Exchange exchange) throws Exception { @Override public boolean process(final Exchange exchange, final AsyncCallback callback) { - Message message = exchange.getIn(); + final Message message = exchange.getIn(); // the values can be dynamic using simple language so compute those - String val = uri; - if (SimpleLanguage.hasSimpleFunction(val)) { - val = SimpleLanguage.simple(val).evaluate(exchange, String.class); - } - message.setHeader(ServiceCallConstants.SERVICE_CALL_URI, val); - - val = contextPath; - if (SimpleLanguage.hasSimpleFunction(val)) { - val = SimpleLanguage.simple(val).evaluate(exchange, String.class); - } - message.setHeader(ServiceCallConstants.SERVICE_CALL_CONTEXT_PATH, val); - - val = scheme; - if (SimpleLanguage.hasSimpleFunction(val)) { - val = SimpleLanguage.simple(val).evaluate(exchange, String.class); - } - message.setHeader(ServiceCallConstants.SERVICE_CALL_SCHEME, val); - - String serviceName = name; - if (SimpleLanguage.hasSimpleFunction(serviceName)) { - serviceName = SimpleLanguage.simple(serviceName).evaluate(exchange, String.class); - } + final String serviceName = applySimpleLanguage(name, exchange); + final String serviceUri = applySimpleLanguage(uri, exchange); + final String servicePath = applySimpleLanguage(contextPath, exchange); + final String serviceScheme = applySimpleLanguage(scheme, exchange); + + message.setHeader(ServiceCallConstants.SERVICE_CALL_URI,serviceUri); + message.setHeader(ServiceCallConstants.SERVICE_CALL_CONTEXT_PATH, servicePath); + message.setHeader(ServiceCallConstants.SERVICE_CALL_SCHEME, serviceScheme); message.setHeader(ServiceCallConstants.SERVICE_NAME, serviceName); try { @@ -189,19 +177,46 @@ public boolean process(final Exchange exchange, final AsyncCallback callback) { } } - private boolean execute(ServiceDefinition server, Exchange exchange, AsyncCallback callback) throws Exception { - String host = server.getHost(); - int port = server.getPort(); + private boolean execute(ServiceDefinition service, Exchange exchange, AsyncCallback callback) throws Exception { + final Message message = exchange.getIn(); + final String host = service.getHost(); + final int port = service.getPort(); + final Map meta = service.getMetadata(); LOGGER.debug("Service {} active at server: {}:{}", name, host, port); // set selected server as header - exchange.getIn().setHeader(ServiceCallConstants.SERVICE_HOST, host); - exchange.getIn().setHeader(ServiceCallConstants.SERVICE_PORT, port > 0 ? port : null); - exchange.getIn().setHeader(ServiceCallConstants.SERVICE_NAME, server.getName()); - exchange.getIn().setHeader(ServiceCallConstants.SERVICE_META, server.getMetadata()); + message.setHeader(ServiceCallConstants.SERVICE_HOST, host); + message.setHeader(ServiceCallConstants.SERVICE_PORT, port > 0 ? port : null); + message.setHeader(ServiceCallConstants.SERVICE_NAME, service.getName()); + message.setHeader(ServiceCallConstants.SERVICE_META, meta); + + // If context path is not set on service call definition, reuse the one from + // ServiceDefinition, if any + message.getHeaders().compute(ServiceCallConstants.SERVICE_CALL_CONTEXT_PATH, (k, v) -> + v == null ? meta.get(ServiceDefinition.SERVICE_META_PATH) : v + ); + + // If port is not set on service call definition, reuse the one from + // ServiceDefinition, if any + message.getHeaders().compute(ServiceCallConstants.SERVICE_PORT, (k, v) -> + v == null ? meta.get(ServiceDefinition.SERVICE_META_PORT) : v + ); // use the dynamic send processor to call the service return processor.process(exchange, callback); } + + /** + * This function applies the simple language to the given expression. + * + * @param expression the expression + * @param exchange the exchange + * @return the computed expression + */ + private String applySimpleLanguage(String expression, Exchange exchange) { + return SimpleLanguage.hasSimpleFunction(expression) + ? SimpleLanguage.simple(expression).evaluate(exchange, String.class) + : expression; + } } diff --git a/components/camel-consul/src/test/java/org/apache/camel/component/consul/cloud/ConsulServiceCallWithRegistrationTest.java b/components/camel-consul/src/test/java/org/apache/camel/component/consul/cloud/ConsulServiceCallWithRegistrationTest.java new file mode 100644 index 0000000000000..b0efaab4882cd --- /dev/null +++ b/components/camel-consul/src/test/java/org/apache/camel/component/consul/cloud/ConsulServiceCallWithRegistrationTest.java @@ -0,0 +1,133 @@ +/** + * 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.camel.component.consul.cloud; + +import java.util.UUID; + +import org.apache.camel.CamelContext; +import org.apache.camel.CamelExecutionException; +import org.apache.camel.builder.RouteBuilder; +import org.apache.camel.component.consul.support.ConsulTestSupport; +import org.apache.camel.impl.cloud.ServiceRegistrationRoutePolicy; +import org.junit.Test; +import org.springframework.util.SocketUtils; + +public class ConsulServiceCallWithRegistrationTest extends ConsulTestSupport { + private final static String SERVICE_HOST = "localhost"; + + // ****************************** + // Setup / tear down + // ****************************** + + @Override + public boolean isUseRouteBuilder() { + return false; + } + + @Override + protected CamelContext createCamelContext() throws Exception { + final CamelContext context = super.createCamelContext(); + + ConsulServiceRegistry registry = new ConsulServiceRegistry(); + registry.setId(context.getUuidGenerator().generateUuid()); + registry.setCamelContext(context()); + registry.setUrl(consulUrl()); + registry.setServiceHost(SERVICE_HOST); + registry.setOverrideServiceHost(true); + + context.addService(registry, true, false); + + return context; + } + + // ****************************** + // Test + // ****************************** + + @Test + public void testServiceCallSuccess() throws Exception { + final int port = SocketUtils.findAvailableTcpPort(); + final String serviceId = UUID.randomUUID().toString(); + final String serviceName = UUID.randomUUID().toString(); + + context.addRoutes(new RouteBuilder() { + @Override + public void configure() throws Exception { + // context path is derived from the the jetty endpoint. + from("direct:start") + .serviceCall() + .name(serviceName) + .component("jetty") + .defaultLoadBalancer() + .consulServiceDiscovery() + .url(consulUrl()) + .end() + .end() + .log("${body}"); + + fromF("jetty:http://%s:%d/service/path", SERVICE_HOST, port) + .routeId(serviceId) + .routeGroup(serviceName) + .routePolicy(new ServiceRegistrationRoutePolicy()) + .transform() + .simple("${in.body} on " + port); + } + }); + + context.start(); + + assertEquals("ping on " + port, template.requestBody("direct:start", "ping", String.class)); + } + + @Test(expected = CamelExecutionException.class) + public void testServiceCallFailure() throws Exception { + final int port = SocketUtils.findAvailableTcpPort(); + final String serviceId = UUID.randomUUID().toString(); + final String serviceName = UUID.randomUUID().toString(); + + context.addRoutes(new RouteBuilder() { + @Override + public void configure() throws Exception { + // context path is had coded so it should fail as it not exposed + // by jetty + from("direct:start") + .serviceCall() + .name(serviceName + "/bad/path") + .component("jetty") + .defaultLoadBalancer() + .consulServiceDiscovery() + .url(consulUrl()) + .end() + .end() + .log("${body}"); + + fromF("jetty:http://%s:%d/service/path", SERVICE_HOST, port) + .routeId(serviceId) + .routeGroup(serviceName) + .routePolicy(new ServiceRegistrationRoutePolicy()) + .transform() + .simple("${in.body} on " + port); + } + }); + + context.start(); + + template.requestBody("direct:start", "ping", String.class); + fail("Should have failed"); + } +} diff --git a/components/camel-consul/src/test/java/org/apache/camel/component/consul/cloud/ConsulServiceRegistrationWithRoutePolicyAndMetadataTest.java b/components/camel-consul/src/test/java/org/apache/camel/component/consul/cloud/ConsulServiceRegistrationWithRoutePolicyAndMetadataTest.java new file mode 100644 index 0000000000000..1f9bafd6caf8d --- /dev/null +++ b/components/camel-consul/src/test/java/org/apache/camel/component/consul/cloud/ConsulServiceRegistrationWithRoutePolicyAndMetadataTest.java @@ -0,0 +1,40 @@ +/** + * 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.camel.component.consul.cloud; + +import org.apache.camel.RoutesBuilder; +import org.apache.camel.builder.RouteBuilder; +import org.apache.camel.cloud.ServiceDefinition; +import org.apache.camel.impl.cloud.ServiceRegistrationRoutePolicy; + +public class ConsulServiceRegistrationWithRoutePolicyAndMetadataTest extends ConsulServiceRegistrationTestBase { + @Override + protected RoutesBuilder createRouteBuilder() throws Exception { + return new RouteBuilder() { + @Override + public void configure() throws Exception { + fromF("jetty:http://0.0.0.0:%d/service/endpoint", SERVICE_PORT) + .routeId(SERVICE_ID) + .routeProperty(ServiceDefinition.SERVICE_META_ID, SERVICE_ID) + .routeProperty(ServiceDefinition.SERVICE_META_NAME, SERVICE_NAME) + .routePolicy(new ServiceRegistrationRoutePolicy()) + .noAutoStartup() + .to("log:service-registry?level=INFO"); + } + }; + } +} From 8f5486d5e8920daa2d80367a0148c6f4aec5f059 Mon Sep 17 00:00:00 2001 From: lburgazzoli Date: Mon, 14 May 2018 14:49:37 +0200 Subject: [PATCH 05/20] CAMEL-12485: camel cloud : create camel-service component --- .../apache/camel/util/CollectionHelper.java | 45 ++ components/camel-consul/pom.xml | 5 + .../consul/cloud/ConsulServiceRegistry.java | 9 +- ...ConsulServiceCallWithRegistrationTest.java | 4 +- .../camel/http/common/HttpCommonEndpoint.java | 12 +- .../component/undertow/UndertowEndpoint.java | 17 +- components/camel-zookeeper/pom.xml | 10 + .../cloud/ZooKeeperServiceDiscovery.java | 5 +- .../ZooKeeperServiceDiscoveryFactory.java | 4 - .../cloud/ZooKeeperServiceRegistry.java | 394 ++++++++++++++++++ ...ZooKeeperServiceRegistryConfiguration.java | 78 ++++ .../cloud/ZooKeeperServiceDiscoveryTest.java | 2 +- .../ZooKeeperServiceRegistrationTestBase.java | 140 +++++++ ...trationWithRoutePolicyAndMetadataTest.java | 40 ++ ...egistrationWithRoutePolicyFactoryTest.java | 46 ++ ...erviceRegistrationWithRoutePolicyTest.java | 38 ++ ...eRegistrationWithServiceComponentTest.java | 56 +++ components/pom.xml | 2 +- parent/pom.xml | 2 +- .../camel-consul-starter/pom.xml | 76 ++++ ...nsulServiceDiscoveryAutoConfiguration.java | 1 - ...onsulServiceRegistryAutoConfiguration.java | 48 +++ .../ConsulServiceRegistryConfiguration.java | 76 ++++ .../ConsulClusterServiceConfiguration.java | 2 +- .../main/resources/META-INF/spring.factories | 9 +- .../ConsulServiceDiscoveryDisabledTest.java | 64 --- .../ConsulServiceDiscoveryEnabledTest.java | 65 --- .../cloud/ConsulServiceDiscoveryTest.java | 63 +++ .../cloud/ConsulServiceRegistryIT.java | 98 +++++ .../cloud/support/ConsulContainerLogger.java | 31 ++ .../cloud/support/ConsulContainerSupport.java | 46 ++ .../support/ConsulContainerWaitStrategy.java | 48 +++ .../src/test/resources/logback.xml | 1 + .../camel-zookeeper-starter/pom.xml | 7 + ...eeperServiceRegistryAutoConfiguration.java | 48 +++ ...ZooKeeperServiceRegistryConfiguration.java | 76 ++++ ...KeeperClusterServiceAutoConfiguration.java | 2 +- .../ZooKeeperClusterServiceConfiguration.java | 2 +- .../main/resources/META-INF/spring.factories | 5 +- .../cloud/ZooKeeperServiceRegistryTest.java | 264 ++++++++++++ .../src/test/resources/application.properties | 17 + .../src/test/resources/logback.xml | 42 ++ platforms/spring-boot/spring-boot-dm/pom.xml | 1 + 43 files changed, 1840 insertions(+), 161 deletions(-) create mode 100644 components/camel-zookeeper/src/main/java/org/apache/camel/component/zookeeper/cloud/ZooKeeperServiceRegistry.java create mode 100644 components/camel-zookeeper/src/main/java/org/apache/camel/component/zookeeper/cloud/ZooKeeperServiceRegistryConfiguration.java create mode 100644 components/camel-zookeeper/src/test/java/org/apache/camel/component/zookeeper/cloud/ZooKeeperServiceRegistrationTestBase.java create mode 100644 components/camel-zookeeper/src/test/java/org/apache/camel/component/zookeeper/cloud/ZooKeeperServiceRegistrationWithRoutePolicyAndMetadataTest.java create mode 100644 components/camel-zookeeper/src/test/java/org/apache/camel/component/zookeeper/cloud/ZooKeeperServiceRegistrationWithRoutePolicyFactoryTest.java create mode 100644 components/camel-zookeeper/src/test/java/org/apache/camel/component/zookeeper/cloud/ZooKeeperServiceRegistrationWithRoutePolicyTest.java create mode 100644 components/camel-zookeeper/src/test/java/org/apache/camel/component/zookeeper/cloud/ZooKeeperServiceRegistrationWithServiceComponentTest.java create mode 100644 platforms/spring-boot/components-starter/camel-consul-starter/src/main/java/org/apache/camel/component/consul/springboot/cloud/ConsulServiceRegistryAutoConfiguration.java create mode 100644 platforms/spring-boot/components-starter/camel-consul-starter/src/main/java/org/apache/camel/component/consul/springboot/cloud/ConsulServiceRegistryConfiguration.java delete mode 100644 platforms/spring-boot/components-starter/camel-consul-starter/src/test/java/org/apache/camel/component/consul/springboot/cloud/ConsulServiceDiscoveryDisabledTest.java delete mode 100644 platforms/spring-boot/components-starter/camel-consul-starter/src/test/java/org/apache/camel/component/consul/springboot/cloud/ConsulServiceDiscoveryEnabledTest.java create mode 100644 platforms/spring-boot/components-starter/camel-consul-starter/src/test/java/org/apache/camel/component/consul/springboot/cloud/ConsulServiceDiscoveryTest.java create mode 100644 platforms/spring-boot/components-starter/camel-consul-starter/src/test/java/org/apache/camel/component/consul/springboot/cloud/ConsulServiceRegistryIT.java create mode 100644 platforms/spring-boot/components-starter/camel-consul-starter/src/test/java/org/apache/camel/component/consul/springboot/cloud/support/ConsulContainerLogger.java create mode 100644 platforms/spring-boot/components-starter/camel-consul-starter/src/test/java/org/apache/camel/component/consul/springboot/cloud/support/ConsulContainerSupport.java create mode 100644 platforms/spring-boot/components-starter/camel-consul-starter/src/test/java/org/apache/camel/component/consul/springboot/cloud/support/ConsulContainerWaitStrategy.java create mode 100644 platforms/spring-boot/components-starter/camel-zookeeper-starter/src/main/java/org/apache/camel/component/zookeeper/springboot/cloud/ZooKeeperServiceRegistryAutoConfiguration.java create mode 100644 platforms/spring-boot/components-starter/camel-zookeeper-starter/src/main/java/org/apache/camel/component/zookeeper/springboot/cloud/ZooKeeperServiceRegistryConfiguration.java rename platforms/spring-boot/components-starter/camel-zookeeper-starter/src/main/java/org/apache/camel/component/zookeeper/{cluster/springboot => springboot/cluster}/ZooKeeperClusterServiceAutoConfiguration.java (97%) rename platforms/spring-boot/components-starter/camel-zookeeper-starter/src/main/java/org/apache/camel/component/zookeeper/{cluster/springboot => springboot/cluster}/ZooKeeperClusterServiceConfiguration.java (97%) create mode 100644 platforms/spring-boot/components-starter/camel-zookeeper-starter/src/test/java/org/apache/camel/component/zookeeper/springboot/cloud/ZooKeeperServiceRegistryTest.java create mode 100644 platforms/spring-boot/components-starter/camel-zookeeper-starter/src/test/resources/application.properties create mode 100644 platforms/spring-boot/components-starter/camel-zookeeper-starter/src/test/resources/logback.xml diff --git a/camel-core/src/main/java/org/apache/camel/util/CollectionHelper.java b/camel-core/src/main/java/org/apache/camel/util/CollectionHelper.java index 168e5ad86c1f2..283682d29252e 100644 --- a/camel-core/src/main/java/org/apache/camel/util/CollectionHelper.java +++ b/camel-core/src/main/java/org/apache/camel/util/CollectionHelper.java @@ -28,6 +28,7 @@ import java.util.List; import java.util.Map; import java.util.Set; +import java.util.function.Supplier; import org.w3c.dom.NodeList; @@ -170,4 +171,48 @@ public static Map unmodifiableMap(Map map) { ? Collections.emptyMap() : Collections.unmodifiableMap(new HashMap<>(map)); } + + + /** + * Build a map from varargs. + */ + public static Map mapOf(Supplier> creator, K key, V value, Object... keyVals) { + Map map = creator.get(); + map.put(key, value); + + for(int i = 0; i < keyVals.length; i += 2) { + map.put( + (K) keyVals[i], + (V) keyVals[i + 1] + ); + } + + return map; + } + + + /** + * Build an immutable map from varargs. + */ + public static Map immutableMapOf(Supplier> creator, K key, V value, Object... keyVals) { + return Collections.unmodifiableMap( + mapOf(creator, key, value, keyVals) + ); + } + + /** + * Build a map from varargs. + */ + public static Map mapOf(K key, V value, Object... keyVals) { + return mapOf(HashMap::new, key, value, keyVals); + } + + /** + * Build an immutable map from varargs. + */ + public static Map immutableMapOf(K key, V value, Object... keyVals) { + return Collections.unmodifiableMap( + mapOf(HashMap::new, key, value, keyVals) + ); + } } diff --git a/components/camel-consul/pom.xml b/components/camel-consul/pom.xml index 4d735c7f6c858..e5807ec6fc35f 100644 --- a/components/camel-consul/pom.xml +++ b/components/camel-consul/pom.xml @@ -69,6 +69,11 @@ camel-jetty test + + org.apache.camel + camel-undertow + test + org.apache.camel camel-master diff --git a/components/camel-consul/src/main/java/org/apache/camel/component/consul/cloud/ConsulServiceRegistry.java b/components/camel-consul/src/main/java/org/apache/camel/component/consul/cloud/ConsulServiceRegistry.java index 2028936de5971..9396207ee460a 100644 --- a/components/camel-consul/src/main/java/org/apache/camel/component/consul/cloud/ConsulServiceRegistry.java +++ b/components/camel-consul/src/main/java/org/apache/camel/component/consul/cloud/ConsulServiceRegistry.java @@ -228,9 +228,9 @@ protected void doStop() throws Exception { } } - // ********************************************* - // - // ********************************************* + // **************** + // Registry + // **************** @Override public void register(ServiceDefinition definition) { @@ -303,6 +303,9 @@ public void deregister(ServiceDefinition definition) { } client.agentClient().deregister(definition.getId()); + + //remove the serviceId to the list of known server + serviceList.remove(definition.getId()); } private String computeServiceHost(ServiceDefinition definition) { diff --git a/components/camel-consul/src/test/java/org/apache/camel/component/consul/cloud/ConsulServiceCallWithRegistrationTest.java b/components/camel-consul/src/test/java/org/apache/camel/component/consul/cloud/ConsulServiceCallWithRegistrationTest.java index b0efaab4882cd..2bbd5478725af 100644 --- a/components/camel-consul/src/test/java/org/apache/camel/component/consul/cloud/ConsulServiceCallWithRegistrationTest.java +++ b/components/camel-consul/src/test/java/org/apache/camel/component/consul/cloud/ConsulServiceCallWithRegistrationTest.java @@ -80,7 +80,7 @@ public void configure() throws Exception { .end() .log("${body}"); - fromF("jetty:http://%s:%d/service/path", SERVICE_HOST, port) + fromF("undertow:http://%s:%d/service/path", SERVICE_HOST, port) .routeId(serviceId) .routeGroup(serviceName) .routePolicy(new ServiceRegistrationRoutePolicy()) @@ -116,7 +116,7 @@ public void configure() throws Exception { .end() .log("${body}"); - fromF("jetty:http://%s:%d/service/path", SERVICE_HOST, port) + fromF("undertow:http://%s:%d/service/path", SERVICE_HOST, port) .routeId(serviceId) .routeGroup(serviceName) .routePolicy(new ServiceRegistrationRoutePolicy()) diff --git a/components/camel-http-common/src/main/java/org/apache/camel/http/common/HttpCommonEndpoint.java b/components/camel-http-common/src/main/java/org/apache/camel/http/common/HttpCommonEndpoint.java index 9e9e196f77e0f..e7d465e481e92 100644 --- a/components/camel-http-common/src/main/java/org/apache/camel/http/common/HttpCommonEndpoint.java +++ b/components/camel-http-common/src/main/java/org/apache/camel/http/common/HttpCommonEndpoint.java @@ -18,7 +18,6 @@ import java.net.URI; import java.net.URISyntaxException; -import java.util.HashMap; import java.util.Map; import org.apache.camel.cloud.DiscoverableService; @@ -30,6 +29,7 @@ import org.apache.camel.spi.Metadata; import org.apache.camel.spi.UriParam; import org.apache.camel.spi.UriPath; +import org.apache.camel.util.CollectionHelper; public abstract class HttpCommonEndpoint extends DefaultEndpoint implements HeaderFilterStrategyAware, DiscoverableService { @@ -202,11 +202,11 @@ public boolean isSingleton() { @Override public Map getServiceProperties() { - return new HashMap() {{ - put(ServiceDefinition.SERVICE_META_PORT, getPort()); - put(ServiceDefinition.SERVICE_META_PATH, getPath()); - put(ServiceDefinition.SERVICE_META_PROTOCOL, getProtocol()); - }}; + return CollectionHelper.immutableMapOf( + ServiceDefinition.SERVICE_META_PORT, getPort(), + ServiceDefinition.SERVICE_META_PATH, getPath(), + ServiceDefinition.SERVICE_META_PROTOCOL, getProtocol() + ); } // Properties diff --git a/components/camel-undertow/src/main/java/org/apache/camel/component/undertow/UndertowEndpoint.java b/components/camel-undertow/src/main/java/org/apache/camel/component/undertow/UndertowEndpoint.java index 23c8caabbdc66..bd6ffa8bafa51 100644 --- a/components/camel-undertow/src/main/java/org/apache/camel/component/undertow/UndertowEndpoint.java +++ b/components/camel-undertow/src/main/java/org/apache/camel/component/undertow/UndertowEndpoint.java @@ -30,6 +30,8 @@ import org.apache.camel.PollingConsumer; import org.apache.camel.Processor; import org.apache.camel.Producer; +import org.apache.camel.cloud.DiscoverableService; +import org.apache.camel.cloud.ServiceDefinition; import org.apache.camel.component.undertow.UndertowConstants.EventType; import org.apache.camel.component.undertow.handlers.CamelWebSocketHandler; import org.apache.camel.http.common.cookie.CookieHandler; @@ -40,6 +42,7 @@ import org.apache.camel.spi.UriEndpoint; import org.apache.camel.spi.UriParam; import org.apache.camel.spi.UriPath; +import org.apache.camel.util.CollectionHelper; import org.apache.camel.util.jsse.SSLContextParameters; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -52,7 +55,7 @@ */ @UriEndpoint(firstVersion = "2.16.0", scheme = "undertow", title = "Undertow", syntax = "undertow:httpURI", consumerClass = UndertowConsumer.class, label = "http,websocket", lenientProperties = true) -public class UndertowEndpoint extends DefaultEndpoint implements AsyncEndpoint, HeaderFilterStrategyAware { +public class UndertowEndpoint extends DefaultEndpoint implements AsyncEndpoint, HeaderFilterStrategyAware, DiscoverableService { private static final Logger LOG = LoggerFactory.getLogger(UndertowEndpoint.class); private UndertowComponent component; @@ -135,6 +138,18 @@ public boolean isLenientProperties() { return true; } + // Service Registration + //------------------------------------------------------------------------- + + @Override + public Map getServiceProperties() { + return CollectionHelper.immutableMapOf( + ServiceDefinition.SERVICE_META_PORT, httpURI.getPort(), + ServiceDefinition.SERVICE_META_PATH, httpURI.getPath(), + ServiceDefinition.SERVICE_META_PROTOCOL, httpURI.getScheme() + ); + } + public Exchange createExchange(HttpServerExchange httpExchange) throws Exception { Exchange exchange = createExchange(ExchangePattern.InOut); diff --git a/components/camel-zookeeper/pom.xml b/components/camel-zookeeper/pom.xml index 0aaaedc2b6995..31b04ee26a1e2 100644 --- a/components/camel-zookeeper/pom.xml +++ b/components/camel-zookeeper/pom.xml @@ -97,11 +97,21 @@ camel-jetty test + + org.apache.camel + camel-undertow + test + org.apache.camel camel-master test + + org.apache.camel + camel-service + test + org.apache.camel camel-test-spring diff --git a/components/camel-zookeeper/src/main/java/org/apache/camel/component/zookeeper/cloud/ZooKeeperServiceDiscovery.java b/components/camel-zookeeper/src/main/java/org/apache/camel/component/zookeeper/cloud/ZooKeeperServiceDiscovery.java index 3a7dde9070b46..a46ce098e61f0 100644 --- a/components/camel-zookeeper/src/main/java/org/apache/camel/component/zookeeper/cloud/ZooKeeperServiceDiscovery.java +++ b/components/camel-zookeeper/src/main/java/org/apache/camel/component/zookeeper/cloud/ZooKeeperServiceDiscovery.java @@ -118,9 +118,8 @@ public List getServices(String name) { Map meta = new HashMap<>(); ObjectHelper.ifNotEmpty(si.getPayload(), meta::putAll); - meta.put("service_name", si.getName()); - meta.put("service_id", si.getId()); - meta.put("service_type", si.getServiceType().name()); + meta.putIfAbsent(ServiceDefinition.SERVICE_META_NAME, si.getName()); + meta.putIfAbsent(ServiceDefinition.SERVICE_META_ID, si.getId()); return new DefaultServiceDefinition( si.getName(), diff --git a/components/camel-zookeeper/src/main/java/org/apache/camel/component/zookeeper/cloud/ZooKeeperServiceDiscoveryFactory.java b/components/camel-zookeeper/src/main/java/org/apache/camel/component/zookeeper/cloud/ZooKeeperServiceDiscoveryFactory.java index b3b0604f58e26..25c29f9f253f4 100644 --- a/components/camel-zookeeper/src/main/java/org/apache/camel/component/zookeeper/cloud/ZooKeeperServiceDiscoveryFactory.java +++ b/components/camel-zookeeper/src/main/java/org/apache/camel/component/zookeeper/cloud/ZooKeeperServiceDiscoveryFactory.java @@ -167,10 +167,6 @@ public void setConnectionTimeoutUnit(TimeUnit connectionTimeoutUnit) { configuration.setConnectionTimeoutUnit(connectionTimeoutUnit); } - public ZooKeeperCuratorConfiguration copy() { - return configuration.copy(); - } - public List getAuthInfoList() { return configuration.getAuthInfoList(); } diff --git a/components/camel-zookeeper/src/main/java/org/apache/camel/component/zookeeper/cloud/ZooKeeperServiceRegistry.java b/components/camel-zookeeper/src/main/java/org/apache/camel/component/zookeeper/cloud/ZooKeeperServiceRegistry.java new file mode 100644 index 0000000000000..89fb08dc15843 --- /dev/null +++ b/components/camel-zookeeper/src/main/java/org/apache/camel/component/zookeeper/cloud/ZooKeeperServiceRegistry.java @@ -0,0 +1,394 @@ +/** + * 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.camel.component.zookeeper.cloud; + +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.Objects; +import java.util.Set; +import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.TimeUnit; + +import org.apache.camel.cloud.ServiceDefinition; +import org.apache.camel.component.zookeeper.ZooKeeperCuratorHelper; +import org.apache.camel.impl.cloud.AbstractServiceRegistry; +import org.apache.camel.util.ObjectHelper; +import org.apache.curator.RetryPolicy; +import org.apache.curator.framework.AuthInfo; +import org.apache.curator.framework.CuratorFramework; +import org.apache.curator.x.discovery.ServiceDiscovery; +import org.apache.curator.x.discovery.ServiceInstance; +import org.codehaus.jackson.map.annotate.JsonRootName; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +public class ZooKeeperServiceRegistry extends AbstractServiceRegistry { + private static final Logger LOGGER = LoggerFactory.getLogger(ZooKeeperServiceRegistry.class); + + private final Set serviceList; + private final boolean managedInstance; + private ZooKeeperServiceRegistryConfiguration configuration; + private CuratorFramework curator; + private ServiceDiscovery serviceDiscovery; + + public ZooKeeperServiceRegistry() { + this.serviceList = ConcurrentHashMap.newKeySet(); + this.configuration = new ZooKeeperServiceRegistryConfiguration(); + this.curator = configuration.getCuratorFramework(); + this.managedInstance = Objects.isNull(curator); + } + + public ZooKeeperServiceRegistry(ZooKeeperServiceRegistryConfiguration configuration) { + this.serviceList = ConcurrentHashMap.newKeySet(); + this.configuration = configuration.copy(); + this.curator = configuration.getCuratorFramework(); + this.managedInstance = Objects.isNull(curator); + } + + // **************** + // Properties + // **************** + + public ZooKeeperServiceRegistryConfiguration getConfiguration() { + return configuration; + } + + public void setConfiguration(ZooKeeperServiceRegistryConfiguration configuration) { + this.configuration = configuration.copy(); + } + + public CuratorFramework getCuratorFramework() { + return configuration.getCuratorFramework(); + } + + public void setCuratorFramework(CuratorFramework curatorFramework) { + configuration.setCuratorFramework(curatorFramework); + } + + public List getNodes() { + return configuration.getNodes(); + } + + public void setNodes(String nodes) { + configuration.setNodes(nodes); + } + + public void setNodes(List nodes) { + configuration.setNodes(nodes); + } + + public String getNamespace() { + return configuration.getNamespace(); + } + + public void setNamespace(String namespace) { + configuration.setNamespace(namespace); + } + + public long getReconnectBaseSleepTime() { + return configuration.getReconnectBaseSleepTime(); + } + + public void setReconnectBaseSleepTime(long reconnectBaseSleepTime) { + configuration.setReconnectBaseSleepTime(reconnectBaseSleepTime); + } + + public void setReconnectBaseSleepTime(long reconnectBaseSleepTime, TimeUnit reconnectBaseSleepTimeUnit) { + configuration.setReconnectBaseSleepTime(reconnectBaseSleepTime, reconnectBaseSleepTimeUnit); + } + + public TimeUnit getReconnectBaseSleepTimeUnit() { + return configuration.getReconnectBaseSleepTimeUnit(); + } + + public void setReconnectBaseSleepTimeUnit(TimeUnit reconnectBaseSleepTimeUnit) { + configuration.setReconnectBaseSleepTimeUnit(reconnectBaseSleepTimeUnit); + } + + public long getReconnectMaxSleepTime() { + return configuration.getReconnectMaxSleepTime(); + } + + public void setReconnectMaxSleepTime(long reconnectMaxSleepTime) { + configuration.setReconnectMaxSleepTime(reconnectMaxSleepTime); + } + + public void setReconnectMaxSleepTime(long reconnectMaxSleepTime, TimeUnit reconnectBaseSleepTimeUnit) { + configuration.setReconnectMaxSleepTime(reconnectMaxSleepTime, reconnectBaseSleepTimeUnit); + } + + public TimeUnit getReconnectMaxSleepTimeUnit() { + return configuration.getReconnectMaxSleepTimeUnit(); + } + + public void setReconnectMaxSleepTimeUnit(TimeUnit reconnectMaxSleepTimeUnit) { + configuration.setReconnectMaxSleepTimeUnit(reconnectMaxSleepTimeUnit); + } + + public int getReconnectMaxRetries() { + return configuration.getReconnectMaxRetries(); + } + + public void setReconnectMaxRetries(int reconnectMaxRetries) { + configuration.setReconnectMaxRetries(reconnectMaxRetries); + } + + public long getSessionTimeout() { + return configuration.getSessionTimeout(); + } + + public void setSessionTimeout(long sessionTimeout) { + configuration.setSessionTimeout(sessionTimeout); + } + + public void setSessionTimeout(long sessionTimeout, TimeUnit sessionTimeoutUnit) { + configuration.setSessionTimeout(sessionTimeout, sessionTimeoutUnit); + } + + public TimeUnit getSessionTimeoutUnit() { + return configuration.getSessionTimeoutUnit(); + } + + public void setSessionTimeoutUnit(TimeUnit sessionTimeoutUnit) { + configuration.setSessionTimeoutUnit(sessionTimeoutUnit); + } + + public long getConnectionTimeout() { + return configuration.getConnectionTimeout(); + } + + public void setConnectionTimeout(long connectionTimeout) { + configuration.setConnectionTimeout(connectionTimeout); + } + + public void setConnectionTimeout(long connectionTimeout, TimeUnit connectionTimeotUnit) { + configuration.setConnectionTimeout(connectionTimeout, connectionTimeotUnit); + } + + public TimeUnit getConnectionTimeoutUnit() { + return configuration.getConnectionTimeoutUnit(); + } + + public void setConnectionTimeoutUnit(TimeUnit connectionTimeoutUnit) { + configuration.setConnectionTimeoutUnit(connectionTimeoutUnit); + } + + public List getAuthInfoList() { + return configuration.getAuthInfoList(); + } + + public void setAuthInfoList(List authInfoList) { + configuration.setAuthInfoList(authInfoList); + } + + public long getMaxCloseWait() { + return configuration.getMaxCloseWait(); + } + + public void setMaxCloseWait(long maxCloseWait) { + configuration.setMaxCloseWait(maxCloseWait); + } + + public TimeUnit getMaxCloseWaitUnit() { + return configuration.getMaxCloseWaitUnit(); + } + + public void setMaxCloseWaitUnit(TimeUnit maxCloseWaitUnit) { + configuration.setMaxCloseWaitUnit(maxCloseWaitUnit); + } + + public RetryPolicy getRetryPolicy() { + return configuration.getRetryPolicy(); + } + + public void setRetryPolicy(RetryPolicy retryPolicy) { + configuration.setRetryPolicy(retryPolicy); + } + + public String getBasePath() { + return configuration.getBasePath(); + } + + public void setBasePath(String basePath) { + configuration.setBasePath(basePath); + } + + public boolean isDeregisterServicesOnStop() { + return configuration.isDeregisterServicesOnStop(); + } + + public void setDeregisterServicesOnStop(boolean deregisterServicesOnStop) { + configuration.setDeregisterServicesOnStop(deregisterServicesOnStop); + } + + public boolean isOverrideServiceHost() { + return configuration.isOverrideServiceHost(); + } + + public void setOverrideServiceHost(boolean overrideServiceHost) { + configuration.setOverrideServiceHost(overrideServiceHost); + } + + public String getServiceHost() { + return configuration.getServiceHost(); + } + + public void setServiceHost(String serviceHost) { + configuration.setServiceHost(serviceHost); + } + + // **************** + // Lifecycle + // **************** + + @Override + protected void doStart() throws Exception { + if (curator == null) { + // Validation + ObjectHelper.notNull(getCamelContext(), "Camel Context"); + ObjectHelper.notNull(configuration.getBasePath(), "ZooKeeper base path"); + + LOGGER.debug("Starting ZooKeeper Curator with namespace '{}', nodes: '{}'", + configuration.getNamespace(), + String.join(",", configuration.getNodes()) + ); + + curator = ZooKeeperCuratorHelper.createCurator(configuration); + curator.start(); + } + + if (serviceDiscovery == null) { + // Validation + ObjectHelper.notNull(configuration.getBasePath(), "ZooKeeper base path"); + + LOGGER.debug("Starting ZooKeeper ServiceDiscoveryBuilder with base path '{}'", + configuration.getBasePath() + ); + + serviceDiscovery = ZooKeeperCuratorHelper.createServiceDiscovery(configuration, curator, MetaData.class); + serviceDiscovery.start(); + } + } + + @Override + protected void doStop() throws Exception { + if (serviceDiscovery != null) { + try { + if (configuration.isDeregisterServicesOnStop()) { + for (String serviceName: serviceDiscovery.queryForNames()) { + for (ServiceInstance serviceInstance: serviceDiscovery.queryForInstances(serviceName)) { + if (serviceList.contains(serviceInstance.getId())) { + serviceDiscovery.unregisterService(serviceInstance); + + // remove the serviceId to the list of known server + serviceList.remove(serviceInstance.getId()); + } + } + } + } + + serviceDiscovery.close(); + } catch (Exception e) { + LOGGER.warn("Error closing Curator ServiceDiscovery", e); + } + } + + if (curator != null && managedInstance) { + curator.close(); + } + } + + // **************** + // Registry + // **************** + + @Override + public void register(ServiceDefinition definition) { + if (definition.getId() == null) { + throw new IllegalArgumentException("Service ID must be defined (definition=" + definition + ")"); + } + if (definition.getName() == null) { + throw new IllegalArgumentException("Service Name must be defined (definition=" + definition + ")"); + } + + try { + ServiceInstance instance = ServiceInstance.builder() + .address(computeServiceHost(definition)) + .port(definition.getPort()) + .name(definition.getName()) + .id(definition.getId()) + .payload(new MetaData(definition.getMetadata())) + .build(); + + serviceDiscovery.registerService(instance); + + // add the serviceId to the list of known server + serviceList.add(definition.getId()); + } catch (Exception e) { + LOGGER.warn("", e); + } + } + + @Override + public void deregister(ServiceDefinition definition) { + if (definition.getId() == null) { + throw new IllegalArgumentException("Service ID must be defined (definition=" + definition + ")"); + } + if (definition.getName() == null) { + throw new IllegalArgumentException("Service Name must be defined (definition=" + definition + ")"); + } + + try { + for (ServiceInstance serviceInstance: serviceDiscovery.queryForInstances(definition.getName())) { + if (Objects.equals(serviceInstance.getId(), definition.getId())) { + serviceDiscovery.unregisterService(serviceInstance); + + // remove the serviceId to the list of known server + serviceList.remove((serviceInstance.getId())); + } + } + } catch (Exception e) { + LOGGER.warn("", e); + } + } + + // ********************************************* + // Helpers + // ********************************************* + + + private String computeServiceHost(ServiceDefinition definition) { + String host = definition.getHost(); + + if (configuration.isOverrideServiceHost() && configuration.getServiceHost() != null) { + host = configuration.getServiceHost(); + } + + return ObjectHelper.notNull(host, "service host"); + } + + @JsonRootName("meta") + public static final class MetaData extends HashMap { + public MetaData() { + } + + public MetaData(Map meta) { + super(meta); + } + } +} diff --git a/components/camel-zookeeper/src/main/java/org/apache/camel/component/zookeeper/cloud/ZooKeeperServiceRegistryConfiguration.java b/components/camel-zookeeper/src/main/java/org/apache/camel/component/zookeeper/cloud/ZooKeeperServiceRegistryConfiguration.java new file mode 100644 index 0000000000000..fd4d6aa5f7f4f --- /dev/null +++ b/components/camel-zookeeper/src/main/java/org/apache/camel/component/zookeeper/cloud/ZooKeeperServiceRegistryConfiguration.java @@ -0,0 +1,78 @@ +/** + * 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.camel.component.zookeeper.cloud; + +import org.apache.camel.RuntimeCamelException; +import org.apache.camel.component.zookeeper.ZooKeeperCuratorConfiguration; + +public class ZooKeeperServiceRegistryConfiguration extends ZooKeeperCuratorConfiguration { + /** + * Should we remove all the registered services know by this registry on stop? + */ + private boolean deregisterServicesOnStop = true; + + /** + * Should we override the service host if given ? + */ + private boolean overrideServiceHost = true; + + /** + * Service host. + */ + private String serviceHost; + + // *********************************************** + // Properties + // *********************************************** + + public boolean isDeregisterServicesOnStop() { + return deregisterServicesOnStop; + } + + public void setDeregisterServicesOnStop(boolean deregisterServicesOnStop) { + this.deregisterServicesOnStop = deregisterServicesOnStop; + } + + public boolean isOverrideServiceHost() { + return overrideServiceHost; + } + + public void setOverrideServiceHost(boolean overrideServiceHost) { + this.overrideServiceHost = overrideServiceHost; + } + + public String getServiceHost() { + return serviceHost; + } + + public void setServiceHost(String serviceHost) { + this.serviceHost = serviceHost; + } + + // *********************************************** + // + // *********************************************** + + @Override + public ZooKeeperServiceRegistryConfiguration copy() { + try { + return (ZooKeeperServiceRegistryConfiguration)super.clone(); + } catch (CloneNotSupportedException e) { + throw new RuntimeCamelException(e); + } + } +} diff --git a/components/camel-zookeeper/src/test/java/org/apache/camel/component/zookeeper/cloud/ZooKeeperServiceDiscoveryTest.java b/components/camel-zookeeper/src/test/java/org/apache/camel/component/zookeeper/cloud/ZooKeeperServiceDiscoveryTest.java index e6d55511c99ec..82eca2d52443f 100644 --- a/components/camel-zookeeper/src/test/java/org/apache/camel/component/zookeeper/cloud/ZooKeeperServiceDiscoveryTest.java +++ b/components/camel-zookeeper/src/test/java/org/apache/camel/component/zookeeper/cloud/ZooKeeperServiceDiscoveryTest.java @@ -92,7 +92,7 @@ public void testServiceDiscovery() throws Exception { i -> { return i.getPort() == service.getPort() && i.getAddress().equals(service.getHost()) - && i.getId().equals(service.getMetadata().get("service_id")) + && i.getId().equals(service.getMetadata().get(ServiceDefinition.SERVICE_META_ID)) && i.getName().equals(service.getName()); } ).count() diff --git a/components/camel-zookeeper/src/test/java/org/apache/camel/component/zookeeper/cloud/ZooKeeperServiceRegistrationTestBase.java b/components/camel-zookeeper/src/test/java/org/apache/camel/component/zookeeper/cloud/ZooKeeperServiceRegistrationTestBase.java new file mode 100644 index 0000000000000..99b08a1713cb5 --- /dev/null +++ b/components/camel-zookeeper/src/test/java/org/apache/camel/component/zookeeper/cloud/ZooKeeperServiceRegistrationTestBase.java @@ -0,0 +1,140 @@ +/** + * 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.camel.component.zookeeper.cloud; + +import java.util.Collection; +import java.util.Collections; +import java.util.Map; +import java.util.UUID; + +import org.apache.camel.CamelContext; +import org.apache.camel.cloud.ServiceDefinition; +import org.apache.camel.component.zookeeper.ZooKeeperTestSupport; +import org.apache.camel.test.AvailablePortFinder; +import org.apache.camel.test.junit4.CamelTestSupport; +import org.apache.curator.framework.CuratorFramework; +import org.apache.curator.framework.CuratorFrameworkFactory; +import org.apache.curator.retry.ExponentialBackoffRetry; +import org.apache.curator.utils.CloseableUtils; +import org.apache.curator.x.discovery.ServiceDiscovery; +import org.apache.curator.x.discovery.ServiceDiscoveryBuilder; +import org.apache.curator.x.discovery.ServiceInstance; +import org.apache.curator.x.discovery.details.JsonInstanceSerializer; +import org.junit.Test; + +public abstract class ZooKeeperServiceRegistrationTestBase extends CamelTestSupport { + protected final static String SERVICE_ID = UUID.randomUUID().toString(); + protected final static String SERVICE_NAME = "my-service"; + protected final static String SERVICE_HOST = "localhost"; + protected static final String SERVICE_PATH = "/camel"; + protected final static int SERVICE_PORT = AvailablePortFinder.getNextAvailable(); + protected final static int SERVER_PORT = AvailablePortFinder.getNextAvailable(); + + protected ZooKeeperTestSupport.TestZookeeperServer server; + protected CuratorFramework curator; + protected ServiceDiscovery discovery; + + // *********************** + // Lifecycle + // *********************** + + @Override + protected void doPreSetup() throws Exception { + super.doPreSetup(); + + server = new ZooKeeperTestSupport.TestZookeeperServer(SERVER_PORT, true); + ZooKeeperTestSupport.waitForServerUp("127.0.0.1:" + SERVER_PORT, 1000); + + curator = CuratorFrameworkFactory.builder() + .connectString("127.0.0.1:" + SERVER_PORT) + .retryPolicy(new ExponentialBackoffRetry(1000, 3)) + .build(); + + discovery = ServiceDiscoveryBuilder.builder(ZooKeeperServiceRegistry.MetaData.class) + .client(curator) + .basePath(SERVICE_PATH) + .serializer(new JsonInstanceSerializer<>(ZooKeeperServiceRegistry.MetaData.class)) + .build(); + + curator.start(); + discovery.start(); + } + + @Override + public void tearDown() throws Exception { + super.tearDown(); + + CloseableUtils.closeQuietly(discovery); + CloseableUtils.closeQuietly(curator); + + server.shutdown(); + } + + + protected Map getMetadata() { + return Collections.emptyMap(); + } + + @Override + protected CamelContext createCamelContext() throws Exception { + final CamelContext context = super.createCamelContext(); + + ZooKeeperServiceRegistry registry = new ZooKeeperServiceRegistry(); + registry.setId(context.getUuidGenerator().generateUuid()); + registry.setCamelContext(context()); + registry.setNodes("localhost:" + SERVER_PORT); + registry.setBasePath(SERVICE_PATH); + registry.setServiceHost(SERVICE_HOST); + registry.setOverrideServiceHost(true); + + context.addService(registry, true, false); + + return context; + } + + @Test + public void testRegistrationFromRoute() throws Exception { + + // the service should not be registered as the route is not running + assertTrue(discovery.queryForInstances(SERVICE_NAME).isEmpty()); + + // let start the route + context().startRoute(SERVICE_ID); + + // check that service has been registered + Collection> services = discovery.queryForInstances(SERVICE_NAME); + assertEquals(1, services.size()); + + ServiceInstance instance = services.iterator().next(); + assertEquals(SERVICE_PORT, (int)instance.getPort()); + assertEquals("localhost", instance.getAddress()); + assertEquals("http", instance.getPayload().get(ServiceDefinition.SERVICE_META_PROTOCOL)); + assertEquals("/service/endpoint", instance.getPayload().get(ServiceDefinition.SERVICE_META_PATH)); + + getMetadata().forEach( + (k, v) -> { + assertEquals(v, instance.getPayload().get(k)); + } + ); + + // let stop the route + context().stopRoute(SERVICE_ID); + + // the service should be removed once the route is stopped + assertTrue(discovery.queryForInstances(SERVICE_NAME).isEmpty()); + } +} diff --git a/components/camel-zookeeper/src/test/java/org/apache/camel/component/zookeeper/cloud/ZooKeeperServiceRegistrationWithRoutePolicyAndMetadataTest.java b/components/camel-zookeeper/src/test/java/org/apache/camel/component/zookeeper/cloud/ZooKeeperServiceRegistrationWithRoutePolicyAndMetadataTest.java new file mode 100644 index 0000000000000..f7d7dc6e9e530 --- /dev/null +++ b/components/camel-zookeeper/src/test/java/org/apache/camel/component/zookeeper/cloud/ZooKeeperServiceRegistrationWithRoutePolicyAndMetadataTest.java @@ -0,0 +1,40 @@ +/** + * 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.camel.component.zookeeper.cloud; + +import org.apache.camel.RoutesBuilder; +import org.apache.camel.builder.RouteBuilder; +import org.apache.camel.cloud.ServiceDefinition; +import org.apache.camel.impl.cloud.ServiceRegistrationRoutePolicy; + +public class ZooKeeperServiceRegistrationWithRoutePolicyAndMetadataTest extends ZooKeeperServiceRegistrationTestBase { + @Override + protected RoutesBuilder createRouteBuilder() throws Exception { + return new RouteBuilder() { + @Override + public void configure() throws Exception { + fromF("jetty:http://0.0.0.0:%d/service/endpoint", SERVICE_PORT) + .routeId(SERVICE_ID) + .routeProperty(ServiceDefinition.SERVICE_META_ID, SERVICE_ID) + .routeProperty(ServiceDefinition.SERVICE_META_NAME, SERVICE_NAME) + .routePolicy(new ServiceRegistrationRoutePolicy()) + .noAutoStartup() + .to("log:service-registry?level=INFO"); + } + }; + } +} diff --git a/components/camel-zookeeper/src/test/java/org/apache/camel/component/zookeeper/cloud/ZooKeeperServiceRegistrationWithRoutePolicyFactoryTest.java b/components/camel-zookeeper/src/test/java/org/apache/camel/component/zookeeper/cloud/ZooKeeperServiceRegistrationWithRoutePolicyFactoryTest.java new file mode 100644 index 0000000000000..6e902247cfc4d --- /dev/null +++ b/components/camel-zookeeper/src/test/java/org/apache/camel/component/zookeeper/cloud/ZooKeeperServiceRegistrationWithRoutePolicyFactoryTest.java @@ -0,0 +1,46 @@ +/** + * 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.camel.component.zookeeper.cloud; + +import org.apache.camel.CamelContext; +import org.apache.camel.RoutesBuilder; +import org.apache.camel.builder.RouteBuilder; +import org.apache.camel.impl.cloud.ServiceRegistrationRoutePolicyFactory; + +public class ZooKeeperServiceRegistrationWithRoutePolicyFactoryTest extends ZooKeeperServiceRegistrationTestBase { + @Override + protected CamelContext createCamelContext() throws Exception { + CamelContext context = super.createCamelContext(); + context.addRoutePolicyFactory(new ServiceRegistrationRoutePolicyFactory()); + + return context; + } + + @Override + protected RoutesBuilder createRouteBuilder() throws Exception { + return new RouteBuilder() { + @Override + public void configure() throws Exception { + fromF("jetty:http://0.0.0.0:%d/service/endpoint", SERVICE_PORT) + .routeId(SERVICE_ID) + .routeGroup(SERVICE_NAME) + .noAutoStartup() + .to("log:service-registry?level=INFO"); + } + }; + } +} diff --git a/components/camel-zookeeper/src/test/java/org/apache/camel/component/zookeeper/cloud/ZooKeeperServiceRegistrationWithRoutePolicyTest.java b/components/camel-zookeeper/src/test/java/org/apache/camel/component/zookeeper/cloud/ZooKeeperServiceRegistrationWithRoutePolicyTest.java new file mode 100644 index 0000000000000..aa52d94078535 --- /dev/null +++ b/components/camel-zookeeper/src/test/java/org/apache/camel/component/zookeeper/cloud/ZooKeeperServiceRegistrationWithRoutePolicyTest.java @@ -0,0 +1,38 @@ +/** + * 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.camel.component.zookeeper.cloud; + +import org.apache.camel.RoutesBuilder; +import org.apache.camel.builder.RouteBuilder; +import org.apache.camel.impl.cloud.ServiceRegistrationRoutePolicy; + +public class ZooKeeperServiceRegistrationWithRoutePolicyTest extends ZooKeeperServiceRegistrationTestBase { + @Override + protected RoutesBuilder createRouteBuilder() throws Exception { + return new RouteBuilder() { + @Override + public void configure() throws Exception { + fromF("jetty:http://0.0.0.0:%d/service/endpoint", SERVICE_PORT) + .routeId(SERVICE_ID) + .routeGroup(SERVICE_NAME) + .routePolicy(new ServiceRegistrationRoutePolicy()) + .noAutoStartup() + .to("log:service-registry?level=INFO"); + } + }; + } +} diff --git a/components/camel-zookeeper/src/test/java/org/apache/camel/component/zookeeper/cloud/ZooKeeperServiceRegistrationWithServiceComponentTest.java b/components/camel-zookeeper/src/test/java/org/apache/camel/component/zookeeper/cloud/ZooKeeperServiceRegistrationWithServiceComponentTest.java new file mode 100644 index 0000000000000..23d70b79c358e --- /dev/null +++ b/components/camel-zookeeper/src/test/java/org/apache/camel/component/zookeeper/cloud/ZooKeeperServiceRegistrationWithServiceComponentTest.java @@ -0,0 +1,56 @@ +/** + * 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.camel.component.zookeeper.cloud; + +import java.util.HashMap; +import java.util.Map; + +import org.apache.camel.RoutesBuilder; +import org.apache.camel.builder.RouteBuilder; +import org.apache.camel.component.service.ServiceComponent; +import org.apache.camel.impl.JndiRegistry; + +public class ZooKeeperServiceRegistrationWithServiceComponentTest extends ZooKeeperServiceRegistrationTestBase { + + protected Map getMetadata() { + return new HashMap() {{ + put("service.type", "zookeeper"); + }}; + } + + @Override + protected JndiRegistry createRegistry() throws Exception { + JndiRegistry registry = super.createRegistry(); + registry.bind("service", new ServiceComponent()); + + return registry; + } + + @Override + protected RoutesBuilder createRouteBuilder() throws Exception { + return new RouteBuilder() { + @Override + public void configure() throws Exception { + fromF("service:%s:jetty:http://0.0.0.0:%d/service/endpoint?service.type=zookeeper", SERVICE_NAME, SERVICE_PORT) + .routeId(SERVICE_ID) + .routeGroup(SERVICE_NAME) + .noAutoStartup() + .to("log:service-registry?level=INFO"); + } + }; + } +} diff --git a/components/pom.xml b/components/pom.xml index 8651841f4582a..f52a66858e099 100644 --- a/components/pom.xml +++ b/components/pom.xml @@ -56,6 +56,7 @@ camel-jetty-common camel-jetty camel-jetty9 + camel-undertow camel-cxf camel-cxf-transport camel-jms @@ -302,7 +303,6 @@ camel-thrift camel-twilio camel-twitter - camel-undertow camel-univocity-parsers camel-urlrewrite camel-velocity diff --git a/parent/pom.xml b/parent/pom.xml index b21be66b725e1..fd31a764f15bb 100644 --- a/parent/pom.xml +++ b/parent/pom.xml @@ -680,7 +680,7 @@ 4.0.8 1.2.1_1 1.2.1 - 1.7.2 + 1.7.3 6.14.2 2.1.1 1.18 diff --git a/platforms/spring-boot/components-starter/camel-consul-starter/pom.xml b/platforms/spring-boot/components-starter/camel-consul-starter/pom.xml index 476c55b770c10..f8787754752ff 100644 --- a/platforms/spring-boot/components-starter/camel-consul-starter/pom.xml +++ b/platforms/spring-boot/components-starter/camel-consul-starter/pom.xml @@ -39,6 +39,20 @@ camel-consul ${project.version} + + + org.apache.camel + camel-jetty + ${project.version} + test + + + + org.testcontainers + testcontainers + ${testcontainers-version} + test + org.apache.camel @@ -50,4 +64,66 @@ + + + + consul-integration-tests-docker-file + + + /var/run/docker.sock + + + + + + org.apache.maven.plugins + maven-failsafe-plugin + + + true + + + + + + integration-test + verify + + + + + + + + + + consul-integration-tests-docker-env + + + env.DOCKER_HOST + + + + + + org.apache.maven.plugins + maven-failsafe-plugin + + + true + + + + + + integration-test + verify + + + + + + + + diff --git a/platforms/spring-boot/components-starter/camel-consul-starter/src/main/java/org/apache/camel/component/consul/springboot/cloud/ConsulServiceDiscoveryAutoConfiguration.java b/platforms/spring-boot/components-starter/camel-consul-starter/src/main/java/org/apache/camel/component/consul/springboot/cloud/ConsulServiceDiscoveryAutoConfiguration.java index ec4e9d31e8686..79e2134f26abd 100644 --- a/platforms/spring-boot/components-starter/camel-consul-starter/src/main/java/org/apache/camel/component/consul/springboot/cloud/ConsulServiceDiscoveryAutoConfiguration.java +++ b/platforms/spring-boot/components-starter/camel-consul-starter/src/main/java/org/apache/camel/component/consul/springboot/cloud/ConsulServiceDiscoveryAutoConfiguration.java @@ -23,7 +23,6 @@ import org.apache.camel.CamelContext; import org.apache.camel.cloud.ServiceDiscovery; import org.apache.camel.component.consul.cloud.ConsulServiceDiscoveryFactory; -import org.apache.camel.component.consul.springboot.ConsulComponentConfiguration; import org.apache.camel.model.cloud.springboot.ConsulServiceCallServiceDiscoveryConfigurationCommon; import org.apache.camel.model.cloud.springboot.ConsulServiceCallServiceDiscoveryConfigurationProperties; import org.apache.camel.spring.boot.CamelAutoConfiguration; diff --git a/platforms/spring-boot/components-starter/camel-consul-starter/src/main/java/org/apache/camel/component/consul/springboot/cloud/ConsulServiceRegistryAutoConfiguration.java b/platforms/spring-boot/components-starter/camel-consul-starter/src/main/java/org/apache/camel/component/consul/springboot/cloud/ConsulServiceRegistryAutoConfiguration.java new file mode 100644 index 0000000000000..e44af748cb2ca --- /dev/null +++ b/platforms/spring-boot/components-starter/camel-consul-starter/src/main/java/org/apache/camel/component/consul/springboot/cloud/ConsulServiceRegistryAutoConfiguration.java @@ -0,0 +1,48 @@ +/** + * 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.camel.component.consul.springboot.cloud; + +import org.apache.camel.component.consul.cloud.ConsulServiceRegistry; +import org.apache.camel.spring.boot.CamelAutoConfiguration; +import org.apache.camel.util.IntrospectionSupport; +import org.springframework.beans.factory.config.ConfigurableBeanFactory; +import org.springframework.boot.autoconfigure.AutoConfigureBefore; +import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; +import org.springframework.boot.context.properties.EnableConfigurationProperties; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.context.annotation.Scope; + +@Configuration +@AutoConfigureBefore(CamelAutoConfiguration.class) +@ConditionalOnProperty(prefix = "camel.component.consul.service-registry", name = "enabled") +@EnableConfigurationProperties(ConsulServiceRegistryConfiguration.class) +public class ConsulServiceRegistryAutoConfiguration { + + @Bean(name = "consul-service-registry") + @Scope(ConfigurableBeanFactory.SCOPE_SINGLETON) + public ConsulServiceRegistry consulServiceRegistry(ConsulServiceRegistryConfiguration configuration) throws Exception { + ConsulServiceRegistry service = new ConsulServiceRegistry(); + + IntrospectionSupport.setProperties( + service, + IntrospectionSupport.getNonNullProperties(configuration) + ); + + return service; + } +} diff --git a/platforms/spring-boot/components-starter/camel-consul-starter/src/main/java/org/apache/camel/component/consul/springboot/cloud/ConsulServiceRegistryConfiguration.java b/platforms/spring-boot/components-starter/camel-consul-starter/src/main/java/org/apache/camel/component/consul/springboot/cloud/ConsulServiceRegistryConfiguration.java new file mode 100644 index 0000000000000..d9e457d0a5ebb --- /dev/null +++ b/platforms/spring-boot/components-starter/camel-consul-starter/src/main/java/org/apache/camel/component/consul/springboot/cloud/ConsulServiceRegistryConfiguration.java @@ -0,0 +1,76 @@ +/** + * 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.camel.component.consul.springboot.cloud; + +import java.util.Map; + +import org.springframework.boot.context.properties.ConfigurationProperties; + +@ConfigurationProperties(prefix = "camel.component.consul.service-registry") +public class ConsulServiceRegistryConfiguration extends org.apache.camel.component.consul.cloud.ConsulServiceRegistryConfiguration { + /** + * Sets if the consul service registry should be enabled or not, default is false. + */ + private boolean enabled; + + /** + * Service Registry ID + */ + private String id; + + /** + * Custom service attributes. + */ + private Map attributes; + + /** + * Service lookup order/priority. + */ + private Integer order; + + public boolean isEnabled() { + return enabled; + } + + public void setEnabled(boolean enabled) { + this.enabled = enabled; + } + + public String getId() { + return id; + } + + public void setId(String id) { + this.id = id; + } + + public Map getAttributes() { + return attributes; + } + + public void setAttributes(Map attributes) { + this.attributes = attributes; + } + + public Integer getOrder() { + return order; + } + + public void setOrder(Integer order) { + this.order = order; + } +} diff --git a/platforms/spring-boot/components-starter/camel-consul-starter/src/main/java/org/apache/camel/component/consul/springboot/cluster/ConsulClusterServiceConfiguration.java b/platforms/spring-boot/components-starter/camel-consul-starter/src/main/java/org/apache/camel/component/consul/springboot/cluster/ConsulClusterServiceConfiguration.java index 6cb1bf9f2e20b..01600ffb7691b 100644 --- a/platforms/spring-boot/components-starter/camel-consul-starter/src/main/java/org/apache/camel/component/consul/springboot/cluster/ConsulClusterServiceConfiguration.java +++ b/platforms/spring-boot/components-starter/camel-consul-starter/src/main/java/org/apache/camel/component/consul/springboot/cluster/ConsulClusterServiceConfiguration.java @@ -24,7 +24,7 @@ @ConfigurationProperties(prefix = "camel.component.consul.cluster.service") public class ConsulClusterServiceConfiguration extends ConsulClusterConfiguration { /** - * Sets if the zookeeper cluster service should be enabled or not, default is false. + * Sets if the consul cluster service should be enabled or not, default is false. */ private boolean enabled; diff --git a/platforms/spring-boot/components-starter/camel-consul-starter/src/main/resources/META-INF/spring.factories b/platforms/spring-boot/components-starter/camel-consul-starter/src/main/resources/META-INF/spring.factories index 1e63a8184b550..a8142d0ac5353 100644 --- a/platforms/spring-boot/components-starter/camel-consul-starter/src/main/resources/META-INF/spring.factories +++ b/platforms/spring-boot/components-starter/camel-consul-starter/src/main/resources/META-INF/spring.factories @@ -15,7 +15,8 @@ ## limitations under the License. ## --------------------------------------------------------------------------- org.springframework.boot.autoconfigure.EnableAutoConfiguration=\ -org.apache.camel.component.consul.springboot.ConsulComponentAutoConfiguration,\ -org.apache.camel.component.consul.springboot.cloud.ConsulServiceDiscoveryAutoConfiguration,\ -org.apache.camel.component.consul.springboot.cluster.ConsulClusterServiceAutoConfiguration,\ -org.apache.camel.component.consul.springboot.health.HealthCheckRepositoryAutoConfiguration \ No newline at end of file + org.apache.camel.component.consul.springboot.ConsulComponentAutoConfiguration,\ + org.apache.camel.component.consul.springboot.cloud.ConsulServiceDiscoveryAutoConfiguration,\ + org.apache.camel.component.consul.springboot.cloud.ConsulServiceRegistryAutoConfiguration,\ + org.apache.camel.component.consul.springboot.cluster.ConsulClusterServiceAutoConfiguration,\ + org.apache.camel.component.consul.springboot.health.HealthCheckRepositoryAutoConfiguration \ No newline at end of file diff --git a/platforms/spring-boot/components-starter/camel-consul-starter/src/test/java/org/apache/camel/component/consul/springboot/cloud/ConsulServiceDiscoveryDisabledTest.java b/platforms/spring-boot/components-starter/camel-consul-starter/src/test/java/org/apache/camel/component/consul/springboot/cloud/ConsulServiceDiscoveryDisabledTest.java deleted file mode 100644 index 01cc2529a3929..0000000000000 --- a/platforms/spring-boot/components-starter/camel-consul-starter/src/test/java/org/apache/camel/component/consul/springboot/cloud/ConsulServiceDiscoveryDisabledTest.java +++ /dev/null @@ -1,64 +0,0 @@ -/** - * 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.camel.component.consul.springboot.cloud; - -import java.util.Map; - -import org.apache.camel.cloud.ServiceDiscovery; -import org.apache.camel.model.cloud.springboot.ConsulServiceCallServiceDiscoveryConfigurationProperties; -import org.junit.Assert; -import org.junit.Test; -import org.junit.runner.RunWith; -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.boot.autoconfigure.SpringBootApplication; -import org.springframework.boot.test.context.SpringBootTest; -import org.springframework.context.ApplicationContext; -import org.springframework.context.annotation.Configuration; -import org.springframework.test.annotation.DirtiesContext; -import org.springframework.test.context.junit4.SpringRunner; - -@RunWith(SpringRunner.class) -@DirtiesContext -@SpringBootApplication -@SpringBootTest( - classes = { - ConsulServiceDiscoveryEnabledTest.TestConfiguration.class - }, - properties = { - "debug=false", - "camel.cloud.consul.service-discovery.enabled=false" -}) -public class ConsulServiceDiscoveryDisabledTest { - @Autowired - ApplicationContext context; - - @Test - public void testConfiguration() throws Exception { - Map beans; - - beans = context.getBeansOfType(ConsulServiceCallServiceDiscoveryConfigurationProperties.class); - Assert.assertTrue(beans.isEmpty()); - - beans = context.getBeansOfType(ServiceDiscovery.class); - Assert.assertFalse(beans.isEmpty()); - Assert.assertFalse(beans.containsKey("consul-service-discovery")); - } - - @Configuration - public static class TestConfiguration { - } -} diff --git a/platforms/spring-boot/components-starter/camel-consul-starter/src/test/java/org/apache/camel/component/consul/springboot/cloud/ConsulServiceDiscoveryEnabledTest.java b/platforms/spring-boot/components-starter/camel-consul-starter/src/test/java/org/apache/camel/component/consul/springboot/cloud/ConsulServiceDiscoveryEnabledTest.java deleted file mode 100644 index cedfde5747d12..0000000000000 --- a/platforms/spring-boot/components-starter/camel-consul-starter/src/test/java/org/apache/camel/component/consul/springboot/cloud/ConsulServiceDiscoveryEnabledTest.java +++ /dev/null @@ -1,65 +0,0 @@ -/** - * 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.camel.component.consul.springboot.cloud; - -import java.util.Map; - -import org.apache.camel.cloud.ServiceDiscovery; -import org.apache.camel.model.cloud.springboot.ConsulServiceCallServiceDiscoveryConfigurationProperties; -import org.junit.Assert; -import org.junit.Test; -import org.junit.runner.RunWith; -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.boot.autoconfigure.SpringBootApplication; -import org.springframework.boot.test.context.SpringBootTest; -import org.springframework.context.ApplicationContext; -import org.springframework.context.annotation.Configuration; -import org.springframework.test.annotation.DirtiesContext; -import org.springframework.test.context.junit4.SpringRunner; - -@RunWith(SpringRunner.class) -@DirtiesContext -@SpringBootApplication -@SpringBootTest( - classes = { - ConsulServiceDiscoveryEnabledTest.TestConfiguration.class - }, - properties = { - "debug=false", - "camel.cloud.consul.service-discovery.enabled=true" -}) -public class ConsulServiceDiscoveryEnabledTest { - @Autowired - ApplicationContext context; - - @Test - public void testConfiguration() throws Exception { - Map beans; - - beans = context.getBeansOfType(ConsulServiceCallServiceDiscoveryConfigurationProperties.class); - Assert.assertFalse(beans.isEmpty()); - Assert.assertEquals(1, beans.size()); - - beans = context.getBeansOfType(ServiceDiscovery.class); - Assert.assertFalse(beans.isEmpty()); - Assert.assertTrue(beans.containsKey("consul-service-discovery")); - } - - @Configuration - public static class TestConfiguration { - } -} diff --git a/platforms/spring-boot/components-starter/camel-consul-starter/src/test/java/org/apache/camel/component/consul/springboot/cloud/ConsulServiceDiscoveryTest.java b/platforms/spring-boot/components-starter/camel-consul-starter/src/test/java/org/apache/camel/component/consul/springboot/cloud/ConsulServiceDiscoveryTest.java new file mode 100644 index 0000000000000..1d2b30dd6abaf --- /dev/null +++ b/platforms/spring-boot/components-starter/camel-consul-starter/src/test/java/org/apache/camel/component/consul/springboot/cloud/ConsulServiceDiscoveryTest.java @@ -0,0 +1,63 @@ +/** + * 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.camel.component.consul.springboot.cloud; + +import org.apache.camel.cloud.ServiceDiscovery; +import org.apache.camel.model.cloud.springboot.ConsulServiceCallServiceDiscoveryConfigurationProperties; +import org.junit.Test; +import org.springframework.boot.autoconfigure.EnableAutoConfiguration; +import org.springframework.boot.test.context.runner.ApplicationContextRunner; +import org.springframework.context.annotation.Configuration; + +import static org.assertj.core.api.Assertions.assertThat; + +public class ConsulServiceDiscoveryTest { + @Test + public void testConsulServiceDiscoveryDisabled() { + new ApplicationContextRunner() + .withUserConfiguration(TestConfiguration.class) + .withPropertyValues( + "spring.main.banner-mode=off", + "camel.cloud.consul.service-discovery.enabled=false") + .run( + context -> { + assertThat(context).doesNotHaveBean(ConsulServiceCallServiceDiscoveryConfigurationProperties.class); + assertThat(context).getBeans(ServiceDiscovery.class).doesNotContainKeys("consul-service-discovery"); + } + ); + } + + @Test + public void testConsulServiceDiscoveryEnabled() { + new ApplicationContextRunner() + .withUserConfiguration(TestConfiguration.class) + .withPropertyValues( + "spring.main.banner-mode=off", + "camel.cloud.consul.service-discovery.enabled=true") + .run( + context -> { + assertThat(context).hasSingleBean(ConsulServiceCallServiceDiscoveryConfigurationProperties.class); + assertThat(context).getBeans(ServiceDiscovery.class).containsKeys("consul-service-discovery"); + } + ); + } + + @EnableAutoConfiguration + @Configuration + public static class TestConfiguration { + } +} diff --git a/platforms/spring-boot/components-starter/camel-consul-starter/src/test/java/org/apache/camel/component/consul/springboot/cloud/ConsulServiceRegistryIT.java b/platforms/spring-boot/components-starter/camel-consul-starter/src/test/java/org/apache/camel/component/consul/springboot/cloud/ConsulServiceRegistryIT.java new file mode 100644 index 0000000000000..b3045d92144d1 --- /dev/null +++ b/platforms/spring-boot/components-starter/camel-consul-starter/src/test/java/org/apache/camel/component/consul/springboot/cloud/ConsulServiceRegistryIT.java @@ -0,0 +1,98 @@ +/** + * 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.camel.component.consul.springboot.cloud; + +import java.util.List; +import java.util.UUID; + +import com.orbitz.consul.Consul; +import com.orbitz.consul.model.catalog.CatalogService; +import org.apache.camel.CamelContext; +import org.apache.camel.cloud.ServiceRegistry; +import org.apache.camel.component.consul.springboot.cloud.support.ConsulContainerSupport; +import org.apache.camel.impl.cloud.DefaultServiceDefinition; +import org.junit.Rule; +import org.junit.Test; +import org.springframework.boot.autoconfigure.EnableAutoConfiguration; +import org.springframework.boot.test.context.runner.ApplicationContextRunner; +import org.springframework.context.annotation.Configuration; +import org.springframework.util.SocketUtils; +import org.testcontainers.containers.GenericContainer; + +import static org.assertj.core.api.Assertions.assertThat; + +public class ConsulServiceRegistryIT { + protected static final String SERVICE_ID = UUID.randomUUID().toString(); + protected static final String SERVICE_NAME = "my-service"; + protected static final String SERVICE_HOST = "localhost"; + protected static final int SERVICE_PORT = SocketUtils.findAvailableTcpPort(); + + @Rule + public GenericContainer container = ConsulContainerSupport.consulContainer(); + + @Test + public void testServiceRegistry() { + new ApplicationContextRunner() + .withUserConfiguration(TestConfiguration.class) + .withPropertyValues( + "debug=false", + "spring.main.banner-mode=OFF", + "spring.application.name=" + UUID.randomUUID().toString(), + "camel.component.consul.service-registry.enabled=true", + "camel.component.consul.service-registry.url=" + ConsulContainerSupport.consulUrl(container), + "camel.component.consul.service-registry.id=" + UUID.randomUUID().toString(), + "camel.component.consul.service-registry.service-host=localhost") + .run( + context -> { + assertThat(context).hasSingleBean(CamelContext.class); + assertThat(context).hasSingleBean(ServiceRegistry.class); + + final CamelContext camelContext = context.getBean(CamelContext.class); + final ServiceRegistry serviceRegistry = camelContext.hasService(ServiceRegistry.class); + + assertThat(serviceRegistry).isNotNull(); + + serviceRegistry.register( + DefaultServiceDefinition.builder() + .withHost(SERVICE_HOST) + .withPort(SERVICE_PORT) + .withName(SERVICE_NAME) + .withId(SERVICE_ID) + .build() + ); + + final Consul client = Consul.builder().withUrl(ConsulContainerSupport.consulUrl(container)).build(); + final List services = client.catalogClient().getService(SERVICE_NAME).getResponse(); + + assertThat(services).hasSize(1); + assertThat(services).first().hasFieldOrPropertyWithValue("serviceId", SERVICE_ID); + assertThat(services).first().hasFieldOrPropertyWithValue("serviceName", SERVICE_NAME); + assertThat(services).first().hasFieldOrPropertyWithValue("serviceAddress", SERVICE_HOST); + assertThat(services).first().hasFieldOrPropertyWithValue("servicePort", SERVICE_PORT); + } + ); + } + + // ************************************* + // Config + // ************************************* + + @EnableAutoConfiguration + @Configuration + public static class TestConfiguration { + } +} diff --git a/platforms/spring-boot/components-starter/camel-consul-starter/src/test/java/org/apache/camel/component/consul/springboot/cloud/support/ConsulContainerLogger.java b/platforms/spring-boot/components-starter/camel-consul-starter/src/test/java/org/apache/camel/component/consul/springboot/cloud/support/ConsulContainerLogger.java new file mode 100644 index 0000000000000..533c3cf5a3ae2 --- /dev/null +++ b/platforms/spring-boot/components-starter/camel-consul-starter/src/test/java/org/apache/camel/component/consul/springboot/cloud/support/ConsulContainerLogger.java @@ -0,0 +1,31 @@ +/** + * 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.camel.component.consul.springboot.cloud.support; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.testcontainers.containers.output.Slf4jLogConsumer; + +public final class ConsulContainerLogger extends Slf4jLogConsumer { + private static final Logger LOGGER = LoggerFactory.getLogger(ConsulContainerLogger.class); + + public ConsulContainerLogger() { + super(LOGGER); + + withPrefix("consul"); + } +} diff --git a/platforms/spring-boot/components-starter/camel-consul-starter/src/test/java/org/apache/camel/component/consul/springboot/cloud/support/ConsulContainerSupport.java b/platforms/spring-boot/components-starter/camel-consul-starter/src/test/java/org/apache/camel/component/consul/springboot/cloud/support/ConsulContainerSupport.java new file mode 100644 index 0000000000000..9a101de14ff27 --- /dev/null +++ b/platforms/spring-boot/components-starter/camel-consul-starter/src/test/java/org/apache/camel/component/consul/springboot/cloud/support/ConsulContainerSupport.java @@ -0,0 +1,46 @@ +/** + * 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.camel.component.consul.springboot.cloud.support; + +import com.orbitz.consul.Consul; +import org.testcontainers.containers.GenericContainer; + +public final class ConsulContainerSupport { + private ConsulContainerSupport() { + } + + public static GenericContainer consulContainer() { + return new GenericContainer("consul:1.0.0") + .withExposedPorts(Consul.DEFAULT_HTTP_PORT) + .waitingFor(new ConsulContainerWaitStrategy()) + .withLogConsumer(new ConsulContainerLogger()) + .withCommand( + "agent", + "-dev", + "-server", + "-bootstrap", + "-client", + "0.0.0.0", + "-log-level", + "trace" + ); + } + + public static String consulUrl(GenericContainer container) { + return String.format("http://%s:%d", container.getContainerIpAddress(), container.getMappedPort(Consul.DEFAULT_HTTP_PORT)); + } +} diff --git a/platforms/spring-boot/components-starter/camel-consul-starter/src/test/java/org/apache/camel/component/consul/springboot/cloud/support/ConsulContainerWaitStrategy.java b/platforms/spring-boot/components-starter/camel-consul-starter/src/test/java/org/apache/camel/component/consul/springboot/cloud/support/ConsulContainerWaitStrategy.java new file mode 100644 index 0000000000000..79633b075afd9 --- /dev/null +++ b/platforms/spring-boot/components-starter/camel-consul-starter/src/test/java/org/apache/camel/component/consul/springboot/cloud/support/ConsulContainerWaitStrategy.java @@ -0,0 +1,48 @@ +/** + * 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.camel.component.consul.springboot.cloud.support; + +import java.util.concurrent.TimeUnit; +import java.util.concurrent.TimeoutException; + +import com.github.dockerjava.api.DockerClient; +import org.testcontainers.DockerClientFactory; +import org.testcontainers.containers.ContainerLaunchException; +import org.testcontainers.containers.output.WaitingConsumer; +import org.testcontainers.containers.wait.strategy.AbstractWaitStrategy; +import org.testcontainers.utility.LogUtils; + +public final class ConsulContainerWaitStrategy extends AbstractWaitStrategy { + @Override + protected void waitUntilReady() { + final DockerClient client = DockerClientFactory.instance().client(); + final WaitingConsumer waitingConsumer = new WaitingConsumer(); + + LogUtils.followOutput(client, waitStrategyTarget.getContainerId(), waitingConsumer); + + try { + waitingConsumer.waitUntil( + f -> f.getUtf8String().contains("Synced node info"), + startupTimeout.getSeconds(), + TimeUnit.SECONDS, + 1 + ); + } catch (TimeoutException e) { + throw new ContainerLaunchException("Timed out"); + } + } +} \ No newline at end of file diff --git a/platforms/spring-boot/components-starter/camel-consul-starter/src/test/resources/logback.xml b/platforms/spring-boot/components-starter/camel-consul-starter/src/test/resources/logback.xml index 9b17d4a06b190..972efdc05c5d8 100644 --- a/platforms/spring-boot/components-starter/camel-consul-starter/src/test/resources/logback.xml +++ b/platforms/spring-boot/components-starter/camel-consul-starter/src/test/resources/logback.xml @@ -35,6 +35,7 @@ + diff --git a/platforms/spring-boot/components-starter/camel-zookeeper-starter/pom.xml b/platforms/spring-boot/components-starter/camel-zookeeper-starter/pom.xml index 7c274d2ee785d..f6996a0facbc3 100644 --- a/platforms/spring-boot/components-starter/camel-zookeeper-starter/pom.xml +++ b/platforms/spring-boot/components-starter/camel-zookeeper-starter/pom.xml @@ -39,6 +39,13 @@ camel-zookeeper ${project.version} + + + org.apache.camel + camel-jetty + ${project.version} + test + org.apache.camel diff --git a/platforms/spring-boot/components-starter/camel-zookeeper-starter/src/main/java/org/apache/camel/component/zookeeper/springboot/cloud/ZooKeeperServiceRegistryAutoConfiguration.java b/platforms/spring-boot/components-starter/camel-zookeeper-starter/src/main/java/org/apache/camel/component/zookeeper/springboot/cloud/ZooKeeperServiceRegistryAutoConfiguration.java new file mode 100644 index 0000000000000..16d97225873a5 --- /dev/null +++ b/platforms/spring-boot/components-starter/camel-zookeeper-starter/src/main/java/org/apache/camel/component/zookeeper/springboot/cloud/ZooKeeperServiceRegistryAutoConfiguration.java @@ -0,0 +1,48 @@ +/** + * 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.camel.component.zookeeper.springboot.cloud; + +import org.apache.camel.component.zookeeper.cloud.ZooKeeperServiceRegistry; +import org.apache.camel.spring.boot.CamelAutoConfiguration; +import org.apache.camel.util.IntrospectionSupport; +import org.springframework.beans.factory.config.ConfigurableBeanFactory; +import org.springframework.boot.autoconfigure.AutoConfigureBefore; +import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; +import org.springframework.boot.context.properties.EnableConfigurationProperties; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.context.annotation.Scope; + +@Configuration +@AutoConfigureBefore(CamelAutoConfiguration.class) +@ConditionalOnProperty(prefix = "camel.component.zookeeper.service-registry", name = "enabled") +@EnableConfigurationProperties(ZooKeeperServiceRegistryConfiguration.class) +public class ZooKeeperServiceRegistryAutoConfiguration { + + @Bean(name = "zookeeper-service-registry") + @Scope(ConfigurableBeanFactory.SCOPE_SINGLETON) + public ZooKeeperServiceRegistry zookeeperServiceRegistry(ZooKeeperServiceRegistryConfiguration configuration) throws Exception { + ZooKeeperServiceRegistry service = new ZooKeeperServiceRegistry(); + + IntrospectionSupport.setProperties( + service, + IntrospectionSupport.getNonNullProperties(configuration) + ); + + return service; + } +} diff --git a/platforms/spring-boot/components-starter/camel-zookeeper-starter/src/main/java/org/apache/camel/component/zookeeper/springboot/cloud/ZooKeeperServiceRegistryConfiguration.java b/platforms/spring-boot/components-starter/camel-zookeeper-starter/src/main/java/org/apache/camel/component/zookeeper/springboot/cloud/ZooKeeperServiceRegistryConfiguration.java new file mode 100644 index 0000000000000..699e8c8bef6b9 --- /dev/null +++ b/platforms/spring-boot/components-starter/camel-zookeeper-starter/src/main/java/org/apache/camel/component/zookeeper/springboot/cloud/ZooKeeperServiceRegistryConfiguration.java @@ -0,0 +1,76 @@ +/** + * 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.camel.component.zookeeper.springboot.cloud; + +import java.util.Map; + +import org.springframework.boot.context.properties.ConfigurationProperties; + +@ConfigurationProperties(prefix = "camel.component.zookeeper.service-registry") +public class ZooKeeperServiceRegistryConfiguration extends org.apache.camel.component.zookeeper.cloud.ZooKeeperServiceRegistryConfiguration { + /** + * Sets if the zookeeper service registry should be enabled or not, default is false. + */ + private boolean enabled; + + /** + * Service Registry ID + */ + private String id; + + /** + * Custom service attributes. + */ + private Map attributes; + + /** + * Service lookup order/priority. + */ + private Integer order; + + public boolean isEnabled() { + return enabled; + } + + public void setEnabled(boolean enabled) { + this.enabled = enabled; + } + + public String getId() { + return id; + } + + public void setId(String id) { + this.id = id; + } + + public Map getAttributes() { + return attributes; + } + + public void setAttributes(Map attributes) { + this.attributes = attributes; + } + + public Integer getOrder() { + return order; + } + + public void setOrder(Integer order) { + this.order = order; + } +} diff --git a/platforms/spring-boot/components-starter/camel-zookeeper-starter/src/main/java/org/apache/camel/component/zookeeper/cluster/springboot/ZooKeeperClusterServiceAutoConfiguration.java b/platforms/spring-boot/components-starter/camel-zookeeper-starter/src/main/java/org/apache/camel/component/zookeeper/springboot/cluster/ZooKeeperClusterServiceAutoConfiguration.java similarity index 97% rename from platforms/spring-boot/components-starter/camel-zookeeper-starter/src/main/java/org/apache/camel/component/zookeeper/cluster/springboot/ZooKeeperClusterServiceAutoConfiguration.java rename to platforms/spring-boot/components-starter/camel-zookeeper-starter/src/main/java/org/apache/camel/component/zookeeper/springboot/cluster/ZooKeeperClusterServiceAutoConfiguration.java index c34d82e2eaa7b..5017b92fb4994 100644 --- a/platforms/spring-boot/components-starter/camel-zookeeper-starter/src/main/java/org/apache/camel/component/zookeeper/cluster/springboot/ZooKeeperClusterServiceAutoConfiguration.java +++ b/platforms/spring-boot/components-starter/camel-zookeeper-starter/src/main/java/org/apache/camel/component/zookeeper/springboot/cluster/ZooKeeperClusterServiceAutoConfiguration.java @@ -14,7 +14,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package org.apache.camel.component.zookeeper.cluster.springboot; +package org.apache.camel.component.zookeeper.springboot.cluster; import org.apache.camel.cluster.CamelClusterService; import org.apache.camel.component.zookeeper.cluster.ZooKeeperClusterService; diff --git a/platforms/spring-boot/components-starter/camel-zookeeper-starter/src/main/java/org/apache/camel/component/zookeeper/cluster/springboot/ZooKeeperClusterServiceConfiguration.java b/platforms/spring-boot/components-starter/camel-zookeeper-starter/src/main/java/org/apache/camel/component/zookeeper/springboot/cluster/ZooKeeperClusterServiceConfiguration.java similarity index 97% rename from platforms/spring-boot/components-starter/camel-zookeeper-starter/src/main/java/org/apache/camel/component/zookeeper/cluster/springboot/ZooKeeperClusterServiceConfiguration.java rename to platforms/spring-boot/components-starter/camel-zookeeper-starter/src/main/java/org/apache/camel/component/zookeeper/springboot/cluster/ZooKeeperClusterServiceConfiguration.java index 1feb5af3e5fa0..fffa14ea4766a 100644 --- a/platforms/spring-boot/components-starter/camel-zookeeper-starter/src/main/java/org/apache/camel/component/zookeeper/cluster/springboot/ZooKeeperClusterServiceConfiguration.java +++ b/platforms/spring-boot/components-starter/camel-zookeeper-starter/src/main/java/org/apache/camel/component/zookeeper/springboot/cluster/ZooKeeperClusterServiceConfiguration.java @@ -14,7 +14,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package org.apache.camel.component.zookeeper.cluster.springboot; +package org.apache.camel.component.zookeeper.springboot.cluster; import java.util.Map; diff --git a/platforms/spring-boot/components-starter/camel-zookeeper-starter/src/main/resources/META-INF/spring.factories b/platforms/spring-boot/components-starter/camel-zookeeper-starter/src/main/resources/META-INF/spring.factories index f3a7b4b2fe8c9..383172c95a789 100644 --- a/platforms/spring-boot/components-starter/camel-zookeeper-starter/src/main/resources/META-INF/spring.factories +++ b/platforms/spring-boot/components-starter/camel-zookeeper-starter/src/main/resources/META-INF/spring.factories @@ -15,5 +15,6 @@ ## limitations under the License. ## --------------------------------------------------------------------------- org.springframework.boot.autoconfigure.EnableAutoConfiguration=\ -org.apache.camel.component.zookeeper.springboot.ZooKeeperComponentAutoConfiguration,\ -org.apache.camel.component.zookeeper.cluster.springboot.ZooKeeperClusterServiceAutoConfiguration + org.apache.camel.component.zookeeper.springboot.ZooKeeperComponentAutoConfiguration,\ + org.apache.camel.component.zookeeper.springboot.cloud.ZooKeeperServiceRegistryAutoConfiguration,\ + org.apache.camel.component.zookeeper.springboot.cluster.ZooKeeperClusterServiceAutoConfiguration diff --git a/platforms/spring-boot/components-starter/camel-zookeeper-starter/src/test/java/org/apache/camel/component/zookeeper/springboot/cloud/ZooKeeperServiceRegistryTest.java b/platforms/spring-boot/components-starter/camel-zookeeper-starter/src/test/java/org/apache/camel/component/zookeeper/springboot/cloud/ZooKeeperServiceRegistryTest.java new file mode 100644 index 0000000000000..7243d61272bf8 --- /dev/null +++ b/platforms/spring-boot/components-starter/camel-zookeeper-starter/src/test/java/org/apache/camel/component/zookeeper/springboot/cloud/ZooKeeperServiceRegistryTest.java @@ -0,0 +1,264 @@ +/** + * 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.camel.component.zookeeper.springboot.cloud; + +import java.io.BufferedReader; +import java.io.File; +import java.io.IOException; +import java.io.InputStreamReader; +import java.io.OutputStream; +import java.net.InetSocketAddress; +import java.net.Socket; +import java.util.Collection; +import java.util.UUID; + +import org.apache.camel.CamelContext; +import org.apache.camel.cloud.ServiceRegistry; +import org.apache.camel.component.zookeeper.cloud.ZooKeeperServiceRegistry; +import org.apache.camel.impl.cloud.DefaultServiceDefinition; +import org.apache.camel.test.AvailablePortFinder; +import org.apache.camel.util.IOHelper; +import org.apache.curator.framework.CuratorFramework; +import org.apache.curator.framework.CuratorFrameworkFactory; +import org.apache.curator.retry.ExponentialBackoffRetry; +import org.apache.curator.utils.CloseableUtils; +import org.apache.curator.x.discovery.ServiceDiscovery; +import org.apache.curator.x.discovery.ServiceDiscoveryBuilder; +import org.apache.curator.x.discovery.ServiceInstance; +import org.apache.curator.x.discovery.details.JsonInstanceSerializer; +import org.apache.zookeeper.server.NIOServerCnxnFactory; +import org.apache.zookeeper.server.ZooKeeperServer; +import org.apache.zookeeper.server.persistence.FileTxnSnapLog; +import org.junit.Rule; +import org.junit.Test; +import org.junit.rules.TemporaryFolder; +import org.junit.rules.TestName; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.boot.autoconfigure.EnableAutoConfiguration; +import org.springframework.boot.test.context.runner.ApplicationContextRunner; +import org.springframework.context.annotation.Configuration; +import org.springframework.util.SocketUtils; + +import static org.assertj.core.api.Assertions.assertThat; + +public class ZooKeeperServiceRegistryTest { + private static final Logger LOGGER = LoggerFactory.getLogger(ZooKeeperServiceRegistryTest.class); + + private static final String SERVICE_PATH = "/camel"; + private static final String SERVICE_ID = UUID.randomUUID().toString(); + private static final String SERVICE_NAME = "my-service"; + private static final String SERVICE_HOST = "localhost"; + private static final int SERVICE_PORT = SocketUtils.findAvailableTcpPort(); + + @Rule + public final TestName testName = new TestName(); + @Rule + public final TemporaryFolder temporaryFolder = new TemporaryFolder(); + + @Test + public void testServiceRegistry() throws Exception { + final ZooKeeperTestServer zkServer = new ZooKeeperTestServer(temporaryFolder.newFolder()); + zkServer.start(); + + final ZooKeeperTestClient zkClient = new ZooKeeperTestClient(zkServer.serverList()); + zkClient.start(); + + try { + new ApplicationContextRunner() + .withUserConfiguration(TestConfiguration.class) + .withPropertyValues( + "debug=false", + "spring.main.banner-mode=OFF", + "spring.application.name=" + UUID.randomUUID().toString(), + "camel.component.zookeeper.service-registry.enabled=true", + "camel.component.zookeeper.service-registry.nodes=" + zkServer.serverList(), + "camel.component.zookeeper.service-registry.id=" + UUID.randomUUID().toString(), + "camel.component.zookeeper.service-registry.base-path=" + SERVICE_PATH, + "camel.component.zookeeper.service-registry.service-host=localhost") + .run( + context -> { + assertThat(context).hasSingleBean(CamelContext.class); + assertThat(context).hasSingleBean(ServiceRegistry.class); + + final CamelContext camelContext = context.getBean(CamelContext.class); + final ServiceRegistry serviceRegistry = camelContext.hasService(ServiceRegistry.class); + + assertThat(serviceRegistry).isNotNull(); + + serviceRegistry.register( + DefaultServiceDefinition.builder() + .withHost(SERVICE_HOST) + .withPort(SERVICE_PORT) + .withName(SERVICE_NAME) + .withId(SERVICE_ID) + .build() + ); + + final Collection> services = zkClient.discovery().queryForInstances(SERVICE_NAME); + + assertThat(services).hasSize(1); + assertThat(services).first().hasFieldOrPropertyWithValue("id", SERVICE_ID); + assertThat(services).first().hasFieldOrPropertyWithValue("name", SERVICE_NAME); + assertThat(services).first().hasFieldOrPropertyWithValue("address", SERVICE_HOST); + assertThat(services).first().hasFieldOrPropertyWithValue("port", SERVICE_PORT); + } + ); + } finally { + zkClient.stop(); + zkServer.stop(); + } + } + + // ************************************* + // Config + // ************************************* + + @EnableAutoConfiguration + @Configuration + public static class TestConfiguration { + } + + // ************************************* + // Helpers + // ************************************* + + public static class ZooKeeperTestServer { + private NIOServerCnxnFactory connectionFactory; + private ZooKeeperServer zkServer; + + public ZooKeeperTestServer(File root) throws Exception { + zkServer = new ZooKeeperServer(); + + File dataDir = new File(root, "log"); + File snapDir = new File(root, "data"); + FileTxnSnapLog ftxn = new FileTxnSnapLog(dataDir, snapDir); + + zkServer.setTxnLogFactory(ftxn); + zkServer.setTickTime(1000); + + connectionFactory = new NIOServerCnxnFactory(); + connectionFactory.configure(new InetSocketAddress("localhost", AvailablePortFinder.getNextAvailable()), 0); + connectionFactory.startup(zkServer); + } + + public String serverList() { + return "localhost:" + connectionFactory.getLocalPort(); + } + + private String send4LetterWord(String hp, String cmd) throws IOException { + String split[] = hp.split(":"); + String host = split[0]; + int port; + try { + port = Integer.parseInt(split[1]); + } catch (RuntimeException e) { + throw new RuntimeException("Problem parsing " + hp + e.toString()); + } + + Socket sock = new Socket(host, port); + BufferedReader reader = null; + try { + OutputStream outstream = sock.getOutputStream(); + outstream.write(cmd.getBytes()); + outstream.flush(); + + reader = IOHelper.buffered(new InputStreamReader(sock.getInputStream())); + StringBuilder sb = new StringBuilder(); + String line; + while ((line = reader.readLine()) != null) { + sb.append(line + "\n"); + } + return sb.toString(); + } finally { + sock.close(); + if (reader != null) { + reader.close(); + } + } + } + + public void start() throws Exception { + long start = System.currentTimeMillis(); + while (true) { + try { + String result = send4LetterWord(serverList(), "stat"); + if (result.startsWith("Zookeeper version:")) { + return; + } + } catch (IOException e) { + LOGGER.info("server {} not up {}", LOGGER, e); + } + + if (System.currentTimeMillis() > start + 1000) { + break; + } + try { + Thread.sleep(250); + } catch (InterruptedException e) { + // ignore + } + } + } + + public void stop() throws Exception { + connectionFactory.shutdown(); + connectionFactory.join(); + zkServer.shutdown(); + + while (zkServer.isRunning()) { + zkServer.shutdown(); + Thread.sleep(100); + } + } + } + + public static class ZooKeeperTestClient { + private final CuratorFramework curator; + private final ServiceDiscovery discovery; + + public ZooKeeperTestClient(String nodes) { + curator = CuratorFrameworkFactory.builder() + .connectString(nodes) + .retryPolicy(new ExponentialBackoffRetry(1000, 3)) + .build(); + discovery = ServiceDiscoveryBuilder.builder(ZooKeeperServiceRegistry.MetaData.class) + .client(curator) + .basePath(SERVICE_PATH) + .serializer(new JsonInstanceSerializer<>(ZooKeeperServiceRegistry.MetaData.class)) + .build(); + } + + public CuratorFramework curator() { + return curator; + } + + public ServiceDiscovery discovery() { + return discovery; + } + + public void start() throws Exception { + curator.start(); + discovery.start(); + } + + public void stop() throws Exception { + CloseableUtils.closeQuietly(discovery); + CloseableUtils.closeQuietly(curator); + } + } +} diff --git a/platforms/spring-boot/components-starter/camel-zookeeper-starter/src/test/resources/application.properties b/platforms/spring-boot/components-starter/camel-zookeeper-starter/src/test/resources/application.properties new file mode 100644 index 0000000000000..2f91fc32820e4 --- /dev/null +++ b/platforms/spring-boot/components-starter/camel-zookeeper-starter/src/test/resources/application.properties @@ -0,0 +1,17 @@ +## --------------------------------------------------------------------------- +## 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. +## --------------------------------------------------------------------------- +spring.main.banner-mode=off diff --git a/platforms/spring-boot/components-starter/camel-zookeeper-starter/src/test/resources/logback.xml b/platforms/spring-boot/components-starter/camel-zookeeper-starter/src/test/resources/logback.xml new file mode 100644 index 0000000000000..3e6fbd7b3a215 --- /dev/null +++ b/platforms/spring-boot/components-starter/camel-zookeeper-starter/src/test/resources/logback.xml @@ -0,0 +1,42 @@ + + + + + + + + %d{HH:mm:ss.SSS} [%-15.15thread] %-5level %-30.30logger - %msg%n + + + + + + %d{HH:mm:ss.SSS} [%-15.15thread] %-5level %-30.30logger - %msg%n + + target/camel-zookeeper-starter-test.log + + + + + + + + \ No newline at end of file diff --git a/platforms/spring-boot/spring-boot-dm/pom.xml b/platforms/spring-boot/spring-boot-dm/pom.xml index 4503db3552dd2..73ab6c2d9845f 100644 --- a/platforms/spring-boot/spring-boot-dm/pom.xml +++ b/platforms/spring-boot/spring-boot-dm/pom.xml @@ -47,6 +47,7 @@ 1.8.2 1.9.12 + 1.7.3 2.1.5 From 3e368ca037bc243a8d6678bfcd734f485e8c319c Mon Sep 17 00:00:00 2001 From: lburgazzoli Date: Tue, 15 May 2018 15:06:54 +0200 Subject: [PATCH 06/20] fix consul kv test --- .../consul/ConsulClientKeyValueTest.java | 12 ++- .../consul/ConsulClientTestSupport.java | 80 ------------------- 2 files changed, 11 insertions(+), 81 deletions(-) delete mode 100644 components/camel-consul/src/test/java/org/apache/camel/component/consul/ConsulClientTestSupport.java diff --git a/components/camel-consul/src/test/java/org/apache/camel/component/consul/ConsulClientKeyValueTest.java b/components/camel-consul/src/test/java/org/apache/camel/component/consul/ConsulClientKeyValueTest.java index 7753245b2a6df..360157d7c0229 100644 --- a/components/camel-consul/src/test/java/org/apache/camel/component/consul/ConsulClientKeyValueTest.java +++ b/components/camel-consul/src/test/java/org/apache/camel/component/consul/ConsulClientKeyValueTest.java @@ -20,10 +20,20 @@ import org.apache.camel.builder.RouteBuilder; import org.apache.camel.component.consul.endpoint.ConsulKeyValueActions; +import org.apache.camel.component.consul.support.ConsulTestSupport; import org.apache.camel.component.mock.MockEndpoint; +import org.apache.camel.impl.JndiRegistry; import org.junit.Test; -public class ConsulClientKeyValueTest extends ConsulClientTestSupport { +public class ConsulClientKeyValueTest extends ConsulTestSupport { + + @Override + protected JndiRegistry createRegistry() throws Exception { + JndiRegistry registry = super.createRegistry(); + registry.bind("consulClient", getConsul()); + + return registry; + } @Test public void testKeyPut() throws Exception { diff --git a/components/camel-consul/src/test/java/org/apache/camel/component/consul/ConsulClientTestSupport.java b/components/camel-consul/src/test/java/org/apache/camel/component/consul/ConsulClientTestSupport.java deleted file mode 100644 index c0d9b78f4a0db..0000000000000 --- a/components/camel-consul/src/test/java/org/apache/camel/component/consul/ConsulClientTestSupport.java +++ /dev/null @@ -1,80 +0,0 @@ -/** - * 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.camel.component.consul; - -import java.util.Arrays; -import java.util.List; -import java.util.UUID; - -import com.orbitz.consul.Consul; -import com.orbitz.consul.KeyValueClient; -import org.apache.camel.impl.JndiRegistry; -import org.apache.camel.test.junit4.CamelTestSupport; -import org.junit.Rule; -import org.junit.rules.TestName; - -public class ConsulClientTestSupport extends CamelTestSupport { - public static final String CONSUL_HOST = System.getProperty("camel.consul.host", Consul.DEFAULT_HTTP_HOST); - public static final int CONSUL_PORT = Integer.getInteger("camel.consul.port", Consul.DEFAULT_HTTP_PORT); - public static final String CONSUL_URL = String.format("http://%s:%d", CONSUL_HOST, CONSUL_PORT); - public static final String KV_PREFIX = "/camel"; - - @Rule - public final TestName testName = new TestName(); - - public Consul consul = Consul.builder().withUrl(CONSUL_URL).build(); - - @Override - protected JndiRegistry createRegistry() throws Exception { - JndiRegistry registry = super.createRegistry(); - - ConsulComponent component = new ConsulComponent(); - component.setUrl(CONSUL_URL); - - registry.bind("consul", component); - registry.bind("consulClient", consul); - - return registry; - } - - protected Consul getConsul() { - return consul; - } - - protected KeyValueClient getKeyValueClient() { - return getConsul().keyValueClient(); - } - - protected String generateRandomString() { - return UUID.randomUUID().toString(); - } - - protected String[] generateRandomArrayOfStrings(int size) { - String[] array = new String[size]; - Arrays.setAll(array, i -> generateRandomString()); - - return array; - } - - protected List generateRandomListOfStrings(int size) { - return Arrays.asList(generateRandomArrayOfStrings(size)); - } - - protected String generateKey() { - return KV_PREFIX + "/" + testName.getMethodName() + "/" + generateRandomString(); - } -} From c08e704a29954c41d2a13c690424df68bd951ce3 Mon Sep 17 00:00:00 2001 From: lburgazzoli Date: Thu, 17 May 2018 17:02:40 +0200 Subject: [PATCH 07/20] CAMEL-12518: camel cloud : leverage spring-cloud ServiceRegistry to register routes --- apache-camel/pom.xml | 18 ++ .../src/main/descriptors/common-bin.xml | 4 + bom/camel-bom/pom.xml | 20 ++ components/camel-spring-cloud-consul/pom.xml | 227 +++++++++++++++ .../src/main/docs/spring-cloud-consul.adoc | 3 + .../CamelCloudConsulAutoConfiguration.java | 53 ++++ ...ServiceDefinitionToConsulRegistration.java | 50 ++++ .../src/main/resources/META-INF/LICENSE.txt | 203 +++++++++++++ .../src/main/resources/META-INF/NOTICE.txt | 11 + .../main/resources/META-INF/spring.factories | 19 ++ ...CamelCloudConsulAutoConfigurationTest.java | 69 +++++ .../CamelCloudConsulServiceRegistryTest.java | 103 +++++++ .../consul/support/ConsulContainerLogger.java | 31 ++ .../support/ConsulContainerSupport.java | 41 +++ .../support/ConsulContainerWaitStrategy.java | 48 ++++ .../src/test/resources/logback.xml | 42 +++ .../camel-spring-cloud-zookeeper/pom.xml | 161 +++++++++++ .../src/main/docs/spring-cloud-zookeeper.adoc | 3 + .../CamelCloudZookeeperAutoConfiguration.java | 53 ++++ ...viceDefinitionToZookeeperRegistration.java | 46 +++ .../src/main/resources/META-INF/LICENSE.txt | 203 +++++++++++++ .../src/main/resources/META-INF/NOTICE.txt | 11 + .../main/resources/META-INF/spring.factories | 19 ++ ...elCloudZookeeperAutoConfigurationTest.java | 85 ++++++ ...amelCloudZookeeperServiceRegistryTest.java | 129 +++++++++ .../zookeeper/support/ZookeeperServer.java | 62 ++++ .../src/test/resources/logback.xml | 43 +++ ...CloudDiscoveryClientAutoConfiguration.java | 2 +- .../CamelSpringCloudServiceRegistry.java | 150 ++++++++++ ...CloudServiceRegistryAutoConfiguration.java | 61 ++++ .../main/resources/META-INF/spring.factories | 4 +- ...CamelSpringCloudServiceCallRibbonTest.java | 7 +- .../CamelSpringCloudServiceRegistryTest.java | 271 ++++++++++++++++++ components/pom.xml | 2 + components/readme.adoc | 8 +- docs/user-manual/en/SUMMARY.md | 2 + parent/pom.xml | 22 ++ .../camel-spring-cloud-consul-starter/pom.xml | 65 +++++ .../src/main/resources/META-INF/LICENSE.txt | 203 +++++++++++++ .../src/main/resources/META-INF/NOTICE.txt | 11 + .../main/resources/META-INF/spring.provides | 17 ++ .../pom.xml | 65 +++++ .../src/main/resources/META-INF/LICENSE.txt | 203 +++++++++++++ .../src/main/resources/META-INF/NOTICE.txt | 11 + .../main/resources/META-INF/spring.provides | 17 ++ .../spring-boot/components-starter/pom.xml | 2 + .../camel-spring-boot-dependencies/pom.xml | 20 ++ 47 files changed, 2893 insertions(+), 7 deletions(-) create mode 100644 components/camel-spring-cloud-consul/pom.xml create mode 100644 components/camel-spring-cloud-consul/src/main/docs/spring-cloud-consul.adoc create mode 100644 components/camel-spring-cloud-consul/src/main/java/org/apache/camel/spring/cloud/consul/CamelCloudConsulAutoConfiguration.java create mode 100644 components/camel-spring-cloud-consul/src/main/java/org/apache/camel/spring/cloud/consul/ServiceDefinitionToConsulRegistration.java create mode 100644 components/camel-spring-cloud-consul/src/main/resources/META-INF/LICENSE.txt create mode 100644 components/camel-spring-cloud-consul/src/main/resources/META-INF/NOTICE.txt create mode 100644 components/camel-spring-cloud-consul/src/main/resources/META-INF/spring.factories create mode 100644 components/camel-spring-cloud-consul/src/test/java/org/apache/camel/spring/cloud/consul/CamelCloudConsulAutoConfigurationTest.java create mode 100644 components/camel-spring-cloud-consul/src/test/java/org/apache/camel/spring/cloud/consul/CamelCloudConsulServiceRegistryTest.java create mode 100644 components/camel-spring-cloud-consul/src/test/java/org/apache/camel/spring/cloud/consul/support/ConsulContainerLogger.java create mode 100644 components/camel-spring-cloud-consul/src/test/java/org/apache/camel/spring/cloud/consul/support/ConsulContainerSupport.java create mode 100644 components/camel-spring-cloud-consul/src/test/java/org/apache/camel/spring/cloud/consul/support/ConsulContainerWaitStrategy.java create mode 100644 components/camel-spring-cloud-consul/src/test/resources/logback.xml create mode 100644 components/camel-spring-cloud-zookeeper/pom.xml create mode 100644 components/camel-spring-cloud-zookeeper/src/main/docs/spring-cloud-zookeeper.adoc create mode 100644 components/camel-spring-cloud-zookeeper/src/main/java/org/apache/camel/spring/cloud/zookeeper/CamelCloudZookeeperAutoConfiguration.java create mode 100644 components/camel-spring-cloud-zookeeper/src/main/java/org/apache/camel/spring/cloud/zookeeper/ServiceDefinitionToZookeeperRegistration.java create mode 100644 components/camel-spring-cloud-zookeeper/src/main/resources/META-INF/LICENSE.txt create mode 100644 components/camel-spring-cloud-zookeeper/src/main/resources/META-INF/NOTICE.txt create mode 100644 components/camel-spring-cloud-zookeeper/src/main/resources/META-INF/spring.factories create mode 100644 components/camel-spring-cloud-zookeeper/src/test/java/org/apache/camel/spring/cloud/zookeeper/CamelCloudZookeeperAutoConfigurationTest.java create mode 100644 components/camel-spring-cloud-zookeeper/src/test/java/org/apache/camel/spring/cloud/zookeeper/CamelCloudZookeeperServiceRegistryTest.java create mode 100644 components/camel-spring-cloud-zookeeper/src/test/java/org/apache/camel/spring/cloud/zookeeper/support/ZookeeperServer.java create mode 100644 components/camel-spring-cloud-zookeeper/src/test/resources/logback.xml create mode 100644 components/camel-spring-cloud/src/main/java/org/apache/camel/spring/cloud/CamelSpringCloudServiceRegistry.java create mode 100644 components/camel-spring-cloud/src/main/java/org/apache/camel/spring/cloud/CamelSpringCloudServiceRegistryAutoConfiguration.java create mode 100644 components/camel-spring-cloud/src/test/java/org/apache/camel/spring/cloud/CamelSpringCloudServiceRegistryTest.java create mode 100644 platforms/spring-boot/components-starter/camel-spring-cloud-consul-starter/pom.xml create mode 100644 platforms/spring-boot/components-starter/camel-spring-cloud-consul-starter/src/main/resources/META-INF/LICENSE.txt create mode 100644 platforms/spring-boot/components-starter/camel-spring-cloud-consul-starter/src/main/resources/META-INF/NOTICE.txt create mode 100644 platforms/spring-boot/components-starter/camel-spring-cloud-consul-starter/src/main/resources/META-INF/spring.provides create mode 100644 platforms/spring-boot/components-starter/camel-spring-cloud-zookeeper-starter/pom.xml create mode 100644 platforms/spring-boot/components-starter/camel-spring-cloud-zookeeper-starter/src/main/resources/META-INF/LICENSE.txt create mode 100644 platforms/spring-boot/components-starter/camel-spring-cloud-zookeeper-starter/src/main/resources/META-INF/NOTICE.txt create mode 100644 platforms/spring-boot/components-starter/camel-spring-cloud-zookeeper-starter/src/main/resources/META-INF/spring.provides diff --git a/apache-camel/pom.xml b/apache-camel/pom.xml index 76da53b6fb13f..51c2868ac5696 100644 --- a/apache-camel/pom.xml +++ b/apache-camel/pom.xml @@ -993,10 +993,18 @@ org.apache.camel camel-spring-cloud + + org.apache.camel + camel-spring-cloud-consul + org.apache.camel camel-spring-cloud-netflix + + org.apache.camel + camel-spring-cloud-zookeeper + org.apache.camel camel-spring-integration @@ -2282,11 +2290,21 @@ camel-spring-cloud-starter ${project.version} + + org.apache.camel + camel-spring-cloud-consul-starter + ${project.version} + org.apache.camel camel-spring-cloud-netflix-starter ${project.version} + + org.apache.camel + camel-spring-cloud-zookeeper-starter + ${project.version} + org.apache.camel camel-spring-integration-starter diff --git a/apache-camel/src/main/descriptors/common-bin.xml b/apache-camel/src/main/descriptors/common-bin.xml index d48a9f5e852ac..d8b53a81beea5 100644 --- a/apache-camel/src/main/descriptors/common-bin.xml +++ b/apache-camel/src/main/descriptors/common-bin.xml @@ -257,7 +257,9 @@ org.apache.camel:camel-spring org.apache.camel:camel-spring-batch org.apache.camel:camel-spring-cloud + org.apache.camel:camel-spring-cloud-consul org.apache.camel:camel-spring-cloud-netflix + org.apache.camel:camel-spring-cloud-zookeeper org.apache.camel:camel-spring-integration org.apache.camel:camel-spring-javaconfig org.apache.camel:camel-spring-ldap @@ -566,7 +568,9 @@ org.apache.camel:camel-spring-boot org.apache.camel:camel-spring-boot-starter org.apache.camel:camel-spring-cloud-starter + org.apache.camel:camel-spring-cloud-consul-starter org.apache.camel:camel-spring-cloud-netflix-starter + org.apache.camel:camel-spring-cloud-zookeeper-starter org.apache.camel:camel-spring-integration-starter org.apache.camel:camel-spring-javaconfig-starter org.apache.camel:camel-spring-ldap-starter diff --git a/bom/camel-bom/pom.xml b/bom/camel-bom/pom.xml index f75a25b291457..3402e64650f53 100644 --- a/bom/camel-bom/pom.xml +++ b/bom/camel-bom/pom.xml @@ -2383,6 +2383,16 @@ camel-spring-cloud ${project.version} + + org.apache.camel + camel-spring-cloud-consul + ${project.version} + + + org.apache.camel + camel-spring-cloud-consul-starter + ${project.version} + org.apache.camel camel-spring-cloud-netflix @@ -2398,6 +2408,16 @@ camel-spring-cloud-starter ${project.version} + + org.apache.camel + camel-spring-cloud-zookeeper + ${project.version} + + + org.apache.camel + camel-spring-cloud-zookeeper-starter + ${project.version} + org.apache.camel camel-spring-integration diff --git a/components/camel-spring-cloud-consul/pom.xml b/components/camel-spring-cloud-consul/pom.xml new file mode 100644 index 0000000000000..7cb5de45a7406 --- /dev/null +++ b/components/camel-spring-cloud-consul/pom.xml @@ -0,0 +1,227 @@ + + + + + 4.0.0 + + + org.apache.camel + components + 2.22.0-SNAPSHOT + + + Camel :: Spring Cloud Consul + camel-spring-cloud-consul + Camel Cloud integration with Spring Cloud Consul + + + + 2.19.0 + + + + + 2.0.0.RC1 + 2.0.0.RC1 + + + + + + spring-milestones + Spring Milestones + https://repo.spring.io/libs-milestone + + false + + + + + + + org.springframework.boot + spring-boot-autoconfigure + ${spring-boot-version} + + + org.springframework.cloud + spring-cloud-starter + ${spring-cloud-commons-version} + + + org.springframework.cloud + spring-cloud-starter-consul-discovery + ${spring-cloud-consul-version} + + + org.springframework.cloud + spring-cloud-consul-core + ${spring-cloud-consul-version} + + + org.springframework.boot + spring-boot-configuration-processor + true + ${spring-boot-version} + + + + org.apache.camel + camel-spring-cloud + + + org.apache.camel + camel-spring-boot + + + + + org.apache.camel + camel-test + test + + + org.apache.camel + camel-test-spring + test + + + org.apache.camel + camel-http4 + test + + + org.apache.camel + camel-jetty + test + + + junit + junit + test + + + org.springframework.boot + spring-boot-starter + ${spring-boot-version} + test + + + org.springframework.boot + spring-boot-starter-web + ${spring-boot-version} + test + + + org.springframework.boot + spring-boot-starter-test + ${spring-boot-version} + test + + + + + org.testcontainers + testcontainers + ${testcontainers-version} + test + + + + + + + org.apache.maven.plugins + maven-surefire-plugin + + 1 + false + + + + + + + + consul-skip-tests + + true + + + + + maven-surefire-plugin + + true + + + + + + + + + consul-tests-docker-file + + + /var/run/docker.sock + + + + + + maven-surefire-plugin + + false + + true + + + + + + + + + + consul-tests-docker-env + + + env.DOCKER_HOST + + + + + + maven-surefire-plugin + + false + + true + + + + + + + + + + diff --git a/components/camel-spring-cloud-consul/src/main/docs/spring-cloud-consul.adoc b/components/camel-spring-cloud-consul/src/main/docs/spring-cloud-consul.adoc new file mode 100644 index 0000000000000..e02775660e507 --- /dev/null +++ b/components/camel-spring-cloud-consul/src/main/docs/spring-cloud-consul.adoc @@ -0,0 +1,3 @@ +=== Spring Cloud Consul + +*Available as of Camel 2.22* diff --git a/components/camel-spring-cloud-consul/src/main/java/org/apache/camel/spring/cloud/consul/CamelCloudConsulAutoConfiguration.java b/components/camel-spring-cloud-consul/src/main/java/org/apache/camel/spring/cloud/consul/CamelCloudConsulAutoConfiguration.java new file mode 100644 index 0000000000000..c83fbc21a8cd2 --- /dev/null +++ b/components/camel-spring-cloud-consul/src/main/java/org/apache/camel/spring/cloud/consul/CamelCloudConsulAutoConfiguration.java @@ -0,0 +1,53 @@ +/** + * 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.camel.spring.cloud.consul; + +import org.apache.camel.cloud.ServiceDefinition; +import org.apache.camel.spring.boot.util.GroupCondition; +import org.apache.camel.spring.cloud.CamelSpringCloudServiceRegistryAutoConfiguration; +import org.springframework.boot.autoconfigure.AutoConfigureBefore; +import org.springframework.cloud.consul.ConditionalOnConsulEnabled; +import org.springframework.cloud.consul.serviceregistry.ConsulRegistration; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Conditional; +import org.springframework.context.annotation.Configuration; +import org.springframework.core.convert.converter.Converter; + +@Configuration +@AutoConfigureBefore(CamelSpringCloudServiceRegistryAutoConfiguration.class) +@ConditionalOnConsulEnabled +@Conditional(CamelCloudConsulAutoConfiguration.Condition.class) +public class CamelCloudConsulAutoConfiguration { + + @Bean(name = "service-definition-to-consul-registration") + public Converter serviceDefinitionToConsulRegistration() { + return new ServiceDefinitionToConsulRegistration(); + } + + // ******************************* + // Condition + // ******************************* + + public static class Condition extends GroupCondition { + public Condition() { + super( + "camel.cloud", + "camel.cloud.consul" + ); + } + } +} diff --git a/components/camel-spring-cloud-consul/src/main/java/org/apache/camel/spring/cloud/consul/ServiceDefinitionToConsulRegistration.java b/components/camel-spring-cloud-consul/src/main/java/org/apache/camel/spring/cloud/consul/ServiceDefinitionToConsulRegistration.java new file mode 100644 index 0000000000000..443fc989118c6 --- /dev/null +++ b/components/camel-spring-cloud-consul/src/main/java/org/apache/camel/spring/cloud/consul/ServiceDefinitionToConsulRegistration.java @@ -0,0 +1,50 @@ +/** + * 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.camel.spring.cloud.consul; + +import java.util.Objects; +import java.util.stream.Collectors; + +import com.ecwid.consul.v1.agent.model.NewService; +import org.apache.camel.cloud.ServiceDefinition; +import org.springframework.cloud.consul.serviceregistry.ConsulRegistration; +import org.springframework.core.convert.converter.Converter; + +public final class ServiceDefinitionToConsulRegistration implements Converter { + + @Override + public ConsulRegistration convert(ServiceDefinition source) { + NewService service = new NewService(); + service.setName(source.getName()); + service.setId(source.getId()); + service.setAddress(source.getHost()); + service.setPort(source.getPort()); + + service.setTags( + source.getMetadata().entrySet().stream() + .map(e -> e.getKey() + "=" + e.getValue()) + .collect(Collectors.toList()) + ); + + return new ConsulRegistration(service, null) { + @Override + public boolean isSecure() { + return service.getPort() == 443 || Objects.equals("https", source.getMetadata().get(ServiceDefinition.SERVICE_META_PROTOCOL)); + } + }; + } +} diff --git a/components/camel-spring-cloud-consul/src/main/resources/META-INF/LICENSE.txt b/components/camel-spring-cloud-consul/src/main/resources/META-INF/LICENSE.txt new file mode 100644 index 0000000000000..6b0b1270ff0ca --- /dev/null +++ b/components/camel-spring-cloud-consul/src/main/resources/META-INF/LICENSE.txt @@ -0,0 +1,203 @@ + + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright [yyyy] [name of copyright owner] + + 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. + diff --git a/components/camel-spring-cloud-consul/src/main/resources/META-INF/NOTICE.txt b/components/camel-spring-cloud-consul/src/main/resources/META-INF/NOTICE.txt new file mode 100644 index 0000000000000..2e215bf2e6b1f --- /dev/null +++ b/components/camel-spring-cloud-consul/src/main/resources/META-INF/NOTICE.txt @@ -0,0 +1,11 @@ + ========================================================================= + == NOTICE file corresponding to the section 4 d of == + == the Apache License, Version 2.0, == + == in this case for the Apache Camel distribution. == + ========================================================================= + + This product includes software developed by + The Apache Software Foundation (http://www.apache.org/). + + Please read the different LICENSE files present in the licenses directory of + this distribution. diff --git a/components/camel-spring-cloud-consul/src/main/resources/META-INF/spring.factories b/components/camel-spring-cloud-consul/src/main/resources/META-INF/spring.factories new file mode 100644 index 0000000000000..56dd79ff4eaff --- /dev/null +++ b/components/camel-spring-cloud-consul/src/main/resources/META-INF/spring.factories @@ -0,0 +1,19 @@ +# +# 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. +# + +org.springframework.boot.autoconfigure.EnableAutoConfiguration=\ + org.apache.camel.spring.cloud.consul.CamelCloudConsulAutoConfiguration \ No newline at end of file diff --git a/components/camel-spring-cloud-consul/src/test/java/org/apache/camel/spring/cloud/consul/CamelCloudConsulAutoConfigurationTest.java b/components/camel-spring-cloud-consul/src/test/java/org/apache/camel/spring/cloud/consul/CamelCloudConsulAutoConfigurationTest.java new file mode 100644 index 0000000000000..83e50df66554c --- /dev/null +++ b/components/camel-spring-cloud-consul/src/test/java/org/apache/camel/spring/cloud/consul/CamelCloudConsulAutoConfigurationTest.java @@ -0,0 +1,69 @@ +/** + * 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.camel.spring.cloud.consul; + +import java.util.Map; +import java.util.UUID; + +import org.junit.Test; +import org.springframework.boot.WebApplicationType; +import org.springframework.boot.autoconfigure.EnableAutoConfiguration; +import org.springframework.boot.builder.SpringApplicationBuilder; +import org.springframework.context.ConfigurableApplicationContext; +import org.springframework.context.annotation.Configuration; +import org.springframework.core.convert.converter.Converter; + +import static org.assertj.core.api.Assertions.assertThat; + +public class CamelCloudConsulAutoConfigurationTest { + @Test + public void testServiceDefinitionToConsulRegistration() throws Exception { + ConfigurableApplicationContext context = new SpringApplicationBuilder(TestConfiguration.class) + .web(WebApplicationType.NONE) + .run( + "--debug=false", + "--spring.main.banner-mode=OFF", + "--spring.application.name=" + UUID.randomUUID().toString(), + "--ribbon.enabled=false", + "--ribbon.eureka.enabled=false", + "--management.endpoint.enabled=false", + "--spring.cloud.consul.enabled=true", + "--spring.cloud.consul.config.enabled=false", + "--spring.cloud.consul.discovery.enabled=true", + "--spring.cloud.service-registry.auto-registration.enabled=false" + ); + + try { + Map converters = context.getBeansOfType(Converter.class); + + assertThat(converters).isNotNull(); + assertThat(converters.values().stream().anyMatch(ServiceDefinitionToConsulRegistration.class::isInstance)).isTrue(); + } finally { + context.close(); + } + } + + // ************************************* + // Config + // ************************************* + + @EnableAutoConfiguration + @Configuration + public static class TestConfiguration { + } +} + diff --git a/components/camel-spring-cloud-consul/src/test/java/org/apache/camel/spring/cloud/consul/CamelCloudConsulServiceRegistryTest.java b/components/camel-spring-cloud-consul/src/test/java/org/apache/camel/spring/cloud/consul/CamelCloudConsulServiceRegistryTest.java new file mode 100644 index 0000000000000..b576351d13acb --- /dev/null +++ b/components/camel-spring-cloud-consul/src/test/java/org/apache/camel/spring/cloud/consul/CamelCloudConsulServiceRegistryTest.java @@ -0,0 +1,103 @@ +/** + * 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.camel.spring.cloud.consul; + +import java.util.List; +import java.util.UUID; + +import com.ecwid.consul.v1.ConsulClient; +import com.ecwid.consul.v1.QueryParams; +import com.ecwid.consul.v1.catalog.model.CatalogService; +import org.apache.camel.cloud.ServiceRegistry; +import org.apache.camel.impl.cloud.DefaultServiceDefinition; +import org.apache.camel.spring.cloud.consul.support.ConsulContainerSupport; +import org.junit.Rule; +import org.junit.Test; +import org.springframework.boot.WebApplicationType; +import org.springframework.boot.autoconfigure.EnableAutoConfiguration; +import org.springframework.boot.builder.SpringApplicationBuilder; +import org.springframework.context.ConfigurableApplicationContext; +import org.springframework.context.annotation.Configuration; +import org.springframework.util.SocketUtils; +import org.testcontainers.containers.GenericContainer; + +import static org.assertj.core.api.Assertions.assertThat; + +public class CamelCloudConsulServiceRegistryTest { + protected static final String SERVICE_ID = UUID.randomUUID().toString(); + protected static final String SERVICE_NAME = "my-service"; + protected static final String SERVICE_HOST = "localhost"; + protected static final int SERVICE_PORT = SocketUtils.findAvailableTcpPort(); + + @Rule + public GenericContainer container = ConsulContainerSupport.consulContainer(); + + @Test + public void testServiceRegistry() throws Exception { + ConfigurableApplicationContext context = new SpringApplicationBuilder(TestConfiguration.class) + .web(WebApplicationType.NONE) + .run( + "--debug=false", + "--spring.main.banner-mode=OFF", + "--spring.application.name=" + UUID.randomUUID().toString(), + "--ribbon.enabled=false", + "--ribbon.eureka.enabled=false", + "--management.endpoint.enabled=false", + "--spring.cloud.consul.enabled=true", + "--spring.cloud.consul.host=" + container.getContainerIpAddress(), + "--spring.cloud.consul.port=" + container.getMappedPort(8500), + "--spring.cloud.consul.config.enabled=false", + "--spring.cloud.consul.discovery.enabled=true", + "--spring.cloud.service-registry.auto-registration.enabled=false" + ); + + try { + final ConsulClient client = context.getBean(ConsulClient.class); + final ServiceRegistry registry = context.getBean(ServiceRegistry.class); + + registry.register( + DefaultServiceDefinition.builder() + .withHost(SERVICE_HOST) + .withPort(SERVICE_PORT) + .withName(SERVICE_NAME) + .withId(SERVICE_ID) + .build() + ); + + List services = client.getCatalogService(SERVICE_NAME, QueryParams.DEFAULT).getValue(); + + assertThat(services).hasSize(1); + assertThat(services).first().hasFieldOrPropertyWithValue("serviceId", SERVICE_ID); + assertThat(services).first().hasFieldOrPropertyWithValue("serviceName", SERVICE_NAME); + assertThat(services).first().hasFieldOrPropertyWithValue("serviceAddress", SERVICE_HOST); + assertThat(services).first().hasFieldOrPropertyWithValue("servicePort", SERVICE_PORT); + + } finally { + context.close(); + } + } + + // ************************************* + // Config + // ************************************* + + @EnableAutoConfiguration + @Configuration + public static class TestConfiguration { + } +} + diff --git a/components/camel-spring-cloud-consul/src/test/java/org/apache/camel/spring/cloud/consul/support/ConsulContainerLogger.java b/components/camel-spring-cloud-consul/src/test/java/org/apache/camel/spring/cloud/consul/support/ConsulContainerLogger.java new file mode 100644 index 0000000000000..59e5bf288f4e5 --- /dev/null +++ b/components/camel-spring-cloud-consul/src/test/java/org/apache/camel/spring/cloud/consul/support/ConsulContainerLogger.java @@ -0,0 +1,31 @@ +/** + * 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.camel.spring.cloud.consul.support; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.testcontainers.containers.output.Slf4jLogConsumer; + +public final class ConsulContainerLogger extends Slf4jLogConsumer { + private static final Logger LOGGER = LoggerFactory.getLogger(ConsulContainerLogger.class); + + public ConsulContainerLogger() { + super(LOGGER); + + withPrefix("consul"); + } +} diff --git a/components/camel-spring-cloud-consul/src/test/java/org/apache/camel/spring/cloud/consul/support/ConsulContainerSupport.java b/components/camel-spring-cloud-consul/src/test/java/org/apache/camel/spring/cloud/consul/support/ConsulContainerSupport.java new file mode 100644 index 0000000000000..677e582896b63 --- /dev/null +++ b/components/camel-spring-cloud-consul/src/test/java/org/apache/camel/spring/cloud/consul/support/ConsulContainerSupport.java @@ -0,0 +1,41 @@ +/** + * 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.camel.spring.cloud.consul.support; + +import org.testcontainers.containers.GenericContainer; + +public final class ConsulContainerSupport { + private ConsulContainerSupport() { + } + + public static GenericContainer consulContainer() { + return new GenericContainer("consul:1.0.0") + .withExposedPorts(8500) + .waitingFor(new ConsulContainerWaitStrategy()) + .withLogConsumer(new ConsulContainerLogger()) + .withCommand( + "agent", + "-dev", + "-server", + "-bootstrap", + "-client", + "0.0.0.0", + "-log-level", + "trace" + ); + } +} diff --git a/components/camel-spring-cloud-consul/src/test/java/org/apache/camel/spring/cloud/consul/support/ConsulContainerWaitStrategy.java b/components/camel-spring-cloud-consul/src/test/java/org/apache/camel/spring/cloud/consul/support/ConsulContainerWaitStrategy.java new file mode 100644 index 0000000000000..5133d3c6e1d67 --- /dev/null +++ b/components/camel-spring-cloud-consul/src/test/java/org/apache/camel/spring/cloud/consul/support/ConsulContainerWaitStrategy.java @@ -0,0 +1,48 @@ +/** + * 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.camel.spring.cloud.consul.support; + +import java.util.concurrent.TimeUnit; +import java.util.concurrent.TimeoutException; + +import com.github.dockerjava.api.DockerClient; +import org.testcontainers.DockerClientFactory; +import org.testcontainers.containers.ContainerLaunchException; +import org.testcontainers.containers.output.WaitingConsumer; +import org.testcontainers.containers.wait.strategy.AbstractWaitStrategy; +import org.testcontainers.utility.LogUtils; + +public final class ConsulContainerWaitStrategy extends AbstractWaitStrategy { + @Override + protected void waitUntilReady() { + final DockerClient client = DockerClientFactory.instance().client(); + final WaitingConsumer waitingConsumer = new WaitingConsumer(); + + LogUtils.followOutput(client, waitStrategyTarget.getContainerId(), waitingConsumer); + + try { + waitingConsumer.waitUntil( + f -> f.getUtf8String().contains("Synced node info"), + startupTimeout.getSeconds(), + TimeUnit.SECONDS, + 1 + ); + } catch (TimeoutException e) { + throw new ContainerLaunchException("Timed out"); + } + } +} \ No newline at end of file diff --git a/components/camel-spring-cloud-consul/src/test/resources/logback.xml b/components/camel-spring-cloud-consul/src/test/resources/logback.xml new file mode 100644 index 0000000000000..39280fd4c9c6c --- /dev/null +++ b/components/camel-spring-cloud-consul/src/test/resources/logback.xml @@ -0,0 +1,42 @@ + + + + + + + + %d{HH:mm:ss.SSS} [%-15.15thread] %-5level %-30.30logger - %msg%n + + + + + + %d{HH:mm:ss.SSS} [%-15.15thread] %-5level %-30.30logger - %msg%n + + target/camel-spring-cloud-consul-test.log + + + + + + + + \ No newline at end of file diff --git a/components/camel-spring-cloud-zookeeper/pom.xml b/components/camel-spring-cloud-zookeeper/pom.xml new file mode 100644 index 0000000000000..01631650b6fa0 --- /dev/null +++ b/components/camel-spring-cloud-zookeeper/pom.xml @@ -0,0 +1,161 @@ + + + + + 4.0.0 + + + org.apache.camel + components + 2.22.0-SNAPSHOT + + + Camel :: Spring Cloud Zookeeper + camel-spring-cloud-zookeeper + Camel Cloud integration with Spring Cloud Zookeeper + + + + 2.19.0 + + + + + 2.0.0.RC1 + 2.0.0.RC1 + + + + + + spring-milestones + Spring Milestones + https://repo.spring.io/libs-milestone + + false + + + + + + + org.springframework.boot + spring-boot-autoconfigure + ${spring-boot-version} + + + org.springframework.cloud + spring-cloud-starter + ${spring-cloud-commons-version} + + + org.springframework.cloud + spring-cloud-starter-zookeeper-discovery + ${spring-cloud-zookeeper-version} + + + org.springframework.cloud + spring-cloud-zookeeper-core + ${spring-cloud-zookeeper-version} + + + org.springframework.boot + spring-boot-configuration-processor + true + ${spring-boot-version} + + + + org.apache.camel + camel-spring-cloud + + + org.apache.camel + camel-spring-boot + + + + + org.apache.camel + camel-test + test + + + org.apache.camel + camel-test-spring + test + + + org.apache.camel + camel-http4 + test + + + org.apache.camel + camel-jetty + test + + + junit + junit + test + + + org.springframework.boot + spring-boot-starter + ${spring-boot-version} + test + + + org.springframework.boot + spring-boot-starter-web + ${spring-boot-version} + test + + + org.springframework.boot + spring-boot-starter-test + ${spring-boot-version} + test + + + + + org.testcontainers + testcontainers + ${testcontainers-version} + test + + + + + + + org.apache.maven.plugins + maven-surefire-plugin + + 1 + false + + + + + + diff --git a/components/camel-spring-cloud-zookeeper/src/main/docs/spring-cloud-zookeeper.adoc b/components/camel-spring-cloud-zookeeper/src/main/docs/spring-cloud-zookeeper.adoc new file mode 100644 index 0000000000000..942fa6084860a --- /dev/null +++ b/components/camel-spring-cloud-zookeeper/src/main/docs/spring-cloud-zookeeper.adoc @@ -0,0 +1,3 @@ +=== Spring Cloud Zookeeper + +*Available as of Camel 2.22* diff --git a/components/camel-spring-cloud-zookeeper/src/main/java/org/apache/camel/spring/cloud/zookeeper/CamelCloudZookeeperAutoConfiguration.java b/components/camel-spring-cloud-zookeeper/src/main/java/org/apache/camel/spring/cloud/zookeeper/CamelCloudZookeeperAutoConfiguration.java new file mode 100644 index 0000000000000..85ba2274695b0 --- /dev/null +++ b/components/camel-spring-cloud-zookeeper/src/main/java/org/apache/camel/spring/cloud/zookeeper/CamelCloudZookeeperAutoConfiguration.java @@ -0,0 +1,53 @@ +/** + * 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.camel.spring.cloud.zookeeper; + +import org.apache.camel.cloud.ServiceDefinition; +import org.apache.camel.spring.boot.util.GroupCondition; +import org.apache.camel.spring.cloud.CamelSpringCloudServiceRegistryAutoConfiguration; +import org.springframework.boot.autoconfigure.AutoConfigureBefore; +import org.springframework.cloud.zookeeper.ConditionalOnZookeeperEnabled; +import org.springframework.cloud.zookeeper.serviceregistry.ZookeeperRegistration; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Conditional; +import org.springframework.context.annotation.Configuration; +import org.springframework.core.convert.converter.Converter; + +@Configuration +@AutoConfigureBefore(CamelSpringCloudServiceRegistryAutoConfiguration.class) +@ConditionalOnZookeeperEnabled +@Conditional(CamelCloudZookeeperAutoConfiguration.Condition.class) +public class CamelCloudZookeeperAutoConfiguration { + + @Bean(name = "service-definition-to-zookeeper-registration") + public Converter serviceDefinitionToConsulRegistration() { + return new ServiceDefinitionToZookeeperRegistration(); + } + + // ******************************* + // Condition + // ******************************* + + public static class Condition extends GroupCondition { + public Condition() { + super( + "camel.cloud", + "camel.cloud.zookeeper" + ); + } + } +} diff --git a/components/camel-spring-cloud-zookeeper/src/main/java/org/apache/camel/spring/cloud/zookeeper/ServiceDefinitionToZookeeperRegistration.java b/components/camel-spring-cloud-zookeeper/src/main/java/org/apache/camel/spring/cloud/zookeeper/ServiceDefinitionToZookeeperRegistration.java new file mode 100644 index 0000000000000..30cc93b1f0afe --- /dev/null +++ b/components/camel-spring-cloud-zookeeper/src/main/java/org/apache/camel/spring/cloud/zookeeper/ServiceDefinitionToZookeeperRegistration.java @@ -0,0 +1,46 @@ +/** + * 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.camel.spring.cloud.zookeeper; + +import org.apache.camel.cloud.ServiceDefinition; +import org.springframework.cloud.zookeeper.discovery.ZookeeperDiscoveryProperties; +import org.springframework.cloud.zookeeper.discovery.ZookeeperInstance; +import org.springframework.cloud.zookeeper.serviceregistry.ServiceInstanceRegistration; +import org.springframework.cloud.zookeeper.serviceregistry.ZookeeperRegistration; +import org.springframework.core.convert.converter.Converter; + +public final class ServiceDefinitionToZookeeperRegistration implements Converter { + public ServiceDefinitionToZookeeperRegistration() { + } + + @Override + public ZookeeperRegistration convert(ServiceDefinition source) { + ZookeeperInstance instance = new ZookeeperInstance( + source.getId(), + source.getName(), + source.getMetadata() + ); + + return ServiceInstanceRegistration.builder() + .address(source.getHost()) + .port(source.getPort()) + .name(source.getName()) + .payload(instance) + .uriSpec(ZookeeperDiscoveryProperties.DEFAULT_URI_SPEC) + .build(); + } +} diff --git a/components/camel-spring-cloud-zookeeper/src/main/resources/META-INF/LICENSE.txt b/components/camel-spring-cloud-zookeeper/src/main/resources/META-INF/LICENSE.txt new file mode 100644 index 0000000000000..6b0b1270ff0ca --- /dev/null +++ b/components/camel-spring-cloud-zookeeper/src/main/resources/META-INF/LICENSE.txt @@ -0,0 +1,203 @@ + + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright [yyyy] [name of copyright owner] + + 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. + diff --git a/components/camel-spring-cloud-zookeeper/src/main/resources/META-INF/NOTICE.txt b/components/camel-spring-cloud-zookeeper/src/main/resources/META-INF/NOTICE.txt new file mode 100644 index 0000000000000..2e215bf2e6b1f --- /dev/null +++ b/components/camel-spring-cloud-zookeeper/src/main/resources/META-INF/NOTICE.txt @@ -0,0 +1,11 @@ + ========================================================================= + == NOTICE file corresponding to the section 4 d of == + == the Apache License, Version 2.0, == + == in this case for the Apache Camel distribution. == + ========================================================================= + + This product includes software developed by + The Apache Software Foundation (http://www.apache.org/). + + Please read the different LICENSE files present in the licenses directory of + this distribution. diff --git a/components/camel-spring-cloud-zookeeper/src/main/resources/META-INF/spring.factories b/components/camel-spring-cloud-zookeeper/src/main/resources/META-INF/spring.factories new file mode 100644 index 0000000000000..04d8a13eb7abb --- /dev/null +++ b/components/camel-spring-cloud-zookeeper/src/main/resources/META-INF/spring.factories @@ -0,0 +1,19 @@ +# +# 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. +# + +org.springframework.boot.autoconfigure.EnableAutoConfiguration=\ + org.apache.camel.spring.cloud.zookeeper.CamelCloudZookeeperAutoConfiguration \ No newline at end of file diff --git a/components/camel-spring-cloud-zookeeper/src/test/java/org/apache/camel/spring/cloud/zookeeper/CamelCloudZookeeperAutoConfigurationTest.java b/components/camel-spring-cloud-zookeeper/src/test/java/org/apache/camel/spring/cloud/zookeeper/CamelCloudZookeeperAutoConfigurationTest.java new file mode 100644 index 0000000000000..205b5a930c2dc --- /dev/null +++ b/components/camel-spring-cloud-zookeeper/src/test/java/org/apache/camel/spring/cloud/zookeeper/CamelCloudZookeeperAutoConfigurationTest.java @@ -0,0 +1,85 @@ +/** + * 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.camel.spring.cloud.zookeeper; + +import java.util.Map; +import java.util.UUID; + +import org.apache.camel.spring.cloud.zookeeper.support.ZookeeperServer; +import org.junit.Rule; +import org.junit.Test; +import org.junit.rules.TemporaryFolder; +import org.junit.rules.TestName; +import org.springframework.boot.WebApplicationType; +import org.springframework.boot.autoconfigure.EnableAutoConfiguration; +import org.springframework.boot.builder.SpringApplicationBuilder; +import org.springframework.context.ConfigurableApplicationContext; +import org.springframework.context.annotation.Configuration; +import org.springframework.core.convert.converter.Converter; + +import static org.assertj.core.api.Assertions.assertThat; + +public class CamelCloudZookeeperAutoConfigurationTest { + @Rule + public final TestName testName = new TestName(); + @Rule + public final TemporaryFolder temporaryFolder = new TemporaryFolder(); + + @Test + public void testServiceDefinitionToConsulRegistration() throws Exception { + final ZookeeperServer server = new ZookeeperServer(temporaryFolder.newFolder(testName.getMethodName())); + + ConfigurableApplicationContext context = new SpringApplicationBuilder(TestConfiguration.class) + .web(WebApplicationType.NONE) + .run( + "--debug=false", + "--spring.main.banner-mode=OFF", + "--spring.application.name=" + UUID.randomUUID().toString(), + "--ribbon.enabled=false", + "--ribbon.eureka.enabled=false", + "--management.endpoint.enabled=false", + "--spring.cloud.zookeeper.enabled=true", + "--spring.cloud.zookeeper.connect-string=" + server.connectString(), + "--spring.cloud.zookeeper.config.enabled=false", + "--spring.cloud.zookeeper.discovery.enabled=true", + "--spring.cloud.service-registry.auto-registration.enabled=false" + ); + + try { + Map converters = context.getBeansOfType(Converter.class); + + assertThat(converters).isNotNull(); + assertThat(converters.values().stream().anyMatch(ServiceDefinitionToZookeeperRegistration.class::isInstance)).isTrue(); + } finally { + // shutdown spring context + context.close(); + + // shutdown zookeeper + server.shutdown(); + } + } + + // ************************************* + // Config + // ************************************* + + @EnableAutoConfiguration + @Configuration + public static class TestConfiguration { + } +} + diff --git a/components/camel-spring-cloud-zookeeper/src/test/java/org/apache/camel/spring/cloud/zookeeper/CamelCloudZookeeperServiceRegistryTest.java b/components/camel-spring-cloud-zookeeper/src/test/java/org/apache/camel/spring/cloud/zookeeper/CamelCloudZookeeperServiceRegistryTest.java new file mode 100644 index 0000000000000..465dcd6cbc048 --- /dev/null +++ b/components/camel-spring-cloud-zookeeper/src/test/java/org/apache/camel/spring/cloud/zookeeper/CamelCloudZookeeperServiceRegistryTest.java @@ -0,0 +1,129 @@ +/** + * 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.camel.spring.cloud.zookeeper; + +import java.util.Collection; +import java.util.UUID; + +import org.apache.camel.builder.RouteBuilder; +import org.apache.camel.cloud.ServiceRegistry; +import org.apache.camel.impl.cloud.DefaultServiceDefinition; +import org.apache.camel.impl.cloud.ServiceRegistrationRoutePolicy; +import org.apache.camel.spring.cloud.zookeeper.support.ZookeeperServer; +import org.apache.curator.x.discovery.ServiceDiscovery; +import org.apache.curator.x.discovery.ServiceInstance; +import org.junit.Rule; +import org.junit.Test; +import org.junit.rules.TemporaryFolder; +import org.junit.rules.TestName; +import org.springframework.boot.WebApplicationType; +import org.springframework.boot.autoconfigure.EnableAutoConfiguration; +import org.springframework.boot.builder.SpringApplicationBuilder; +import org.springframework.cloud.zookeeper.discovery.ZookeeperInstance; +import org.springframework.context.ConfigurableApplicationContext; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.util.SocketUtils; + +import static org.assertj.core.api.Assertions.assertThat; + +public class CamelCloudZookeeperServiceRegistryTest { + protected final static String SERVICE_ID = UUID.randomUUID().toString(); + protected final static String SERVICE_NAME = "my-service"; + protected final static String SERVICE_HOST = "localhost"; + protected final static int SERVICE_PORT = SocketUtils.findAvailableTcpPort(); + + @Rule + public final TestName testName = new TestName(); + @Rule + public final TemporaryFolder temporaryFolder = new TemporaryFolder(); + + @Test + public void testServiceRegistry() throws Exception { + final ZookeeperServer server = new ZookeeperServer(temporaryFolder.newFolder(testName.getMethodName())); + + ConfigurableApplicationContext context = new SpringApplicationBuilder(TestConfiguration.class) + .web(WebApplicationType.NONE) + .run( + "--debug=false", + "--spring.main.banner-mode=OFF", + "--spring.application.name=" + UUID.randomUUID().toString(), + "--ribbon.enabled=false", + "--ribbon.eureka.enabled=false", + "--management.endpoint.enabled=false", + "--spring.cloud.zookeeper.enabled=true", + "--spring.cloud.zookeeper.connect-string=" + server.connectString(), + "--spring.cloud.zookeeper.config.enabled=false", + "--spring.cloud.zookeeper.discovery.enabled=true", + "--spring.cloud.service-registry.auto-registration.enabled=false" + ); + + try { + final ServiceDiscovery client = context.getBean(ServiceDiscovery.class); + final ServiceRegistry registry = context.getBean(ServiceRegistry.class); + + registry.register( + DefaultServiceDefinition.builder() + .withHost(SERVICE_HOST) + .withPort(SERVICE_PORT) + .withName(SERVICE_NAME) + .withId(SERVICE_ID) + .build() + ); + + Collection> services = client.queryForInstances(SERVICE_NAME); + + assertThat(services).hasSize(1); + assertThat(services).first().hasFieldOrPropertyWithValue("address", SERVICE_HOST); + assertThat(services).first().hasFieldOrPropertyWithValue("port", SERVICE_PORT); + assertThat(services).first().extracting("payload").first().hasFieldOrPropertyWithValue("id", SERVICE_ID); + assertThat(services).first().extracting("payload").first().hasFieldOrPropertyWithValue("name", SERVICE_NAME); + + } finally { + // shutdown spring context + context.close(); + + // shutdown zookeeper + server.shutdown(); + } + } + + // ************************************* + // Config + // ************************************* + + @EnableAutoConfiguration + @Configuration + public static class TestConfiguration { + @Bean + public RouteBuilder routes() { + return new RouteBuilder() { + @Override + public void configure() throws Exception { + fromF("jetty:http://0.0.0.0:%d/service/endpoint", SERVICE_PORT) + .routeId(SERVICE_ID) + .routeGroup(SERVICE_NAME) + .routePolicy(new ServiceRegistrationRoutePolicy()) + .noAutoStartup() + .to("log:service-registry?level=INFO"); + } + }; + } + } + +} + diff --git a/components/camel-spring-cloud-zookeeper/src/test/java/org/apache/camel/spring/cloud/zookeeper/support/ZookeeperServer.java b/components/camel-spring-cloud-zookeeper/src/test/java/org/apache/camel/spring/cloud/zookeeper/support/ZookeeperServer.java new file mode 100644 index 0000000000000..628ec344f989a --- /dev/null +++ b/components/camel-spring-cloud-zookeeper/src/test/java/org/apache/camel/spring/cloud/zookeeper/support/ZookeeperServer.java @@ -0,0 +1,62 @@ +/** + * 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.camel.spring.cloud.zookeeper.support; + +import java.io.File; +import java.io.IOException; +import java.net.InetSocketAddress; + +import org.apache.zookeeper.server.NIOServerCnxnFactory; +import org.apache.zookeeper.server.ZooKeeperServer; +import org.apache.zookeeper.server.persistence.FileTxnSnapLog; +import org.springframework.util.SocketUtils; + +public class ZookeeperServer { + private NIOServerCnxnFactory connectionFactory; + private ZooKeeperServer zkServer; + + public ZookeeperServer(File root) throws IOException, InterruptedException { + zkServer = new ZooKeeperServer(); + + File dataDir = new File(root, "log"); + File snapDir = new File(root, "data"); + FileTxnSnapLog ftxn = new FileTxnSnapLog(dataDir, snapDir); + + zkServer.setTxnLogFactory(ftxn); + zkServer.setTickTime(1000); + + connectionFactory = new NIOServerCnxnFactory(); + connectionFactory.configure(new InetSocketAddress("localhost", SocketUtils.findAvailableTcpPort()), 0); + connectionFactory.startup(zkServer); + } + + public void shutdown() throws Exception { + connectionFactory.shutdown(); + connectionFactory.join(); + + zkServer.shutdown(); + + while (zkServer.isRunning()) { + zkServer.shutdown(); + Thread.sleep(100); + } + } + + public String connectString() { + return "localhost:" + connectionFactory.getLocalPort(); + } +} diff --git a/components/camel-spring-cloud-zookeeper/src/test/resources/logback.xml b/components/camel-spring-cloud-zookeeper/src/test/resources/logback.xml new file mode 100644 index 0000000000000..1d4cdbe7e4138 --- /dev/null +++ b/components/camel-spring-cloud-zookeeper/src/test/resources/logback.xml @@ -0,0 +1,43 @@ + + + + + + + + %d{HH:mm:ss.SSS} [%-15.15thread] %-5level %-30.30logger - %msg%n + + + + + + %d{HH:mm:ss.SSS} [%-15.15thread] %-5level %-30.30logger - %msg%n + + target/camel-spring-cloud-zookeeper-test.log + + + + + + + + + \ No newline at end of file diff --git a/components/camel-spring-cloud/src/main/java/org/apache/camel/spring/cloud/CamelSpringCloudDiscoveryClientAutoConfiguration.java b/components/camel-spring-cloud/src/main/java/org/apache/camel/spring/cloud/CamelSpringCloudDiscoveryClientAutoConfiguration.java index dec3bc07f2e35..d6eb30849c2b4 100644 --- a/components/camel-spring-cloud/src/main/java/org/apache/camel/spring/cloud/CamelSpringCloudDiscoveryClientAutoConfiguration.java +++ b/components/camel-spring-cloud/src/main/java/org/apache/camel/spring/cloud/CamelSpringCloudDiscoveryClientAutoConfiguration.java @@ -39,7 +39,7 @@ @Conditional(CamelSpringCloudDiscoveryClientAutoConfiguration.LoadBalancerCondition.class) public class CamelSpringCloudDiscoveryClientAutoConfiguration { - @Bean(name = "load-balancer-discovery-client") + @Bean(name = "service-discovery-client") @ConditionalOnMissingBean public DiscoveryClient serviceDiscoveryClient(CamelCloudServiceDiscovery serviceDiscovery) { return new CamelSpringCloudDiscoveryClient("service-discovery-client", serviceDiscovery); diff --git a/components/camel-spring-cloud/src/main/java/org/apache/camel/spring/cloud/CamelSpringCloudServiceRegistry.java b/components/camel-spring-cloud/src/main/java/org/apache/camel/spring/cloud/CamelSpringCloudServiceRegistry.java new file mode 100644 index 0000000000000..e376e08b68468 --- /dev/null +++ b/components/camel-spring-cloud/src/main/java/org/apache/camel/spring/cloud/CamelSpringCloudServiceRegistry.java @@ -0,0 +1,150 @@ +/** + * 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.camel.spring.cloud; + +import java.lang.reflect.Method; +import java.util.ArrayList; +import java.util.Collection; +import java.util.List; +import java.util.Objects; + +import org.apache.camel.cloud.ServiceDefinition; +import org.apache.camel.impl.cloud.AbstractServiceRegistry; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.cloud.client.serviceregistry.Registration; +import org.springframework.cloud.client.serviceregistry.ServiceRegistry; +import org.springframework.core.convert.ConversionService; + +public class CamelSpringCloudServiceRegistry extends AbstractServiceRegistry { + private static final Logger LOGGER = LoggerFactory.getLogger(CamelSpringCloudServiceRegistry.class); + + private final List definitions; + private final List conversionServices; + private final ServiceRegistry serviceRegistry; + private final Class registrationType; + + public CamelSpringCloudServiceRegistry(Collection conversionServices, ServiceRegistry serviceRegistry) { + this.definitions = new ArrayList<>(); + this.conversionServices = new ArrayList<>(conversionServices); + this.serviceRegistry = serviceRegistry; + this.registrationType = determineRegistrationType("register"); + } + + @Override + public void register(ServiceDefinition definition) { + Registration result = convertServiceDefinition(definition); + + synchronized (this) { + LOGGER.debug("Register service with definition: {} with registrations: {}", definition, registrationType); + + serviceRegistry.register(result); + + // keep track of registered definition to remove them upon registry + // shutdown + if (definitions.stream().noneMatch(d -> matchById(d, definition))) { + definitions.add(definition); + } + } + } + + @Override + public void deregister(ServiceDefinition definition) { + Registration result = convertServiceDefinition(definition); + + synchronized (this) { + LOGGER.debug("Deregister service with definition: {} with registrations: {}", definition, registrationType); + + serviceRegistry.deregister(result); + + // remove any instance with the same id + definitions.removeIf(d -> matchById(d, definition)); + } + } + + @Override + protected void doStart() throws Exception { + } + + @Override + protected void doStop() throws Exception { + // TODO: need to be improved + new ArrayList<>(definitions).forEach(this::deregister); + } + + public ServiceRegistry getNativeServiceRegistry() { + return this.serviceRegistry; + } + + public > T getNativeServiceRegistry(Class type) { + return type.cast(this.serviceRegistry); + } + + /** + * Determine the native registration type. This is needed because the registry + * specific implementation provided by spring-cloud-xyz does not handle generic + * Registration object but needs a Registration specific to the underlying + * technology used. + * + * @return the registration type + */ + private Class determineRegistrationType(String methodName) { + Class type = null; + Method[] methods = serviceRegistry.getClass().getDeclaredMethods(); + + for (Method method: methods) { + if (!methodName.equals(method.getName())) { + continue; + } + + if (method.getParameterCount() != 1) { + continue; + } + + Class parameterType = method.getParameterTypes()[0]; + if (Registration.class.isAssignableFrom(parameterType)) { + if (type == null) { + type = (Class)parameterType; + } else { + if (type.isAssignableFrom(parameterType)) { + type = (Class)parameterType; + } + } + } + } + + return type != null ? type : Registration.class; + } + + private boolean matchById(ServiceDefinition definition, ServiceDefinition reference) { + if (definition.getId() == null || reference.getId() == null) { + return false; + } + + return Objects.equals(definition.getId(), reference.getId()); + } + + private Registration convertServiceDefinition(ServiceDefinition definition) { + for (ConversionService conversionService: conversionServices) { + if (conversionService.canConvert(ServiceDefinition.class, registrationType)) { + return conversionService.convert(definition, registrationType); + } + } + + throw new IllegalStateException("Unable to convert service definition to native registration of type:" + registrationType); + } +} diff --git a/components/camel-spring-cloud/src/main/java/org/apache/camel/spring/cloud/CamelSpringCloudServiceRegistryAutoConfiguration.java b/components/camel-spring-cloud/src/main/java/org/apache/camel/spring/cloud/CamelSpringCloudServiceRegistryAutoConfiguration.java new file mode 100644 index 0000000000000..caffeaa76cd1a --- /dev/null +++ b/components/camel-spring-cloud/src/main/java/org/apache/camel/spring/cloud/CamelSpringCloudServiceRegistryAutoConfiguration.java @@ -0,0 +1,61 @@ +/** + * 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.camel.spring.cloud; + +import java.util.Collection; + +import org.apache.camel.cloud.ServiceRegistry; +import org.apache.camel.spring.boot.CamelAutoConfiguration; +import org.apache.camel.spring.boot.util.GroupCondition; +import org.springframework.boot.autoconfigure.AutoConfigureAfter; +import org.springframework.boot.autoconfigure.AutoConfigureBefore; +import org.springframework.boot.autoconfigure.condition.ConditionalOnBean; +import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean; +import org.springframework.cloud.client.serviceregistry.ServiceRegistryAutoConfiguration; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Conditional; +import org.springframework.context.annotation.Configuration; + +@Configuration +@AutoConfigureAfter(ServiceRegistryAutoConfiguration.class) +@AutoConfigureBefore(CamelAutoConfiguration.class) +@ConditionalOnBean(org.springframework.cloud.client.serviceregistry.ServiceRegistry.class) +@Conditional(CamelSpringCloudServiceRegistryAutoConfiguration.ServiceRegistryCondition.class) +public class CamelSpringCloudServiceRegistryAutoConfiguration { + + @Bean + @ConditionalOnMissingBean + public ServiceRegistry camelServiceRegistry( + Collection conversionServices, + org.springframework.cloud.client.serviceregistry.ServiceRegistry serviceRegistry) { + + return new CamelSpringCloudServiceRegistry(conversionServices, serviceRegistry); + } + + // ******************************* + // Condition + // ******************************* + + public static class ServiceRegistryCondition extends GroupCondition { + public ServiceRegistryCondition() { + super( + "camel.cloud", + "camel.cloud.service-registry" + ); + } + } +} diff --git a/components/camel-spring-cloud/src/main/resources/META-INF/spring.factories b/components/camel-spring-cloud/src/main/resources/META-INF/spring.factories index 9de8f8bf8deb1..ae6d0750af1e5 100644 --- a/components/camel-spring-cloud/src/main/resources/META-INF/spring.factories +++ b/components/camel-spring-cloud/src/main/resources/META-INF/spring.factories @@ -16,4 +16,6 @@ # org.springframework.boot.autoconfigure.EnableAutoConfiguration=\ -org.apache.camel.spring.cloud.CamelSpringCloudServiceLoadBalancerAutoConfiguration + org.apache.camel.spring.cloud.CamelSpringCloudDiscoveryClientAutoConfiguration,\ + org.apache.camel.spring.cloud.CamelSpringCloudServiceLoadBalancerAutoConfiguration,\ + org.apache.camel.spring.cloud.CamelSpringCloudServiceRegistryAutoConfiguration diff --git a/components/camel-spring-cloud/src/test/java/org/apache/camel/spring/cloud/CamelSpringCloudServiceCallRibbonTest.java b/components/camel-spring-cloud/src/test/java/org/apache/camel/spring/cloud/CamelSpringCloudServiceCallRibbonTest.java index 708f04f403b7f..00d1696f5af40 100644 --- a/components/camel-spring-cloud/src/test/java/org/apache/camel/spring/cloud/CamelSpringCloudServiceCallRibbonTest.java +++ b/components/camel-spring-cloud/src/test/java/org/apache/camel/spring/cloud/CamelSpringCloudServiceCallRibbonTest.java @@ -42,10 +42,10 @@ CamelSpringCloudServiceCallRibbonTest.TestConfiguration.class }, properties = { + "spring.main.banner-mode=off", "ribbon.eureka.enabled=false", "ribbon.listOfServers=localhost:9090,localhost:9092", - "ribbon.ServerListRefreshInterval=15000", - "debug=false" + "ribbon.ServerListRefreshInterval=15000" } ) public class CamelSpringCloudServiceCallRibbonTest { @@ -71,7 +71,8 @@ public RouteBuilder myRouteBuilder() { public void configure() throws Exception { from("direct:start") .serviceCall() - .name("custom-svc-list/hello"); + .name("custom-svc-list/hello") + .end(); from("jetty:http://localhost:9090/hello") .transform() diff --git a/components/camel-spring-cloud/src/test/java/org/apache/camel/spring/cloud/CamelSpringCloudServiceRegistryTest.java b/components/camel-spring-cloud/src/test/java/org/apache/camel/spring/cloud/CamelSpringCloudServiceRegistryTest.java new file mode 100644 index 0000000000000..f62843d7a3d04 --- /dev/null +++ b/components/camel-spring-cloud/src/test/java/org/apache/camel/spring/cloud/CamelSpringCloudServiceRegistryTest.java @@ -0,0 +1,271 @@ +package org.apache.camel.spring.cloud; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; +import java.util.UUID; +import java.util.concurrent.ThreadLocalRandom; + +import org.apache.camel.cloud.ServiceDefinition; +import org.apache.camel.impl.cloud.DefaultServiceDefinition; +import org.apache.camel.spring.boot.CamelAutoConfiguration; +import org.apache.camel.spring.boot.cloud.CamelCloudAutoConfiguration; +import org.junit.Test; +import org.springframework.boot.autoconfigure.AutoConfigurations; +import org.springframework.boot.test.context.runner.ApplicationContextRunner; +import org.springframework.cloud.client.DefaultServiceInstance; +import org.springframework.cloud.client.serviceregistry.Registration; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.core.convert.converter.Converter; + +import static org.assertj.core.api.Assertions.assertThat; + +public class CamelSpringCloudServiceRegistryTest { + + // ************************************* + // Test Auto Configuration + // ************************************* + + @Test + public void testAutoConfiguration() { + new ApplicationContextRunner() + .withConfiguration( + AutoConfigurations.of( + CamelAutoConfiguration.class, + CamelCloudAutoConfiguration.class, + CamelSpringCloudServiceRegistryAutoConfiguration.class + )) + .withUserConfiguration( + TestConfiguration.class) + .withPropertyValues( + "spring.main.banner-mode=off", + "ribbon.eureka.enabled=false", + "ribbon.enabled=false") + .run( + context -> { + // spring cloud registry + assertThat(context).hasSingleBean(org.springframework.cloud.client.serviceregistry.ServiceRegistry.class); + assertThat(context).getBean(org.springframework.cloud.client.serviceregistry.ServiceRegistry.class).isInstanceOf(MyServiceRegistry.class); + + // camel registry + assertThat(context).hasSingleBean(org.apache.camel.cloud.ServiceRegistry.class); + assertThat(context).getBean(org.apache.camel.cloud.ServiceRegistry.class).isInstanceOf(CamelSpringCloudServiceRegistry.class); + + assertThat(context).getBean(org.apache.camel.cloud.ServiceRegistry.class).hasFieldOrPropertyWithValue( + "nativeServiceRegistry", + context.getBean(org.springframework.cloud.client.serviceregistry.ServiceRegistry.class) + ); + } + ); + } + + @Test + public void testDisabledCamelCloud() { + new ApplicationContextRunner() + .withConfiguration( + AutoConfigurations.of( + CamelAutoConfiguration.class, + CamelCloudAutoConfiguration.class, + CamelSpringCloudServiceRegistryAutoConfiguration.class + )) + .withUserConfiguration( + TestConfiguration.class) + .withPropertyValues( + "spring.main.banner-mode=off", + "ribbon.eureka.enabled=false", + "ribbon.enabled=false", + "camel.cloud.enabled=false") + .run( + context -> { + // spring cloud registry + assertThat(context).hasSingleBean(org.springframework.cloud.client.serviceregistry.ServiceRegistry.class); + assertThat(context).getBean(org.springframework.cloud.client.serviceregistry.ServiceRegistry.class).isInstanceOf(MyServiceRegistry.class); + + // camel registry + assertThat(context).doesNotHaveBean(org.apache.camel.cloud.ServiceRegistry.class); + } + ); + } + + @Test + public void testDisabledCamelServiceRegistry() { + new ApplicationContextRunner() + .withConfiguration( + AutoConfigurations.of( + CamelAutoConfiguration.class, + CamelCloudAutoConfiguration.class, + CamelSpringCloudServiceRegistryAutoConfiguration.class + )) + .withUserConfiguration( + TestConfiguration.class) + .withPropertyValues( + "spring.main.banner-mode=off", + "ribbon.eureka.enabled=false", + "ribbon.enabled=false", + "camel.cloud.enabled=true", + "camel.cloud.service-registry.enabled=false") + .run( + context -> { + // spring cloud registry + assertThat(context).hasSingleBean(org.springframework.cloud.client.serviceregistry.ServiceRegistry.class); + assertThat(context).getBean(org.springframework.cloud.client.serviceregistry.ServiceRegistry.class).isInstanceOf(MyServiceRegistry.class); + + // camel registry + assertThat(context).doesNotHaveBean(org.apache.camel.cloud.ServiceRegistry.class); + } + ); + } + + @Test + public void testEnabledCamelServiceRegistry() { + new ApplicationContextRunner() + .withConfiguration( + AutoConfigurations.of( + CamelAutoConfiguration.class, + CamelCloudAutoConfiguration.class, + CamelSpringCloudServiceRegistryAutoConfiguration.class + )) + .withUserConfiguration( + TestConfiguration.class) + .withPropertyValues( + "spring.main.banner-mode=off", + "ribbon.eureka.enabled=false", + "ribbon.enabled=false", + "camel.cloud.enabled=false", + "camel.cloud.service-registry.enabled=true") + .run( + context -> { + // spring cloud registry + assertThat(context).hasSingleBean(org.springframework.cloud.client.serviceregistry.ServiceRegistry.class); + assertThat(context).getBean(org.springframework.cloud.client.serviceregistry.ServiceRegistry.class).isInstanceOf(MyServiceRegistry.class); + + // camel registry + assertThat(context).hasSingleBean(org.apache.camel.cloud.ServiceRegistry.class); + assertThat(context).getBean(org.apache.camel.cloud.ServiceRegistry.class).isInstanceOf(CamelSpringCloudServiceRegistry.class); + + assertThat(context).getBean(org.apache.camel.cloud.ServiceRegistry.class).hasFieldOrPropertyWithValue( + "nativeServiceRegistry", + context.getBean(org.springframework.cloud.client.serviceregistry.ServiceRegistry.class) + ); + } + ); + } + + // ************************************* + // Test Registry + // ************************************* + + @Test + public void testServiceRegistry() { + new ApplicationContextRunner() + .withConfiguration( + AutoConfigurations.of( + CamelAutoConfiguration.class, + CamelCloudAutoConfiguration.class, + CamelSpringCloudServiceRegistryAutoConfiguration.class + )) + .withUserConfiguration( + TestConfiguration.class) + .withPropertyValues( + "spring.main.banner-mode=off", + "ribbon.eureka.enabled=false", + "ribbon.enabled=false") + .run( + context -> { + CamelSpringCloudServiceRegistry camelRgistry = context.getBean(CamelSpringCloudServiceRegistry.class); + + final String serviceName = "my-.service"; + final String serviceId = UUID.randomUUID().toString(); + final int port = ThreadLocalRandom.current().nextInt(); + + camelRgistry.register( + DefaultServiceDefinition.builder() + .withHost("localhost") + .withPort(port) + .withName(serviceName) + .withId(serviceId) + .build() + ); + + MyServiceRegistry cloudRegistry = camelRgistry.getNativeServiceRegistry(MyServiceRegistry.class); + + assertThat(cloudRegistry.registrations).hasSize(1); + assertThat(cloudRegistry.registrations.get(0)).hasFieldOrPropertyWithValue("serviceId", serviceName); + assertThat(cloudRegistry.registrations.get(0)).hasFieldOrPropertyWithValue("host", "localhost"); + assertThat(cloudRegistry.registrations.get(0)).hasFieldOrPropertyWithValue("port", port); + + } + ); + } + + // ************************************* + // Config + // ************************************* + + @Configuration + public static class TestConfiguration { + @Bean + public org.springframework.cloud.client.serviceregistry.ServiceRegistry myServiceRegistry() { + return new MyServiceRegistry(); + } + + @Bean + public Converter definitionToRegistration() { + return new Converter() { + @Override + public MyServiceRegistration convert(ServiceDefinition source) { + return new MyServiceRegistration( + source.getName(), + source.getHost(), + source.getPort() + ); + } + }; + } + + } + + // ************************************* + // Service Registry Impl + // ************************************* + + public static class MyServiceRegistry implements org.springframework.cloud.client.serviceregistry.ServiceRegistry { + List registrations; + + public MyServiceRegistry() { + this.registrations = new ArrayList<>(); + } + + @Override + public void register(MyServiceRegistration registration) { + this.registrations.add(registration); + } + + @Override + public void deregister(MyServiceRegistration registration) { + this.registrations.remove(registration); + } + + @Override + public void close() { + this.registrations.clear(); + } + + @Override + public void setStatus(MyServiceRegistration registration, String status) { + throw new UnsupportedOperationException(); + } + + @Override + public Object getStatus(MyServiceRegistration registration) { + throw new UnsupportedOperationException(); + } + } + + public static class MyServiceRegistration extends DefaultServiceInstance implements Registration { + public MyServiceRegistration(String serviceId, String host, int port) { + super(serviceId, host, port, false, Collections.emptyMap()); + } + } +} diff --git a/components/pom.xml b/components/pom.xml index f52a66858e099..9e35306d0fbb1 100644 --- a/components/pom.xml +++ b/components/pom.xml @@ -280,7 +280,9 @@ camel-spring-batch camel-spring-boot camel-spring-cloud + camel-spring-cloud-consul camel-spring-cloud-netflix + camel-spring-cloud-zookeeper camel-spring-javaconfig camel-spring-integration camel-spring-ldap diff --git a/components/readme.adoc b/components/readme.adoc index d79a3e1974f0d..8d9bd00c2971c 100644 --- a/components/readme.adoc +++ b/components/readme.adoc @@ -2,7 +2,7 @@ Components ^^^^^^^^^^ // components: START -Number of Components: 294 in 201 JAR artifacts (20 deprecated) +Number of Components: 295 in 202 JAR artifacts (20 deprecated) [width="100%",cols="4,1,5",options="header"] |=== @@ -1075,7 +1075,7 @@ Miscellaneous Components ^^^^^^^^^^^^^^^^^^^^^^^^ // others: START -Number of Miscellaneous Components: 41 in 41 JAR artifacts (13 deprecated) +Number of Miscellaneous Components: 43 in 43 JAR artifacts (13 deprecated) [width="100%",cols="4,1,5",options="header"] |=== @@ -1135,8 +1135,12 @@ Number of Miscellaneous Components: 41 in 41 JAR artifacts (13 deprecated) | link:camel-spring-cloud/src/main/docs/spring-cloud.adoc[Spring Cloud] (camel-spring-cloud) | 2.19 | Camel Cloud integration with Spring Cloud +| link:camel-spring-cloud-consul/src/main/docs/spring-cloud-consul.adoc[Spring Cloud Consul] (camel-spring-cloud-consul) | 2.19 | Camel Cloud integration with Spring Cloud Consul + | link:camel-spring-cloud-netflix/src/main/docs/spring-cloud-netflix.adoc[Spring Cloud Netflix] (camel-spring-cloud-netflix) | 2.19 | Camel Cloud integration with Spring Cloud Netflix +| link:camel-spring-cloud-zookeeper/src/main/docs/spring-cloud-zookeeper.adoc[Spring Cloud Zookeeper] (camel-spring-cloud-zookeeper) | 2.19 | Camel Cloud integration with Spring Cloud Zookeeper + | link:camel-spring-javaconfig/src/main/docs/spring-javaconfig.adoc[Spring Java Configuration] (camel-spring-javaconfig) | 2.0 | Using Camel with Spring Java Configuration | link:camel-spring-security/src/main/docs/spring-security.adoc[Spring Security] (camel-spring-security) | 2.3 | Security using Spring Security diff --git a/docs/user-manual/en/SUMMARY.md b/docs/user-manual/en/SUMMARY.md index 0d7ce31d7587c..9aebcfbae900b 100644 --- a/docs/user-manual/en/SUMMARY.md +++ b/docs/user-manual/en/SUMMARY.md @@ -426,7 +426,9 @@ * [Shiro](shiro.adoc) * [Spring Boot](spring-boot.adoc) * [Spring Cloud](spring-cloud.adoc) + * [Spring Cloud Consul](spring-cloud-consul.adoc) * [Spring Cloud Netflix](spring-cloud-netflix.adoc) + * [Spring Cloud Zookeeper](spring-cloud-zookeeper.adoc) * [Spring Java Configuration](spring-javaconfig.adoc) * [Spring Security](spring-security.adoc) * [Swagger](swagger.adoc) diff --git a/parent/pom.xml b/parent/pom.xml index fd31a764f15bb..b491be3c8f549 100644 --- a/parent/pom.xml +++ b/parent/pom.xml @@ -637,7 +637,9 @@ 4.0.1.RELEASE_1 2.0.2.RELEASE 1.3.3.RELEASE + 1.3.3.RELEASE 1.4.4.RELEASE + 1.2.1.RELEASE 1.2.0 2.0.5.RELEASE 2.0.5.RELEASE @@ -2012,11 +2014,21 @@ camel-spring-cloud ${project.version} + + org.apache.camel + camel-spring-cloud-consul + ${project.version} + org.apache.camel camel-spring-cloud-netflix ${project.version} + + org.apache.camel + camel-spring-cloud-zookeeper + ${project.version} + org.apache.camel camel-spring-integration @@ -3446,11 +3458,21 @@ camel-spring-cloud-starter ${project.version} + + org.apache.camel + camel-spring-cloud-consul-starter + ${project.version} + org.apache.camel camel-spring-cloud-netflix-starter ${project.version} + + org.apache.camel + camel-spring-cloud-zookeeper-starter + ${project.version} + org.apache.camel camel-spring-integration-starter diff --git a/platforms/spring-boot/components-starter/camel-spring-cloud-consul-starter/pom.xml b/platforms/spring-boot/components-starter/camel-spring-cloud-consul-starter/pom.xml new file mode 100644 index 0000000000000..040625a36a6b4 --- /dev/null +++ b/platforms/spring-boot/components-starter/camel-spring-cloud-consul-starter/pom.xml @@ -0,0 +1,65 @@ + + + + 4.0.0 + + org.apache.camel + components-starter + 2.22.0-SNAPSHOT + + camel-spring-cloud-consul-starter + jar + Spring-Boot Starter :: Camel :: Spring Cloud Consul + Spring-Boot Starter for Camel Cloud integration with Spring Cloud Consul + + + org.springframework.boot + spring-boot-starter + ${spring-boot-version} + + + org.apache.camel + camel-spring-cloud-consul + ${project.version} + + + + org.apache.camel + camel-core-starter + + + org.apache.camel + camel-spring-boot-starter + + + + + + + spring-milestones + Spring Milestones + https://repo.spring.io/libs-milestone + + false + + + + + diff --git a/platforms/spring-boot/components-starter/camel-spring-cloud-consul-starter/src/main/resources/META-INF/LICENSE.txt b/platforms/spring-boot/components-starter/camel-spring-cloud-consul-starter/src/main/resources/META-INF/LICENSE.txt new file mode 100644 index 0000000000000..6b0b1270ff0ca --- /dev/null +++ b/platforms/spring-boot/components-starter/camel-spring-cloud-consul-starter/src/main/resources/META-INF/LICENSE.txt @@ -0,0 +1,203 @@ + + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright [yyyy] [name of copyright owner] + + 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. + diff --git a/platforms/spring-boot/components-starter/camel-spring-cloud-consul-starter/src/main/resources/META-INF/NOTICE.txt b/platforms/spring-boot/components-starter/camel-spring-cloud-consul-starter/src/main/resources/META-INF/NOTICE.txt new file mode 100644 index 0000000000000..2e215bf2e6b1f --- /dev/null +++ b/platforms/spring-boot/components-starter/camel-spring-cloud-consul-starter/src/main/resources/META-INF/NOTICE.txt @@ -0,0 +1,11 @@ + ========================================================================= + == NOTICE file corresponding to the section 4 d of == + == the Apache License, Version 2.0, == + == in this case for the Apache Camel distribution. == + ========================================================================= + + This product includes software developed by + The Apache Software Foundation (http://www.apache.org/). + + Please read the different LICENSE files present in the licenses directory of + this distribution. diff --git a/platforms/spring-boot/components-starter/camel-spring-cloud-consul-starter/src/main/resources/META-INF/spring.provides b/platforms/spring-boot/components-starter/camel-spring-cloud-consul-starter/src/main/resources/META-INF/spring.provides new file mode 100644 index 0000000000000..c50ba537ebb3e --- /dev/null +++ b/platforms/spring-boot/components-starter/camel-spring-cloud-consul-starter/src/main/resources/META-INF/spring.provides @@ -0,0 +1,17 @@ +## --------------------------------------------------------------------------- +## 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. +## --------------------------------------------------------------------------- +provides: camel-spring-cloud-consul diff --git a/platforms/spring-boot/components-starter/camel-spring-cloud-zookeeper-starter/pom.xml b/platforms/spring-boot/components-starter/camel-spring-cloud-zookeeper-starter/pom.xml new file mode 100644 index 0000000000000..cd5975e9d2a4b --- /dev/null +++ b/platforms/spring-boot/components-starter/camel-spring-cloud-zookeeper-starter/pom.xml @@ -0,0 +1,65 @@ + + + + 4.0.0 + + org.apache.camel + components-starter + 2.22.0-SNAPSHOT + + camel-spring-cloud-zookeeper-starter + jar + Spring-Boot Starter :: Camel :: Spring Cloud Zookeeper + Spring-Boot Starter for Camel Cloud integration with Spring Cloud Zookeeper + + + org.springframework.boot + spring-boot-starter + ${spring-boot-version} + + + org.apache.camel + camel-spring-cloud-zookeeper + ${project.version} + + + + org.apache.camel + camel-core-starter + + + org.apache.camel + camel-spring-boot-starter + + + + + + + spring-milestones + Spring Milestones + https://repo.spring.io/libs-milestone + + false + + + + + diff --git a/platforms/spring-boot/components-starter/camel-spring-cloud-zookeeper-starter/src/main/resources/META-INF/LICENSE.txt b/platforms/spring-boot/components-starter/camel-spring-cloud-zookeeper-starter/src/main/resources/META-INF/LICENSE.txt new file mode 100644 index 0000000000000..6b0b1270ff0ca --- /dev/null +++ b/platforms/spring-boot/components-starter/camel-spring-cloud-zookeeper-starter/src/main/resources/META-INF/LICENSE.txt @@ -0,0 +1,203 @@ + + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright [yyyy] [name of copyright owner] + + 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. + diff --git a/platforms/spring-boot/components-starter/camel-spring-cloud-zookeeper-starter/src/main/resources/META-INF/NOTICE.txt b/platforms/spring-boot/components-starter/camel-spring-cloud-zookeeper-starter/src/main/resources/META-INF/NOTICE.txt new file mode 100644 index 0000000000000..2e215bf2e6b1f --- /dev/null +++ b/platforms/spring-boot/components-starter/camel-spring-cloud-zookeeper-starter/src/main/resources/META-INF/NOTICE.txt @@ -0,0 +1,11 @@ + ========================================================================= + == NOTICE file corresponding to the section 4 d of == + == the Apache License, Version 2.0, == + == in this case for the Apache Camel distribution. == + ========================================================================= + + This product includes software developed by + The Apache Software Foundation (http://www.apache.org/). + + Please read the different LICENSE files present in the licenses directory of + this distribution. diff --git a/platforms/spring-boot/components-starter/camel-spring-cloud-zookeeper-starter/src/main/resources/META-INF/spring.provides b/platforms/spring-boot/components-starter/camel-spring-cloud-zookeeper-starter/src/main/resources/META-INF/spring.provides new file mode 100644 index 0000000000000..64db25de17990 --- /dev/null +++ b/platforms/spring-boot/components-starter/camel-spring-cloud-zookeeper-starter/src/main/resources/META-INF/spring.provides @@ -0,0 +1,17 @@ +## --------------------------------------------------------------------------- +## 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. +## --------------------------------------------------------------------------- +provides: camel-spring-cloud-zookeeper diff --git a/platforms/spring-boot/components-starter/pom.xml b/platforms/spring-boot/components-starter/pom.xml index f0751895eeb12..9d8cc35a5a94d 100644 --- a/platforms/spring-boot/components-starter/pom.xml +++ b/platforms/spring-boot/components-starter/pom.xml @@ -292,8 +292,10 @@ camel-splunk-starter camel-spring-batch-starter camel-spring-boot-starter + camel-spring-cloud-consul-starter camel-spring-cloud-netflix-starter camel-spring-cloud-starter + camel-spring-cloud-zookeeper-starter camel-spring-integration-starter camel-spring-javaconfig-starter camel-spring-ldap-starter diff --git a/platforms/spring-boot/spring-boot-dm/camel-spring-boot-dependencies/pom.xml b/platforms/spring-boot/spring-boot-dm/camel-spring-boot-dependencies/pom.xml index 8f335eb852478..090c6ad95e7c8 100644 --- a/platforms/spring-boot/spring-boot-dm/camel-spring-boot-dependencies/pom.xml +++ b/platforms/spring-boot/spring-boot-dm/camel-spring-boot-dependencies/pom.xml @@ -2579,6 +2579,16 @@ camel-spring-cloud ${project.version} + + org.apache.camel + camel-spring-cloud-consul + ${project.version} + + + org.apache.camel + camel-spring-cloud-consul-starter + ${project.version} + org.apache.camel camel-spring-cloud-netflix @@ -2594,6 +2604,16 @@ camel-spring-cloud-starter ${project.version} + + org.apache.camel + camel-spring-cloud-zookeeper + ${project.version} + + + org.apache.camel + camel-spring-cloud-zookeeper-starter + ${project.version} + org.apache.camel camel-spring-integration From 4988f0c4ff40f83c6d9fe18f1b6d5be6a3a17ad4 Mon Sep 17 00:00:00 2001 From: lburgazzoli Date: Wed, 23 May 2018 12:57:51 +0200 Subject: [PATCH 08/20] CAMEL-12502: camel cloud : create a service route policy --- .../cloud/ServiceRegistrationRoutePolicy.java | 108 +++++++++--------- 1 file changed, 57 insertions(+), 51 deletions(-) diff --git a/camel-core/src/main/java/org/apache/camel/impl/cloud/ServiceRegistrationRoutePolicy.java b/camel-core/src/main/java/org/apache/camel/impl/cloud/ServiceRegistrationRoutePolicy.java index c432fa7218233..f506a04dc74ac 100644 --- a/camel-core/src/main/java/org/apache/camel/impl/cloud/ServiceRegistrationRoutePolicy.java +++ b/camel-core/src/main/java/org/apache/camel/impl/cloud/ServiceRegistrationRoutePolicy.java @@ -16,6 +16,7 @@ */ package org.apache.camel.impl.cloud; +import java.util.HashMap; import java.util.Map; import java.util.Optional; @@ -121,66 +122,71 @@ private void deregister(Route route) { } private Optional computeServiceDefinition(Route route) { - Endpoint endpoint = route.getConsumer().getEndpoint(); + final Endpoint endpoint = route.getConsumer().getEndpoint(); + final Map properties = new HashMap<>(); if (endpoint instanceof DiscoverableService) { - final DiscoverableService service = (DiscoverableService)endpoint; - final Map properties = service.getServiceProperties(); - - // try to get the service id from route properties - String serviceId = (String)route.getProperties().get(ServiceDefinition.SERVICE_META_ID); - if (serviceId == null) { - // if not check if the route id is custom and use it - if (route.getRouteContext().getRoute().hasCustomIdAssigned()) { - serviceId = route.getId(); - } - } - if (serviceId == null) { - // finally get the id from the DiscoverableService - serviceId = (String)properties.get(ServiceDefinition.SERVICE_META_ID); - } + final DiscoverableService service = (DiscoverableService) endpoint; - // try to get the service name from route properties - String serviceName = (String)route.getProperties().get(ServiceDefinition.SERVICE_META_NAME); - if (serviceName == null) { - // if not check if the route group is defined use the route group - serviceName = route.getGroup(); - } - if (serviceName == null) { - // finally get the name from the DiscoverableService - serviceName = (String)properties.get(ServiceDefinition.SERVICE_META_NAME); + // first load all the properties from the endpoint + properties.putAll(service.getServiceProperties()); + } + + // try to get the service id from route properties + String serviceId = (String)route.getProperties().get(ServiceDefinition.SERVICE_META_ID); + if (serviceId == null) { + // if not check if the route id is custom and use it + if (route.getRouteContext().getRoute().hasCustomIdAssigned()) { + serviceId = route.getId(); } + } + if (serviceId == null) { + // finally get the id from the DiscoverableService + serviceId = (String)properties.get(ServiceDefinition.SERVICE_META_ID); + } - ObjectHelper.notNull(serviceId, "Service ID"); - ObjectHelper.notNull(serviceName, "Service Name"); - - // Build the final resource definition from bits collected from the - // endpoint and the route. - DefaultServiceDefinition.Builder builder = DefaultServiceDefinition.builder() - .from(properties) - .withId(serviceId) - .withName(serviceName); - - // Add additional metadata from route properties whose name starts - // with ServiceDefinition.SERVICE_META_PREFIX. - // - // NOTE: At the moment it is not possible to add properties to a route - // with fluent DSL - for (Map.Entry entry: route.getProperties().entrySet()) { - if (!entry.getKey().startsWith(ServiceDefinition.SERVICE_META_PREFIX)) { - continue; - } - - final String key = entry.getKey().substring(ServiceDefinition.SERVICE_META_PREFIX.length()); - final String val = camelContext.getTypeConverter().convertTo(String.class, entry.getValue()); - - builder.addMeta(key, val); + // try to get the service name from route properties + String serviceName = (String)route.getProperties().get(ServiceDefinition.SERVICE_META_NAME); + if (serviceName == null) { + // if not check if the route group is defined use the route group + serviceName = route.getGroup(); + } + if (serviceName == null) { + // finally get the name from the DiscoverableService + serviceName = (String)properties.get(ServiceDefinition.SERVICE_META_NAME); + } + + if (ObjectHelper.isEmpty(serviceId) || ObjectHelper.isEmpty(serviceName)) { + LOGGER.debug("Route {} has not enough information for service registration"); + return Optional.empty(); + } + + // Build the final resource definition from bits collected from the + // endpoint and the route. + DefaultServiceDefinition.Builder builder = DefaultServiceDefinition.builder() + .from(properties) + .withId(serviceId) + .withName(serviceName) + .addMeta(ServiceDefinition.SERVICE_META_NAME, serviceName) + .addMeta(ServiceDefinition.SERVICE_META_ID, serviceId); + + // Add additional metadata from route properties whose name starts + // with ServiceDefinition.SERVICE_META_PREFIX. + // + // NOTE: At the moment it is not possible to add properties to a route + // with fluent DSL + for (Map.Entry entry: route.getProperties().entrySet()) { + if (!entry.getKey().startsWith(ServiceDefinition.SERVICE_META_PREFIX)) { + continue; } - return Optional.of(builder.build()); + final String key = entry.getKey().substring(ServiceDefinition.SERVICE_META_PREFIX.length()); + final String val = camelContext.getTypeConverter().convertTo(String.class, entry.getValue()); + + builder.addMeta(key, val); } - return Optional.empty(); + return Optional.of(builder.build()); } } From ea88ec23da3e9e886e2cdbbe87992615c11b6c6d Mon Sep 17 00:00:00 2001 From: lburgazzoli Date: Wed, 23 May 2018 16:35:40 +0200 Subject: [PATCH 09/20] CAMEL-12531: camel cloud : create a spring cloud based camel-service example --- .../CamelCloudConfigurationProperties.java | 13 ++ ...iceCallConfigurationAutoConfiguration.java | 24 +-- .../CamelSpringCloudServiceDiscovery.java | 49 ++++++ ...loudServiceDiscoveryAutoConfiguration.java | 61 ++++++++ .../main/resources/META-INF/spring.factories | 1 + examples/README.adoc | 2 + .../README.adoc | 67 +++++++++ .../consumer/pom.xml | 141 ++++++++++++++++++ .../camel/example/ConsumerApplication.java | 57 +++++++ .../src/main/resources/application.properties | 51 +++++++ .../pom.xml | 58 +++++++ .../service/pom.xml | 132 ++++++++++++++++ .../camel/example/ServiceApplication.java | 50 +++++++ .../src/main/resources/application.properties | 33 ++++ examples/pom.xml | 1 + 15 files changed, 731 insertions(+), 9 deletions(-) create mode 100644 components/camel-spring-cloud/src/main/java/org/apache/camel/spring/cloud/CamelSpringCloudServiceDiscovery.java create mode 100644 components/camel-spring-cloud/src/main/java/org/apache/camel/spring/cloud/CamelSpringCloudServiceDiscoveryAutoConfiguration.java create mode 100644 examples/camel-example-spring-cloud-serviceregistry/README.adoc create mode 100644 examples/camel-example-spring-cloud-serviceregistry/consumer/pom.xml create mode 100644 examples/camel-example-spring-cloud-serviceregistry/consumer/src/main/java/org/apache/camel/example/ConsumerApplication.java create mode 100644 examples/camel-example-spring-cloud-serviceregistry/consumer/src/main/resources/application.properties create mode 100644 examples/camel-example-spring-cloud-serviceregistry/pom.xml create mode 100644 examples/camel-example-spring-cloud-serviceregistry/service/pom.xml create mode 100644 examples/camel-example-spring-cloud-serviceregistry/service/src/main/java/org/apache/camel/example/ServiceApplication.java create mode 100644 examples/camel-example-spring-cloud-serviceregistry/service/src/main/resources/application.properties diff --git a/components/camel-spring-boot/src/main/java/org/apache/camel/spring/boot/cloud/CamelCloudConfigurationProperties.java b/components/camel-spring-boot/src/main/java/org/apache/camel/spring/boot/cloud/CamelCloudConfigurationProperties.java index 7fbd65d232bab..bfbd8b2aadcc9 100644 --- a/components/camel-spring-boot/src/main/java/org/apache/camel/spring/boot/cloud/CamelCloudConfigurationProperties.java +++ b/components/camel-spring-boot/src/main/java/org/apache/camel/spring/boot/cloud/CamelCloudConfigurationProperties.java @@ -98,6 +98,11 @@ public class ServiceCall { */ private String loadBalancer; + /** + * Determine if the default load balancer should be used instead of any auto discovered one. + */ + private boolean defaultLoadBalancer; + /** * The {@link Expression} to use. */ @@ -156,6 +161,14 @@ public void setLoadBalancer(String loadBalancer) { this.loadBalancer = loadBalancer; } + public boolean isDefaultLoadBalancer() { + return defaultLoadBalancer; + } + + public void setDefaultLoadBalancer(boolean defaultLoadBalancer) { + this.defaultLoadBalancer = defaultLoadBalancer; + } + public String getExpression() { return expression; } diff --git a/components/camel-spring-boot/src/main/java/org/apache/camel/spring/boot/cloud/CamelCloudServiceCallConfigurationAutoConfiguration.java b/components/camel-spring-boot/src/main/java/org/apache/camel/spring/boot/cloud/CamelCloudServiceCallConfigurationAutoConfiguration.java index 4da2718bad4cb..0c8f93b00e6d7 100644 --- a/components/camel-spring-boot/src/main/java/org/apache/camel/spring/boot/cloud/CamelCloudServiceCallConfigurationAutoConfiguration.java +++ b/components/camel-spring-boot/src/main/java/org/apache/camel/spring/boot/cloud/CamelCloudServiceCallConfigurationAutoConfiguration.java @@ -45,16 +45,22 @@ public class CamelCloudServiceCallConfigurationAutoConfiguration { @Bean(name = ServiceCallDefinitionConstants.DEFAULT_SERVICE_CALL_CONFIG_ID) @ConditionalOnMissingBean(name = ServiceCallDefinitionConstants.DEFAULT_SERVICE_CALL_CONFIG_ID) public ServiceCallConfigurationDefinition serviceCallConfiguration() throws Exception { - ServiceCallConfigurationDefinition definition = new ServiceCallConfigurationDefinition(); - ObjectHelper.ifNotEmpty(configurationProperties.getServiceCall().getComponent(), definition::setComponent); - ObjectHelper.ifNotEmpty(configurationProperties.getServiceCall().getUri(), definition::setUri); - ObjectHelper.ifNotEmpty(configurationProperties.getServiceCall().getServiceDiscovery(), definition::setServiceDiscoveryRef); - ObjectHelper.ifNotEmpty(configurationProperties.getServiceCall().getServiceFilter(), definition::setServiceFilterRef); - ObjectHelper.ifNotEmpty(configurationProperties.getServiceCall().getServiceChooser(), definition::setServiceChooserRef); - ObjectHelper.ifNotEmpty(configurationProperties.getServiceCall().getLoadBalancer(), definition::setLoadBalancerRef); + final ServiceCallConfigurationDefinition definition = new ServiceCallConfigurationDefinition(); + final CamelCloudConfigurationProperties.ServiceCall serviceCall = configurationProperties.getServiceCall(); - String expression = configurationProperties.getServiceCall().getExpression(); - String expressionLanguage = configurationProperties.getServiceCall().getExpressionLanguage(); + ObjectHelper.ifNotEmpty(serviceCall.getComponent(), definition::setComponent); + ObjectHelper.ifNotEmpty(serviceCall.getUri(), definition::setUri); + ObjectHelper.ifNotEmpty(serviceCall.getServiceDiscovery(), definition::setServiceDiscoveryRef); + ObjectHelper.ifNotEmpty(serviceCall.getServiceFilter(), definition::setServiceFilterRef); + ObjectHelper.ifNotEmpty(serviceCall.getServiceChooser(), definition::setServiceChooserRef); + ObjectHelper.ifNotEmpty(serviceCall.getLoadBalancer(), definition::setLoadBalancerRef); + + if (serviceCall.getLoadBalancer() == null && serviceCall.isDefaultLoadBalancer()) { + definition.defaultLoadBalancer(); + } + + final String expression = serviceCall.getExpression(); + final String expressionLanguage = serviceCall.getExpressionLanguage(); if (ObjectHelper.isNotEmpty(expression) && ObjectHelper.isNotEmpty(expressionLanguage)) { Language language = camelContext.resolveLanguage(expressionLanguage); diff --git a/components/camel-spring-cloud/src/main/java/org/apache/camel/spring/cloud/CamelSpringCloudServiceDiscovery.java b/components/camel-spring-cloud/src/main/java/org/apache/camel/spring/cloud/CamelSpringCloudServiceDiscovery.java new file mode 100644 index 0000000000000..211f551a3859c --- /dev/null +++ b/components/camel-spring-cloud/src/main/java/org/apache/camel/spring/cloud/CamelSpringCloudServiceDiscovery.java @@ -0,0 +1,49 @@ +/** + * 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.camel.spring.cloud; + +import java.util.List; +import java.util.stream.Collectors; + +import org.apache.camel.cloud.ServiceDefinition; +import org.apache.camel.cloud.ServiceDiscovery; +import org.apache.camel.impl.cloud.DefaultServiceDefinition; +import org.springframework.cloud.client.discovery.DiscoveryClient; + +public class CamelSpringCloudServiceDiscovery implements ServiceDiscovery { + private final DiscoveryClient discoveryClient; + + public CamelSpringCloudServiceDiscovery(DiscoveryClient discoveryClient) { + this.discoveryClient = discoveryClient; + } + + @Override + public List getServices(String name) { + return discoveryClient.getInstances(name).stream() + .map( + si -> { + return DefaultServiceDefinition.builder() + .withName(si.getServiceId()) + .withHost(si.getHost()) + .withPort(si.getPort()) + .withId(name) + .withMeta(si.getMetadata()) + .build(); + } + ).collect(Collectors.toList()); + } +} diff --git a/components/camel-spring-cloud/src/main/java/org/apache/camel/spring/cloud/CamelSpringCloudServiceDiscoveryAutoConfiguration.java b/components/camel-spring-cloud/src/main/java/org/apache/camel/spring/cloud/CamelSpringCloudServiceDiscoveryAutoConfiguration.java new file mode 100644 index 0000000000000..014b9f7797977 --- /dev/null +++ b/components/camel-spring-cloud/src/main/java/org/apache/camel/spring/cloud/CamelSpringCloudServiceDiscoveryAutoConfiguration.java @@ -0,0 +1,61 @@ +/** + * 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.camel.spring.cloud; + +import org.apache.camel.cloud.ServiceDiscovery; +import org.apache.camel.spring.boot.cloud.CamelCloudAutoConfiguration; +import org.apache.camel.spring.boot.cloud.CamelCloudConfigurationProperties; +import org.apache.camel.spring.boot.cloud.CamelCloudServiceDiscoveryAutoConfiguration; +import org.apache.camel.spring.boot.util.GroupCondition; +import org.springframework.boot.autoconfigure.AutoConfigureAfter; +import org.springframework.boot.autoconfigure.AutoConfigureBefore; +import org.springframework.boot.autoconfigure.condition.ConditionalOnBean; +import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean; +import org.springframework.boot.context.properties.EnableConfigurationProperties; +import org.springframework.cloud.client.discovery.DiscoveryClient; +import org.springframework.cloud.client.discovery.composite.CompositeDiscoveryClientAutoConfiguration; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Conditional; +import org.springframework.context.annotation.Configuration; + +@Configuration +@ConditionalOnBean({ CamelCloudAutoConfiguration.class, DiscoveryClient.class }) +@AutoConfigureAfter(CompositeDiscoveryClientAutoConfiguration.class) +@AutoConfigureBefore(CamelCloudServiceDiscoveryAutoConfiguration.class) +@EnableConfigurationProperties(CamelCloudConfigurationProperties.class) +@Conditional(CamelSpringCloudServiceDiscoveryAutoConfiguration.ServiceDiscoveryCondition.class) +public class CamelSpringCloudServiceDiscoveryAutoConfiguration { + + @Bean(name = "spring-cloud-service-discovery") + @ConditionalOnMissingBean + public ServiceDiscovery springCloudServiceDiscovery(DiscoveryClient discoveryClient) { + return new CamelSpringCloudServiceDiscovery(discoveryClient); + } + + // ******************************* + // Condition + // ******************************* + + public static class ServiceDiscoveryCondition extends GroupCondition { + public ServiceDiscoveryCondition() { + super( + "camel.cloud", + "camel.cloud.service-discovery" + ); + } + } +} diff --git a/components/camel-spring-cloud/src/main/resources/META-INF/spring.factories b/components/camel-spring-cloud/src/main/resources/META-INF/spring.factories index ae6d0750af1e5..654c17d0bc761 100644 --- a/components/camel-spring-cloud/src/main/resources/META-INF/spring.factories +++ b/components/camel-spring-cloud/src/main/resources/META-INF/spring.factories @@ -17,5 +17,6 @@ org.springframework.boot.autoconfigure.EnableAutoConfiguration=\ org.apache.camel.spring.cloud.CamelSpringCloudDiscoveryClientAutoConfiguration,\ + org.apache.camel.spring.cloud.CamelSpringCloudServiceDiscoveryAutoConfiguration,\ org.apache.camel.spring.cloud.CamelSpringCloudServiceLoadBalancerAutoConfiguration,\ org.apache.camel.spring.cloud.CamelSpringCloudServiceRegistryAutoConfiguration diff --git a/examples/README.adoc b/examples/README.adoc index e19ce9543bc40..5e45c4ce257d7 100644 --- a/examples/README.adoc +++ b/examples/README.adoc @@ -78,6 +78,8 @@ Number of Examples: 102 (8 deprecated) | link:camel-example-spring-cloud-servicecall/README.adoc[Spring Cloud Servicecall] (camel-example-spring-cloud-servicecall) | Cloud | An example showing how to work with Camel ServiceCall EIP and Spring Cloud +| link:camel-example-spring-cloud-serviceregistry/README.adoc[Spring Cloud Service registry] (camel-example-spring-cloud-serviceregistry) | Cloud | An example showing how to work with Camel Service Registry and Spring Cloud + | link:camel-example-spring-boot-clustered-route-controller/readme.adoc[Spring Boot Clustered Route Controller] (camel-example-spring-boot-clustered-route-controller) | Clustering | An example showing how to work with Camel's Clustered Route Controller and Spring Boot | link:camel-example-spring-boot-master/readme.adoc[Spring Boot Master] (camel-example-spring-boot-master) | Clustering | An example showing how to work with Camel's Master component and Spring Boot diff --git a/examples/camel-example-spring-cloud-serviceregistry/README.adoc b/examples/camel-example-spring-cloud-serviceregistry/README.adoc new file mode 100644 index 0000000000000..28e37d2226a4f --- /dev/null +++ b/examples/camel-example-spring-cloud-serviceregistry/README.adoc @@ -0,0 +1,67 @@ +# Spring Cloud and ServiceCall EIP Example + +This example show how to use Camel with Service Registry, spring-cloud and consul. + +This example includes two maven modules: + + - service that exposes a number of services + - consumer that consumes services + +## Configuration + +The consumer is configured in the src/main/resources/application.properties + +## Build + +You can build this example using + + mvn compile + +## Run the example + +Using multiple shells: + + - start consul: + + docker run --rm -ti --publish 8500:8500 \ + consul:1.0.0 \ + agent \ + -dev \ + -server \ + -ui \ + -bootstrap \ + -datacenter camel \ + -client 0.0.0.0 \ + -log-level trace + + - start the service: + + $ cd service + $ mvn spring-boot:run + + - start the consumer + + $ cd consumer + $ mvn spring-boot:run + +## Test the example: + +In a new shell: + + $ curl localhost:8080/camel/serviceCall + Hi!, I'm service-1 on path: /path/to/service/1 + $ curl localhost:8080/camel/serviceCall + Hi!, I'm service-1 on path: /path/to/service/2 + +## Web console + +You can open the Consul web console + + http://localhost:8500/ui + +Where you can find information about the services and its state. + +## More information + +You can find more information about Apache Camel at the website: http://camel.apache.org/ + diff --git a/examples/camel-example-spring-cloud-serviceregistry/consumer/pom.xml b/examples/camel-example-spring-cloud-serviceregistry/consumer/pom.xml new file mode 100644 index 0000000000000..c0c293c8b95a2 --- /dev/null +++ b/examples/camel-example-spring-cloud-serviceregistry/consumer/pom.xml @@ -0,0 +1,141 @@ + + + + + 4.0.0 + + + org.apache.camel.example + camel-example-spring-cloud-serviceregistry + 2.22.0-SNAPSHOT + + + camel-example-spring-cloud-serviceregistry-consumer + Camel :: Example :: Spring Cloud :: Service Registry :: Consumer + An example showing how to work with Camel Service Registry and Spring Cloud (Consumer) + + + Cloud + + UTF-8 + UTF-8 + ${spring-boot-version} + 2.0.0.RC1 + + + + + + + org.springframework.boot + spring-boot-dependencies + ${spring.boot-version} + pom + import + + + org.springframework.cloud + spring-cloud-consul-dependencies + ${spring.cloud-version} + pom + import + + + + org.apache.camel + camel-spring-boot-dependencies + ${project.version} + pom + import + + + + + + + + + org.springframework.boot + spring-boot-starter-web + + + org.springframework.boot + spring-boot-starter-tomcat + + + + + org.springframework.boot + spring-boot-starter-undertow + + + org.springframework.cloud + spring-cloud-starter-consul-discovery + + + + + org.apache.camel + camel-spring-boot-starter + + + org.apache.camel + camel-spring-cloud-starter + + + org.apache.camel + camel-spring-cloud-consul-starter + + + org.apache.camel + camel-undertow-starter + + + org.apache.camel + camel-servlet-starter + + + org.apache.camel + camel-jackson-starter + + + + + + + + org.springframework.boot + spring-boot-maven-plugin + ${spring-boot-version} + + + + repackage + + + + + + + + + diff --git a/examples/camel-example-spring-cloud-serviceregistry/consumer/src/main/java/org/apache/camel/example/ConsumerApplication.java b/examples/camel-example-spring-cloud-serviceregistry/consumer/src/main/java/org/apache/camel/example/ConsumerApplication.java new file mode 100644 index 0000000000000..9134f3a75355f --- /dev/null +++ b/examples/camel-example-spring-cloud-serviceregistry/consumer/src/main/java/org/apache/camel/example/ConsumerApplication.java @@ -0,0 +1,57 @@ +/** + * 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.camel.example; + +import org.apache.camel.builder.RouteBuilder; +import org.springframework.boot.SpringApplication; +import org.springframework.boot.autoconfigure.SpringBootApplication; +import org.springframework.cloud.client.discovery.EnableDiscoveryClient; +import org.springframework.stereotype.Component; + +//CHECKSTYLE:OFF +/** + * A sample Spring Boot application that starts the Camel routes. + */ +@SpringBootApplication +@EnableDiscoveryClient +public class ConsumerApplication { + @Component + public class ConsumerRoute extends RouteBuilder { + @Override + public void configure() throws Exception { + rest("/serviceCall") + .verb("GET") + .to("direct:service-call"); + + from("direct:service-call") + .setBody().constant(null) + .removeHeaders("CamelHttp*") + .serviceCall("my-service") + .convertBodyTo(String.class) + .log("answer: ${body}"); + } + } + + /** + * A main method to start this application. + */ + public static void main(String[] args) { + SpringApplication.run(ConsumerApplication.class, args); + } + +} +//CHECKSTYLE:ON diff --git a/examples/camel-example-spring-cloud-serviceregistry/consumer/src/main/resources/application.properties b/examples/camel-example-spring-cloud-serviceregistry/consumer/src/main/resources/application.properties new file mode 100644 index 0000000000000..6f6ca73ce8440 --- /dev/null +++ b/examples/camel-example-spring-cloud-serviceregistry/consumer/src/main/resources/application.properties @@ -0,0 +1,51 @@ +## --------------------------------------------------------------------------- +## 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. +## --------------------------------------------------------------------------- + + +# Spring Boot +management.endpoints.enabled-by-default=false +management.endpoint.health.enabled=true + +spring.application.name = ${random.uuid} + +# Spring cloud +spring.cloud.consul.enabled = true +spring.cloud.consul.config.enabled = false +spring.cloud.consul.discovery.enabled = true +spring.cloud.service-registry.auto-registration.enabled = false +ribbon.enabled = false + +# Camel +camel.springboot.main-run-controller=true +camel.springboot.jmx-enabled=false + +camel.rest.component = servlet +camel.rest.binding-mode = auto + + + +camel.cloud.service-call.component = undertow + +# this should not be needed but there is a bug or misbehavior +# on spring cloud netflix side that prevent ribbon load +# balancer to propagate metadata from i.e. consul, see: +# +# https://github.com/spring-cloud/spring-cloud-consul/issues/424 +# +camel.cloud.ribbon.load-balancer.enabled = false +camel.cloud.service-call.default-load-balancer = true +camel.cloud.service-call.service-chooser = roundrobin diff --git a/examples/camel-example-spring-cloud-serviceregistry/pom.xml b/examples/camel-example-spring-cloud-serviceregistry/pom.xml new file mode 100644 index 0000000000000..c207de166400b --- /dev/null +++ b/examples/camel-example-spring-cloud-serviceregistry/pom.xml @@ -0,0 +1,58 @@ + + + + + 4.0.0 + + + org.apache.camel.example + examples + 2.22.0-SNAPSHOT + + + camel-example-spring-cloud-serviceregistry + Camel :: Example :: Spring Cloud :: ServiceRegistry + An example showing how to work with Camel Service registry and Spring Cloud + pom + + + Cloud + + + + + + spring-milestones + Spring Milestones + https://repo.spring.io/libs-milestone + + false + + + + + + consumer + service + + + diff --git a/examples/camel-example-spring-cloud-serviceregistry/service/pom.xml b/examples/camel-example-spring-cloud-serviceregistry/service/pom.xml new file mode 100644 index 0000000000000..eb57bb056724c --- /dev/null +++ b/examples/camel-example-spring-cloud-serviceregistry/service/pom.xml @@ -0,0 +1,132 @@ + + + + + 4.0.0 + + + org.apache.camel.example + camel-example-spring-cloud-serviceregistry + 2.22.0-SNAPSHOT + + + camel-example-spring-cloud-serviceregistry-service + Camel :: Example :: Spring Cloud :: Service Registry :: Service + An example showing how to work with Camel Service Registry and Spring Cloud (Service) + + + Cloud + + UTF-8 + UTF-8 + ${spring-boot-version} + 2.0.0.RC1 + + + + + + + org.springframework.boot + spring-boot-dependencies + ${spring.boot-version} + pom + import + + + org.springframework.cloud + spring-cloud-consul-dependencies + ${spring.cloud-version} + pom + import + + + + org.apache.camel + camel-spring-boot-dependencies + ${project.version} + pom + import + + + + + + + + + org.springframework.boot + spring-boot-starter + + + org.springframework.boot + spring-boot-starter-tomcat + + + + + org.springframework.boot + spring-boot-starter-undertow + + + org.springframework.cloud + spring-cloud-starter-consul-discovery + + + + + org.apache.camel + camel-spring-boot-starter + + + org.apache.camel + camel-undertow-starter + + + org.apache.camel + camel-spring-cloud-consul-starter + + + org.apache.camel + camel-service-starter + + + + + + + + org.springframework.boot + spring-boot-maven-plugin + ${spring-boot-version} + + + + repackage + + + + + + + + diff --git a/examples/camel-example-spring-cloud-serviceregistry/service/src/main/java/org/apache/camel/example/ServiceApplication.java b/examples/camel-example-spring-cloud-serviceregistry/service/src/main/java/org/apache/camel/example/ServiceApplication.java new file mode 100644 index 0000000000000..31ff9c80b463b --- /dev/null +++ b/examples/camel-example-spring-cloud-serviceregistry/service/src/main/java/org/apache/camel/example/ServiceApplication.java @@ -0,0 +1,50 @@ +/** + * 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.camel.example; + +import org.apache.camel.builder.RouteBuilder; +import org.springframework.boot.SpringApplication; +import org.springframework.boot.autoconfigure.SpringBootApplication; +import org.springframework.stereotype.Component; +import org.springframework.util.SocketUtils; + +//CHECKSTYLE:OFF +/** + * A sample Spring Boot application that starts the Camel routes. + */ +@SpringBootApplication +public class ServiceApplication { + + @Component + public class Services extends RouteBuilder { + public void configure() throws Exception { + fromF("service:my-service:undertow:http://localhost:%d/path/to/service/1", SocketUtils.findAvailableTcpPort()) + .transform().simple("Hi!, I'm service-1 on path: /path/to/service/1"); + fromF("service:my-service:undertow:http://localhost:%d/path/to/service/2", SocketUtils.findAvailableTcpPort()) + .transform().simple("Hi!, I'm service-1 on path: /path/to/service/2"); + } + } + + /** + * A main method to start this application. + */ + public static void main(String[] args) { + SpringApplication.run(ServiceApplication.class, args); + } + +} +//CHECKSTYLE:ON diff --git a/examples/camel-example-spring-cloud-serviceregistry/service/src/main/resources/application.properties b/examples/camel-example-spring-cloud-serviceregistry/service/src/main/resources/application.properties new file mode 100644 index 0000000000000..b0e9629883e18 --- /dev/null +++ b/examples/camel-example-spring-cloud-serviceregistry/service/src/main/resources/application.properties @@ -0,0 +1,33 @@ +## --------------------------------------------------------------------------- +## 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. +## --------------------------------------------------------------------------- + +# Spring Boot +management.endpoints.enabled-by-default = false +management.endpoint.health.enabled = true + +spring.application.name = ${random.uuid} + +# Spring cloud +spring.cloud.consul.enabled = true +spring.cloud.consul.config.enabled = false +spring.cloud.consul.discovery.enabled = true +spring.cloud.service-registry.auto-registration.enabled = false + +# Camel +camel.springboot.main-run-controller = true +camel.springboot.jmx-enabled = false + diff --git a/examples/pom.xml b/examples/pom.xml index cc9b066d9c021..9c3eceb42d6da 100644 --- a/examples/pom.xml +++ b/examples/pom.xml @@ -116,6 +116,7 @@ camel-example-spring-boot-supervising-route-controller camel-example-spring-boot-xml camel-example-spring-cloud-servicecall + camel-example-spring-cloud-serviceregistry camel-example-spring-javaconfig camel-example-spring-jms camel-example-spring-ws From 5ec6c5ed1f10c369e23296aba2671577fb2d3334 Mon Sep 17 00:00:00 2001 From: lburgazzoli Date: Wed, 23 May 2018 20:23:40 +0200 Subject: [PATCH 10/20] CAMEL-12518: camel cloud : leverage spring-cloud ServiceRegistry to register routes --- .../xml/AbstractCamelContextFactoryBean.java | 5 +++ .../spring/boot/CamelAutoConfiguration.java | 5 +++ .../CamelCloudConfigurationProperties.java | 38 +++++++++++++++++++ .../CamelCloudConsulAutoConfiguration.java | 8 +++- ...ServiceDefinitionToConsulRegistration.java | 8 +++- .../CamelCloudZookeeperAutoConfiguration.java | 8 +++- ...viceDefinitionToZookeeperRegistration.java | 8 +++- ...CloudServiceRegistryAutoConfiguration.java | 3 ++ .../camel/example/ServiceApplication.java | 13 ++++--- .../src/main/resources/application.properties | 3 +- 10 files changed, 85 insertions(+), 14 deletions(-) diff --git a/components/camel-core-xml/src/main/java/org/apache/camel/core/xml/AbstractCamelContextFactoryBean.java b/components/camel-core-xml/src/main/java/org/apache/camel/core/xml/AbstractCamelContextFactoryBean.java index de873c89db7a6..2d87e0352aca8 100644 --- a/components/camel-core-xml/src/main/java/org/apache/camel/core/xml/AbstractCamelContextFactoryBean.java +++ b/components/camel-core-xml/src/main/java/org/apache/camel/core/xml/AbstractCamelContextFactoryBean.java @@ -350,6 +350,11 @@ public void afterPropertiesSet() throws Exception { if (serviceRegistries != null && !serviceRegistries.isEmpty()) { for (Map.Entry entry : serviceRegistries.entrySet()) { ServiceRegistry service = entry.getValue(); + + if (service.getId() == null) { + service.setId(getContext().getUuidGenerator().generateUuid()); + } + LOG.info("Using ServiceRegistry with id: {} and implementation: {}", service.getId(), service); getContext().addService(service); } diff --git a/components/camel-spring-boot/src/main/java/org/apache/camel/spring/boot/CamelAutoConfiguration.java b/components/camel-spring-boot/src/main/java/org/apache/camel/spring/boot/CamelAutoConfiguration.java index 9e967dd65ed70..95cb392de1e36 100644 --- a/components/camel-spring-boot/src/main/java/org/apache/camel/spring/boot/CamelAutoConfiguration.java +++ b/components/camel-spring-boot/src/main/java/org/apache/camel/spring/boot/CamelAutoConfiguration.java @@ -480,6 +480,11 @@ static void afterPropertiesSet(ApplicationContext applicationContext, CamelConte if (serviceRegistries != null && !serviceRegistries.isEmpty()) { for (Map.Entry entry : serviceRegistries.entrySet()) { ServiceRegistry service = entry.getValue(); + + if (service.getId() == null) { + service.setId(camelContext.getUuidGenerator().generateUuid()); + } + LOG.info("Using ServiceRegistry with id: {} and implementation: {}", service.getId(), service); camelContext.addService(service); } diff --git a/components/camel-spring-boot/src/main/java/org/apache/camel/spring/boot/cloud/CamelCloudConfigurationProperties.java b/components/camel-spring-boot/src/main/java/org/apache/camel/spring/boot/cloud/CamelCloudConfigurationProperties.java index bfbd8b2aadcc9..753bd77732228 100644 --- a/components/camel-spring-boot/src/main/java/org/apache/camel/spring/boot/cloud/CamelCloudConfigurationProperties.java +++ b/components/camel-spring-boot/src/main/java/org/apache/camel/spring/boot/cloud/CamelCloudConfigurationProperties.java @@ -33,6 +33,7 @@ public class CamelCloudConfigurationProperties { private ServiceDiscovery serviceDiscovery = new ServiceDiscovery(); private ServiceFilter serviceFilter = new ServiceFilter(); private ServiceChooser serviceChooser = new ServiceChooser(); + private ServiceRegistry serviceRegistry = new ServiceRegistry(); public boolean isEnabled() { return enabled; @@ -62,6 +63,10 @@ public ServiceChooser getServiceChooser() { return serviceChooser; } + public ServiceRegistry getServiceRegistry() { + return serviceRegistry; + } + // ***************************************** // Service Call // ***************************************** @@ -284,4 +289,37 @@ public void setEnabled(boolean enabled) { this.enabled = enabled; } } + + // ***************************************** + // Service Registry + // ***************************************** + + public static class ServiceRegistry { + /** + * Configure if service registry should be enabled or not, default true. + */ + private boolean enabled = true; + + /** + * Configure the service listening address. + */ + private String serviceHost; + + public boolean isEnabled() { + return enabled; + } + + public void setEnabled(boolean enabled) { + this.enabled = enabled; + } + + + public String getServiceHost() { + return serviceHost; + } + + public void setServiceHost(String serviceHost) { + this.serviceHost = serviceHost; + } + } } diff --git a/components/camel-spring-cloud-consul/src/main/java/org/apache/camel/spring/cloud/consul/CamelCloudConsulAutoConfiguration.java b/components/camel-spring-cloud-consul/src/main/java/org/apache/camel/spring/cloud/consul/CamelCloudConsulAutoConfiguration.java index c83fbc21a8cd2..a1ba31ff0b81a 100644 --- a/components/camel-spring-cloud-consul/src/main/java/org/apache/camel/spring/cloud/consul/CamelCloudConsulAutoConfiguration.java +++ b/components/camel-spring-cloud-consul/src/main/java/org/apache/camel/spring/cloud/consul/CamelCloudConsulAutoConfiguration.java @@ -17,9 +17,11 @@ package org.apache.camel.spring.cloud.consul; import org.apache.camel.cloud.ServiceDefinition; +import org.apache.camel.spring.boot.cloud.CamelCloudConfigurationProperties; import org.apache.camel.spring.boot.util.GroupCondition; import org.apache.camel.spring.cloud.CamelSpringCloudServiceRegistryAutoConfiguration; import org.springframework.boot.autoconfigure.AutoConfigureBefore; +import org.springframework.boot.context.properties.EnableConfigurationProperties; import org.springframework.cloud.consul.ConditionalOnConsulEnabled; import org.springframework.cloud.consul.serviceregistry.ConsulRegistration; import org.springframework.context.annotation.Bean; @@ -31,11 +33,13 @@ @AutoConfigureBefore(CamelSpringCloudServiceRegistryAutoConfiguration.class) @ConditionalOnConsulEnabled @Conditional(CamelCloudConsulAutoConfiguration.Condition.class) +@EnableConfigurationProperties(CamelCloudConfigurationProperties.class) public class CamelCloudConsulAutoConfiguration { @Bean(name = "service-definition-to-consul-registration") - public Converter serviceDefinitionToConsulRegistration() { - return new ServiceDefinitionToConsulRegistration(); + public Converter serviceDefinitionToConsulRegistration( + CamelCloudConfigurationProperties properties) { + return new ServiceDefinitionToConsulRegistration(properties); } // ******************************* diff --git a/components/camel-spring-cloud-consul/src/main/java/org/apache/camel/spring/cloud/consul/ServiceDefinitionToConsulRegistration.java b/components/camel-spring-cloud-consul/src/main/java/org/apache/camel/spring/cloud/consul/ServiceDefinitionToConsulRegistration.java index 443fc989118c6..648f8cb951252 100644 --- a/components/camel-spring-cloud-consul/src/main/java/org/apache/camel/spring/cloud/consul/ServiceDefinitionToConsulRegistration.java +++ b/components/camel-spring-cloud-consul/src/main/java/org/apache/camel/spring/cloud/consul/ServiceDefinitionToConsulRegistration.java @@ -21,17 +21,23 @@ import com.ecwid.consul.v1.agent.model.NewService; import org.apache.camel.cloud.ServiceDefinition; +import org.apache.camel.spring.boot.cloud.CamelCloudConfigurationProperties; import org.springframework.cloud.consul.serviceregistry.ConsulRegistration; import org.springframework.core.convert.converter.Converter; public final class ServiceDefinitionToConsulRegistration implements Converter { + private final CamelCloudConfigurationProperties properties; + + public ServiceDefinitionToConsulRegistration(CamelCloudConfigurationProperties properties) { + this.properties = properties; + } @Override public ConsulRegistration convert(ServiceDefinition source) { NewService service = new NewService(); service.setName(source.getName()); service.setId(source.getId()); - service.setAddress(source.getHost()); + service.setAddress(properties.getServiceRegistry().getServiceHost()); service.setPort(source.getPort()); service.setTags( diff --git a/components/camel-spring-cloud-zookeeper/src/main/java/org/apache/camel/spring/cloud/zookeeper/CamelCloudZookeeperAutoConfiguration.java b/components/camel-spring-cloud-zookeeper/src/main/java/org/apache/camel/spring/cloud/zookeeper/CamelCloudZookeeperAutoConfiguration.java index 85ba2274695b0..b223e029e4875 100644 --- a/components/camel-spring-cloud-zookeeper/src/main/java/org/apache/camel/spring/cloud/zookeeper/CamelCloudZookeeperAutoConfiguration.java +++ b/components/camel-spring-cloud-zookeeper/src/main/java/org/apache/camel/spring/cloud/zookeeper/CamelCloudZookeeperAutoConfiguration.java @@ -17,9 +17,11 @@ package org.apache.camel.spring.cloud.zookeeper; import org.apache.camel.cloud.ServiceDefinition; +import org.apache.camel.spring.boot.cloud.CamelCloudConfigurationProperties; import org.apache.camel.spring.boot.util.GroupCondition; import org.apache.camel.spring.cloud.CamelSpringCloudServiceRegistryAutoConfiguration; import org.springframework.boot.autoconfigure.AutoConfigureBefore; +import org.springframework.boot.context.properties.EnableConfigurationProperties; import org.springframework.cloud.zookeeper.ConditionalOnZookeeperEnabled; import org.springframework.cloud.zookeeper.serviceregistry.ZookeeperRegistration; import org.springframework.context.annotation.Bean; @@ -31,11 +33,13 @@ @AutoConfigureBefore(CamelSpringCloudServiceRegistryAutoConfiguration.class) @ConditionalOnZookeeperEnabled @Conditional(CamelCloudZookeeperAutoConfiguration.Condition.class) +@EnableConfigurationProperties(CamelCloudConfigurationProperties.class) public class CamelCloudZookeeperAutoConfiguration { @Bean(name = "service-definition-to-zookeeper-registration") - public Converter serviceDefinitionToConsulRegistration() { - return new ServiceDefinitionToZookeeperRegistration(); + public Converter serviceDefinitionToConsulRegistration( + CamelCloudConfigurationProperties properties) { + return new ServiceDefinitionToZookeeperRegistration(properties); } // ******************************* diff --git a/components/camel-spring-cloud-zookeeper/src/main/java/org/apache/camel/spring/cloud/zookeeper/ServiceDefinitionToZookeeperRegistration.java b/components/camel-spring-cloud-zookeeper/src/main/java/org/apache/camel/spring/cloud/zookeeper/ServiceDefinitionToZookeeperRegistration.java index 30cc93b1f0afe..0ad2ee1b3744e 100644 --- a/components/camel-spring-cloud-zookeeper/src/main/java/org/apache/camel/spring/cloud/zookeeper/ServiceDefinitionToZookeeperRegistration.java +++ b/components/camel-spring-cloud-zookeeper/src/main/java/org/apache/camel/spring/cloud/zookeeper/ServiceDefinitionToZookeeperRegistration.java @@ -17,6 +17,7 @@ package org.apache.camel.spring.cloud.zookeeper; import org.apache.camel.cloud.ServiceDefinition; +import org.apache.camel.spring.boot.cloud.CamelCloudConfigurationProperties; import org.springframework.cloud.zookeeper.discovery.ZookeeperDiscoveryProperties; import org.springframework.cloud.zookeeper.discovery.ZookeeperInstance; import org.springframework.cloud.zookeeper.serviceregistry.ServiceInstanceRegistration; @@ -24,7 +25,10 @@ import org.springframework.core.convert.converter.Converter; public final class ServiceDefinitionToZookeeperRegistration implements Converter { - public ServiceDefinitionToZookeeperRegistration() { + private final CamelCloudConfigurationProperties properties; + + public ServiceDefinitionToZookeeperRegistration(CamelCloudConfigurationProperties properties) { + this.properties = properties; } @Override @@ -36,7 +40,7 @@ public ZookeeperRegistration convert(ServiceDefinition source) { ); return ServiceInstanceRegistration.builder() - .address(source.getHost()) + .address(properties.getServiceRegistry().getServiceHost()) .port(source.getPort()) .name(source.getName()) .payload(instance) diff --git a/components/camel-spring-cloud/src/main/java/org/apache/camel/spring/cloud/CamelSpringCloudServiceRegistryAutoConfiguration.java b/components/camel-spring-cloud/src/main/java/org/apache/camel/spring/cloud/CamelSpringCloudServiceRegistryAutoConfiguration.java index caffeaa76cd1a..2d8daba1c3f5d 100644 --- a/components/camel-spring-cloud/src/main/java/org/apache/camel/spring/cloud/CamelSpringCloudServiceRegistryAutoConfiguration.java +++ b/components/camel-spring-cloud/src/main/java/org/apache/camel/spring/cloud/CamelSpringCloudServiceRegistryAutoConfiguration.java @@ -20,11 +20,13 @@ import org.apache.camel.cloud.ServiceRegistry; import org.apache.camel.spring.boot.CamelAutoConfiguration; +import org.apache.camel.spring.boot.cloud.CamelCloudConfigurationProperties; import org.apache.camel.spring.boot.util.GroupCondition; import org.springframework.boot.autoconfigure.AutoConfigureAfter; import org.springframework.boot.autoconfigure.AutoConfigureBefore; import org.springframework.boot.autoconfigure.condition.ConditionalOnBean; import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean; +import org.springframework.boot.context.properties.EnableConfigurationProperties; import org.springframework.cloud.client.serviceregistry.ServiceRegistryAutoConfiguration; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Conditional; @@ -35,6 +37,7 @@ @AutoConfigureBefore(CamelAutoConfiguration.class) @ConditionalOnBean(org.springframework.cloud.client.serviceregistry.ServiceRegistry.class) @Conditional(CamelSpringCloudServiceRegistryAutoConfiguration.ServiceRegistryCondition.class) +@EnableConfigurationProperties(CamelCloudConfigurationProperties.class) public class CamelSpringCloudServiceRegistryAutoConfiguration { @Bean diff --git a/examples/camel-example-spring-cloud-serviceregistry/service/src/main/java/org/apache/camel/example/ServiceApplication.java b/examples/camel-example-spring-cloud-serviceregistry/service/src/main/java/org/apache/camel/example/ServiceApplication.java index 31ff9c80b463b..869f46d037e7a 100644 --- a/examples/camel-example-spring-cloud-serviceregistry/service/src/main/java/org/apache/camel/example/ServiceApplication.java +++ b/examples/camel-example-spring-cloud-serviceregistry/service/src/main/java/org/apache/camel/example/ServiceApplication.java @@ -28,14 +28,17 @@ */ @SpringBootApplication public class ServiceApplication { - + @Component public class Services extends RouteBuilder { public void configure() throws Exception { - fromF("service:my-service:undertow:http://localhost:%d/path/to/service/1", SocketUtils.findAvailableTcpPort()) - .transform().simple("Hi!, I'm service-1 on path: /path/to/service/1"); - fromF("service:my-service:undertow:http://localhost:%d/path/to/service/2", SocketUtils.findAvailableTcpPort()) - .transform().simple("Hi!, I'm service-1 on path: /path/to/service/2"); + // TODO: service.host should be set using properties + fromF("service:my-service:undertow:http://localhost:%d/path/to/service/1?service.host=localhost", SocketUtils.findAvailableTcpPort()) + .transform().simple("Hi!, I'm service-1 on path: /path/to/service/1"); + + // TODO: service.host should be set using properties + fromF("service:my-service:undertow:http://localhost:%d/path/to/service/2?service.host=localhost", SocketUtils.findAvailableTcpPort()) + .transform().simple("Hi!, I'm service-1 on path: /path/to/service/2"); } } diff --git a/examples/camel-example-spring-cloud-serviceregistry/service/src/main/resources/application.properties b/examples/camel-example-spring-cloud-serviceregistry/service/src/main/resources/application.properties index b0e9629883e18..8ea17360212af 100644 --- a/examples/camel-example-spring-cloud-serviceregistry/service/src/main/resources/application.properties +++ b/examples/camel-example-spring-cloud-serviceregistry/service/src/main/resources/application.properties @@ -29,5 +29,4 @@ spring.cloud.service-registry.auto-registration.enabled = false # Camel camel.springboot.main-run-controller = true -camel.springboot.jmx-enabled = false - +camel.springboot.jmx-enabled = false \ No newline at end of file From 086bdfff5fa13e58976f6a77b5b0ef3e05769046 Mon Sep 17 00:00:00 2001 From: lburgazzoli Date: Wed, 23 May 2018 20:25:12 +0200 Subject: [PATCH 11/20] CAMEL-12531: camel cloud : create a spring cloud based camel-service example --- .../consumer/src/main/resources/application.properties | 5 +++-- .../java/org/apache/camel/example/ServiceApplication.java | 7 ++----- .../service/src/main/resources/application.properties | 8 +++++++- 3 files changed, 12 insertions(+), 8 deletions(-) diff --git a/examples/camel-example-spring-cloud-serviceregistry/consumer/src/main/resources/application.properties b/examples/camel-example-spring-cloud-serviceregistry/consumer/src/main/resources/application.properties index 6f6ca73ce8440..0eaa50eb928aa 100644 --- a/examples/camel-example-spring-cloud-serviceregistry/consumer/src/main/resources/application.properties +++ b/examples/camel-example-spring-cloud-serviceregistry/consumer/src/main/resources/application.properties @@ -15,6 +15,8 @@ ## limitations under the License. ## --------------------------------------------------------------------------- +# Logging +logging.level.org.apache.camel.spring.cloud = DEBUG # Spring Boot management.endpoints.enabled-by-default=false @@ -36,8 +38,7 @@ camel.springboot.jmx-enabled=false camel.rest.component = servlet camel.rest.binding-mode = auto - - +# Camel Service Call camel.cloud.service-call.component = undertow # this should not be needed but there is a bug or misbehavior diff --git a/examples/camel-example-spring-cloud-serviceregistry/service/src/main/java/org/apache/camel/example/ServiceApplication.java b/examples/camel-example-spring-cloud-serviceregistry/service/src/main/java/org/apache/camel/example/ServiceApplication.java index 869f46d037e7a..84d7b1d114d0e 100644 --- a/examples/camel-example-spring-cloud-serviceregistry/service/src/main/java/org/apache/camel/example/ServiceApplication.java +++ b/examples/camel-example-spring-cloud-serviceregistry/service/src/main/java/org/apache/camel/example/ServiceApplication.java @@ -32,12 +32,9 @@ public class ServiceApplication { @Component public class Services extends RouteBuilder { public void configure() throws Exception { - // TODO: service.host should be set using properties - fromF("service:my-service:undertow:http://localhost:%d/path/to/service/1?service.host=localhost", SocketUtils.findAvailableTcpPort()) + fromF("service:my-service:undertow:http://localhost:%d/path/to/service/1", SocketUtils.findAvailableTcpPort()) .transform().simple("Hi!, I'm service-1 on path: /path/to/service/1"); - - // TODO: service.host should be set using properties - fromF("service:my-service:undertow:http://localhost:%d/path/to/service/2?service.host=localhost", SocketUtils.findAvailableTcpPort()) + fromF("service:my-service:undertow:http://localhost:%d/path/to/service/2", SocketUtils.findAvailableTcpPort()) .transform().simple("Hi!, I'm service-1 on path: /path/to/service/2"); } } diff --git a/examples/camel-example-spring-cloud-serviceregistry/service/src/main/resources/application.properties b/examples/camel-example-spring-cloud-serviceregistry/service/src/main/resources/application.properties index 8ea17360212af..db451b555870f 100644 --- a/examples/camel-example-spring-cloud-serviceregistry/service/src/main/resources/application.properties +++ b/examples/camel-example-spring-cloud-serviceregistry/service/src/main/resources/application.properties @@ -15,6 +15,9 @@ ## limitations under the License. ## --------------------------------------------------------------------------- +# Logging +logging.level.org.apache.camel.spring.cloud = DEBUG + # Spring Boot management.endpoints.enabled-by-default = false management.endpoint.health.enabled = true @@ -29,4 +32,7 @@ spring.cloud.service-registry.auto-registration.enabled = false # Camel camel.springboot.main-run-controller = true -camel.springboot.jmx-enabled = false \ No newline at end of file +camel.springboot.jmx-enabled = false + +# Camel Cloud +camel.cloud.service-registry.service-host = localhost \ No newline at end of file From bbc6e5fc7fe686d37ee085bd4600dfd1166c28b2 Mon Sep 17 00:00:00 2001 From: lburgazzoli Date: Thu, 24 May 2018 15:16:08 +0200 Subject: [PATCH 12/20] CAMEL-12518: camel cloud : leverage spring-cloud ServiceRegistry to register routes --- camel-core/pom.xml | 6 + .../camel/cloud/DiscoverableService.java | 2 +- .../impl/cloud/DefaultServiceDefinition.java | 44 ++---- .../cloud/ServiceRegistrationRoutePolicy.java | 60 ++++---- .../camel/impl/cloud/ServiceRegistryTest.java | 134 ++++++++++++++++++ .../camel/http/common/HttpCommonEndpoint.java | 4 +- .../component/service/ServiceComponent.java | 24 ++-- .../component/service/ServiceEndpoint.java | 6 +- .../boot/util/CompositeConversionService.java | 83 +++++++++++ .../ConsulServerToServiceDefinition.java | 35 +++++ ...ToServiceDefinitionAutoConfiguration.java} | 34 +++-- ...oConsulRegistrationAutoConfiguration.java} | 4 +- .../main/resources/META-INF/spring.factories | 3 +- ...CamelCloudConsulAutoConfigurationTest.java | 27 ++++ .../CamelCloudConsulServiceRegistryTest.java | 3 +- components/camel-spring-cloud-netflix/pom.xml | 4 + ...CloudNetflixRibbonClientConfiguration.java | 47 ------ .../CamelCloudNetflixServiceLoadBalancer.java | 88 ++++++++++++ ...xServiceLoadBalancerAutoConfiguration.java | 60 ++++++++ .../main/resources/META-INF/spring.factories | 4 +- .../CamelCloudNetflixServiceCallTest.java | 93 ------------ ...viceLoadBalancerAutoConfigurationTest.java | 78 ++++++++++ .../src/test/resources/logback.xml | 1 + ...okeeperRegistrationAutoConfiguration.java} | 6 +- .../ZookeeperServerToServiceDefinition.java | 35 +++++ ...rToServiceDefinitionAutoConfiguration.java | 53 +++++++ .../main/resources/META-INF/spring.factories | 3 +- ...elCloudZookeeperAutoConfigurationTest.java | 35 +++++ ...amelCloudZookeeperServiceRegistryTest.java | 3 +- .../CamelSpringCloudServiceLoadBalancer.java | 23 ++- ...dServiceLoadBalancerAutoConfiguration.java | 9 +- .../CamelSpringCloudServiceRegistry.java | 37 +++-- .../DefaultLoadBalancerClientAdapter.java | 27 ++++ .../cloud/DefaultServiceLoadBalancer.java | 50 +++++++ .../component/undertow/UndertowEndpoint.java | 4 +- .../consumer/pom.xml | 4 + .../src/main/resources/application.properties | 9 -- 37 files changed, 854 insertions(+), 288 deletions(-) create mode 100644 camel-core/src/test/java/org/apache/camel/impl/cloud/ServiceRegistryTest.java create mode 100644 components/camel-spring-boot/src/main/java/org/apache/camel/spring/boot/util/CompositeConversionService.java create mode 100644 components/camel-spring-cloud-consul/src/main/java/org/apache/camel/spring/cloud/consul/ConsulServerToServiceDefinition.java rename components/{camel-spring-cloud-netflix/src/main/java/org/apache/camel/spring/cloud/netflix/CamelCloudNetflixRibbonAutoConfiguration.java => camel-spring-cloud-consul/src/main/java/org/apache/camel/spring/cloud/consul/ConsulServerToServiceDefinitionAutoConfiguration.java} (54%) rename components/camel-spring-cloud-consul/src/main/java/org/apache/camel/spring/cloud/consul/{CamelCloudConsulAutoConfiguration.java => ServiceDefinitionToConsulRegistrationAutoConfiguration.java} (94%) delete mode 100644 components/camel-spring-cloud-netflix/src/main/java/org/apache/camel/spring/cloud/netflix/CamelCloudNetflixRibbonClientConfiguration.java create mode 100644 components/camel-spring-cloud-netflix/src/main/java/org/apache/camel/spring/cloud/netflix/CamelCloudNetflixServiceLoadBalancer.java create mode 100644 components/camel-spring-cloud-netflix/src/main/java/org/apache/camel/spring/cloud/netflix/CamelCloudNetflixServiceLoadBalancerAutoConfiguration.java delete mode 100644 components/camel-spring-cloud-netflix/src/test/java/org/apache/camel/spring/cloud/netflix/CamelCloudNetflixServiceCallTest.java create mode 100644 components/camel-spring-cloud-netflix/src/test/java/org/apache/camel/spring/cloud/netflix/CamelCloudNetflixServiceLoadBalancerAutoConfigurationTest.java rename components/camel-spring-cloud-zookeeper/src/main/java/org/apache/camel/spring/cloud/zookeeper/{CamelCloudZookeeperAutoConfiguration.java => ServiceDefinitionToZookeeperRegistrationAutoConfiguration.java} (92%) create mode 100644 components/camel-spring-cloud-zookeeper/src/main/java/org/apache/camel/spring/cloud/zookeeper/ZookeeperServerToServiceDefinition.java create mode 100644 components/camel-spring-cloud-zookeeper/src/main/java/org/apache/camel/spring/cloud/zookeeper/ZookeeperToServiceDefinitionAutoConfiguration.java create mode 100644 components/camel-spring-cloud/src/main/java/org/apache/camel/spring/cloud/DefaultLoadBalancerClientAdapter.java create mode 100644 components/camel-spring-cloud/src/main/java/org/apache/camel/spring/cloud/DefaultServiceLoadBalancer.java diff --git a/camel-core/pom.xml b/camel-core/pom.xml index 22aa6969c27d9..0166d5cd44ed7 100644 --- a/camel-core/pom.xml +++ b/camel-core/pom.xml @@ -174,6 +174,12 @@ awaitility test + + org.assertj + assertj-core + ${assertj-version} + test + diff --git a/camel-core/src/main/java/org/apache/camel/cloud/DiscoverableService.java b/camel-core/src/main/java/org/apache/camel/cloud/DiscoverableService.java index 6d1946c6501b6..d8cc9e3c6802c 100644 --- a/camel-core/src/main/java/org/apache/camel/cloud/DiscoverableService.java +++ b/camel-core/src/main/java/org/apache/camel/cloud/DiscoverableService.java @@ -22,5 +22,5 @@ public interface DiscoverableService { /** * Get the service properties. */ - Map getServiceProperties(); + Map getServiceProperties(); } diff --git a/camel-core/src/main/java/org/apache/camel/impl/cloud/DefaultServiceDefinition.java b/camel-core/src/main/java/org/apache/camel/impl/cloud/DefaultServiceDefinition.java index 8deace605ec8a..cfc1bc1d604ac 100644 --- a/camel-core/src/main/java/org/apache/camel/impl/cloud/DefaultServiceDefinition.java +++ b/camel-core/src/main/java/org/apache/camel/impl/cloud/DefaultServiceDefinition.java @@ -169,7 +169,7 @@ public static Builder builder() { } /** - * Fluent builder to contruct ServiceDefinition. + * Fluent builder to construct ServiceDefinition. */ public static class Builder { private String id; @@ -190,46 +190,18 @@ public Builder from(ServiceDefinition source) { return this; } - public Builder from(Map properties) { - Map options = new HashMap<>(properties); - Object val = null; + public Builder from(Map properties) { + ObjectHelper.ifNotEmpty(properties.get(ServiceDefinition.SERVICE_META_ID), this::withId); + ObjectHelper.ifNotEmpty(properties.get(ServiceDefinition.SERVICE_META_NAME), this::withName); + ObjectHelper.ifNotEmpty(properties.get(ServiceDefinition.SERVICE_META_HOST), this::withHost); + ObjectHelper.ifNotEmpty(properties.get(ServiceDefinition.SERVICE_META_PORT), this::withPort); - val = options.remove(ServiceDefinition.SERVICE_META_ID); - if (val != null && val instanceof String) { - withId((String)val); - } - - val = options.remove(ServiceDefinition.SERVICE_META_NAME); - if (val != null && val instanceof String) { - withName((String)val); - } - - val = options.remove(ServiceDefinition.SERVICE_META_HOST); - if (val != null && val instanceof String) { - withHost((String)val); - } - - val = options.remove(ServiceDefinition.SERVICE_META_PORT); - if (val != null && val instanceof String) { - withPort((String)val); - } - if (val != null && val instanceof Integer) { - withPort((Integer)val); - } - - val = options.remove(ServiceDefinition.SERVICE_META_HOST); - if (val != null && val instanceof String) { - withHost((String)val); - } - - for (Map.Entry entry : options.entrySet()) { + for (Map.Entry entry : properties.entrySet()) { if (!entry.getKey().startsWith(ServiceDefinition.SERVICE_META_PREFIX)) { continue; } - if (entry.getValue() instanceof String) { - addMeta(entry.getKey(), (String)entry.getValue()); - } + addMeta(entry.getKey(), entry.getValue()); } return this; diff --git a/camel-core/src/main/java/org/apache/camel/impl/cloud/ServiceRegistrationRoutePolicy.java b/camel-core/src/main/java/org/apache/camel/impl/cloud/ServiceRegistrationRoutePolicy.java index f506a04dc74ac..130b51346acf4 100644 --- a/camel-core/src/main/java/org/apache/camel/impl/cloud/ServiceRegistrationRoutePolicy.java +++ b/camel-core/src/main/java/org/apache/camel/impl/cloud/ServiceRegistrationRoutePolicy.java @@ -123,7 +123,7 @@ private void deregister(Route route) { private Optional computeServiceDefinition(Route route) { final Endpoint endpoint = route.getConsumer().getEndpoint(); - final Map properties = new HashMap<>(); + final Map properties = new HashMap<>(); if (endpoint instanceof DiscoverableService) { final DiscoverableService service = (DiscoverableService) endpoint; @@ -132,8 +132,22 @@ private Optional computeServiceDefinition(Route route) { properties.putAll(service.getServiceProperties()); } + // then add additional properties from route with ServiceDefinition.SERVICE_META_PREFIX, + // note that route defined properties may override DiscoverableService + // provided ones + for (Map.Entry entry: route.getProperties().entrySet()) { + if (!entry.getKey().startsWith(ServiceDefinition.SERVICE_META_PREFIX)) { + continue; + } + + final String key = entry.getKey(); + final String val = camelContext.getTypeConverter().convertTo(String.class, entry.getValue()); + + properties.put(key, val); + } + // try to get the service id from route properties - String serviceId = (String)route.getProperties().get(ServiceDefinition.SERVICE_META_ID); + String serviceId = properties.get(ServiceDefinition.SERVICE_META_ID); if (serviceId == null) { // if not check if the route id is custom and use it if (route.getRouteContext().getRoute().hasCustomIdAssigned()) { @@ -142,18 +156,18 @@ private Optional computeServiceDefinition(Route route) { } if (serviceId == null) { // finally get the id from the DiscoverableService - serviceId = (String)properties.get(ServiceDefinition.SERVICE_META_ID); + serviceId = properties.get(ServiceDefinition.SERVICE_META_ID); } // try to get the service name from route properties - String serviceName = (String)route.getProperties().get(ServiceDefinition.SERVICE_META_NAME); + String serviceName = properties.get(ServiceDefinition.SERVICE_META_NAME); if (serviceName == null) { // if not check if the route group is defined use the route group serviceName = route.getGroup(); } if (serviceName == null) { // finally get the name from the DiscoverableService - serviceName = (String)properties.get(ServiceDefinition.SERVICE_META_NAME); + serviceName = properties.get(ServiceDefinition.SERVICE_META_NAME); } if (ObjectHelper.isEmpty(serviceId) || ObjectHelper.isEmpty(serviceName)) { @@ -161,32 +175,20 @@ private Optional computeServiceDefinition(Route route) { return Optional.empty(); } + String serviceHost = properties.get(ServiceDefinition.SERVICE_META_HOST); + String servicePort = properties.getOrDefault(ServiceDefinition.SERVICE_META_PORT, "-1"); + // Build the final resource definition from bits collected from the // endpoint and the route. - DefaultServiceDefinition.Builder builder = DefaultServiceDefinition.builder() - .from(properties) - .withId(serviceId) - .withName(serviceName) - .addMeta(ServiceDefinition.SERVICE_META_NAME, serviceName) - .addMeta(ServiceDefinition.SERVICE_META_ID, serviceId); - - // Add additional metadata from route properties whose name starts - // with ServiceDefinition.SERVICE_META_PREFIX. - // - // NOTE: At the moment it is not possible to add properties to a route - // with fluent DSL - for (Map.Entry entry: route.getProperties().entrySet()) { - if (!entry.getKey().startsWith(ServiceDefinition.SERVICE_META_PREFIX)) { - continue; - } - - final String key = entry.getKey().substring(ServiceDefinition.SERVICE_META_PREFIX.length()); - final String val = camelContext.getTypeConverter().convertTo(String.class, entry.getValue()); - - builder.addMeta(key, val); - } - - return Optional.of(builder.build()); + return Optional.of( + new DefaultServiceDefinition( + serviceId, + serviceName, + serviceHost, + Integer.parseInt(servicePort), + properties + ) + ); } } diff --git a/camel-core/src/test/java/org/apache/camel/impl/cloud/ServiceRegistryTest.java b/camel-core/src/test/java/org/apache/camel/impl/cloud/ServiceRegistryTest.java new file mode 100644 index 0000000000000..6c3cdaa81bdc6 --- /dev/null +++ b/camel-core/src/test/java/org/apache/camel/impl/cloud/ServiceRegistryTest.java @@ -0,0 +1,134 @@ +/** + * 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.camel.impl.cloud; + +import java.util.Collections; +import java.util.Map; +import java.util.Objects; +import java.util.UUID; +import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.ConcurrentMap; + +import org.apache.camel.ContextTestSupport; +import org.apache.camel.cloud.ServiceDefinition; +import org.apache.camel.model.RouteDefinition; +import org.junit.Test; + +import static org.assertj.core.api.Assertions.assertThat; + +public class ServiceRegistryTest extends ContextTestSupport { + + // ********************* + // Set up + // ********************* + + @Override + public boolean isUseRouteBuilder() { + return false; + } + + // ********************* + // Tests + // ********************* + + @Test + public void testServiceRegistrationWithRouteproperties() throws Exception { + final String serviceName = UUID.randomUUID().toString(); + final String serviceId = UUID.randomUUID().toString(); + final int port = 9090; + + context.addRouteDefinition( + new RouteDefinition() + .from("direct:start") + .routeProperty(ServiceDefinition.SERVICE_META_NAME, serviceName) + .routeProperty(ServiceDefinition.SERVICE_META_ID, serviceId) + .routeProperty(ServiceDefinition.SERVICE_META_HOST, "localhost") + .routeProperty(ServiceDefinition.SERVICE_META_PORT, "" + port) + .routeProperty("service.meta1", "meta1") + .routeProperty("meta2", "meta2") + .routePolicy(new ServiceRegistrationRoutePolicy()) + .to("mock:end") + ); + + InMemoryServiceRegistry sr = new InMemoryServiceRegistry(); + + context.addService(sr); + context.start(); + + final Map defs = sr.getDefinitions(); + + assertThat(defs).hasSize(1); + + // basic properties + assertThat(defs.values()).first().hasFieldOrPropertyWithValue("name", serviceName); + assertThat(defs.values()).first().hasFieldOrPropertyWithValue("id", serviceId); + assertThat(defs.values()).first().hasFieldOrPropertyWithValue("host", "localhost"); + assertThat(defs.values()).first().hasFieldOrPropertyWithValue("port", port); + + // metadata + assertThat(defs.get(serviceId).getMetadata()).containsEntry(ServiceDefinition.SERVICE_META_NAME, serviceName); + assertThat(defs.get(serviceId).getMetadata()).containsEntry(ServiceDefinition.SERVICE_META_ID, serviceId); + assertThat(defs.get(serviceId).getMetadata()).containsEntry(ServiceDefinition.SERVICE_META_HOST, "localhost"); + assertThat(defs.get(serviceId).getMetadata()).containsEntry(ServiceDefinition.SERVICE_META_PORT, "" + port); + assertThat(defs.get(serviceId).getMetadata()).containsEntry("service.meta1", "meta1"); + assertThat(defs.get(serviceId).getMetadata()).doesNotContainKeys("meta2"); + + } + + // ********************* + // Helpers + // ********************* + + private static class InMemoryServiceRegistry extends AbstractServiceRegistry { + private final ConcurrentMap definitions; + + public InMemoryServiceRegistry() { + super(UUID.randomUUID().toString()); + + this.definitions = new ConcurrentHashMap<>(); + } + + @Override + public void register(ServiceDefinition definition) { + Objects.requireNonNull(definition.getId(), "ServiceDefinition ID"); + Objects.requireNonNull(definition.getName(), "ServiceDefinition Name"); + + definitions.put(definition.getId(), definition); + } + + @Override + public void deregister(ServiceDefinition definition) { + Objects.requireNonNull(definition.getId(), "ServiceDefinition ID"); + Objects.requireNonNull(definition.getName(), "ServiceDefinition Name"); + + definitions.remove(definition.getId()); + } + + @Override + protected void doStart() throws Exception { + } + + @Override + protected void doStop() throws Exception { + definitions.clear(); + } + + Map getDefinitions() { + return Collections.unmodifiableMap(definitions); + } + } +} diff --git a/components/camel-http-common/src/main/java/org/apache/camel/http/common/HttpCommonEndpoint.java b/components/camel-http-common/src/main/java/org/apache/camel/http/common/HttpCommonEndpoint.java index e7d465e481e92..834cb635a8ca6 100644 --- a/components/camel-http-common/src/main/java/org/apache/camel/http/common/HttpCommonEndpoint.java +++ b/components/camel-http-common/src/main/java/org/apache/camel/http/common/HttpCommonEndpoint.java @@ -201,9 +201,9 @@ public boolean isSingleton() { //------------------------------------------------------------------------- @Override - public Map getServiceProperties() { + public Map getServiceProperties() { return CollectionHelper.immutableMapOf( - ServiceDefinition.SERVICE_META_PORT, getPort(), + ServiceDefinition.SERVICE_META_PORT, Integer.toString(getPort()), ServiceDefinition.SERVICE_META_PATH, getPath(), ServiceDefinition.SERVICE_META_PROTOCOL, getProtocol() ); diff --git a/components/camel-service/src/main/java/org/apache/camel/component/service/ServiceComponent.java b/components/camel-service/src/main/java/org/apache/camel/component/service/ServiceComponent.java index b7e249be53e61..26c3cb7ae4b1b 100644 --- a/components/camel-service/src/main/java/org/apache/camel/component/service/ServiceComponent.java +++ b/components/camel-service/src/main/java/org/apache/camel/component/service/ServiceComponent.java @@ -56,23 +56,27 @@ protected Endpoint createEndpoint(String uri, String remaining, Map params = new HashMap<>(); - parameters.forEach( - (k, v) -> { - if (k.startsWith(ServiceDefinition.SERVICE_META_PREFIX)) { - params.put(k, v); - } + final Map params = new HashMap<>(); + + for (Map.Entry entry: parameters.entrySet()) { + if (!entry.getKey().startsWith(ServiceDefinition.SERVICE_META_PREFIX)) { + continue; } - ); + + final String key = entry.getKey(); + final String val = getCamelContext().getTypeConverter().convertTo(String.class, entry.getValue()); + + params.put(key, val); + } + + // add service name, this is always set from an uri path param + params.put(ServiceDefinition.SERVICE_META_NAME, serviceName); // remove all the service related options so the underlying component // does not fail because of unknown parameters diff --git a/components/camel-service/src/main/java/org/apache/camel/component/service/ServiceEndpoint.java b/components/camel-service/src/main/java/org/apache/camel/component/service/ServiceEndpoint.java index f4bcd55e32719..d16f4483ec309 100644 --- a/components/camel-service/src/main/java/org/apache/camel/component/service/ServiceEndpoint.java +++ b/components/camel-service/src/main/java/org/apache/camel/component/service/ServiceEndpoint.java @@ -54,14 +54,14 @@ public class ServiceEndpoint extends DefaultEndpoint implements DelegateEndpoint { private final Endpoint delegateEndpoint; private final ServiceRegistry serviceRegistry; - private final Map serviceParameters; + private final Map serviceParameters; private final ServiceDefinition serviceDefinition; @UriPath(description = "The endpoint uri to expose as service") @Metadata(required = "true") private final String delegateUri; - public ServiceEndpoint(String uri, ServiceComponent component, ServiceRegistry serviceRegistry, Map serviceParameters, String delegateUri) { + public ServiceEndpoint(String uri, ServiceComponent component, ServiceRegistry serviceRegistry, Map serviceParameters, String delegateUri) { super(uri, component); this.serviceRegistry = serviceRegistry; @@ -101,7 +101,7 @@ public boolean isSingleton() { } private ServiceDefinition computeServiceDefinition(CamelContext context, Endpoint delegateEndpoint) { - Map parameters = new HashMap<>(); + Map parameters = new HashMap<>(); if (delegateEndpoint instanceof DiscoverableService) { parameters.putAll(((DiscoverableService)delegateEndpoint).getServiceProperties()); diff --git a/components/camel-spring-boot/src/main/java/org/apache/camel/spring/boot/util/CompositeConversionService.java b/components/camel-spring-boot/src/main/java/org/apache/camel/spring/boot/util/CompositeConversionService.java new file mode 100644 index 0000000000000..e3976535e771f --- /dev/null +++ b/components/camel-spring-boot/src/main/java/org/apache/camel/spring/boot/util/CompositeConversionService.java @@ -0,0 +1,83 @@ +/** + * 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.camel.spring.boot.util; + +import java.util.List; + +import org.springframework.core.convert.ConversionException; +import org.springframework.core.convert.ConversionService; +import org.springframework.core.convert.TypeDescriptor; + +public class CompositeConversionService implements ConversionService { + private final List delegates; + + public CompositeConversionService(List delegates) { + this.delegates = delegates; + } + + @Override + public boolean canConvert(Class sourceType, Class targetType) { + for (ConversionService service : this.delegates) { + if (service.canConvert(sourceType, targetType)) { + return true; + } + } + return false; + } + + @Override + public boolean canConvert(TypeDescriptor sourceType, TypeDescriptor targetType) { + for (ConversionService service : this.delegates) { + if (service.canConvert(sourceType, targetType)) { + return true; + } + } + return false; + } + + @Override + public T convert(Object source, Class targetType) { + for (int i = 0; i < this.delegates.size() - 1; i++) { + try { + ConversionService delegate = this.delegates.get(i); + if (delegate.canConvert(source.getClass(), targetType)) { + return delegate.convert(source, targetType); + } + } catch (ConversionException e) { + // ignored + } + } + + return this.delegates.get(this.delegates.size() - 1).convert(source, targetType); + } + + @Override + public Object convert(Object source, TypeDescriptor sourceType, TypeDescriptor targetType) { + for (int i = 0; i < this.delegates.size() - 1; i++) { + try { + ConversionService delegate = this.delegates.get(i); + if (delegate.canConvert(sourceType, targetType)) { + return delegate.convert(source, sourceType, targetType); + } + } catch (ConversionException e) { + // ignored + } + } + + return this.delegates.get(this.delegates.size() - 1).convert(source, sourceType, targetType); + } +} diff --git a/components/camel-spring-cloud-consul/src/main/java/org/apache/camel/spring/cloud/consul/ConsulServerToServiceDefinition.java b/components/camel-spring-cloud-consul/src/main/java/org/apache/camel/spring/cloud/consul/ConsulServerToServiceDefinition.java new file mode 100644 index 0000000000000..cf507107e3c35 --- /dev/null +++ b/components/camel-spring-cloud-consul/src/main/java/org/apache/camel/spring/cloud/consul/ConsulServerToServiceDefinition.java @@ -0,0 +1,35 @@ +/** + * 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.camel.spring.cloud.consul; + +import org.apache.camel.cloud.ServiceDefinition; +import org.apache.camel.impl.cloud.DefaultServiceDefinition; +import org.springframework.cloud.consul.discovery.ConsulServer; +import org.springframework.core.convert.converter.Converter; + +public final class ConsulServerToServiceDefinition implements Converter { + + @Override + public ServiceDefinition convert(ConsulServer source) { + return new DefaultServiceDefinition( + source.getId(), + source.getHost(), + source.getPort(), + source.getMetadata() + ); + } +} diff --git a/components/camel-spring-cloud-netflix/src/main/java/org/apache/camel/spring/cloud/netflix/CamelCloudNetflixRibbonAutoConfiguration.java b/components/camel-spring-cloud-consul/src/main/java/org/apache/camel/spring/cloud/consul/ConsulServerToServiceDefinitionAutoConfiguration.java similarity index 54% rename from components/camel-spring-cloud-netflix/src/main/java/org/apache/camel/spring/cloud/netflix/CamelCloudNetflixRibbonAutoConfiguration.java rename to components/camel-spring-cloud-consul/src/main/java/org/apache/camel/spring/cloud/consul/ConsulServerToServiceDefinitionAutoConfiguration.java index 5631507a4a222..ff941038039a2 100644 --- a/components/camel-spring-cloud-netflix/src/main/java/org/apache/camel/spring/cloud/netflix/CamelCloudNetflixRibbonAutoConfiguration.java +++ b/components/camel-spring-cloud-consul/src/main/java/org/apache/camel/spring/cloud/consul/ConsulServerToServiceDefinitionAutoConfiguration.java @@ -14,25 +14,29 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package org.apache.camel.spring.cloud.netflix; +package org.apache.camel.spring.cloud.consul; +import org.apache.camel.cloud.ServiceDefinition; import org.apache.camel.spring.boot.util.GroupCondition; -import org.springframework.boot.autoconfigure.AutoConfigureAfter; -import org.springframework.boot.autoconfigure.condition.ConditionalOnBean; -import org.springframework.boot.context.properties.EnableConfigurationProperties; -import org.springframework.cloud.netflix.ribbon.RibbonAutoConfiguration; -import org.springframework.cloud.netflix.ribbon.RibbonClients; -import org.springframework.cloud.netflix.ribbon.SpringClientFactory; +import org.apache.camel.spring.cloud.CamelSpringCloudServiceLoadBalancerAutoConfiguration; +import org.springframework.boot.autoconfigure.AutoConfigureBefore; +import org.springframework.cloud.consul.ConditionalOnConsulEnabled; +import org.springframework.cloud.consul.discovery.ConsulServer; +import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Conditional; import org.springframework.context.annotation.Configuration; +import org.springframework.core.convert.converter.Converter; @Configuration -@EnableConfigurationProperties -@ConditionalOnBean(SpringClientFactory.class) -@Conditional(CamelCloudNetflixRibbonAutoConfiguration.Condition.class) -@AutoConfigureAfter({ CamelCloudNetflixAutoConfiguration.class, RibbonAutoConfiguration.class }) -@RibbonClients(defaultConfiguration = CamelCloudNetflixRibbonClientConfiguration.class) -public class CamelCloudNetflixRibbonAutoConfiguration { +@AutoConfigureBefore(CamelSpringCloudServiceLoadBalancerAutoConfiguration.class) +@ConditionalOnConsulEnabled +@Conditional(ConsulServerToServiceDefinitionAutoConfiguration.Condition.class) +public class ConsulServerToServiceDefinitionAutoConfiguration { + + @Bean(name = "consul-server-to-service-definition") + public Converter consulServerToServiceDefinition() { + return new ConsulServerToServiceDefinition(); + } // ******************************* // Condition @@ -41,8 +45,8 @@ public class CamelCloudNetflixRibbonAutoConfiguration { public static class Condition extends GroupCondition { public Condition() { super( - "camel.cloud.netflix", - "camel.cloud.netflix.ribbon" + "camel.cloud", + "camel.cloud.consul" ); } } diff --git a/components/camel-spring-cloud-consul/src/main/java/org/apache/camel/spring/cloud/consul/CamelCloudConsulAutoConfiguration.java b/components/camel-spring-cloud-consul/src/main/java/org/apache/camel/spring/cloud/consul/ServiceDefinitionToConsulRegistrationAutoConfiguration.java similarity index 94% rename from components/camel-spring-cloud-consul/src/main/java/org/apache/camel/spring/cloud/consul/CamelCloudConsulAutoConfiguration.java rename to components/camel-spring-cloud-consul/src/main/java/org/apache/camel/spring/cloud/consul/ServiceDefinitionToConsulRegistrationAutoConfiguration.java index a1ba31ff0b81a..1f42dcf4451b3 100644 --- a/components/camel-spring-cloud-consul/src/main/java/org/apache/camel/spring/cloud/consul/CamelCloudConsulAutoConfiguration.java +++ b/components/camel-spring-cloud-consul/src/main/java/org/apache/camel/spring/cloud/consul/ServiceDefinitionToConsulRegistrationAutoConfiguration.java @@ -32,9 +32,9 @@ @Configuration @AutoConfigureBefore(CamelSpringCloudServiceRegistryAutoConfiguration.class) @ConditionalOnConsulEnabled -@Conditional(CamelCloudConsulAutoConfiguration.Condition.class) +@Conditional(ServiceDefinitionToConsulRegistrationAutoConfiguration.Condition.class) @EnableConfigurationProperties(CamelCloudConfigurationProperties.class) -public class CamelCloudConsulAutoConfiguration { +public class ServiceDefinitionToConsulRegistrationAutoConfiguration { @Bean(name = "service-definition-to-consul-registration") public Converter serviceDefinitionToConsulRegistration( diff --git a/components/camel-spring-cloud-consul/src/main/resources/META-INF/spring.factories b/components/camel-spring-cloud-consul/src/main/resources/META-INF/spring.factories index 56dd79ff4eaff..43705caac7009 100644 --- a/components/camel-spring-cloud-consul/src/main/resources/META-INF/spring.factories +++ b/components/camel-spring-cloud-consul/src/main/resources/META-INF/spring.factories @@ -16,4 +16,5 @@ # org.springframework.boot.autoconfigure.EnableAutoConfiguration=\ - org.apache.camel.spring.cloud.consul.CamelCloudConsulAutoConfiguration \ No newline at end of file + org.apache.camel.spring.cloud.consul.ServiceDefinitionToConsulRegistrationAutoConfiguration,\ + org.apache.camel.spring.cloud.consul.ConsulServerToServiceDefinitionAutoConfiguration \ No newline at end of file diff --git a/components/camel-spring-cloud-consul/src/test/java/org/apache/camel/spring/cloud/consul/CamelCloudConsulAutoConfigurationTest.java b/components/camel-spring-cloud-consul/src/test/java/org/apache/camel/spring/cloud/consul/CamelCloudConsulAutoConfigurationTest.java index 83e50df66554c..a291515c44570 100644 --- a/components/camel-spring-cloud-consul/src/test/java/org/apache/camel/spring/cloud/consul/CamelCloudConsulAutoConfigurationTest.java +++ b/components/camel-spring-cloud-consul/src/test/java/org/apache/camel/spring/cloud/consul/CamelCloudConsulAutoConfigurationTest.java @@ -57,6 +57,33 @@ public void testServiceDefinitionToConsulRegistration() throws Exception { } } + @Test + public void testConsulServerToServiceDefinition() throws Exception { + ConfigurableApplicationContext context = new SpringApplicationBuilder(TestConfiguration.class) + .web(WebApplicationType.NONE) + .run( + "--debug=false", + "--spring.main.banner-mode=OFF", + "--spring.application.name=" + UUID.randomUUID().toString(), + "--ribbon.enabled=false", + "--ribbon.eureka.enabled=false", + "--management.endpoint.enabled=false", + "--spring.cloud.consul.enabled=true", + "--spring.cloud.consul.config.enabled=false", + "--spring.cloud.consul.discovery.enabled=true", + "--spring.cloud.service-registry.auto-registration.enabled=false" + ); + + try { + Map converters = context.getBeansOfType(Converter.class); + + assertThat(converters).isNotNull(); + assertThat(converters.values().stream().anyMatch(ConsulServerToServiceDefinition.class::isInstance)).isTrue(); + } finally { + context.close(); + } + } + // ************************************* // Config // ************************************* diff --git a/components/camel-spring-cloud-consul/src/test/java/org/apache/camel/spring/cloud/consul/CamelCloudConsulServiceRegistryTest.java b/components/camel-spring-cloud-consul/src/test/java/org/apache/camel/spring/cloud/consul/CamelCloudConsulServiceRegistryTest.java index b576351d13acb..e1a9e95d3d176 100644 --- a/components/camel-spring-cloud-consul/src/test/java/org/apache/camel/spring/cloud/consul/CamelCloudConsulServiceRegistryTest.java +++ b/components/camel-spring-cloud-consul/src/test/java/org/apache/camel/spring/cloud/consul/CamelCloudConsulServiceRegistryTest.java @@ -62,7 +62,8 @@ public void testServiceRegistry() throws Exception { "--spring.cloud.consul.port=" + container.getMappedPort(8500), "--spring.cloud.consul.config.enabled=false", "--spring.cloud.consul.discovery.enabled=true", - "--spring.cloud.service-registry.auto-registration.enabled=false" + "--spring.cloud.service-registry.auto-registration.enabled=false", + "--camel.cloud.service-registry.service-host=localhost" ); try { diff --git a/components/camel-spring-cloud-netflix/pom.xml b/components/camel-spring-cloud-netflix/pom.xml index b412e93601e2e..c5111e797c09e 100644 --- a/components/camel-spring-cloud-netflix/pom.xml +++ b/components/camel-spring-cloud-netflix/pom.xml @@ -90,6 +90,10 @@ org.apache.camel camel-spring-boot + + org.apache.camel + camel-spring-cloud + io.reactivex rxjava diff --git a/components/camel-spring-cloud-netflix/src/main/java/org/apache/camel/spring/cloud/netflix/CamelCloudNetflixRibbonClientConfiguration.java b/components/camel-spring-cloud-netflix/src/main/java/org/apache/camel/spring/cloud/netflix/CamelCloudNetflixRibbonClientConfiguration.java deleted file mode 100644 index 1b492a830ca50..0000000000000 --- a/components/camel-spring-cloud-netflix/src/main/java/org/apache/camel/spring/cloud/netflix/CamelCloudNetflixRibbonClientConfiguration.java +++ /dev/null @@ -1,47 +0,0 @@ -/** - * 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.camel.spring.cloud.netflix; - -import com.netflix.client.config.IClientConfig; -import com.netflix.loadbalancer.ServerList; -import org.apache.camel.spring.boot.cloud.CamelCloudServiceDiscovery; -import org.apache.camel.spring.boot.cloud.CamelCloudServiceFilter; -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean; -import org.springframework.context.annotation.Bean; -import org.springframework.context.annotation.Configuration; - -@Configuration -public class CamelCloudNetflixRibbonClientConfiguration { - @Autowired - private IClientConfig clientConfig; - @Autowired - private CamelCloudServiceDiscovery serviceDiscovery; - @Autowired - private CamelCloudServiceFilter serviceFilter; - - @Bean - @ConditionalOnMissingBean - public ServerList ribbonServerList() { - CamelCloudNetflixServerList serverList = new CamelCloudNetflixServerList(); - serverList.setServiceDiscovery(serviceDiscovery); - serverList.setServiceFilter(serviceFilter); - serverList.initWithNiwsConfig(clientConfig); - - return serverList; - } -} diff --git a/components/camel-spring-cloud-netflix/src/main/java/org/apache/camel/spring/cloud/netflix/CamelCloudNetflixServiceLoadBalancer.java b/components/camel-spring-cloud-netflix/src/main/java/org/apache/camel/spring/cloud/netflix/CamelCloudNetflixServiceLoadBalancer.java new file mode 100644 index 0000000000000..a13211dad8ed1 --- /dev/null +++ b/components/camel-spring-cloud-netflix/src/main/java/org/apache/camel/spring/cloud/netflix/CamelCloudNetflixServiceLoadBalancer.java @@ -0,0 +1,88 @@ +/** + * 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.camel.spring.cloud.netflix; + +import java.util.List; + +import com.netflix.loadbalancer.Server; +import org.apache.camel.cloud.ServiceDefinition; +import org.apache.camel.cloud.ServiceLoadBalancer; +import org.apache.camel.cloud.ServiceLoadBalancerFunction; +import org.apache.camel.impl.cloud.DefaultServiceDefinition; +import org.springframework.cloud.client.loadbalancer.LoadBalancerClient; +import org.springframework.cloud.netflix.ribbon.RibbonLoadBalancerClient; +import org.springframework.core.convert.ConversionService; + +public class CamelCloudNetflixServiceLoadBalancer implements ServiceLoadBalancer { + private final LoadBalancerClient client; + private final List conversionServices; + + public CamelCloudNetflixServiceLoadBalancer(LoadBalancerClient client, List conversionServices) { + this.client = client; + this.conversionServices = conversionServices; + } + + @Override + public T process(String serviceName, ServiceLoadBalancerFunction function) throws Exception { + return client.execute(serviceName, instance -> { + ServiceDefinition definition = null; + + // + // this should not be needed but there is a bug or misbehavior on + // spring cloud netflix side (2.x) that prevent ribbon load balancer + // to propagate metadata from i.e. consul, see: + // + // https://github.com/spring-cloud/spring-cloud-consul/issues/424 + // + // so here we do try to find a converter that is able to use the + // underlying server implementation to extract meta-data and any + // other thing needed by Camel. + // + + if (instance instanceof RibbonLoadBalancerClient.RibbonServer) { + Server server = RibbonLoadBalancerClient.RibbonServer.class.cast(instance).getServer(); + + for (int i = 0; i < conversionServices.size(); i++) { + ConversionService cs = conversionServices.get(i); + + if (cs.canConvert(server.getClass(), ServiceDefinition.class)) { + definition = cs.convert(server, ServiceDefinition.class); + + if (definition != null) { + break; + } + } + } + } + + // If no conversion is possible we use the info found on service + // instance given by the load balancer as it is so the result may + // be incomplete + + if (definition == null) { + definition = new DefaultServiceDefinition( + instance.getServiceId(), + instance.getHost(), + instance.getPort(), + instance.getMetadata() + ); + } + + return function.apply(definition); + }); + } +} diff --git a/components/camel-spring-cloud-netflix/src/main/java/org/apache/camel/spring/cloud/netflix/CamelCloudNetflixServiceLoadBalancerAutoConfiguration.java b/components/camel-spring-cloud-netflix/src/main/java/org/apache/camel/spring/cloud/netflix/CamelCloudNetflixServiceLoadBalancerAutoConfiguration.java new file mode 100644 index 0000000000000..9f01558461224 --- /dev/null +++ b/components/camel-spring-cloud-netflix/src/main/java/org/apache/camel/spring/cloud/netflix/CamelCloudNetflixServiceLoadBalancerAutoConfiguration.java @@ -0,0 +1,60 @@ +/** + * 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.camel.spring.cloud.netflix; + +import java.util.List; + +import org.apache.camel.spring.boot.util.GroupCondition; +import org.apache.camel.spring.cloud.CamelSpringCloudServiceLoadBalancer; +import org.apache.camel.spring.cloud.CamelSpringCloudServiceRegistryAutoConfiguration; +import org.springframework.boot.autoconfigure.AutoConfigureAfter; +import org.springframework.boot.autoconfigure.AutoConfigureBefore; +import org.springframework.boot.autoconfigure.condition.ConditionalOnBean; +import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean; +import org.springframework.cloud.client.loadbalancer.LoadBalancerClient; +import org.springframework.cloud.netflix.ribbon.RibbonAutoConfiguration; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Conditional; +import org.springframework.context.annotation.Configuration; +import org.springframework.core.convert.ConversionService; + +@Configuration +@AutoConfigureAfter(RibbonAutoConfiguration.class) +@AutoConfigureBefore(CamelSpringCloudServiceRegistryAutoConfiguration.class) +@Conditional(CamelCloudNetflixServiceLoadBalancerAutoConfiguration.Condition.class) +public class CamelCloudNetflixServiceLoadBalancerAutoConfiguration { + + @ConditionalOnBean(LoadBalancerClient.class) + @ConditionalOnMissingBean + @Bean("netflix-client-load-balancer-adapter") + public CamelSpringCloudServiceLoadBalancer.LoadBalancerClientAdapter netflixClientLoadBalancerAdapter(List conversionServices) { + return client -> new CamelCloudNetflixServiceLoadBalancer(client, conversionServices); + } + + // ******************************* + // Condition + // ******************************* + + public static class Condition extends GroupCondition { + public Condition() { + super( + "camel.cloud", + "camel.cloud.netflix" + ); + } + } +} diff --git a/components/camel-spring-cloud-netflix/src/main/resources/META-INF/spring.factories b/components/camel-spring-cloud-netflix/src/main/resources/META-INF/spring.factories index 1926cebeb0a97..4d9302738237f 100644 --- a/components/camel-spring-cloud-netflix/src/main/resources/META-INF/spring.factories +++ b/components/camel-spring-cloud-netflix/src/main/resources/META-INF/spring.factories @@ -16,5 +16,5 @@ # org.springframework.boot.autoconfigure.EnableAutoConfiguration=\ -org.apache.camel.spring.cloud.netflix.CamelCloudNetflixAutoConfiguration,\ -org.apache.camel.spring.cloud.netflix.CamelCloudNetflixRibbonAutoConfiguration \ No newline at end of file + org.apache.camel.spring.cloud.netflix.CamelCloudNetflixAutoConfiguration,\ + org.apache.camel.spring.cloud.netflix.CamelCloudNetflixServiceLoadBalancerAutoConfiguration \ No newline at end of file diff --git a/components/camel-spring-cloud-netflix/src/test/java/org/apache/camel/spring/cloud/netflix/CamelCloudNetflixServiceCallTest.java b/components/camel-spring-cloud-netflix/src/test/java/org/apache/camel/spring/cloud/netflix/CamelCloudNetflixServiceCallTest.java deleted file mode 100644 index 0cd76bbda9bfe..0000000000000 --- a/components/camel-spring-cloud-netflix/src/test/java/org/apache/camel/spring/cloud/netflix/CamelCloudNetflixServiceCallTest.java +++ /dev/null @@ -1,93 +0,0 @@ -/** - * 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.camel.spring.cloud.netflix; - -import org.apache.camel.ProducerTemplate; -import org.apache.camel.builder.RouteBuilder; -import org.apache.camel.spring.boot.CamelAutoConfiguration; -import org.junit.Assert; -import org.junit.Test; -import org.junit.runner.RunWith; -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.boot.autoconfigure.SpringBootApplication; -import org.springframework.boot.test.context.SpringBootTest; -import org.springframework.cloud.netflix.ribbon.RibbonClientConfiguration; -import org.springframework.context.annotation.Bean; -import org.springframework.context.annotation.Configuration; -import org.springframework.test.annotation.DirtiesContext; -import org.springframework.test.context.junit4.SpringRunner; - -@DirtiesContext -@RunWith(SpringRunner.class) -@SpringBootApplication -@SpringBootTest( - classes = { - CamelAutoConfiguration.class, - CamelCloudNetflixAutoConfiguration.class, - CamelCloudNetflixServiceCallTest.TestConfiguration.class, - RibbonClientConfiguration.class, - }, - properties = { - "camel.cloud.load-balancer.enabled=true", - "camel.cloud.service-discovery.services[custom-svc-list]=localhost:9090,localhost:9091,localhost:9092", - "camel.cloud.service-filter.blacklist[custom-svc-list]=localhost:9091", - "ribbon.enabled=true", - "ribbon.client.name=custom-svc-list", - "ribbon.eureka.enabled=false", - "debug=false" - } -) -public class CamelCloudNetflixServiceCallTest { - @Autowired - private ProducerTemplate template; - - @Test - public void testServiceCall() throws Exception { - Assert.assertEquals("9090", template.requestBody("direct:start", null, String.class)); - Assert.assertEquals("9092", template.requestBody("direct:start", null, String.class)); - } - - // *********************************************** - // Configuration - // *********************************************** - - @Configuration - public static class TestConfiguration { - @Bean - public RouteBuilder myRouteBuilder() { - return new RouteBuilder() { - @Override - public void configure() throws Exception { - from("direct:start") - .serviceCall() - .name("custom-svc-list/hello"); - - from("jetty:http://localhost:9090/hello") - .transform() - .constant("9090"); - from("jetty:http://localhost:9091/hello") - .transform() - .constant("9091"); - from("jetty:http://localhost:9092/hello") - .transform() - .constant("9092"); - } - }; - } - } -} - diff --git a/components/camel-spring-cloud-netflix/src/test/java/org/apache/camel/spring/cloud/netflix/CamelCloudNetflixServiceLoadBalancerAutoConfigurationTest.java b/components/camel-spring-cloud-netflix/src/test/java/org/apache/camel/spring/cloud/netflix/CamelCloudNetflixServiceLoadBalancerAutoConfigurationTest.java new file mode 100644 index 0000000000000..297638a446fbf --- /dev/null +++ b/components/camel-spring-cloud-netflix/src/test/java/org/apache/camel/spring/cloud/netflix/CamelCloudNetflixServiceLoadBalancerAutoConfigurationTest.java @@ -0,0 +1,78 @@ +/** + * 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.camel.spring.cloud.netflix; + +import org.apache.camel.cloud.ServiceLoadBalancer; +import org.apache.camel.spring.boot.CamelAutoConfiguration; +import org.apache.camel.spring.boot.cloud.CamelCloudAutoConfiguration; +import org.apache.camel.spring.cloud.CamelSpringCloudServiceLoadBalancer; +import org.apache.camel.spring.cloud.CamelSpringCloudServiceRegistryAutoConfiguration; +import org.junit.Test; +import org.springframework.boot.autoconfigure.AutoConfigurations; +import org.springframework.boot.autoconfigure.EnableAutoConfiguration; +import org.springframework.boot.test.context.runner.ApplicationContextRunner; +import org.springframework.cloud.client.loadbalancer.LoadBalancerClient; +import org.springframework.cloud.netflix.ribbon.RibbonAutoConfiguration; +import org.springframework.cloud.netflix.ribbon.RibbonClientConfiguration; +import org.springframework.cloud.netflix.ribbon.RibbonLoadBalancerClient; +import org.springframework.context.annotation.Configuration; + +import static org.assertj.core.api.Assertions.assertThat; + +public class CamelCloudNetflixServiceLoadBalancerAutoConfigurationTest { + + @Test + public void testAutoConfiguration() { + new ApplicationContextRunner() + .withConfiguration( + AutoConfigurations.of( + CamelAutoConfiguration.class, + CamelCloudAutoConfiguration.class, + CamelSpringCloudServiceRegistryAutoConfiguration.class, + CamelCloudNetflixServiceLoadBalancerAutoConfiguration.class, + RibbonAutoConfiguration.class, + RibbonClientConfiguration.class + )) + .withUserConfiguration( + TestConfiguration.class) + .withPropertyValues( + "debug=true", + "spring.main.banner-mode=off", + "ribbon.client.name=test") + .run( + context -> { + assertThat(context).hasSingleBean(LoadBalancerClient.class); + assertThat(context).getBean(LoadBalancerClient.class).isInstanceOf(RibbonLoadBalancerClient.class); + + assertThat(context).hasSingleBean(CamelSpringCloudServiceLoadBalancer.LoadBalancerClientAdapter.class); + + LoadBalancerClient client = context.getBean(LoadBalancerClient.class); + ServiceLoadBalancer balancer = context.getBean(CamelSpringCloudServiceLoadBalancer.LoadBalancerClientAdapter.class).adapt(client); + + assertThat(balancer).isInstanceOf(CamelCloudNetflixServiceLoadBalancer.class); + } + ); + } + + + @EnableAutoConfiguration + @Configuration + public static class TestConfiguration { + + } +} + diff --git a/components/camel-spring-cloud-netflix/src/test/resources/logback.xml b/components/camel-spring-cloud-netflix/src/test/resources/logback.xml index 75c70d11e62c2..848cd0f1b66c5 100644 --- a/components/camel-spring-cloud-netflix/src/test/resources/logback.xml +++ b/components/camel-spring-cloud-netflix/src/test/resources/logback.xml @@ -35,6 +35,7 @@ + diff --git a/components/camel-spring-cloud-zookeeper/src/main/java/org/apache/camel/spring/cloud/zookeeper/CamelCloudZookeeperAutoConfiguration.java b/components/camel-spring-cloud-zookeeper/src/main/java/org/apache/camel/spring/cloud/zookeeper/ServiceDefinitionToZookeeperRegistrationAutoConfiguration.java similarity index 92% rename from components/camel-spring-cloud-zookeeper/src/main/java/org/apache/camel/spring/cloud/zookeeper/CamelCloudZookeeperAutoConfiguration.java rename to components/camel-spring-cloud-zookeeper/src/main/java/org/apache/camel/spring/cloud/zookeeper/ServiceDefinitionToZookeeperRegistrationAutoConfiguration.java index b223e029e4875..38435af6d0218 100644 --- a/components/camel-spring-cloud-zookeeper/src/main/java/org/apache/camel/spring/cloud/zookeeper/CamelCloudZookeeperAutoConfiguration.java +++ b/components/camel-spring-cloud-zookeeper/src/main/java/org/apache/camel/spring/cloud/zookeeper/ServiceDefinitionToZookeeperRegistrationAutoConfiguration.java @@ -32,12 +32,12 @@ @Configuration @AutoConfigureBefore(CamelSpringCloudServiceRegistryAutoConfiguration.class) @ConditionalOnZookeeperEnabled -@Conditional(CamelCloudZookeeperAutoConfiguration.Condition.class) +@Conditional(ServiceDefinitionToZookeeperRegistrationAutoConfiguration.Condition.class) @EnableConfigurationProperties(CamelCloudConfigurationProperties.class) -public class CamelCloudZookeeperAutoConfiguration { +public class ServiceDefinitionToZookeeperRegistrationAutoConfiguration { @Bean(name = "service-definition-to-zookeeper-registration") - public Converter serviceDefinitionToConsulRegistration( + public Converter serviceDefinitionToZookeeperRegistration( CamelCloudConfigurationProperties properties) { return new ServiceDefinitionToZookeeperRegistration(properties); } diff --git a/components/camel-spring-cloud-zookeeper/src/main/java/org/apache/camel/spring/cloud/zookeeper/ZookeeperServerToServiceDefinition.java b/components/camel-spring-cloud-zookeeper/src/main/java/org/apache/camel/spring/cloud/zookeeper/ZookeeperServerToServiceDefinition.java new file mode 100644 index 0000000000000..463146ae2a7d2 --- /dev/null +++ b/components/camel-spring-cloud-zookeeper/src/main/java/org/apache/camel/spring/cloud/zookeeper/ZookeeperServerToServiceDefinition.java @@ -0,0 +1,35 @@ +/** + * 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.camel.spring.cloud.zookeeper; + +import org.apache.camel.cloud.ServiceDefinition; +import org.apache.camel.impl.cloud.DefaultServiceDefinition; +import org.springframework.cloud.zookeeper.discovery.ZookeeperServer; +import org.springframework.core.convert.converter.Converter; + +public final class ZookeeperServerToServiceDefinition implements Converter { + + @Override + public ServiceDefinition convert(ZookeeperServer source) { + return new DefaultServiceDefinition( + source.getId(), + source.getHost(), + source.getPort(), + source.getInstance().getPayload().getMetadata() + ); + } +} diff --git a/components/camel-spring-cloud-zookeeper/src/main/java/org/apache/camel/spring/cloud/zookeeper/ZookeeperToServiceDefinitionAutoConfiguration.java b/components/camel-spring-cloud-zookeeper/src/main/java/org/apache/camel/spring/cloud/zookeeper/ZookeeperToServiceDefinitionAutoConfiguration.java new file mode 100644 index 0000000000000..535c9e57d73d4 --- /dev/null +++ b/components/camel-spring-cloud-zookeeper/src/main/java/org/apache/camel/spring/cloud/zookeeper/ZookeeperToServiceDefinitionAutoConfiguration.java @@ -0,0 +1,53 @@ +/** + * 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.camel.spring.cloud.zookeeper; + +import org.apache.camel.cloud.ServiceDefinition; +import org.apache.camel.spring.boot.util.GroupCondition; +import org.apache.camel.spring.cloud.CamelSpringCloudServiceLoadBalancerAutoConfiguration; +import org.springframework.boot.autoconfigure.AutoConfigureBefore; +import org.springframework.cloud.zookeeper.ConditionalOnZookeeperEnabled; +import org.springframework.cloud.zookeeper.discovery.ZookeeperServer; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Conditional; +import org.springframework.context.annotation.Configuration; +import org.springframework.core.convert.converter.Converter; + +@Configuration +@AutoConfigureBefore(CamelSpringCloudServiceLoadBalancerAutoConfiguration.class) +@ConditionalOnZookeeperEnabled +@Conditional(ZookeeperToServiceDefinitionAutoConfiguration.Condition.class) +public class ZookeeperToServiceDefinitionAutoConfiguration { + + @Bean(name = "zookeeper-server-to-service-definition") + public Converter zookeeperServerToServiceDefinition() { + return new ZookeeperServerToServiceDefinition(); + } + + // ******************************* + // Condition + // ******************************* + + public static class Condition extends GroupCondition { + public Condition() { + super( + "camel.cloud", + "camel.cloud.zookeeper" + ); + } + } +} diff --git a/components/camel-spring-cloud-zookeeper/src/main/resources/META-INF/spring.factories b/components/camel-spring-cloud-zookeeper/src/main/resources/META-INF/spring.factories index 04d8a13eb7abb..f6b9530188ae3 100644 --- a/components/camel-spring-cloud-zookeeper/src/main/resources/META-INF/spring.factories +++ b/components/camel-spring-cloud-zookeeper/src/main/resources/META-INF/spring.factories @@ -16,4 +16,5 @@ # org.springframework.boot.autoconfigure.EnableAutoConfiguration=\ - org.apache.camel.spring.cloud.zookeeper.CamelCloudZookeeperAutoConfiguration \ No newline at end of file + org.apache.camel.spring.cloud.zookeeper.ServiceDefinitionToZookeeperRegistrationAutoConfiguration,\ + org.apache.camel.spring.cloud.zookeeper.ZookeeperToServiceDefinitionAutoConfiguration \ No newline at end of file diff --git a/components/camel-spring-cloud-zookeeper/src/test/java/org/apache/camel/spring/cloud/zookeeper/CamelCloudZookeeperAutoConfigurationTest.java b/components/camel-spring-cloud-zookeeper/src/test/java/org/apache/camel/spring/cloud/zookeeper/CamelCloudZookeeperAutoConfigurationTest.java index 205b5a930c2dc..9d28b932d7a3c 100644 --- a/components/camel-spring-cloud-zookeeper/src/test/java/org/apache/camel/spring/cloud/zookeeper/CamelCloudZookeeperAutoConfigurationTest.java +++ b/components/camel-spring-cloud-zookeeper/src/test/java/org/apache/camel/spring/cloud/zookeeper/CamelCloudZookeeperAutoConfigurationTest.java @@ -73,6 +73,41 @@ public void testServiceDefinitionToConsulRegistration() throws Exception { } } + @Test + public void testZookeeperServerToServiceDefinition() throws Exception { + final ZookeeperServer server = new ZookeeperServer(temporaryFolder.newFolder(testName.getMethodName())); + + ConfigurableApplicationContext context = new SpringApplicationBuilder(TestConfiguration.class) + .web(WebApplicationType.NONE) + .run( + "--debug=false", + "--spring.main.banner-mode=OFF", + "--spring.application.name=" + UUID.randomUUID().toString(), + "--ribbon.enabled=false", + "--ribbon.eureka.enabled=false", + "--management.endpoint.enabled=false", + "--spring.cloud.zookeeper.enabled=true", + "--spring.cloud.zookeeper.connect-string=" + server.connectString(), + "--spring.cloud.zookeeper.config.enabled=false", + "--spring.cloud.zookeeper.discovery.enabled=true", + "--spring.cloud.service-registry.auto-registration.enabled=false" + ); + + try { + Map converters = context.getBeansOfType(Converter.class); + + assertThat(converters).isNotNull(); + assertThat(converters.values().stream().anyMatch(ZookeeperServerToServiceDefinition.class::isInstance)).isTrue(); + } finally { + + // shutdown spring context + context.close(); + + // shutdown zookeeper + server.shutdown(); + } + } + // ************************************* // Config // ************************************* diff --git a/components/camel-spring-cloud-zookeeper/src/test/java/org/apache/camel/spring/cloud/zookeeper/CamelCloudZookeeperServiceRegistryTest.java b/components/camel-spring-cloud-zookeeper/src/test/java/org/apache/camel/spring/cloud/zookeeper/CamelCloudZookeeperServiceRegistryTest.java index 465dcd6cbc048..4c620849d0048 100644 --- a/components/camel-spring-cloud-zookeeper/src/test/java/org/apache/camel/spring/cloud/zookeeper/CamelCloudZookeeperServiceRegistryTest.java +++ b/components/camel-spring-cloud-zookeeper/src/test/java/org/apache/camel/spring/cloud/zookeeper/CamelCloudZookeeperServiceRegistryTest.java @@ -69,7 +69,8 @@ public void testServiceRegistry() throws Exception { "--spring.cloud.zookeeper.connect-string=" + server.connectString(), "--spring.cloud.zookeeper.config.enabled=false", "--spring.cloud.zookeeper.discovery.enabled=true", - "--spring.cloud.service-registry.auto-registration.enabled=false" + "--spring.cloud.service-registry.auto-registration.enabled=false", + "--camel.cloud.service-registry.service-host=" + SERVICE_HOST ); try { diff --git a/components/camel-spring-cloud/src/main/java/org/apache/camel/spring/cloud/CamelSpringCloudServiceLoadBalancer.java b/components/camel-spring-cloud/src/main/java/org/apache/camel/spring/cloud/CamelSpringCloudServiceLoadBalancer.java index 50447f71360fb..d1b6dd6afc99f 100644 --- a/components/camel-spring-cloud/src/main/java/org/apache/camel/spring/cloud/CamelSpringCloudServiceLoadBalancer.java +++ b/components/camel-spring-cloud/src/main/java/org/apache/camel/spring/cloud/CamelSpringCloudServiceLoadBalancer.java @@ -16,27 +16,28 @@ */ package org.apache.camel.spring.cloud; +import java.util.Optional; + import org.apache.camel.CamelContext; import org.apache.camel.CamelContextAware; -import org.apache.camel.cloud.ServiceDefinition; import org.apache.camel.cloud.ServiceLoadBalancer; import org.apache.camel.cloud.ServiceLoadBalancerFunction; -import org.apache.camel.impl.cloud.DefaultServiceDefinition; import org.apache.camel.support.ServiceSupport; import org.apache.camel.util.ObjectHelper; import org.slf4j.Logger; import org.slf4j.LoggerFactory; -import org.springframework.cloud.client.ServiceInstance; import org.springframework.cloud.client.loadbalancer.LoadBalancerClient; public class CamelSpringCloudServiceLoadBalancer extends ServiceSupport implements CamelContextAware, ServiceLoadBalancer { private static final Logger LOGGER = LoggerFactory.getLogger(CamelSpringCloudServiceLoadBalancer.class); private final LoadBalancerClient loadBalancerClient; + private final ServiceLoadBalancer loadBalancer; private CamelContext camelContext; - public CamelSpringCloudServiceLoadBalancer(LoadBalancerClient loadBalancerClient) { + public CamelSpringCloudServiceLoadBalancer(LoadBalancerClient loadBalancerClient, Optional clientAdapter) { this.loadBalancerClient = loadBalancerClient; + this.loadBalancer = clientAdapter.orElseGet(DefaultLoadBalancerClientAdapter::new).adapt(loadBalancerClient); } @Override @@ -63,19 +64,15 @@ protected void doStop() throws Exception { @Override public T process(String serviceName, ServiceLoadBalancerFunction function) throws Exception { - return loadBalancerClient.execute(serviceName, i -> function.apply(instanceToDefinition(i))); + return loadBalancer.process(serviceName, function); } // ******************************* - // Helpers + // // ******************************* - private ServiceDefinition instanceToDefinition(ServiceInstance instance) { - return new DefaultServiceDefinition( - instance.getServiceId(), - instance.getHost(), - instance.getPort(), - instance.getMetadata() - ); + @FunctionalInterface + public interface LoadBalancerClientAdapter { + ServiceLoadBalancer adapt(LoadBalancerClient client); } } \ No newline at end of file diff --git a/components/camel-spring-cloud/src/main/java/org/apache/camel/spring/cloud/CamelSpringCloudServiceLoadBalancerAutoConfiguration.java b/components/camel-spring-cloud/src/main/java/org/apache/camel/spring/cloud/CamelSpringCloudServiceLoadBalancerAutoConfiguration.java index eab3441a5f61e..1406724d91d3f 100644 --- a/components/camel-spring-cloud/src/main/java/org/apache/camel/spring/cloud/CamelSpringCloudServiceLoadBalancerAutoConfiguration.java +++ b/components/camel-spring-cloud/src/main/java/org/apache/camel/spring/cloud/CamelSpringCloudServiceLoadBalancerAutoConfiguration.java @@ -16,6 +16,8 @@ */ package org.apache.camel.spring.cloud; +import java.util.Optional; + import org.apache.camel.cloud.ServiceLoadBalancer; import org.apache.camel.spring.boot.cloud.CamelCloudAutoConfiguration; import org.apache.camel.spring.boot.cloud.CamelCloudConfigurationProperties; @@ -40,8 +42,11 @@ public class CamelSpringCloudServiceLoadBalancerAutoConfiguration { @Bean(name = "load-balancer") @ConditionalOnMissingBean - public ServiceLoadBalancer cloudLoadBalancer(LoadBalancerClient loadBalancerClient) { - return new CamelSpringCloudServiceLoadBalancer(loadBalancerClient); + public ServiceLoadBalancer cloudLoadBalancer( + LoadBalancerClient loadBalancerClient, + Optional clientAdapter) { + + return new CamelSpringCloudServiceLoadBalancer(loadBalancerClient, clientAdapter); } // ******************************* diff --git a/components/camel-spring-cloud/src/main/java/org/apache/camel/spring/cloud/CamelSpringCloudServiceRegistry.java b/components/camel-spring-cloud/src/main/java/org/apache/camel/spring/cloud/CamelSpringCloudServiceRegistry.java index e376e08b68468..f7674acfd8020 100644 --- a/components/camel-spring-cloud/src/main/java/org/apache/camel/spring/cloud/CamelSpringCloudServiceRegistry.java +++ b/components/camel-spring-cloud/src/main/java/org/apache/camel/spring/cloud/CamelSpringCloudServiceRegistry.java @@ -47,16 +47,17 @@ public CamelSpringCloudServiceRegistry(Collection conversionS @Override public void register(ServiceDefinition definition) { - Registration result = convertServiceDefinition(definition); - synchronized (this) { - LOGGER.debug("Register service with definition: {} with registrations: {}", definition, registrationType); - - serviceRegistry.register(result); - // keep track of registered definition to remove them upon registry // shutdown if (definitions.stream().noneMatch(d -> matchById(d, definition))) { + LOGGER.debug("Register service with definition: {} with registrations: {}", definition, registrationType); + + // compute registration from definition + Registration result = convertServiceDefinition(definition); + + serviceRegistry.register(result); + definitions.add(definition); } } @@ -64,12 +65,15 @@ public void register(ServiceDefinition definition) { @Override public void deregister(ServiceDefinition definition) { - Registration result = convertServiceDefinition(definition); - synchronized (this) { - LOGGER.debug("Deregister service with definition: {} with registrations: {}", definition, registrationType); + if (definitions.stream().noneMatch(d -> matchById(d, definition))) { + LOGGER.debug("Deregister service with definition: {} with registrations: {}", definition, registrationType); + + // compute registration from definition + Registration result = convertServiceDefinition(definition); - serviceRegistry.deregister(result); + serviceRegistry.deregister(result); + } // remove any instance with the same id definitions.removeIf(d -> matchById(d, definition)); @@ -82,8 +86,9 @@ protected void doStart() throws Exception { @Override protected void doStop() throws Exception { - // TODO: need to be improved - new ArrayList<>(definitions).forEach(this::deregister); + synchronized (this) { + new ArrayList<>(definitions).forEach(this::deregister); + } } public ServiceRegistry getNativeServiceRegistry() { @@ -139,9 +144,11 @@ private boolean matchById(ServiceDefinition definition, ServiceDefinition refere } private Registration convertServiceDefinition(ServiceDefinition definition) { - for (ConversionService conversionService: conversionServices) { - if (conversionService.canConvert(ServiceDefinition.class, registrationType)) { - return conversionService.convert(definition, registrationType); + for (int i = 0; i < conversionServices.size(); i++) { + ConversionService cs = conversionServices.get(i); + + if (cs.canConvert(ServiceDefinition.class, registrationType)) { + return cs.convert(definition, registrationType); } } diff --git a/components/camel-spring-cloud/src/main/java/org/apache/camel/spring/cloud/DefaultLoadBalancerClientAdapter.java b/components/camel-spring-cloud/src/main/java/org/apache/camel/spring/cloud/DefaultLoadBalancerClientAdapter.java new file mode 100644 index 0000000000000..3ecb6d2325c93 --- /dev/null +++ b/components/camel-spring-cloud/src/main/java/org/apache/camel/spring/cloud/DefaultLoadBalancerClientAdapter.java @@ -0,0 +1,27 @@ +/** + * 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.camel.spring.cloud; + +import org.apache.camel.cloud.ServiceLoadBalancer; +import org.springframework.cloud.client.loadbalancer.LoadBalancerClient; + +public class DefaultLoadBalancerClientAdapter implements CamelSpringCloudServiceLoadBalancer.LoadBalancerClientAdapter { + @Override + public ServiceLoadBalancer adapt(LoadBalancerClient client) { + return new DefaultServiceLoadBalancer(client); + } +} diff --git a/components/camel-spring-cloud/src/main/java/org/apache/camel/spring/cloud/DefaultServiceLoadBalancer.java b/components/camel-spring-cloud/src/main/java/org/apache/camel/spring/cloud/DefaultServiceLoadBalancer.java new file mode 100644 index 0000000000000..5ab886bb41787 --- /dev/null +++ b/components/camel-spring-cloud/src/main/java/org/apache/camel/spring/cloud/DefaultServiceLoadBalancer.java @@ -0,0 +1,50 @@ +/** + * 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.camel.spring.cloud; + +import org.apache.camel.cloud.ServiceDefinition; +import org.apache.camel.cloud.ServiceLoadBalancer; +import org.apache.camel.cloud.ServiceLoadBalancerFunction; +import org.apache.camel.impl.cloud.DefaultServiceDefinition; +import org.springframework.cloud.client.ServiceInstance; +import org.springframework.cloud.client.loadbalancer.LoadBalancerClient; + +public class DefaultServiceLoadBalancer implements ServiceLoadBalancer { + private final LoadBalancerClient client; + + public DefaultServiceLoadBalancer(LoadBalancerClient client) { + this.client = client; + } + + @Override + public T process(String serviceName, ServiceLoadBalancerFunction function) throws Exception { + return client.execute(serviceName, instance -> { + return function.apply( + convertServiceInstanceToServiceDefinition(instance) + ); + }); + } + + protected ServiceDefinition convertServiceInstanceToServiceDefinition(ServiceInstance instance) { + return new DefaultServiceDefinition( + instance.getServiceId(), + instance.getHost(), + instance.getPort(), + instance.getMetadata() + ); + } +} diff --git a/components/camel-undertow/src/main/java/org/apache/camel/component/undertow/UndertowEndpoint.java b/components/camel-undertow/src/main/java/org/apache/camel/component/undertow/UndertowEndpoint.java index bd6ffa8bafa51..1165137f1035e 100644 --- a/components/camel-undertow/src/main/java/org/apache/camel/component/undertow/UndertowEndpoint.java +++ b/components/camel-undertow/src/main/java/org/apache/camel/component/undertow/UndertowEndpoint.java @@ -142,9 +142,9 @@ public boolean isLenientProperties() { //------------------------------------------------------------------------- @Override - public Map getServiceProperties() { + public Map getServiceProperties() { return CollectionHelper.immutableMapOf( - ServiceDefinition.SERVICE_META_PORT, httpURI.getPort(), + ServiceDefinition.SERVICE_META_PORT, Integer.toString(httpURI.getPort()), ServiceDefinition.SERVICE_META_PATH, httpURI.getPath(), ServiceDefinition.SERVICE_META_PROTOCOL, httpURI.getScheme() ); diff --git a/examples/camel-example-spring-cloud-serviceregistry/consumer/pom.xml b/examples/camel-example-spring-cloud-serviceregistry/consumer/pom.xml index c0c293c8b95a2..31188c5a88f50 100644 --- a/examples/camel-example-spring-cloud-serviceregistry/consumer/pom.xml +++ b/examples/camel-example-spring-cloud-serviceregistry/consumer/pom.xml @@ -101,6 +101,10 @@ org.apache.camel camel-spring-cloud-starter + + org.apache.camel + camel-spring-cloud-netflix-starter + org.apache.camel camel-spring-cloud-consul-starter diff --git a/examples/camel-example-spring-cloud-serviceregistry/consumer/src/main/resources/application.properties b/examples/camel-example-spring-cloud-serviceregistry/consumer/src/main/resources/application.properties index 0eaa50eb928aa..cfd926ad4346b 100644 --- a/examples/camel-example-spring-cloud-serviceregistry/consumer/src/main/resources/application.properties +++ b/examples/camel-example-spring-cloud-serviceregistry/consumer/src/main/resources/application.properties @@ -41,12 +41,3 @@ camel.rest.binding-mode = auto # Camel Service Call camel.cloud.service-call.component = undertow -# this should not be needed but there is a bug or misbehavior -# on spring cloud netflix side that prevent ribbon load -# balancer to propagate metadata from i.e. consul, see: -# -# https://github.com/spring-cloud/spring-cloud-consul/issues/424 -# -camel.cloud.ribbon.load-balancer.enabled = false -camel.cloud.service-call.default-load-balancer = true -camel.cloud.service-call.service-chooser = roundrobin From b02d2da88b9459149dcedd1f37d83f5c63eef70b Mon Sep 17 00:00:00 2001 From: lburgazzoli Date: Mon, 28 May 2018 13:38:55 +0200 Subject: [PATCH 13/20] CAMEL-12502: camel cloud : create a service route policy --- .../cloud/ServiceRegistrationRoutePolicy.java | 38 ++++++++++--------- 1 file changed, 21 insertions(+), 17 deletions(-) diff --git a/camel-core/src/main/java/org/apache/camel/impl/cloud/ServiceRegistrationRoutePolicy.java b/camel-core/src/main/java/org/apache/camel/impl/cloud/ServiceRegistrationRoutePolicy.java index 130b51346acf4..54dd8bab2ad7d 100644 --- a/camel-core/src/main/java/org/apache/camel/impl/cloud/ServiceRegistrationRoutePolicy.java +++ b/camel-core/src/main/java/org/apache/camel/impl/cloud/ServiceRegistrationRoutePolicy.java @@ -146,19 +146,6 @@ private Optional computeServiceDefinition(Route route) { properties.put(key, val); } - // try to get the service id from route properties - String serviceId = properties.get(ServiceDefinition.SERVICE_META_ID); - if (serviceId == null) { - // if not check if the route id is custom and use it - if (route.getRouteContext().getRoute().hasCustomIdAssigned()) { - serviceId = route.getId(); - } - } - if (serviceId == null) { - // finally get the id from the DiscoverableService - serviceId = properties.get(ServiceDefinition.SERVICE_META_ID); - } - // try to get the service name from route properties String serviceName = properties.get(ServiceDefinition.SERVICE_META_NAME); if (serviceName == null) { @@ -170,13 +157,30 @@ private Optional computeServiceDefinition(Route route) { serviceName = properties.get(ServiceDefinition.SERVICE_META_NAME); } - if (ObjectHelper.isEmpty(serviceId) || ObjectHelper.isEmpty(serviceName)) { - LOGGER.debug("Route {} has not enough information for service registration"); + if (ObjectHelper.isEmpty(serviceName)) { + LOGGER.debug("Route {} has not enough information for service registration", route); return Optional.empty(); } - String serviceHost = properties.get(ServiceDefinition.SERVICE_META_HOST); - String servicePort = properties.getOrDefault(ServiceDefinition.SERVICE_META_PORT, "-1"); + // try to get the service id from route properties + String serviceId = properties.get(ServiceDefinition.SERVICE_META_ID); + if (serviceId == null) { + // if not check if the route id is custom and use it + if (route.getRouteContext().getRoute().hasCustomIdAssigned()) { + serviceId = route.getId(); + } + } + if (serviceId == null) { + // then get the id from the DiscoverableService + serviceId = properties.get(ServiceDefinition.SERVICE_META_ID); + } + if (serviceId == null) { + // finally auto generate the service id + serviceId = getCamelContext().getUuidGenerator().generateUuid(); + } + + final String serviceHost = properties.get(ServiceDefinition.SERVICE_META_HOST); + final String servicePort = properties.getOrDefault(ServiceDefinition.SERVICE_META_PORT, "-1"); // Build the final resource definition from bits collected from the // endpoint and the route. From b412b6282298fe03e9bf80d4cccae675130a27a5 Mon Sep 17 00:00:00 2001 From: lburgazzoli Date: Mon, 28 May 2018 15:00:08 +0200 Subject: [PATCH 14/20] CAMEL-12502: camel cloud : create a service route policy --- .../camel/impl/cloud/ServiceRegistrationRoutePolicy.java | 8 -------- 1 file changed, 8 deletions(-) diff --git a/camel-core/src/main/java/org/apache/camel/impl/cloud/ServiceRegistrationRoutePolicy.java b/camel-core/src/main/java/org/apache/camel/impl/cloud/ServiceRegistrationRoutePolicy.java index 54dd8bab2ad7d..57a3000750d02 100644 --- a/camel-core/src/main/java/org/apache/camel/impl/cloud/ServiceRegistrationRoutePolicy.java +++ b/camel-core/src/main/java/org/apache/camel/impl/cloud/ServiceRegistrationRoutePolicy.java @@ -152,10 +152,6 @@ private Optional computeServiceDefinition(Route route) { // if not check if the route group is defined use the route group serviceName = route.getGroup(); } - if (serviceName == null) { - // finally get the name from the DiscoverableService - serviceName = properties.get(ServiceDefinition.SERVICE_META_NAME); - } if (ObjectHelper.isEmpty(serviceName)) { LOGGER.debug("Route {} has not enough information for service registration", route); @@ -170,10 +166,6 @@ private Optional computeServiceDefinition(Route route) { serviceId = route.getId(); } } - if (serviceId == null) { - // then get the id from the DiscoverableService - serviceId = properties.get(ServiceDefinition.SERVICE_META_ID); - } if (serviceId == null) { // finally auto generate the service id serviceId = getCamelContext().getUuidGenerator().generateUuid(); From 2adbaab0ebda6a5c6a27dbd2ccd29ec558b09ab3 Mon Sep 17 00:00:00 2001 From: lburgazzoli Date: Mon, 28 May 2018 15:03:57 +0200 Subject: [PATCH 15/20] CAMEL-12502: camel cloud : create a service route policy --- .../cloud/ServiceRegistrationRoutePolicy.java | 8 ++ .../camel/impl/cloud/ServiceRegistryTest.java | 94 ++++++++++++++++++- 2 files changed, 101 insertions(+), 1 deletion(-) diff --git a/camel-core/src/main/java/org/apache/camel/impl/cloud/ServiceRegistrationRoutePolicy.java b/camel-core/src/main/java/org/apache/camel/impl/cloud/ServiceRegistrationRoutePolicy.java index 57a3000750d02..e0eb073f55469 100644 --- a/camel-core/src/main/java/org/apache/camel/impl/cloud/ServiceRegistrationRoutePolicy.java +++ b/camel-core/src/main/java/org/apache/camel/impl/cloud/ServiceRegistrationRoutePolicy.java @@ -151,6 +151,10 @@ private Optional computeServiceDefinition(Route route) { if (serviceName == null) { // if not check if the route group is defined use the route group serviceName = route.getGroup(); + + if (serviceName != null) { + properties.put(ServiceDefinition.SERVICE_META_NAME, serviceName); + } } if (ObjectHelper.isEmpty(serviceName)) { @@ -165,6 +169,10 @@ private Optional computeServiceDefinition(Route route) { if (route.getRouteContext().getRoute().hasCustomIdAssigned()) { serviceId = route.getId(); } + + if (serviceId != null) { + properties.put(ServiceDefinition.SERVICE_META_ID, serviceId); + } } if (serviceId == null) { // finally auto generate the service id diff --git a/camel-core/src/test/java/org/apache/camel/impl/cloud/ServiceRegistryTest.java b/camel-core/src/test/java/org/apache/camel/impl/cloud/ServiceRegistryTest.java index 6c3cdaa81bdc6..914f839ace1ad 100644 --- a/camel-core/src/test/java/org/apache/camel/impl/cloud/ServiceRegistryTest.java +++ b/camel-core/src/test/java/org/apache/camel/impl/cloud/ServiceRegistryTest.java @@ -41,12 +41,13 @@ public boolean isUseRouteBuilder() { return false; } + // ********************* // Tests // ********************* @Test - public void testServiceRegistrationWithRouteproperties() throws Exception { + public void testServiceRegistrationWithRouteIdAndGroup() throws Exception { final String serviceName = UUID.randomUUID().toString(); final String serviceId = UUID.randomUUID().toString(); final int port = 9090; @@ -54,6 +55,51 @@ public void testServiceRegistrationWithRouteproperties() throws Exception { context.addRouteDefinition( new RouteDefinition() .from("direct:start") + .routeGroup(serviceName) + .routeId(serviceId) + .routeProperty(ServiceDefinition.SERVICE_META_HOST, "localhost") + .routeProperty(ServiceDefinition.SERVICE_META_PORT, "" + port) + .routeProperty("service.meta1", "meta1") + .routeProperty("meta2", "meta2") + .routePolicy(new ServiceRegistrationRoutePolicy()) + .to("mock:end") + ); + + InMemoryServiceRegistry sr = new InMemoryServiceRegistry(); + + context.addService(sr); + context.start(); + + final Map defs = sr.getDefinitions(); + + assertThat(defs).hasSize(1); + + // basic properties + assertThat(defs.values()).first().hasFieldOrPropertyWithValue("name", serviceName); + assertThat(defs.values()).first().hasFieldOrPropertyWithValue("id", serviceId); + assertThat(defs.values()).first().hasFieldOrPropertyWithValue("host", "localhost"); + assertThat(defs.values()).first().hasFieldOrPropertyWithValue("port", port); + + // metadata + assertThat(defs.get(serviceId).getMetadata()).containsEntry(ServiceDefinition.SERVICE_META_NAME, serviceName); + assertThat(defs.get(serviceId).getMetadata()).containsEntry(ServiceDefinition.SERVICE_META_ID, serviceId); + assertThat(defs.get(serviceId).getMetadata()).containsEntry(ServiceDefinition.SERVICE_META_HOST, "localhost"); + assertThat(defs.get(serviceId).getMetadata()).containsEntry(ServiceDefinition.SERVICE_META_PORT, "" + port); + assertThat(defs.get(serviceId).getMetadata()).containsEntry("service.meta1", "meta1"); + assertThat(defs.get(serviceId).getMetadata()).doesNotContainKeys("meta2"); + } + + @Test + public void testServiceRegistrationWithRouteIdAndGroupOverride() throws Exception { + final String serviceName = UUID.randomUUID().toString(); + final String serviceId = UUID.randomUUID().toString(); + final int port = 9090; + + context.addRouteDefinition( + new RouteDefinition() + .from("direct:start") + .routeGroup("service-name") + .routeId("service-id") .routeProperty(ServiceDefinition.SERVICE_META_NAME, serviceName) .routeProperty(ServiceDefinition.SERVICE_META_ID, serviceId) .routeProperty(ServiceDefinition.SERVICE_META_HOST, "localhost") @@ -86,7 +132,53 @@ public void testServiceRegistrationWithRouteproperties() throws Exception { assertThat(defs.get(serviceId).getMetadata()).containsEntry(ServiceDefinition.SERVICE_META_PORT, "" + port); assertThat(defs.get(serviceId).getMetadata()).containsEntry("service.meta1", "meta1"); assertThat(defs.get(serviceId).getMetadata()).doesNotContainKeys("meta2"); + } + + // ********************* + // Tests + // ********************* + + @Test + public void testServiceRegistrationWithRouteProperties() throws Exception { + final String serviceName = UUID.randomUUID().toString(); + final String serviceId = UUID.randomUUID().toString(); + final int port = 9090; + + context.addRouteDefinition( + new RouteDefinition() + .from("direct:start") + .routeProperty(ServiceDefinition.SERVICE_META_NAME, serviceName) + .routeProperty(ServiceDefinition.SERVICE_META_ID, serviceId) + .routeProperty(ServiceDefinition.SERVICE_META_HOST, "localhost") + .routeProperty(ServiceDefinition.SERVICE_META_PORT, "" + port) + .routeProperty("service.meta1", "meta1") + .routeProperty("meta2", "meta2") + .routePolicy(new ServiceRegistrationRoutePolicy()) + .to("mock:end") + ); + InMemoryServiceRegistry sr = new InMemoryServiceRegistry(); + + context.addService(sr); + context.start(); + + final Map defs = sr.getDefinitions(); + + assertThat(defs).hasSize(1); + + // basic properties + assertThat(defs.values()).first().hasFieldOrPropertyWithValue("name", serviceName); + assertThat(defs.values()).first().hasFieldOrPropertyWithValue("id", serviceId); + assertThat(defs.values()).first().hasFieldOrPropertyWithValue("host", "localhost"); + assertThat(defs.values()).first().hasFieldOrPropertyWithValue("port", port); + + // metadata + assertThat(defs.get(serviceId).getMetadata()).containsEntry(ServiceDefinition.SERVICE_META_NAME, serviceName); + assertThat(defs.get(serviceId).getMetadata()).containsEntry(ServiceDefinition.SERVICE_META_ID, serviceId); + assertThat(defs.get(serviceId).getMetadata()).containsEntry(ServiceDefinition.SERVICE_META_HOST, "localhost"); + assertThat(defs.get(serviceId).getMetadata()).containsEntry(ServiceDefinition.SERVICE_META_PORT, "" + port); + assertThat(defs.get(serviceId).getMetadata()).containsEntry("service.meta1", "meta1"); + assertThat(defs.get(serviceId).getMetadata()).doesNotContainKeys("meta2"); } // ********************* From 14666bfe1b3d81458aee1cf383b7262a513c61e8 Mon Sep 17 00:00:00 2001 From: lburgazzoli Date: Mon, 28 May 2018 16:08:27 +0200 Subject: [PATCH 16/20] service registry doc --- .../src/main/docs/service-registry.adoc | 157 ++++++++++++++++++ 1 file changed, 157 insertions(+) create mode 100644 camel-core/src/main/docs/service-registry.adoc diff --git a/camel-core/src/main/docs/service-registry.adoc b/camel-core/src/main/docs/service-registry.adoc new file mode 100644 index 0000000000000..e7c1833184528 --- /dev/null +++ b/camel-core/src/main/docs/service-registry.adoc @@ -0,0 +1,157 @@ +[[ServiceRegistry-ServiceRegistry]] +== Service Registry + +*Available as of Camel 2.22* + +[WARNING] +==== +*Experimental feature* +==== + +Service registration is a key part of service discovery which Camel leverages through the _Service Call EIP_ and as of v 2.22.0 Camel provides an experimental support to ease the process to expose routes in a cloud environment and consume them with minimal configuration. + +=== Service Registry Set-Up + +A _Service Registry_ is just like any other camel service so set it up you only need to register your implementations to the camel context: + +[source,java] +---- +ServiceRegistry service = new MyServiceRegistry(); + +context.addService(service); +---- + +The configuration of the _Service Registry_ depends on the implementation you have chosen. +Out of the box camel provides the following implementations: + +[cols="1,1,2", options="header"] +|==== +|Type |Module | Class +|consul |camel-consul | org.apache.camel.component.consul.cloud.ConsulServiceRegistry +|zookeeper |camel-zookeeper | org.apache.camel.component.zookeeper.cloud.ZooKeeperServiceRegistry +|spring-cloud |camel-spring-cloud | org.apache.camel.component.spring.cloud.CamelSpringCloudServiceRegistry +|==== + +On Spring/Blueprint all the _Service Registry_ instances are automatically added to the camel context. + +=== Serice Registry Usage + +The _Service Registry SPI_ is leveraged by the following new implementations: + +- *ServiceRegistryRoutePolicy* ++ +This is an implementation of a RoutePolicy that register/deregister routes to a given _Service Registry_ according to route's life-cycle ++ +[source,java] +---- +fiRoutePolicy policy = new ServiceRegistrationRoutePolicy() + +// bind the policy to one or more routes +from("undertow:http://0.0.0.0:8080") + .routePolicy(policy) + .log("Route ${routeId} has been invoked"); +---- ++ +To apply the same policy to all the routes a dedicated _RoutePolicyFactory_ can be used: ++ +[source,java] +---- +// add the service registry route policy factory to context +context.addRoutePolicyFactory(new ServiceRegistrationRoutePolicyFactory())); +---- ++ +To configure how the service is exposed you can add route specific properties like: ++ +[source,java] +---- +// bind the policy to one or more routes +from("undertow:http://0.0.0.0:8080") + .routePolicy(policy) + .routeProperty(ServiceDefinition.SERVICE_META_NAME, "my-service") + .routeProperty(ServiceDefinition.SERVICE_META_ID, "my-id") + .routeProperty(ServiceDefinition.SERVICE_META_PORT, "8080") + .log("Route ${routeId} has been invoked"); +---- ++ +Service name and service id can also be provided by _routeId_ and _routeGroup_ ++ +[source,java] +---- +// bind the policy to one or more routes +from("undertow:http://0.0.0.0:8080") + .routePolicy(policy) + .routeGroup("my-service") + .routeId("my-id") + .routeProperty(ServiceDefinition.SERVICE_META_PORT, "8080") + .log("Route ${routeId} has been invoked"); +---- ++ +[TIP] +==== +Some component such has camel-undertow and those based on camel-http-common implement _DiscoverableService_ and they can automatically provide the metadata needed for service registration. +==== ++ +[TIP] +==== +Any property prefixed with _service._ is automatically added to the service's metadata. +==== + +- *Service Component* ++ +The service component is similar to a _ServiceRegistrationRoutePolicyFactory_ but let to "tags" routes that need to be registered to the _Service Registry_ by prefixing the related endpoints according to the service component syntax: ++ +[source] +---- +service:serviceName:delegateUri[?options] +---- ++ +Example: ++ +[source,java] +---- +from("service:my-service:undertow:http://0.0.0.0:8080") + .log("Route ${routeId} has been invoked"); +---- + +To configure how the service is exposed you can add service specific endpoint options such as: + +[source,java] +---- +from("service:my-service:undertow:http://0.0.0.0:8080?service.id=my-service-id") + .log("Route ${routeId} has been invoked"); +---- + +[TIP] +==== +Any option prefixed with _service._ is automatically added to the service's metadata. +==== + +== Spring Cloud + +The _Service Registry_ binding for _Spring Cloud_ let you register your route with minimal code changes a a _Service Registry_ is automatically added to the Camel Context as soon as the camel-spring-cloud dependency is added to the classpath. + +[WARNING] +==== +As the spring-cloud backend has some limitations you need to include also some additional dependencies according to the selected backend. At the moment, the following implementations are provided: + +[options="header"] +|==== +|Spring Cloud |Camel +|spring-cloud-consul |camel-spring-cloud-consul +|spring-cloud-zookeeper |camel-spring-cloud-zookeeper +|==== +==== + +Assuming the consul backend has been chosen the following code will configure and activate the _Service Registry_: + +[source,properties] +---- +# Spring cloud +spring.cloud.consul.enabled = true +spring.cloud.consul.discovery.enabled = true + +# Camel Cloud +camel.cloud.service-registry.service-host = localhost +---- + +To register a route, the easy way is then to use the _service_ component as described above. From c09d5fc8ce116fe11d0726cd1839688d9eaf3103 Mon Sep 17 00:00:00 2001 From: lburgazzoli Date: Tue, 29 May 2018 16:48:19 +0200 Subject: [PATCH 17/20] service registry --- .../ConsulServiceDiscoveryDisabledTest.java | 64 +++++++++++++++ .../ConsulServiceDiscoveryEnabledTest.java | 65 ++++++++++++++++ .../camel-zookeeper-starter/pom.xml | 7 -- ...KeeperClusterServiceAutoConfiguration.java | 53 +++++++++++++ .../ZooKeeperClusterServiceConfiguration.java | 77 +++++++++++++++++++ .../main/resources/META-INF/spring.factories | 5 +- 6 files changed, 261 insertions(+), 10 deletions(-) create mode 100644 platforms/spring-boot/components-starter/camel-consul-starter/src/test/java/org/apache/camel/component/consul/springboot/cloud/ConsulServiceDiscoveryDisabledTest.java create mode 100644 platforms/spring-boot/components-starter/camel-consul-starter/src/test/java/org/apache/camel/component/consul/springboot/cloud/ConsulServiceDiscoveryEnabledTest.java create mode 100644 platforms/spring-boot/components-starter/camel-zookeeper-starter/src/main/java/org/apache/camel/component/zookeeper/cluster/springboot/ZooKeeperClusterServiceAutoConfiguration.java create mode 100644 platforms/spring-boot/components-starter/camel-zookeeper-starter/src/main/java/org/apache/camel/component/zookeeper/cluster/springboot/ZooKeeperClusterServiceConfiguration.java diff --git a/platforms/spring-boot/components-starter/camel-consul-starter/src/test/java/org/apache/camel/component/consul/springboot/cloud/ConsulServiceDiscoveryDisabledTest.java b/platforms/spring-boot/components-starter/camel-consul-starter/src/test/java/org/apache/camel/component/consul/springboot/cloud/ConsulServiceDiscoveryDisabledTest.java new file mode 100644 index 0000000000000..01cc2529a3929 --- /dev/null +++ b/platforms/spring-boot/components-starter/camel-consul-starter/src/test/java/org/apache/camel/component/consul/springboot/cloud/ConsulServiceDiscoveryDisabledTest.java @@ -0,0 +1,64 @@ +/** + * 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.camel.component.consul.springboot.cloud; + +import java.util.Map; + +import org.apache.camel.cloud.ServiceDiscovery; +import org.apache.camel.model.cloud.springboot.ConsulServiceCallServiceDiscoveryConfigurationProperties; +import org.junit.Assert; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.autoconfigure.SpringBootApplication; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.context.ApplicationContext; +import org.springframework.context.annotation.Configuration; +import org.springframework.test.annotation.DirtiesContext; +import org.springframework.test.context.junit4.SpringRunner; + +@RunWith(SpringRunner.class) +@DirtiesContext +@SpringBootApplication +@SpringBootTest( + classes = { + ConsulServiceDiscoveryEnabledTest.TestConfiguration.class + }, + properties = { + "debug=false", + "camel.cloud.consul.service-discovery.enabled=false" +}) +public class ConsulServiceDiscoveryDisabledTest { + @Autowired + ApplicationContext context; + + @Test + public void testConfiguration() throws Exception { + Map beans; + + beans = context.getBeansOfType(ConsulServiceCallServiceDiscoveryConfigurationProperties.class); + Assert.assertTrue(beans.isEmpty()); + + beans = context.getBeansOfType(ServiceDiscovery.class); + Assert.assertFalse(beans.isEmpty()); + Assert.assertFalse(beans.containsKey("consul-service-discovery")); + } + + @Configuration + public static class TestConfiguration { + } +} diff --git a/platforms/spring-boot/components-starter/camel-consul-starter/src/test/java/org/apache/camel/component/consul/springboot/cloud/ConsulServiceDiscoveryEnabledTest.java b/platforms/spring-boot/components-starter/camel-consul-starter/src/test/java/org/apache/camel/component/consul/springboot/cloud/ConsulServiceDiscoveryEnabledTest.java new file mode 100644 index 0000000000000..cedfde5747d12 --- /dev/null +++ b/platforms/spring-boot/components-starter/camel-consul-starter/src/test/java/org/apache/camel/component/consul/springboot/cloud/ConsulServiceDiscoveryEnabledTest.java @@ -0,0 +1,65 @@ +/** + * 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.camel.component.consul.springboot.cloud; + +import java.util.Map; + +import org.apache.camel.cloud.ServiceDiscovery; +import org.apache.camel.model.cloud.springboot.ConsulServiceCallServiceDiscoveryConfigurationProperties; +import org.junit.Assert; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.autoconfigure.SpringBootApplication; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.context.ApplicationContext; +import org.springframework.context.annotation.Configuration; +import org.springframework.test.annotation.DirtiesContext; +import org.springframework.test.context.junit4.SpringRunner; + +@RunWith(SpringRunner.class) +@DirtiesContext +@SpringBootApplication +@SpringBootTest( + classes = { + ConsulServiceDiscoveryEnabledTest.TestConfiguration.class + }, + properties = { + "debug=false", + "camel.cloud.consul.service-discovery.enabled=true" +}) +public class ConsulServiceDiscoveryEnabledTest { + @Autowired + ApplicationContext context; + + @Test + public void testConfiguration() throws Exception { + Map beans; + + beans = context.getBeansOfType(ConsulServiceCallServiceDiscoveryConfigurationProperties.class); + Assert.assertFalse(beans.isEmpty()); + Assert.assertEquals(1, beans.size()); + + beans = context.getBeansOfType(ServiceDiscovery.class); + Assert.assertFalse(beans.isEmpty()); + Assert.assertTrue(beans.containsKey("consul-service-discovery")); + } + + @Configuration + public static class TestConfiguration { + } +} diff --git a/platforms/spring-boot/components-starter/camel-zookeeper-starter/pom.xml b/platforms/spring-boot/components-starter/camel-zookeeper-starter/pom.xml index f6996a0facbc3..7c274d2ee785d 100644 --- a/platforms/spring-boot/components-starter/camel-zookeeper-starter/pom.xml +++ b/platforms/spring-boot/components-starter/camel-zookeeper-starter/pom.xml @@ -39,13 +39,6 @@ camel-zookeeper ${project.version} - - - org.apache.camel - camel-jetty - ${project.version} - test - org.apache.camel diff --git a/platforms/spring-boot/components-starter/camel-zookeeper-starter/src/main/java/org/apache/camel/component/zookeeper/cluster/springboot/ZooKeeperClusterServiceAutoConfiguration.java b/platforms/spring-boot/components-starter/camel-zookeeper-starter/src/main/java/org/apache/camel/component/zookeeper/cluster/springboot/ZooKeeperClusterServiceAutoConfiguration.java new file mode 100644 index 0000000000000..c34d82e2eaa7b --- /dev/null +++ b/platforms/spring-boot/components-starter/camel-zookeeper-starter/src/main/java/org/apache/camel/component/zookeeper/cluster/springboot/ZooKeeperClusterServiceAutoConfiguration.java @@ -0,0 +1,53 @@ +/** + * 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.camel.component.zookeeper.cluster.springboot; + +import org.apache.camel.cluster.CamelClusterService; +import org.apache.camel.component.zookeeper.cluster.ZooKeeperClusterService; +import org.apache.camel.spring.boot.CamelAutoConfiguration; +import org.apache.camel.spring.boot.cluster.ClusteredRouteControllerAutoConfiguration; +import org.apache.camel.util.IntrospectionSupport; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.beans.factory.config.ConfigurableBeanFactory; +import org.springframework.boot.autoconfigure.AutoConfigureBefore; +import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; +import org.springframework.boot.context.properties.EnableConfigurationProperties; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.context.annotation.Scope; + +@Configuration +@AutoConfigureBefore({ ClusteredRouteControllerAutoConfiguration.class, CamelAutoConfiguration.class }) +@ConditionalOnProperty(prefix = "camel.component.zookeeper.cluster.service", name = "enabled") +@EnableConfigurationProperties(ZooKeeperClusterServiceConfiguration.class) +public class ZooKeeperClusterServiceAutoConfiguration { + @Autowired + private ZooKeeperClusterServiceConfiguration configuration; + + @Bean(name = "zookeeper-cluster-service") + @Scope(ConfigurableBeanFactory.SCOPE_SINGLETON) + public CamelClusterService zookeeperClusterService() throws Exception { + ZooKeeperClusterService service = new ZooKeeperClusterService(); + + IntrospectionSupport.setProperties( + service, + IntrospectionSupport.getNonNullProperties(configuration) + ); + + return service; + } +} diff --git a/platforms/spring-boot/components-starter/camel-zookeeper-starter/src/main/java/org/apache/camel/component/zookeeper/cluster/springboot/ZooKeeperClusterServiceConfiguration.java b/platforms/spring-boot/components-starter/camel-zookeeper-starter/src/main/java/org/apache/camel/component/zookeeper/cluster/springboot/ZooKeeperClusterServiceConfiguration.java new file mode 100644 index 0000000000000..1feb5af3e5fa0 --- /dev/null +++ b/platforms/spring-boot/components-starter/camel-zookeeper-starter/src/main/java/org/apache/camel/component/zookeeper/cluster/springboot/ZooKeeperClusterServiceConfiguration.java @@ -0,0 +1,77 @@ +/** + * 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.camel.component.zookeeper.cluster.springboot; + +import java.util.Map; + +import org.apache.camel.component.zookeeper.ZooKeeperCuratorConfiguration; +import org.springframework.boot.context.properties.ConfigurationProperties; + +@ConfigurationProperties(prefix = "camel.component.zookeeper.cluster.service") +public class ZooKeeperClusterServiceConfiguration extends ZooKeeperCuratorConfiguration { + /** + * Sets if the zookeeper cluster service should be enabled or not, default is false. + */ + private boolean enabled; + + /** + * Cluster Service ID + */ + private String id; + + /** + * Custom service attributes. + */ + private Map attributes; + + /** + * Service lookup order/priority. + */ + private Integer order; + + public boolean isEnabled() { + return enabled; + } + + public void setEnabled(boolean enabled) { + this.enabled = enabled; + } + + public String getId() { + return id; + } + + public void setId(String id) { + this.id = id; + } + + public Map getAttributes() { + return attributes; + } + + public void setAttributes(Map attributes) { + this.attributes = attributes; + } + + public Integer getOrder() { + return order; + } + + public void setOrder(Integer order) { + this.order = order; + } +} diff --git a/platforms/spring-boot/components-starter/camel-zookeeper-starter/src/main/resources/META-INF/spring.factories b/platforms/spring-boot/components-starter/camel-zookeeper-starter/src/main/resources/META-INF/spring.factories index 383172c95a789..f3a7b4b2fe8c9 100644 --- a/platforms/spring-boot/components-starter/camel-zookeeper-starter/src/main/resources/META-INF/spring.factories +++ b/platforms/spring-boot/components-starter/camel-zookeeper-starter/src/main/resources/META-INF/spring.factories @@ -15,6 +15,5 @@ ## limitations under the License. ## --------------------------------------------------------------------------- org.springframework.boot.autoconfigure.EnableAutoConfiguration=\ - org.apache.camel.component.zookeeper.springboot.ZooKeeperComponentAutoConfiguration,\ - org.apache.camel.component.zookeeper.springboot.cloud.ZooKeeperServiceRegistryAutoConfiguration,\ - org.apache.camel.component.zookeeper.springboot.cluster.ZooKeeperClusterServiceAutoConfiguration +org.apache.camel.component.zookeeper.springboot.ZooKeeperComponentAutoConfiguration,\ +org.apache.camel.component.zookeeper.cluster.springboot.ZooKeeperClusterServiceAutoConfiguration From 67a86f57424b976053d86f316b64d44e27437b51 Mon Sep 17 00:00:00 2001 From: lburgazzoli Date: Tue, 29 May 2018 17:14:24 +0200 Subject: [PATCH 18/20] fix typos --- camel-core/pom.xml | 2 +- .../apache/camel/core/xml/AbstractCamelContextFactoryBean.java | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/camel-core/pom.xml b/camel-core/pom.xml index 0166d5cd44ed7..1933d7b395bcb 100644 --- a/camel-core/pom.xml +++ b/camel-core/pom.xml @@ -136,7 +136,7 @@ com.github.ben-manes.caffeine caffeine - + org.apache.camel json-simple-ordered diff --git a/components/camel-core-xml/src/main/java/org/apache/camel/core/xml/AbstractCamelContextFactoryBean.java b/components/camel-core-xml/src/main/java/org/apache/camel/core/xml/AbstractCamelContextFactoryBean.java index 2d87e0352aca8..22a6507abf776 100644 --- a/components/camel-core-xml/src/main/java/org/apache/camel/core/xml/AbstractCamelContextFactoryBean.java +++ b/components/camel-core-xml/src/main/java/org/apache/camel/core/xml/AbstractCamelContextFactoryBean.java @@ -706,7 +706,7 @@ protected void initPropertyPlaceholder() throws Exception { pc.setPropertiesResolver(resolver); } - // if using a custom parse + // if using a custom parser if (ObjectHelper.isNotEmpty(def.getPropertiesParserRef())) { PropertiesParser parser = CamelContextHelper.mandatoryLookup(getContext(), def.getPropertiesParserRef(), PropertiesParser.class); From 2a7e54e9fea7d8e00d8e104a3b14d3756e33a6dc Mon Sep 17 00:00:00 2001 From: lburgazzoli Date: Tue, 29 May 2018 18:01:31 +0200 Subject: [PATCH 19/20] regen --- .../jsonpath/springboot/JsonPathLanguageConfiguration.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/platforms/spring-boot/components-starter/camel-jsonpath-starter/src/main/java/org/apache/camel/jsonpath/springboot/JsonPathLanguageConfiguration.java b/platforms/spring-boot/components-starter/camel-jsonpath-starter/src/main/java/org/apache/camel/jsonpath/springboot/JsonPathLanguageConfiguration.java index 848cd466f41ed..ba22f22c8d4ba 100644 --- a/platforms/spring-boot/components-starter/camel-jsonpath-starter/src/main/java/org/apache/camel/jsonpath/springboot/JsonPathLanguageConfiguration.java +++ b/platforms/spring-boot/components-starter/camel-jsonpath-starter/src/main/java/org/apache/camel/jsonpath/springboot/JsonPathLanguageConfiguration.java @@ -40,7 +40,7 @@ public class JsonPathLanguageConfiguration */ private Boolean allowSimple = true; /** - * Whether to allow using the easy predicate parse to pre-parse predicates. + * Whether to allow using the easy predicate parser to pre-parse predicates. */ private Boolean allowEasyPredicate = true; /** From 453e63d42b5b5709a78b3a0295d48abeec5ba3cc Mon Sep 17 00:00:00 2001 From: lburgazzoli Date: Wed, 30 May 2018 10:12:04 +0200 Subject: [PATCH 20/20] service registry: fix findings --- .../impl/cloud/ServiceDefinitionHelper.java | 23 ------------------- .../cloud/ServiceRegistrationRoutePolicy.java | 2 +- .../camel/impl/cloud/ServiceRegistryTest.java | 2 +- .../src/main/docs/service-component.adoc | 13 ++++------- .../component/service/ServiceEndpoint.java | 7 ++++-- 5 files changed, 11 insertions(+), 36 deletions(-) delete mode 100644 camel-core/src/main/java/org/apache/camel/impl/cloud/ServiceDefinitionHelper.java diff --git a/camel-core/src/main/java/org/apache/camel/impl/cloud/ServiceDefinitionHelper.java b/camel-core/src/main/java/org/apache/camel/impl/cloud/ServiceDefinitionHelper.java deleted file mode 100644 index 6bc252da01e93..0000000000000 --- a/camel-core/src/main/java/org/apache/camel/impl/cloud/ServiceDefinitionHelper.java +++ /dev/null @@ -1,23 +0,0 @@ -/** - * 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.camel.impl.cloud; - -public final class ServiceDefinitionHelper { - - private ServiceDefinitionHelper() { - } -} diff --git a/camel-core/src/main/java/org/apache/camel/impl/cloud/ServiceRegistrationRoutePolicy.java b/camel-core/src/main/java/org/apache/camel/impl/cloud/ServiceRegistrationRoutePolicy.java index e0eb073f55469..4b641db520732 100644 --- a/camel-core/src/main/java/org/apache/camel/impl/cloud/ServiceRegistrationRoutePolicy.java +++ b/camel-core/src/main/java/org/apache/camel/impl/cloud/ServiceRegistrationRoutePolicy.java @@ -33,7 +33,7 @@ import org.slf4j.Logger; import org.slf4j.LoggerFactory; -@ManagedResource(description = "Clustered Route policy using") +@ManagedResource(description = "Service Registration Route policy") public class ServiceRegistrationRoutePolicy extends RoutePolicySupport implements CamelContextAware { private static final Logger LOGGER = LoggerFactory.getLogger(ServiceRegistrationRoutePolicy.class); diff --git a/camel-core/src/test/java/org/apache/camel/impl/cloud/ServiceRegistryTest.java b/camel-core/src/test/java/org/apache/camel/impl/cloud/ServiceRegistryTest.java index 914f839ace1ad..9f7fbb15f6318 100644 --- a/camel-core/src/test/java/org/apache/camel/impl/cloud/ServiceRegistryTest.java +++ b/camel-core/src/test/java/org/apache/camel/impl/cloud/ServiceRegistryTest.java @@ -30,7 +30,7 @@ import static org.assertj.core.api.Assertions.assertThat; -public class ServiceRegistryTest extends ContextTestSupport { +public class ServiceRegistryTest extends ContextTestSupport { // ********************* // Set up diff --git a/components/camel-service/src/main/docs/service-component.adoc b/components/camel-service/src/main/docs/service-component.adoc index b1dece8a97909..afa4b3171355c 100644 --- a/components/camel-service/src/main/docs/service-component.adoc +++ b/components/camel-service/src/main/docs/service-component.adoc @@ -1,20 +1,17 @@ [[service-component]] == Service Component -== Master Component -*Available as of Camel version 2.22* - *Available as of Camel version 2.22* -### Using the master endpoint +### Using the service endpoint ### URI format [source] ---- -service:endpoint[?options] +service:serviceName:endpoint[?options] ---- @@ -72,10 +69,8 @@ with the following path and query parameters: Camel provide the following ServiceRegistry implementations: - camel-consul - -Camel provide the following ServiceCall implementations: - -- camel-consul +- camel-zookeeper +- camel-spring-cloud ### See Also diff --git a/components/camel-service/src/main/java/org/apache/camel/component/service/ServiceEndpoint.java b/components/camel-service/src/main/java/org/apache/camel/component/service/ServiceEndpoint.java index d16f4483ec309..bd6b52ec99802 100644 --- a/components/camel-service/src/main/java/org/apache/camel/component/service/ServiceEndpoint.java +++ b/components/camel-service/src/main/java/org/apache/camel/component/service/ServiceEndpoint.java @@ -74,13 +74,16 @@ public ServiceEndpoint(String uri, ServiceComponent component, ServiceRegistry s this.serviceDefinition = computeServiceDefinition(component.getCamelContext(), delegateEndpoint); } - @ManagedAttribute(description = "The consumer endpoint to expose as a service", mask = true) @Override public Endpoint getEndpoint() { return this.delegateEndpoint; } - @ManagedAttribute(description = "The service definition", mask = true) + @ManagedAttribute(description = "The consumer endpoint to expose as a service", mask = true) + public String getDelegateEndpointUri() { + return this.delegateEndpoint.getEndpointUri(); + } + public ServiceDefinition getServiceDefinition() { return this.serviceDefinition; }