From cd2c68081f210c1f3407f871164dce9779d70032 Mon Sep 17 00:00:00 2001 From: Avgustin Marinov Date: Mon, 30 Jun 2025 15:50:30 +0300 Subject: [PATCH] Refactor RabbitMQ configuration (#2519) Signed-off-by: Avgustin Marinov --- .../workflows/reusable_workflow_verify.yaml | 6 + .github/workflows/verify-hibernate.yaml | 2 +- .github/workflows/verify.yaml | 2 +- .../src/main/resources/application.properties | 13 +- .../hawkbit/app/ddi/AbstractSecurityTest.java | 4 +- .../hawkbit/app/ddi/AllowedHostNamesTest.java | 8 +- .../hawkbit/amqp/AmqpConfiguration.java | 324 ------------------ .../eclipse/hawkbit/amqp/AmqpProperties.java | 5 - .../hawkbit/amqp/DmfApiConfiguration.java | 317 ++++++++++++++++- .../src/main/resources/application.properties | 14 +- .../src/main/resources/application.properties | 13 +- .../app/mgmt/AbstractSecurityTest.java | 4 +- .../app/mgmt/AllowedHostNamesTest.java | 6 +- .../eclipse/hawkbit/app/mgmt/CorsTest.java | 9 +- .../hawkbit-update-server/README.md | 6 - .../src/main/resources/application.properties | 14 +- .../hawkbit/app/AbstractSecurityTest.java | 4 +- .../hawkbit/app/AllowedHostNamesTest.java | 8 +- .../org/eclipse/hawkbit/app/CorsTest.java | 9 +- .../hawkbit-eventbus-defaults.properties | 2 +- .../src/main/resources/application.properties | 11 +- site/content/guides/runhawkbit.md | 20 +- site/pom.xml | 7 +- .../src/main/resources/application.properties | 3 - 24 files changed, 373 insertions(+), 438 deletions(-) delete mode 100644 hawkbit-dmf/hawkbit-dmf-amqp/src/main/java/org/eclipse/hawkbit/amqp/AmqpConfiguration.java diff --git a/.github/workflows/reusable_workflow_verify.yaml b/.github/workflows/reusable_workflow_verify.yaml index 6b454fa66..4c4d0cef1 100644 --- a/.github/workflows/reusable_workflow_verify.yaml +++ b/.github/workflows/reusable_workflow_verify.yaml @@ -32,6 +32,12 @@ jobs: - 5672:5672 steps: + - name: Parameters + run: | + echo "Repository: ${{ inputs.repository }}," + echo "Ref: ${{ inputs.ref }}," + echo "Maven Properties: ${{ inputs.maven_properties }}" + - uses: actions/checkout@v4 with: repository: ${{ inputs.repository }} diff --git a/.github/workflows/verify-hibernate.yaml b/.github/workflows/verify-hibernate.yaml index 107ba1c54..200a1da86 100644 --- a/.github/workflows/verify-hibernate.yaml +++ b/.github/workflows/verify-hibernate.yaml @@ -22,6 +22,6 @@ jobs: verify-hibernate: uses: ./.github/workflows/reusable_workflow_verify.yaml with: - repository: ${{ github.event_name == 'pull_request' && github.event.pull_request.head.repository || github.repositor }} + repository: ${{ github.event_name == 'pull_request' && github.event.pull_request.head.repo.full_name || github.repository }} ref: ${{ github.event_name == 'pull_request' && github.event.pull_request.head.ref || github.ref }} maven_properties: '-Djpa.vendor=hibernate -Dlogging.level.org.hibernate.collection.spi.AbstractPersistentCollection=ERROR' \ No newline at end of file diff --git a/.github/workflows/verify.yaml b/.github/workflows/verify.yaml index abdaa56a6..6fa5cfca2 100644 --- a/.github/workflows/verify.yaml +++ b/.github/workflows/verify.yaml @@ -22,5 +22,5 @@ jobs: verify: uses: ./.github/workflows/reusable_workflow_verify.yaml with: - repository: ${{ github.event_name == 'pull_request' && github.event.pull_request.head.repository || github.repositor }} + repository: ${{ github.event_name == 'pull_request' && github.event.pull_request.head.repo.full_name || github.repository }} ref: ${{ github.event_name == 'pull_request' && github.event.pull_request.head.ref || github.ref }} \ No newline at end of file diff --git a/hawkbit-ddi/hawkbit-ddi-server/src/main/resources/application.properties b/hawkbit-ddi/hawkbit-ddi-server/src/main/resources/application.properties index bb50ac518..a26f83e3f 100644 --- a/hawkbit-ddi/hawkbit-ddi-server/src/main/resources/application.properties +++ b/hawkbit-ddi/hawkbit-ddi-server/src/main/resources/application.properties @@ -38,13 +38,6 @@ hawkbit.server.ddi.security.authentication.gatewaytoken.enabled=false # Optional events hawkbit.server.repository.publish-target-poll-event=false -## Configuration for DMF/RabbitMQ integration -spring.rabbitmq.username=guest -spring.rabbitmq.password=guest -spring.rabbitmq.virtual-host=/ -spring.rabbitmq.host=localhost -spring.rabbitmq.port=5672 - # Enable CORS and specify the allowed origins: #hawkbit.server.security.cors.enabled=true #hawkbit.server.security.cors.allowedOrigins=http://localhost @@ -57,11 +50,7 @@ hawkbit.lock=inMemory # Disable discovery client of spring-cloud-commons spring.cloud.discovery.enabled=false -# Enable communication between services -spring.cloud.bus.enabled=true -spring.cloud.bus.ack.enabled=false -spring.cloud.bus.refresh.enabled=false -spring.cloud.bus.env.enabled=false +# Configure communication between services endpoints.spring.cloud.bus.refresh.enabled=false endpoints.spring.cloud.bus.env.enabled=false spring.cloud.stream.bindings.springCloudBusInput.group=ddi-server diff --git a/hawkbit-ddi/hawkbit-ddi-server/src/test/java/org/eclipse/hawkbit/app/ddi/AbstractSecurityTest.java b/hawkbit-ddi/hawkbit-ddi-server/src/test/java/org/eclipse/hawkbit/app/ddi/AbstractSecurityTest.java index fb25b9592..2553cf36c 100644 --- a/hawkbit-ddi/hawkbit-ddi-server/src/test/java/org/eclipse/hawkbit/app/ddi/AbstractSecurityTest.java +++ b/hawkbit-ddi/hawkbit-ddi-server/src/test/java/org/eclipse/hawkbit/app/ddi/AbstractSecurityTest.java @@ -19,9 +19,9 @@ import org.springframework.test.web.servlet.MockMvc; import org.springframework.test.web.servlet.setup.MockMvcBuilders; import org.springframework.web.context.WebApplicationContext; -@SpringBootTest(properties = { "hawkbit.dmf.rabbitmq.enabled=false" }) +@SpringBootTest @ExtendWith(SharedSqlTestDatabaseExtension.class) -public abstract class AbstractSecurityTest { +abstract class AbstractSecurityTest { protected MockMvc mvc; @Autowired diff --git a/hawkbit-ddi/hawkbit-ddi-server/src/test/java/org/eclipse/hawkbit/app/ddi/AllowedHostNamesTest.java b/hawkbit-ddi/hawkbit-ddi-server/src/test/java/org/eclipse/hawkbit/app/ddi/AllowedHostNamesTest.java index 7a93f7e66..fafe4155c 100644 --- a/hawkbit-ddi/hawkbit-ddi-server/src/test/java/org/eclipse/hawkbit/app/ddi/AllowedHostNamesTest.java +++ b/hawkbit-ddi/hawkbit-ddi-server/src/test/java/org/eclipse/hawkbit/app/ddi/AllowedHostNamesTest.java @@ -16,14 +16,14 @@ import org.junit.jupiter.api.Test; import org.springframework.http.HttpHeaders; import org.springframework.test.context.TestPropertySource; -@TestPropertySource(properties = { - "spring.flyway.enabled=true", // if hibernate is used there could be db inconsistencies when executing tests with and without flyway - "hawkbit.server.security.allowedHostNames=localhost", - "hawkbit.server.security.httpFirewallIgnoredPaths=/index.html" }) /** * Feature: Integration Test - Security
* Story: Allowed Host Names */ +@TestPropertySource(properties = { + "spring.flyway.enabled=true", // if hibernate is used there could be db inconsistencies when executing tests with and without flyway + "hawkbit.server.security.allowedHostNames=localhost", + "hawkbit.server.security.httpFirewallIgnoredPaths=/index.html" }) class AllowedHostNamesTest extends AbstractSecurityTest { /** diff --git a/hawkbit-dmf/hawkbit-dmf-amqp/src/main/java/org/eclipse/hawkbit/amqp/AmqpConfiguration.java b/hawkbit-dmf/hawkbit-dmf-amqp/src/main/java/org/eclipse/hawkbit/amqp/AmqpConfiguration.java deleted file mode 100644 index df528940d..000000000 --- a/hawkbit-dmf/hawkbit-dmf-amqp/src/main/java/org/eclipse/hawkbit/amqp/AmqpConfiguration.java +++ /dev/null @@ -1,324 +0,0 @@ -/** - * Copyright (c) 2015 Bosch Software Innovations GmbH and others - * - * This program and the accompanying materials are made - * available under the terms of the Eclipse Public License 2.0 - * which is available at https://www.eclipse.org/legal/epl-2.0/ - * - * SPDX-License-Identifier: EPL-2.0 - */ -package org.eclipse.hawkbit.amqp; - -import java.sql.SQLException; -import java.time.Duration; -import java.util.HashMap; -import java.util.List; -import java.util.Map; -import java.util.regex.Pattern; - -import lombok.ToString; -import lombok.extern.slf4j.Slf4j; -import org.eclipse.hawkbit.artifact.repository.urlhandler.ArtifactUrlHandler; -import org.eclipse.hawkbit.dmf.amqp.api.AmqpSettings; -import org.eclipse.hawkbit.repository.ConfirmationManagement; -import org.eclipse.hawkbit.repository.ControllerManagement; -import org.eclipse.hawkbit.repository.DeploymentManagement; -import org.eclipse.hawkbit.repository.DistributionSetManagement; -import org.eclipse.hawkbit.repository.EntityFactory; -import org.eclipse.hawkbit.repository.SoftwareModuleManagement; -import org.eclipse.hawkbit.repository.SystemManagement; -import org.eclipse.hawkbit.repository.TargetManagement; -import org.eclipse.hawkbit.repository.TenantConfigurationManagement; -import org.eclipse.hawkbit.security.SystemSecurityContext; -import org.springframework.amqp.core.Binding; -import org.springframework.amqp.core.BindingBuilder; -import org.springframework.amqp.core.FanoutExchange; -import org.springframework.amqp.core.Queue; -import org.springframework.amqp.core.QueueBuilder; -import org.springframework.amqp.rabbit.connection.ConnectionFactory; -import org.springframework.amqp.rabbit.core.RabbitAdmin; -import org.springframework.amqp.rabbit.core.RabbitTemplate; -import org.springframework.amqp.rabbit.listener.ConditionalRejectingErrorHandler; -import org.springframework.amqp.rabbit.listener.FatalExceptionStrategy; -import org.springframework.amqp.rabbit.listener.RabbitListenerContainerFactory; -import org.springframework.amqp.rabbit.listener.SimpleMessageListenerContainer; -import org.springframework.amqp.support.converter.Jackson2JsonMessageConverter; -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.beans.factory.annotation.Value; -import org.springframework.boot.autoconfigure.amqp.SimpleRabbitListenerContainerFactoryConfigurer; -import org.springframework.boot.autoconfigure.condition.ConditionalOnExpression; -import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean; -import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; -import org.springframework.boot.context.properties.EnableConfigurationProperties; -import org.springframework.cloud.bus.ServiceMatcher; -import org.springframework.context.annotation.Bean; -import org.springframework.context.annotation.PropertySource; -import org.springframework.retry.backoff.ExponentialBackOffPolicy; -import org.springframework.retry.support.RetryTemplate; -import org.springframework.util.ErrorHandler; - -/** - * Spring configuration for AMQP based DMF communication for indirect device integration. - */ -@Slf4j -@EnableConfigurationProperties({ AmqpProperties.class, AmqpDeadletterProperties.class }) -@ConditionalOnProperty(prefix = "hawkbit.dmf.rabbitmq", name = "enabled", matchIfMissing = true) -@PropertySource("classpath:/hawkbit-dmf-defaults.properties") -public class AmqpConfiguration { - - private final AmqpProperties amqpProperties; - private final AmqpDeadletterProperties amqpDeadletterProperties; - private final ConnectionFactory rabbitConnectionFactory; - private ServiceMatcher serviceMatcher; - - public AmqpConfiguration(final AmqpProperties amqpProperties, final AmqpDeadletterProperties amqpDeadletterProperties, - final ConnectionFactory rabbitConnectionFactory) { - this.amqpProperties = amqpProperties; - this.amqpDeadletterProperties = amqpDeadletterProperties; - this.rabbitConnectionFactory = rabbitConnectionFactory; - } - - @Autowired(required = false) // spring setter injection - public void setServiceMatcher(final ServiceMatcher serviceMatcher) { - this.serviceMatcher = serviceMatcher; - } - - @Bean - public FatalExceptionStrategy sqlFatalSQLExceptionStrategy(final AmqpProperties amqpProperties) { - return new SqlFatalExceptionStrategy(amqpProperties.getFatalSqlExceptionPolicy()); - } - - /** - * Creates a custom error handler bean. - * - * @param fatalExceptionStrategies list of {@link FatalExceptionStrategy} handlers. isFatal will be called for causes, - * up to the first fatal, so the implementation don't need to iterate over the causes. - * @return the delegating error handler bean - */ - @Bean - @ConditionalOnMissingBean - public ErrorHandler errorHandler( - final List fatalExceptionStrategies, - @Value("${hawkbit.dmf.rabbitmq.fatal-exception-types:}") final List fatalExceptionTypes) { - return new ConditionalRejectingErrorHandler(new RequeueExceptionStrategy(fatalExceptionStrategies, fatalExceptionTypes)); - } - - /** - * Create a {@link RabbitAdmin} and ignore declaration exceptions. - * {@link RabbitAdmin#setIgnoreDeclarationExceptions(boolean)} - * - * @return the bean - */ - @Bean - public RabbitAdmin rabbitAdmin() { - final RabbitAdmin rabbitAdmin = new RabbitAdmin(rabbitConnectionFactory); - rabbitAdmin.setIgnoreDeclarationExceptions(true); - return rabbitAdmin; - } - - /** - * @return {@link RabbitTemplate} with automatic retry, published confirms and {@link Jackson2JsonMessageConverter}. - */ - @Bean - public RabbitTemplate rabbitTemplate() { - final RabbitTemplate rabbitTemplate = new RabbitTemplate(rabbitConnectionFactory); - rabbitTemplate.setMessageConverter(new Jackson2JsonMessageConverter()); - - final RetryTemplate retryTemplate = new RetryTemplate(); - retryTemplate.setBackOffPolicy(new ExponentialBackOffPolicy()); - rabbitTemplate.setRetryTemplate(retryTemplate); - - rabbitTemplate.setConfirmCallback((correlationData, ack, cause) -> { - if (ack) { - log.debug("Message with {} confirmed by broker.", correlationData); - } else { - log.error("Broker is unable to handle message with {} : {}", correlationData, cause); - } - }); - - return rabbitTemplate; - } - - /** - * Create the DMF API receiver queue for retrieving DMF messages. - * - * @return the receiver queue - */ - @Bean - public Queue dmfReceiverQueue() { - return new Queue( - amqpProperties.getReceiverQueue(), - true, false, false, - amqpDeadletterProperties.getDeadLetterExchangeArgs(amqpProperties.getDeadLetterExchange())); - } - - /** - * Create the DMF API receiver queue for authentication requests called by 3rd - * party artifact storages for download authorization by devices. - * - * @return the receiver queue - */ - @Bean - public Queue authenticationReceiverQueue() { - return QueueBuilder.nonDurable(amqpProperties.getAuthenticationReceiverQueue()) - .autoDelete() - .withArguments(getTTLMaxArgsAuthenticationQueue()) - .build(); - } - - /** - * Create DMF exchange. - * - * @return the fanout exchange - */ - @Bean - public FanoutExchange dmfSenderExchange() { - return new FanoutExchange(AmqpSettings.DMF_EXCHANGE); - } - - /** - * Create the Binding {@link AmqpConfiguration#dmfReceiverQueue()} to - * {@link AmqpConfiguration#dmfSenderExchange()}. - * - * @return the binding and create the queue and exchange - */ - @Bean - public Binding bindDmfSenderExchangeToDmfQueue() { - return BindingBuilder.bind(dmfReceiverQueue()).to(dmfSenderExchange()); - } - - /** - * Create dead letter queue. - * - * @return the queue - */ - @Bean - public Queue deadLetterQueue() { - return amqpDeadletterProperties.createDeadletterQueue(amqpProperties.getDeadLetterQueue()); - } - - /** - * Create the dead letter fanout exchange. - * - * @return the fanout exchange - */ - @Bean - public FanoutExchange deadLetterExchange() { - return new FanoutExchange(amqpProperties.getDeadLetterExchange()); - } - - /** - * Create the Binding deadLetterQueue to deadLetterExchange. - * - * @return the binding - */ - @Bean - public Binding bindDeadLetterQueueToDeadLetterExchange() { - return BindingBuilder.bind(deadLetterQueue()).to(deadLetterExchange()); - } - - /** - * Create AMQP handler service bean. - * - * @param rabbitTemplate for converting messages - * @param amqpMessageDispatcherService to sending events to DMF client - * @param controllerManagement for target repo access - * @param entityFactory to create entities - * @return handler service bean - */ - @Bean - @ConditionalOnMissingBean - public AmqpMessageHandlerService amqpMessageHandlerService( - final RabbitTemplate rabbitTemplate, - final AmqpMessageDispatcherService amqpMessageDispatcherService, - final ControllerManagement controllerManagement, final EntityFactory entityFactory, - final SystemSecurityContext systemSecurityContext, - final TenantConfigurationManagement tenantConfigurationManagement, - final ConfirmationManagement confirmationManagement) { - return new AmqpMessageHandlerService( - rabbitTemplate, amqpMessageDispatcherService, controllerManagement, - entityFactory, systemSecurityContext, tenantConfigurationManagement, confirmationManagement); - } - - /** - * Create default amqp sender service bean. - * - * @return the default amqp sender service bean - */ - @Bean - @ConditionalOnMissingBean - public AmqpMessageSenderService amqpSenderServiceBean() { - return new DefaultAmqpMessageSenderService(rabbitTemplate()); - } - - /** - * Create RabbitListenerContainerFactory bean if no listenerContainerFactory bean found - * - * @return RabbitListenerContainerFactory bean - */ - @Bean - @ConditionalOnMissingBean(name = "listenerContainerFactory") - public RabbitListenerContainerFactory listenerContainerFactory( - final SimpleRabbitListenerContainerFactoryConfigurer configurer, final ErrorHandler errorHandler) { - final ConfigurableRabbitListenerContainerFactory factory = new ConfigurableRabbitListenerContainerFactory( - amqpProperties.isMissingQueuesFatal(), amqpProperties.getDeclarationRetries(), errorHandler); - configurer.configure(factory, rabbitConnectionFactory); - return factory; - } - - @Bean - @ConditionalOnMissingBean(AmqpMessageDispatcherService.class) - AmqpMessageDispatcherService amqpMessageDispatcherService( - final RabbitTemplate rabbitTemplate, - final AmqpMessageSenderService amqpSenderService, final ArtifactUrlHandler artifactUrlHandler, - final SystemSecurityContext systemSecurityContext, final SystemManagement systemManagement, - final TargetManagement targetManagement, final DistributionSetManagement distributionSetManagement, - final SoftwareModuleManagement softwareModuleManagement, final DeploymentManagement deploymentManagement, - final TenantConfigurationManagement tenantConfigurationManagement) { - return new AmqpMessageDispatcherService(rabbitTemplate, amqpSenderService, artifactUrlHandler, - systemSecurityContext, systemManagement, targetManagement, serviceMatcher, distributionSetManagement, - softwareModuleManagement, deploymentManagement, tenantConfigurationManagement); - } - - private static Map getTTLMaxArgsAuthenticationQueue() { - final Map args = new HashMap<>(2); - args.put("x-message-ttl", Duration.ofSeconds(30).toMillis()); - args.put("x-max-length", 1_000); - return args; - } - - @ToString - private static class SqlFatalExceptionStrategy implements FatalExceptionStrategy { - - private final boolean fatalByDefault; - private final List unlessErrorCodeIn; - private final List unlessSqlStateIn; - private final List unlessMessageMatches; - - public SqlFatalExceptionStrategy(final AmqpProperties.FatalSqlExceptionPolicy fatalSqlExceptions) { - this.fatalByDefault = fatalSqlExceptions.isByDefault(); - this.unlessErrorCodeIn = fatalSqlExceptions.getUnlessErrorCodeIn(); - this.unlessSqlStateIn = fatalSqlExceptions.getUnlessSqlStateIn(); - this.unlessMessageMatches = fatalSqlExceptions.getUnlessMessageMatches(); - } - - @Override - public boolean isFatal(final Throwable t) { - if (t instanceof SQLException sqlException) { - if (unlessErrorCodeIn.contains(sqlException.getErrorCode())) { - return !fatalByDefault; - } else if (unlessSqlStateIn.contains(sqlException.getSQLState())) { - return !fatalByDefault; - } else { - for (final Pattern pattern : unlessMessageMatches) { - if (pattern.matcher(sqlException.getMessage()).matches()) { - return !fatalByDefault; - } - } - return fatalByDefault; - } - } - return false; - } - } -} \ No newline at end of file diff --git a/hawkbit-dmf/hawkbit-dmf-amqp/src/main/java/org/eclipse/hawkbit/amqp/AmqpProperties.java b/hawkbit-dmf/hawkbit-dmf-amqp/src/main/java/org/eclipse/hawkbit/amqp/AmqpProperties.java index cf6d0e98d..bd5bec24b 100644 --- a/hawkbit-dmf/hawkbit-dmf-amqp/src/main/java/org/eclipse/hawkbit/amqp/AmqpProperties.java +++ b/hawkbit-dmf/hawkbit-dmf-amqp/src/main/java/org/eclipse/hawkbit/amqp/AmqpProperties.java @@ -25,11 +25,6 @@ public class AmqpProperties { private static final int DEFAULT_QUEUE_DECLARATION_RETRIES = 50; - /** - * Enable DMF API based on AMQP 0.9 - */ - private boolean enabled = true; - /** * DMF API dead letter queue. */ diff --git a/hawkbit-dmf/hawkbit-dmf-amqp/src/main/java/org/eclipse/hawkbit/amqp/DmfApiConfiguration.java b/hawkbit-dmf/hawkbit-dmf-amqp/src/main/java/org/eclipse/hawkbit/amqp/DmfApiConfiguration.java index 4648e8fbc..05df8ee6b 100644 --- a/hawkbit-dmf/hawkbit-dmf-amqp/src/main/java/org/eclipse/hawkbit/amqp/DmfApiConfiguration.java +++ b/hawkbit-dmf/hawkbit-dmf-amqp/src/main/java/org/eclipse/hawkbit/amqp/DmfApiConfiguration.java @@ -9,14 +9,319 @@ */ package org.eclipse.hawkbit.amqp; +import java.sql.SQLException; +import java.time.Duration; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.regex.Pattern; + +import lombok.ToString; +import lombok.extern.slf4j.Slf4j; +import org.eclipse.hawkbit.artifact.repository.urlhandler.ArtifactUrlHandler; +import org.eclipse.hawkbit.dmf.amqp.api.AmqpSettings; +import org.eclipse.hawkbit.repository.ConfirmationManagement; +import org.eclipse.hawkbit.repository.ControllerManagement; +import org.eclipse.hawkbit.repository.DeploymentManagement; +import org.eclipse.hawkbit.repository.DistributionSetManagement; +import org.eclipse.hawkbit.repository.EntityFactory; +import org.eclipse.hawkbit.repository.SoftwareModuleManagement; +import org.eclipse.hawkbit.repository.SystemManagement; +import org.eclipse.hawkbit.repository.TargetManagement; +import org.eclipse.hawkbit.repository.TenantConfigurationManagement; +import org.eclipse.hawkbit.security.SystemSecurityContext; +import org.springframework.amqp.core.Binding; +import org.springframework.amqp.core.BindingBuilder; +import org.springframework.amqp.core.FanoutExchange; +import org.springframework.amqp.core.Queue; +import org.springframework.amqp.core.QueueBuilder; +import org.springframework.amqp.rabbit.connection.ConnectionFactory; +import org.springframework.amqp.rabbit.core.RabbitAdmin; +import org.springframework.amqp.rabbit.core.RabbitTemplate; +import org.springframework.amqp.rabbit.listener.ConditionalRejectingErrorHandler; +import org.springframework.amqp.rabbit.listener.FatalExceptionStrategy; +import org.springframework.amqp.rabbit.listener.RabbitListenerContainerFactory; +import org.springframework.amqp.rabbit.listener.SimpleMessageListenerContainer; +import org.springframework.amqp.support.converter.Jackson2JsonMessageConverter; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.boot.autoconfigure.amqp.SimpleRabbitListenerContainerFactoryConfigurer; +import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean; +import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; +import org.springframework.boot.context.properties.EnableConfigurationProperties; +import org.springframework.cloud.bus.ServiceMatcher; +import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.ComponentScan; -import org.springframework.context.annotation.Configuration; -import org.springframework.context.annotation.Import; +import org.springframework.context.annotation.PropertySource; +import org.springframework.retry.backoff.ExponentialBackOffPolicy; +import org.springframework.retry.support.RetryTemplate; +import org.springframework.util.ErrorHandler; /** - * Enable Device Management Federation API. + * Spring configuration for AMQP based DMF communication for indirect device integration. */ -@Configuration +@Slf4j @ComponentScan -@Import(AmqpConfiguration.class) -public class DmfApiConfiguration {} \ No newline at end of file +@EnableConfigurationProperties({ AmqpProperties.class, AmqpDeadletterProperties.class }) +@ConditionalOnProperty(prefix = "hawkbit.dmf", name = "enabled", matchIfMissing = true) +@PropertySource("classpath:/hawkbit-dmf-defaults.properties") +public class DmfApiConfiguration { + + private final AmqpProperties amqpProperties; + private final AmqpDeadletterProperties amqpDeadletterProperties; + private final ConnectionFactory rabbitConnectionFactory; + + private ServiceMatcher serviceMatcher; + + public DmfApiConfiguration( + final AmqpProperties amqpProperties, final AmqpDeadletterProperties amqpDeadletterProperties, + final ConnectionFactory rabbitConnectionFactory) { + this.amqpProperties = amqpProperties; + this.amqpDeadletterProperties = amqpDeadletterProperties; + this.rabbitConnectionFactory = rabbitConnectionFactory; + } + + @Autowired(required = false) // spring setter injection + public void setServiceMatcher(final ServiceMatcher serviceMatcher) { + this.serviceMatcher = serviceMatcher; + } + + @Bean + public FatalExceptionStrategy sqlFatalSQLExceptionStrategy(final AmqpProperties amqpProperties) { + return new SqlFatalExceptionStrategy(amqpProperties.getFatalSqlExceptionPolicy()); + } + + /** + * Creates a custom error handler bean. + * + * @param fatalExceptionStrategies list of {@link FatalExceptionStrategy} handlers. isFatal will be called for causes, + * up to the first fatal, so the implementation don't need to iterate over the causes. + * @return the delegating error handler bean + */ + @Bean + @ConditionalOnMissingBean + public ErrorHandler errorHandler( + final List fatalExceptionStrategies, + @Value("${hawkbit.dmf.rabbitmq.fatal-exception-types:}") final List fatalExceptionTypes) { + return new ConditionalRejectingErrorHandler(new RequeueExceptionStrategy(fatalExceptionStrategies, fatalExceptionTypes)); + } + + /** + * Create a {@link RabbitAdmin} and ignore declaration exceptions. + * {@link RabbitAdmin#setIgnoreDeclarationExceptions(boolean)} + * + * @return the bean + */ + @Bean + public RabbitAdmin rabbitAdmin() { + final RabbitAdmin rabbitAdmin = new RabbitAdmin(rabbitConnectionFactory); + rabbitAdmin.setIgnoreDeclarationExceptions(true); + return rabbitAdmin; + } + + /** + * @return {@link RabbitTemplate} with automatic retry, published confirms and {@link Jackson2JsonMessageConverter}. + */ + @Bean + public RabbitTemplate rabbitTemplate() { + final RabbitTemplate rabbitTemplate = new RabbitTemplate(rabbitConnectionFactory); + rabbitTemplate.setMessageConverter(new Jackson2JsonMessageConverter()); + + final RetryTemplate retryTemplate = new RetryTemplate(); + retryTemplate.setBackOffPolicy(new ExponentialBackOffPolicy()); + rabbitTemplate.setRetryTemplate(retryTemplate); + + rabbitTemplate.setConfirmCallback((correlationData, ack, cause) -> { + if (ack) { + log.debug("Message with {} confirmed by broker.", correlationData); + } else { + log.error("Broker is unable to handle message with {} : {}", correlationData, cause); + } + }); + + return rabbitTemplate; + } + + /** + * Create the DMF API receiver queue for retrieving DMF messages. + * + * @return the receiver queue + */ + @Bean + public Queue dmfReceiverQueue() { + return new Queue( + amqpProperties.getReceiverQueue(), + true, false, false, + amqpDeadletterProperties.getDeadLetterExchangeArgs(amqpProperties.getDeadLetterExchange())); + } + + /** + * Create the DMF API receiver queue for authentication requests called by 3rd + * party artifact storages for download authorization by devices. + * + * @return the receiver queue + */ + @Bean + public Queue authenticationReceiverQueue() { + return QueueBuilder.nonDurable(amqpProperties.getAuthenticationReceiverQueue()) + .autoDelete() + .withArguments(getTTLMaxArgsAuthenticationQueue()) + .build(); + } + + /** + * Create DMF exchange. + * + * @return the fanout exchange + */ + @Bean + public FanoutExchange dmfSenderExchange() { + return new FanoutExchange(AmqpSettings.DMF_EXCHANGE); + } + + /** + * Create the Binding {@link DmfApiConfiguration#dmfReceiverQueue()} to + * {@link DmfApiConfiguration#dmfSenderExchange()}. + * + * @return the binding and create the queue and exchange + */ + @Bean + public Binding bindDmfSenderExchangeToDmfQueue() { + return BindingBuilder.bind(dmfReceiverQueue()).to(dmfSenderExchange()); + } + + /** + * Create dead letter queue. + * + * @return the queue + */ + @Bean + public Queue deadLetterQueue() { + return amqpDeadletterProperties.createDeadletterQueue(amqpProperties.getDeadLetterQueue()); + } + + /** + * Create the dead letter fanout exchange. + * + * @return the fanout exchange + */ + @Bean + public FanoutExchange deadLetterExchange() { + return new FanoutExchange(amqpProperties.getDeadLetterExchange()); + } + + /** + * Create the Binding deadLetterQueue to deadLetterExchange. + * + * @return the binding + */ + @Bean + public Binding bindDeadLetterQueueToDeadLetterExchange() { + return BindingBuilder.bind(deadLetterQueue()).to(deadLetterExchange()); + } + + /** + * Create AMQP handler service bean. + * + * @param rabbitTemplate for converting messages + * @param amqpMessageDispatcherService to sending events to DMF client + * @param controllerManagement for target repo access + * @param entityFactory to create entities + * @return handler service bean + */ + @Bean + @ConditionalOnMissingBean + public AmqpMessageHandlerService amqpMessageHandlerService( + final RabbitTemplate rabbitTemplate, + final AmqpMessageDispatcherService amqpMessageDispatcherService, + final ControllerManagement controllerManagement, final EntityFactory entityFactory, + final SystemSecurityContext systemSecurityContext, + final TenantConfigurationManagement tenantConfigurationManagement, + final ConfirmationManagement confirmationManagement) { + return new AmqpMessageHandlerService( + rabbitTemplate, amqpMessageDispatcherService, controllerManagement, + entityFactory, systemSecurityContext, tenantConfigurationManagement, confirmationManagement); + } + + /** + * Create default amqp sender service bean. + * + * @return the default amqp sender service bean + */ + @Bean + @ConditionalOnMissingBean + public AmqpMessageSenderService amqpSenderServiceBean() { + return new DefaultAmqpMessageSenderService(rabbitTemplate()); + } + + /** + * Create RabbitListenerContainerFactory bean if no listenerContainerFactory bean found + * + * @return RabbitListenerContainerFactory bean + */ + @Bean + @ConditionalOnMissingBean(name = "listenerContainerFactory") + public RabbitListenerContainerFactory listenerContainerFactory( + final SimpleRabbitListenerContainerFactoryConfigurer configurer, final ErrorHandler errorHandler) { + final ConfigurableRabbitListenerContainerFactory factory = new ConfigurableRabbitListenerContainerFactory( + amqpProperties.isMissingQueuesFatal(), amqpProperties.getDeclarationRetries(), errorHandler); + configurer.configure(factory, rabbitConnectionFactory); + return factory; + } + + @Bean + @ConditionalOnMissingBean(AmqpMessageDispatcherService.class) + AmqpMessageDispatcherService amqpMessageDispatcherService( + final RabbitTemplate rabbitTemplate, + final AmqpMessageSenderService amqpSenderService, final ArtifactUrlHandler artifactUrlHandler, + final SystemSecurityContext systemSecurityContext, final SystemManagement systemManagement, + final TargetManagement targetManagement, final DistributionSetManagement distributionSetManagement, + final SoftwareModuleManagement softwareModuleManagement, final DeploymentManagement deploymentManagement, + final TenantConfigurationManagement tenantConfigurationManagement) { + return new AmqpMessageDispatcherService(rabbitTemplate, amqpSenderService, artifactUrlHandler, + systemSecurityContext, systemManagement, targetManagement, serviceMatcher, distributionSetManagement, + softwareModuleManagement, deploymentManagement, tenantConfigurationManagement); + } + + private static Map getTTLMaxArgsAuthenticationQueue() { + final Map args = new HashMap<>(2); + args.put("x-message-ttl", Duration.ofSeconds(30).toMillis()); + args.put("x-max-length", 1_000); + return args; + } + + @ToString + private static class SqlFatalExceptionStrategy implements FatalExceptionStrategy { + + private final boolean fatalByDefault; + private final List unlessErrorCodeIn; + private final List unlessSqlStateIn; + private final List unlessMessageMatches; + + public SqlFatalExceptionStrategy(final AmqpProperties.FatalSqlExceptionPolicy fatalSqlExceptions) { + this.fatalByDefault = fatalSqlExceptions.isByDefault(); + this.unlessErrorCodeIn = fatalSqlExceptions.getUnlessErrorCodeIn(); + this.unlessSqlStateIn = fatalSqlExceptions.getUnlessSqlStateIn(); + this.unlessMessageMatches = fatalSqlExceptions.getUnlessMessageMatches(); + } + + @Override + public boolean isFatal(final Throwable t) { + if (t instanceof SQLException sqlException) { + if (unlessErrorCodeIn.contains(sqlException.getErrorCode())) { + return !fatalByDefault; + } else if (unlessSqlStateIn.contains(sqlException.getSQLState())) { + return !fatalByDefault; + } else { + for (final Pattern pattern : unlessMessageMatches) { + if (pattern.matcher(sqlException.getMessage()).matches()) { + return !fatalByDefault; + } + } + return fatalByDefault; + } + } + return false; + } + } +} \ No newline at end of file diff --git a/hawkbit-dmf/hawkbit-dmf-server/src/main/resources/application.properties b/hawkbit-dmf/hawkbit-dmf-server/src/main/resources/application.properties index ceb47e613..60e3d08ad 100644 --- a/hawkbit-dmf/hawkbit-dmf-server/src/main/resources/application.properties +++ b/hawkbit-dmf/hawkbit-dmf-server/src/main/resources/application.properties @@ -24,14 +24,6 @@ logging.pattern.console=%clr(%d{${logging.pattern.dateformat:yyyy-MM-dd'T'HH:mm: # Optional events hawkbit.server.repository.publish-target-poll-event=false -## Configuration for DMF/RabbitMQ integration -hawkbit.dmf.rabbitmq.enabled=true -spring.rabbitmq.username=guest -spring.rabbitmq.password=guest -spring.rabbitmq.virtual-host=/ -spring.rabbitmq.host=localhost -spring.rabbitmq.port=5672 - spring.main.web-application-type=none hawkbit.server.security.dos.filter.enabled=false @@ -43,11 +35,7 @@ hawkbit.lock=inMemory # Disable discovery client of spring-cloud-commons spring.cloud.discovery.enabled=false -# Enable communication between services -spring.cloud.bus.enabled=true -spring.cloud.bus.ack.enabled=false -spring.cloud.bus.refresh.enabled=false -spring.cloud.bus.env.enabled=false +# Configure communication between services endpoints.spring.cloud.bus.refresh.enabled=false endpoints.spring.cloud.bus.env.enabled=false spring.cloud.stream.bindings.springCloudBusInput.group=dmf-server diff --git a/hawkbit-mgmt/hawkbit-mgmt-server/src/main/resources/application.properties b/hawkbit-mgmt/hawkbit-mgmt-server/src/main/resources/application.properties index d71ed5ab3..5d3f86195 100644 --- a/hawkbit-mgmt/hawkbit-mgmt-server/src/main/resources/application.properties +++ b/hawkbit-mgmt/hawkbit-mgmt-server/src/main/resources/application.properties @@ -37,24 +37,13 @@ server.servlet.encoding.force=true # Optional events hawkbit.server.repository.publish-target-poll-event=false -## Configuration for DMF/RabbitMQ integration -spring.rabbitmq.username=guest -spring.rabbitmq.password=guest -spring.rabbitmq.virtual-host=/ -spring.rabbitmq.host=localhost -spring.rabbitmq.port=5672 - # Enable CORS and specify the allowed origins: #hawkbit.server.security.cors.enabled=true #hawkbit.server.security.cors.allowedOrigins=http://localhost # Disable discovery client of spring-cloud-commons spring.cloud.discovery.enabled=false -# Enable communication between services -spring.cloud.bus.enabled=true -spring.cloud.bus.ack.enabled=false -spring.cloud.bus.refresh.enabled=false -spring.cloud.bus.env.enabled=false +# Configure communication between services endpoints.spring.cloud.bus.refresh.enabled=false endpoints.spring.cloud.bus.env.enabled=false spring.cloud.stream.bindings.springCloudBusInput.group=mgmt-server diff --git a/hawkbit-mgmt/hawkbit-mgmt-server/src/test/java/org/eclipse/hawkbit/app/mgmt/AbstractSecurityTest.java b/hawkbit-mgmt/hawkbit-mgmt-server/src/test/java/org/eclipse/hawkbit/app/mgmt/AbstractSecurityTest.java index a2a52cbd8..04e3a7760 100644 --- a/hawkbit-mgmt/hawkbit-mgmt-server/src/test/java/org/eclipse/hawkbit/app/mgmt/AbstractSecurityTest.java +++ b/hawkbit-mgmt/hawkbit-mgmt-server/src/test/java/org/eclipse/hawkbit/app/mgmt/AbstractSecurityTest.java @@ -20,9 +20,9 @@ import org.springframework.test.web.servlet.setup.DefaultMockMvcBuilder; import org.springframework.test.web.servlet.setup.MockMvcBuilders; import org.springframework.web.context.WebApplicationContext; -@SpringBootTest(properties = { "hawkbit.dmf.rabbitmq.enabled=false" }) +@SpringBootTest @ExtendWith(SharedSqlTestDatabaseExtension.class) -public abstract class AbstractSecurityTest { +abstract class AbstractSecurityTest { protected MockMvc mvc; @Autowired diff --git a/hawkbit-mgmt/hawkbit-mgmt-server/src/test/java/org/eclipse/hawkbit/app/mgmt/AllowedHostNamesTest.java b/hawkbit-mgmt/hawkbit-mgmt-server/src/test/java/org/eclipse/hawkbit/app/mgmt/AllowedHostNamesTest.java index 6a2b507e5..a29e06108 100644 --- a/hawkbit-mgmt/hawkbit-mgmt-server/src/test/java/org/eclipse/hawkbit/app/mgmt/AllowedHostNamesTest.java +++ b/hawkbit-mgmt/hawkbit-mgmt-server/src/test/java/org/eclipse/hawkbit/app/mgmt/AllowedHostNamesTest.java @@ -16,13 +16,13 @@ import org.junit.jupiter.api.Test; import org.springframework.http.HttpHeaders; import org.springframework.test.context.TestPropertySource; -@TestPropertySource(properties = { - "hawkbit.server.security.allowedHostNames=localhost", - "hawkbit.server.security.httpFirewallIgnoredPaths=/index.html" }) /** * Feature: Integration Test - Security
* Story: Allowed Host Names */ +@TestPropertySource(properties = { + "hawkbit.server.security.allowedHostNames=localhost", + "hawkbit.server.security.httpFirewallIgnoredPaths=/index.html" }) class AllowedHostNamesTest extends AbstractSecurityTest { /** diff --git a/hawkbit-mgmt/hawkbit-mgmt-server/src/test/java/org/eclipse/hawkbit/app/mgmt/CorsTest.java b/hawkbit-mgmt/hawkbit-mgmt-server/src/test/java/org/eclipse/hawkbit/app/mgmt/CorsTest.java index 18fd18f0c..27e0a9f42 100644 --- a/hawkbit-mgmt/hawkbit-mgmt-server/src/test/java/org/eclipse/hawkbit/app/mgmt/CorsTest.java +++ b/hawkbit-mgmt/hawkbit-mgmt-server/src/test/java/org/eclipse/hawkbit/app/mgmt/CorsTest.java @@ -22,19 +22,18 @@ import org.springframework.boot.test.context.SpringBootTest; import org.springframework.http.HttpHeaders; import org.springframework.test.web.servlet.ResultActions; +/** + * Feature: Integration Test - Security
+ * Story: CORS + */ @SpringBootTest( properties = { - "hawkbit.dmf.rabbitmq.enabled=false", "hawkbit.server.security.cors.enabled=true", "hawkbit.server.security.cors.allowedOrigins=" + CorsTest.ALLOWED_ORIGIN_FIRST + "," + CorsTest.ALLOWED_ORIGIN_SECOND, "hawkbit.server.security.cors.exposedHeaders=" + HttpHeaders.ACCESS_CONTROL_ALLOW_ORIGIN }) -/** - * Feature: Integration Test - Security
- * Story: CORS - */ class CorsTest extends AbstractSecurityTest { static final String ALLOWED_ORIGIN_FIRST = "http://test.first.origin"; diff --git a/hawkbit-monolith/hawkbit-update-server/README.md b/hawkbit-monolith/hawkbit-update-server/README.md index cf174f5c5..85467856c 100644 --- a/hawkbit-monolith/hawkbit-update-server/README.md +++ b/hawkbit-monolith/hawkbit-update-server/README.md @@ -26,12 +26,6 @@ The Management API can be accessed via http://localhost:8080/rest/v1 Clustering in hawkBit is based on _Spring Cloud Bus_. It is not enabled in the example app by default. -Add to your `application.properties` : - -```properties -spring.cloud.bus.enabled=true -``` - Add to your `pom.xml` : ```xml diff --git a/hawkbit-monolith/hawkbit-update-server/src/main/resources/application.properties b/hawkbit-monolith/hawkbit-update-server/src/main/resources/application.properties index fe9431aec..406594630 100644 --- a/hawkbit-monolith/hawkbit-update-server/src/main/resources/application.properties +++ b/hawkbit-monolith/hawkbit-update-server/src/main/resources/application.properties @@ -45,12 +45,14 @@ hawkbit.cache.global.ttl=0 # Optional events hawkbit.server.repository.publish-target-poll-event=false -## Configuration for DMF/RabbitMQ integration -spring.rabbitmq.username=guest -spring.rabbitmq.password=guest -spring.rabbitmq.virtual-host=/ -spring.rabbitmq.host=localhost -spring.rabbitmq.port=5672 +## Disable RabbitMQ auto configuration. Comment it to enable RabbitMQ support. +spring.autoconfigure.exclude=org.springframework.boot.autoconfigure.amqp.RabbitAutoConfiguration +## Configuration Spring Bus (disabled by default) - no cluster support. To enable it, enable RabbitMQ (see above) +## and comment the line (spring.cloud.bus.enabled=false) or set spring.cloud.bus.enabled=true +spring.cloud.bus.enabled=false +## Disable DMF (by default) - no DMF support. To enable it, enable RabbitMQ (see above) and comment the line +## (hawkbit.dmf.rabbitmq.enabled=false) set hawkbit.dmf.rabbitmq.enabled=true +hawkbit.dmf.enabled=false # Enable CORS and specify the allowed origins: #hawkbit.server.security.cors.enabled=true diff --git a/hawkbit-monolith/hawkbit-update-server/src/test/java/org/eclipse/hawkbit/app/AbstractSecurityTest.java b/hawkbit-monolith/hawkbit-update-server/src/test/java/org/eclipse/hawkbit/app/AbstractSecurityTest.java index 808e5a2f6..5bba9eaaa 100644 --- a/hawkbit-monolith/hawkbit-update-server/src/test/java/org/eclipse/hawkbit/app/AbstractSecurityTest.java +++ b/hawkbit-monolith/hawkbit-update-server/src/test/java/org/eclipse/hawkbit/app/AbstractSecurityTest.java @@ -20,9 +20,9 @@ import org.springframework.test.web.servlet.setup.DefaultMockMvcBuilder; import org.springframework.test.web.servlet.setup.MockMvcBuilders; import org.springframework.web.context.WebApplicationContext; -@SpringBootTest(properties = { "hawkbit.dmf.rabbitmq.enabled=false" }) +@SpringBootTest @ExtendWith(SharedSqlTestDatabaseExtension.class) -public abstract class AbstractSecurityTest { +abstract class AbstractSecurityTest { protected MockMvc mvc; @Autowired diff --git a/hawkbit-monolith/hawkbit-update-server/src/test/java/org/eclipse/hawkbit/app/AllowedHostNamesTest.java b/hawkbit-monolith/hawkbit-update-server/src/test/java/org/eclipse/hawkbit/app/AllowedHostNamesTest.java index 0eb48d7c8..d48512b60 100644 --- a/hawkbit-monolith/hawkbit-update-server/src/test/java/org/eclipse/hawkbit/app/AllowedHostNamesTest.java +++ b/hawkbit-monolith/hawkbit-update-server/src/test/java/org/eclipse/hawkbit/app/AllowedHostNamesTest.java @@ -16,14 +16,14 @@ import org.junit.jupiter.api.Test; import org.springframework.http.HttpHeaders; import org.springframework.test.context.TestPropertySource; -@TestPropertySource(properties = { - "hawkbit.server.security.allowedHostNames=localhost", - "hawkbit.server.security.httpFirewallIgnoredPaths=/index.html" -}) /** * Feature: Integration Test - Security
* Story: Allowed Host Names */ +@TestPropertySource(properties = { + "hawkbit.server.security.allowedHostNames=localhost", + "hawkbit.server.security.httpFirewallIgnoredPaths=/index.html" +}) class AllowedHostNamesTest extends AbstractSecurityTest { /** diff --git a/hawkbit-monolith/hawkbit-update-server/src/test/java/org/eclipse/hawkbit/app/CorsTest.java b/hawkbit-monolith/hawkbit-update-server/src/test/java/org/eclipse/hawkbit/app/CorsTest.java index 36bf3ab03..c64da218f 100644 --- a/hawkbit-monolith/hawkbit-update-server/src/test/java/org/eclipse/hawkbit/app/CorsTest.java +++ b/hawkbit-monolith/hawkbit-update-server/src/test/java/org/eclipse/hawkbit/app/CorsTest.java @@ -22,19 +22,18 @@ import org.springframework.boot.test.context.SpringBootTest; import org.springframework.http.HttpHeaders; import org.springframework.test.web.servlet.ResultActions; +/** + * Feature: Integration Test - Security
+ * Story: CORS + */ @SpringBootTest( properties = { - "hawkbit.dmf.rabbitmq.enabled=false", "hawkbit.server.security.cors.enabled=true", "hawkbit.server.security.cors.allowedOrigins=" + CorsTest.ALLOWED_ORIGIN_FIRST + "," + CorsTest.ALLOWED_ORIGIN_SECOND, "hawkbit.server.security.cors.exposedHeaders=" + HttpHeaders.ACCESS_CONTROL_ALLOW_ORIGIN }) -/** - * Feature: Integration Test - Security
- * Story: CORS - */ class CorsTest extends AbstractSecurityTest { static final String ALLOWED_ORIGIN_FIRST = "http://test.first.origin"; diff --git a/hawkbit-repository/hawkbit-repository-core/src/main/resources/hawkbit-eventbus-defaults.properties b/hawkbit-repository/hawkbit-repository-core/src/main/resources/hawkbit-eventbus-defaults.properties index 29c90ada8..4c7bf9e96 100644 --- a/hawkbit-repository/hawkbit-repository-core/src/main/resources/hawkbit-eventbus-defaults.properties +++ b/hawkbit-repository/hawkbit-repository-core/src/main/resources/hawkbit-eventbus-defaults.properties @@ -9,7 +9,7 @@ # # Spring cloud bus and stream -spring.cloud.bus.enabled=false +spring.cloud.bus.enabled=true # Disable Cloud Bus default events spring.cloud.bus.env.enabled=false spring.cloud.bus.ack.enabled=false diff --git a/hawkbit-sdk/hawkbit-sdk-dmf/src/main/resources/application.properties b/hawkbit-sdk/hawkbit-sdk-dmf/src/main/resources/application.properties index ff70a9e8d..314497070 100644 --- a/hawkbit-sdk/hawkbit-sdk-dmf/src/main/resources/application.properties +++ b/hawkbit-sdk/hawkbit-sdk-dmf/src/main/resources/application.properties @@ -8,16 +8,7 @@ # SPDX-License-Identifier: EPL-2.0 # -## Configuration for local RabbitMQ integration -spring.rabbitmq.username=guest -spring.rabbitmq.password=guest -spring.rabbitmq.virtualHost=/ -spring.rabbitmq.host=localhost -spring.rabbitmq.port=5672 -spring.rabbitmq.dynamic=true - -## Configuration for sdk dmf -hawkbit.sdk.dmf.enabled=true +## Configuration for SDK DMF hawkbit.sdk.dmf.amqp.receiverConnectorQueueFromSp=sdk_receiver hawkbit.sdk.dmf.amqp.senderForSpExchange=sdk.replyTo diff --git a/site/content/guides/runhawkbit.md b/site/content/guides/runhawkbit.md index 7ed8d951f..52e8ceb68 100644 --- a/site/content/guides/runhawkbit.md +++ b/site/content/guides/runhawkbit.md @@ -4,7 +4,7 @@ parent: Guides weight: 31 --- -In this guide we describe how to run a full featured hawkBit setup based on a production ready infrastructure. It is +In this guide we describe how to run a full-featured hawkBit setup based on a production ready infrastructure. It is based on the hawkBit example modules and update server. @@ -20,7 +20,7 @@ This guide describes a target architecture that you will probably expect in a pr - hawkBit [Update Server](https://github.com/eclipse-hawkbit/hawkbit/tree/master/hawkbit-monolith/hawkbit-update-server). - [MariaDB](https://mariadb.org) for the repository. -- [RabbitMQ](https://www.rabbitmq.com) for DMF communication. +- [RabbitMQ](https://www.rabbitmq.com) for DMF communication (optional for monolith / single host deployment). For testing, demonstration or integrations purposes you could also use hawkBit SDK: - [hawkBit SDK Management API client](https://github.com/eclipse-hawkbit/hawkbit/blob/master/hawkbit-sdk/hawkbit-sdk-commons/src/main/java/org/eclipse/hawkbit/sdk/HawkbitClient.java). @@ -50,17 +50,17 @@ spring.datasource.username= spring.datasource.password= ``` -### Configure RabbitMQ connection settings for update server and device simulator (optional). +### Configure RabbitMQ connection settings for services (optional for monolith single host deployments). -We provide already defaults that should work with a standard Rabbit installation. Otherwise configure the following in -the `application.properties` of the two services: +We provide already defaults that should work with a standard Rabbit installation (spring boot RabbitProperties defaults). +Otherwise, configure the following in the `application.properties` of the services: ```properties -spring.rabbitmq.host=localhost -spring.rabbitmq.port=5672 -spring.rabbitmq.virtualHost=/ -spring.rabbitmq.username=guest -spring.rabbitmq.password=guest +spring.rabbitmq.host= +spring.rabbitmq.port= +spring.rabbitmq.virtualHost= +spring.rabbitmq.username= +spring.rabbitmq.password= ``` ### Adapt hostnames of demo simulator diff --git a/site/pom.xml b/site/pom.xml index d2842c594..fb7f0996a 100644 --- a/site/pom.xml +++ b/site/pom.xml @@ -129,7 +129,12 @@ org.eclipse.hawkbit - hawkbit-starter + hawkbit-ddi-starter + ${project.version} + + + org.eclipse.hawkbit + hawkbit-mgmt-starter ${project.version} diff --git a/site/src/main/resources/application.properties b/site/src/main/resources/application.properties index 25d6a627b..908e33338 100644 --- a/site/src/main/resources/application.properties +++ b/site/src/main/resources/application.properties @@ -27,9 +27,6 @@ hawkbit.server.ddi.security.authentication.gatewaytoken.enabled=false # Optional events hawkbit.server.repository.publish-target-poll-event=false -## Configuration for DMF/RabbitMQ integration -hawkbit.dmf.rabbitmq.enabled=false - # Swagger Configuration springdoc.api-docs.version=openapi_3_0 springdoc.show-oauth2-endpoints=true