Refactor RabbitMQ configuration (#2519)
Signed-off-by: Avgustin Marinov <Avgustin.Marinov@bosch.com>
This commit is contained in:
@@ -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 }}
|
||||
|
||||
2
.github/workflows/verify-hibernate.yaml
vendored
2
.github/workflows/verify-hibernate.yaml
vendored
@@ -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'
|
||||
2
.github/workflows/verify.yaml
vendored
2
.github/workflows/verify.yaml
vendored
@@ -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 }}
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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<br/>
|
||||
* 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 {
|
||||
|
||||
/**
|
||||
|
||||
@@ -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<FatalExceptionStrategy> fatalExceptionStrategies,
|
||||
@Value("${hawkbit.dmf.rabbitmq.fatal-exception-types:}") final List<String> 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<SimpleMessageListenerContainer> 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<String, Object> getTTLMaxArgsAuthenticationQueue() {
|
||||
final Map<String, Object> 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<Integer> unlessErrorCodeIn;
|
||||
private final List<String> unlessSqlStateIn;
|
||||
private final List<Pattern> 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;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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.
|
||||
*/
|
||||
|
||||
@@ -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 {}
|
||||
@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<FatalExceptionStrategy> fatalExceptionStrategies,
|
||||
@Value("${hawkbit.dmf.rabbitmq.fatal-exception-types:}") final List<String> 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<SimpleMessageListenerContainer> 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<String, Object> getTTLMaxArgsAuthenticationQueue() {
|
||||
final Map<String, Object> 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<Integer> unlessErrorCodeIn;
|
||||
private final List<String> unlessSqlStateIn;
|
||||
private final List<Pattern> 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;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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<br/>
|
||||
* Story: Allowed Host Names
|
||||
*/
|
||||
@TestPropertySource(properties = {
|
||||
"hawkbit.server.security.allowedHostNames=localhost",
|
||||
"hawkbit.server.security.httpFirewallIgnoredPaths=/index.html" })
|
||||
class AllowedHostNamesTest extends AbstractSecurityTest {
|
||||
|
||||
/**
|
||||
|
||||
@@ -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<br/>
|
||||
* 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<br/>
|
||||
* Story: CORS
|
||||
*/
|
||||
class CorsTest extends AbstractSecurityTest {
|
||||
|
||||
static final String ALLOWED_ORIGIN_FIRST = "http://test.first.origin";
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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<br/>
|
||||
* Story: Allowed Host Names
|
||||
*/
|
||||
@TestPropertySource(properties = {
|
||||
"hawkbit.server.security.allowedHostNames=localhost",
|
||||
"hawkbit.server.security.httpFirewallIgnoredPaths=/index.html"
|
||||
})
|
||||
class AllowedHostNamesTest extends AbstractSecurityTest {
|
||||
|
||||
/**
|
||||
|
||||
@@ -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<br/>
|
||||
* 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<br/>
|
||||
* Story: CORS
|
||||
*/
|
||||
class CorsTest extends AbstractSecurityTest {
|
||||
|
||||
static final String ALLOWED_ORIGIN_FIRST = "http://test.first.origin";
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -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.
|
||||
|
||||
<!--more-->
|
||||
@@ -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=<YOUR_USER>
|
||||
spring.datasource.password=<YOUR_PWD>
|
||||
```
|
||||
|
||||
### 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=<Rabbit MQ host>
|
||||
spring.rabbitmq.port=<Rabbit MQ port>
|
||||
spring.rabbitmq.virtualHost=<virtual host to be used>
|
||||
spring.rabbitmq.username=<YOUR_USER>
|
||||
spring.rabbitmq.password=<YOUR_PWD>
|
||||
```
|
||||
|
||||
### Adapt hostnames of demo simulator
|
||||
|
||||
@@ -129,7 +129,12 @@
|
||||
<dependencies>
|
||||
<dependency>
|
||||
<groupId>org.eclipse.hawkbit</groupId>
|
||||
<artifactId>hawkbit-starter</artifactId>
|
||||
<artifactId>hawkbit-ddi-starter</artifactId>
|
||||
<version>${project.version}</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.eclipse.hawkbit</groupId>
|
||||
<artifactId>hawkbit-mgmt-starter</artifactId>
|
||||
<version>${project.version}</version>
|
||||
</dependency>
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
Reference in New Issue
Block a user