Code format hawkbit-dmf-amqp (#1936)
Signed-off-by: Marinov Avgustin <Avgustin.Marinov@bosch.com>
This commit is contained in:
@@ -10,88 +10,88 @@
|
|||||||
|
|
||||||
-->
|
-->
|
||||||
|
|
||||||
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
<project xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns="http://maven.apache.org/POM/4.0.0"
|
||||||
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
|
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
|
||||||
<modelVersion>4.0.0</modelVersion>
|
<modelVersion>4.0.0</modelVersion>
|
||||||
<parent>
|
<parent>
|
||||||
<groupId>org.eclipse.hawkbit</groupId>
|
<groupId>org.eclipse.hawkbit</groupId>
|
||||||
<artifactId>hawkbit-dmf-parent</artifactId>
|
<artifactId>hawkbit-dmf-parent</artifactId>
|
||||||
<version>${revision}</version>
|
<version>${revision}</version>
|
||||||
</parent>
|
</parent>
|
||||||
<artifactId>hawkbit-dmf-amqp</artifactId>
|
<artifactId>hawkbit-dmf-amqp</artifactId>
|
||||||
<name>hawkBit :: DMF :: AMQP 0.9 Implementation</name>
|
<name>hawkBit :: DMF :: AMQP 0.9 Implementation</name>
|
||||||
|
|
||||||
<dependencies>
|
<dependencies>
|
||||||
<dependency>
|
<dependency>
|
||||||
<groupId>org.eclipse.hawkbit</groupId>
|
<groupId>org.eclipse.hawkbit</groupId>
|
||||||
<artifactId>hawkbit-repository-api</artifactId>
|
<artifactId>hawkbit-repository-api</artifactId>
|
||||||
<version>${project.version}</version>
|
<version>${project.version}</version>
|
||||||
</dependency>
|
</dependency>
|
||||||
<dependency>
|
<dependency>
|
||||||
<groupId>org.eclipse.hawkbit</groupId>
|
<groupId>org.eclipse.hawkbit</groupId>
|
||||||
<artifactId>hawkbit-core</artifactId>
|
<artifactId>hawkbit-core</artifactId>
|
||||||
<version>${project.version}</version>
|
<version>${project.version}</version>
|
||||||
</dependency>
|
</dependency>
|
||||||
<dependency>
|
<dependency>
|
||||||
<groupId>org.eclipse.hawkbit</groupId>
|
<groupId>org.eclipse.hawkbit</groupId>
|
||||||
<artifactId>hawkbit-security-integration</artifactId>
|
<artifactId>hawkbit-security-integration</artifactId>
|
||||||
<version>${project.version}</version>
|
<version>${project.version}</version>
|
||||||
</dependency>
|
</dependency>
|
||||||
<dependency>
|
<dependency>
|
||||||
<groupId>org.eclipse.hawkbit</groupId>
|
<groupId>org.eclipse.hawkbit</groupId>
|
||||||
<artifactId>hawkbit-dmf-api</artifactId>
|
<artifactId>hawkbit-dmf-api</artifactId>
|
||||||
<version>${project.version}</version>
|
<version>${project.version}</version>
|
||||||
</dependency>
|
</dependency>
|
||||||
<dependency>
|
<dependency>
|
||||||
<groupId>org.springframework.boot</groupId>
|
<groupId>org.springframework.boot</groupId>
|
||||||
<artifactId>spring-boot-autoconfigure</artifactId>
|
<artifactId>spring-boot-autoconfigure</artifactId>
|
||||||
</dependency>
|
</dependency>
|
||||||
<dependency>
|
<dependency>
|
||||||
<groupId>org.springframework.amqp</groupId>
|
<groupId>org.springframework.amqp</groupId>
|
||||||
<artifactId>spring-rabbit</artifactId>
|
<artifactId>spring-rabbit</artifactId>
|
||||||
</dependency>
|
</dependency>
|
||||||
<dependency>
|
<dependency>
|
||||||
<groupId>org.springframework.security</groupId>
|
<groupId>org.springframework.security</groupId>
|
||||||
<artifactId>spring-security-web</artifactId>
|
<artifactId>spring-security-web</artifactId>
|
||||||
</dependency>
|
</dependency>
|
||||||
<dependency>
|
<dependency>
|
||||||
<groupId>org.springframework.boot</groupId>
|
<groupId>org.springframework.boot</groupId>
|
||||||
<artifactId>spring-boot</artifactId>
|
<artifactId>spring-boot</artifactId>
|
||||||
</dependency>
|
</dependency>
|
||||||
<dependency>
|
<dependency>
|
||||||
<groupId>org.springframework.boot</groupId>
|
<groupId>org.springframework.boot</groupId>
|
||||||
<artifactId>spring-boot-starter-json</artifactId>
|
<artifactId>spring-boot-starter-json</artifactId>
|
||||||
</dependency>
|
</dependency>
|
||||||
<dependency>
|
<dependency>
|
||||||
<groupId>org.slf4j</groupId>
|
<groupId>org.slf4j</groupId>
|
||||||
<artifactId>slf4j-api</artifactId>
|
<artifactId>slf4j-api</artifactId>
|
||||||
</dependency>
|
</dependency>
|
||||||
<dependency>
|
<dependency>
|
||||||
<groupId>org.springframework.boot</groupId>
|
<groupId>org.springframework.boot</groupId>
|
||||||
<artifactId>spring-boot-starter-logging</artifactId>
|
<artifactId>spring-boot-starter-logging</artifactId>
|
||||||
</dependency>
|
</dependency>
|
||||||
<dependency>
|
<dependency>
|
||||||
<groupId>org.apache.commons</groupId>
|
<groupId>org.apache.commons</groupId>
|
||||||
<artifactId>commons-collections4</artifactId>
|
<artifactId>commons-collections4</artifactId>
|
||||||
</dependency>
|
</dependency>
|
||||||
|
|
||||||
|
|
||||||
<!-- Test -->
|
|
||||||
<dependency>
|
<!-- Test -->
|
||||||
<groupId>org.eclipse.hawkbit</groupId>
|
<dependency>
|
||||||
<artifactId>hawkbit-dmf-rabbitmq-test</artifactId>
|
<groupId>org.eclipse.hawkbit</groupId>
|
||||||
<version>${project.version}</version>
|
<artifactId>hawkbit-dmf-rabbitmq-test</artifactId>
|
||||||
<scope>test</scope>
|
<version>${project.version}</version>
|
||||||
</dependency>
|
<scope>test</scope>
|
||||||
<dependency>
|
</dependency>
|
||||||
<groupId>io.qameta.allure</groupId>
|
<dependency>
|
||||||
<artifactId>allure-junit5</artifactId>
|
<groupId>io.qameta.allure</groupId>
|
||||||
<scope>test</scope>
|
<artifactId>allure-junit5</artifactId>
|
||||||
</dependency>
|
<scope>test</scope>
|
||||||
<dependency>
|
</dependency>
|
||||||
<groupId>org.springframework.amqp</groupId>
|
<dependency>
|
||||||
<artifactId>spring-rabbit-test</artifactId>
|
<groupId>org.springframework.amqp</groupId>
|
||||||
<scope>test</scope>
|
<artifactId>spring-rabbit-test</artifactId>
|
||||||
</dependency>
|
<scope>test</scope>
|
||||||
</dependencies>
|
</dependency>
|
||||||
|
</dependencies>
|
||||||
</project>
|
</project>
|
||||||
@@ -31,19 +31,17 @@ public abstract class AbstractAmqpErrorHandler<T> implements AmqpErrorHandler {
|
|||||||
/**
|
/**
|
||||||
* Returns the class of the exception.
|
* Returns the class of the exception.
|
||||||
*
|
*
|
||||||
* @return
|
* @return the exception class
|
||||||
* the exception class
|
|
||||||
*/
|
*/
|
||||||
public abstract Class<T> getExceptionClass();
|
public abstract Class<T> getExceptionClass();
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns the customized error message.
|
* Returns the customized error message.
|
||||||
*
|
*
|
||||||
* @return
|
* @return the customized error message
|
||||||
* the customized error message
|
|
||||||
*/
|
*/
|
||||||
public String getErrorMessage(Throwable throwable){
|
public String getErrorMessage(Throwable throwable) {
|
||||||
return AmqpErrorMessageComposer.constructErrorMessage(throwable);
|
return AmqpErrorMessageComposer.constructErrorMessage(throwable);
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -9,6 +9,11 @@
|
|||||||
*/
|
*/
|
||||||
package org.eclipse.hawkbit.amqp;
|
package org.eclipse.hawkbit.amqp;
|
||||||
|
|
||||||
|
import java.time.Duration;
|
||||||
|
import java.util.HashMap;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Map;
|
||||||
|
|
||||||
import lombok.extern.slf4j.Slf4j;
|
import lombok.extern.slf4j.Slf4j;
|
||||||
import org.eclipse.hawkbit.api.ArtifactUrlHandler;
|
import org.eclipse.hawkbit.api.ArtifactUrlHandler;
|
||||||
import org.eclipse.hawkbit.dmf.amqp.api.AmqpSettings;
|
import org.eclipse.hawkbit.dmf.amqp.api.AmqpSettings;
|
||||||
@@ -46,11 +51,6 @@ import org.springframework.retry.backoff.ExponentialBackOffPolicy;
|
|||||||
import org.springframework.retry.support.RetryTemplate;
|
import org.springframework.retry.support.RetryTemplate;
|
||||||
import org.springframework.util.ErrorHandler;
|
import org.springframework.util.ErrorHandler;
|
||||||
|
|
||||||
import java.time.Duration;
|
|
||||||
import java.util.HashMap;
|
|
||||||
import java.util.List;
|
|
||||||
import java.util.Map;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Spring configuration for AMQP based DMF communication for indirect device
|
* Spring configuration for AMQP based DMF communication for indirect device
|
||||||
* integration.
|
* integration.
|
||||||
@@ -76,9 +76,7 @@ public class AmqpConfiguration {
|
|||||||
/**
|
/**
|
||||||
* Creates a custom error handler bean.
|
* Creates a custom error handler bean.
|
||||||
*
|
*
|
||||||
* @param handlers
|
* @param handlers list of {@link AmqpErrorHandler} handlers
|
||||||
* list of {@link AmqpErrorHandler} handlers
|
|
||||||
|
|
||||||
* @return the delegating error handler bean
|
* @return the delegating error handler bean
|
||||||
*/
|
*/
|
||||||
@Bean
|
@Bean
|
||||||
@@ -253,15 +251,10 @@ public class AmqpConfiguration {
|
|||||||
/**
|
/**
|
||||||
* Create AMQP handler service bean.
|
* Create AMQP handler service bean.
|
||||||
*
|
*
|
||||||
* @param rabbitTemplate
|
* @param rabbitTemplate for converting messages
|
||||||
* for converting messages
|
* @param amqpMessageDispatcherService to sending events to DMF client
|
||||||
* @param amqpMessageDispatcherService
|
* @param controllerManagement for target repo access
|
||||||
* to sending events to DMF client
|
* @param entityFactory to create entities
|
||||||
* @param controllerManagement
|
|
||||||
* for target repo access
|
|
||||||
* @param entityFactory
|
|
||||||
* to create entities
|
|
||||||
*
|
|
||||||
* @return handler service bean
|
* @return handler service bean
|
||||||
*/
|
*/
|
||||||
@Bean
|
@Bean
|
||||||
|
|||||||
@@ -35,9 +35,8 @@ public class AmqpDeadletterProperties {
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* Return the deadletter arguments.
|
* Return the deadletter arguments.
|
||||||
*
|
*
|
||||||
* @param exchange
|
* @param exchange the deadletter exchange
|
||||||
* the deadletter exchange
|
|
||||||
* @return map which holds the properties
|
* @return map which holds the properties
|
||||||
*/
|
*/
|
||||||
public Map<String, Object> getDeadLetterExchangeArgs(final String exchange) {
|
public Map<String, Object> getDeadLetterExchangeArgs(final String exchange) {
|
||||||
@@ -48,7 +47,7 @@ public class AmqpDeadletterProperties {
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* Create a deadletter queue with ttl for messages
|
* Create a deadletter queue with ttl for messages
|
||||||
*
|
*
|
||||||
* @param queueName the deadletter queue name
|
* @param queueName the deadletter queue name
|
||||||
* @return the deadletter queue
|
* @return the deadletter queue
|
||||||
*/
|
*/
|
||||||
|
|||||||
@@ -19,10 +19,8 @@ public interface AmqpErrorHandler {
|
|||||||
/**
|
/**
|
||||||
* Handles the error based on the type of exception
|
* Handles the error based on the type of exception
|
||||||
*
|
*
|
||||||
* @param throwable
|
* @param throwable the throwable
|
||||||
* the throwable
|
* @param chain an {@link AmqpErrorHandlerChain}
|
||||||
* @param chain
|
|
||||||
* an {@link AmqpErrorHandlerChain}
|
|
||||||
*/
|
*/
|
||||||
void doHandle(final Throwable throwable, final AmqpErrorHandlerChain chain);
|
void doHandle(final Throwable throwable, final AmqpErrorHandlerChain chain);
|
||||||
}
|
}
|
||||||
@@ -9,27 +9,26 @@
|
|||||||
*/
|
*/
|
||||||
package org.eclipse.hawkbit.amqp;
|
package org.eclipse.hawkbit.amqp;
|
||||||
|
|
||||||
import org.springframework.util.ErrorHandler;
|
|
||||||
|
|
||||||
import java.util.Iterator;
|
import java.util.Iterator;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
|
||||||
|
import org.springframework.util.ErrorHandler;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* An error handler chain that delegates the error to the matching error handler based on the type of exception
|
* An error handler chain that delegates the error to the matching error handler based on the type of exception
|
||||||
*/
|
*/
|
||||||
public final class AmqpErrorHandlerChain {
|
public final class AmqpErrorHandlerChain {
|
||||||
|
|
||||||
private final Iterator<AmqpErrorHandler> iterator;
|
private final Iterator<AmqpErrorHandler> iterator;
|
||||||
private final ErrorHandler defaultHandler;
|
private final ErrorHandler defaultHandler;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Constructor.
|
* Constructor.
|
||||||
*
|
*
|
||||||
* @param iterator
|
* @param iterator the {@link AmqpErrorHandler} iterator
|
||||||
* the {@link AmqpErrorHandler} iterator
|
* @param defaultHandler the default handler
|
||||||
* @param defaultHandler
|
|
||||||
* the default handler
|
|
||||||
*/
|
*/
|
||||||
private AmqpErrorHandlerChain(Iterator<AmqpErrorHandler> iterator, ErrorHandler defaultHandler) {
|
private AmqpErrorHandlerChain(Iterator<AmqpErrorHandler> iterator, ErrorHandler defaultHandler) {
|
||||||
this.iterator = iterator;
|
this.iterator = iterator;
|
||||||
this.defaultHandler = defaultHandler;
|
this.defaultHandler = defaultHandler;
|
||||||
}
|
}
|
||||||
@@ -37,10 +36,8 @@ public final class AmqpErrorHandlerChain {
|
|||||||
/**
|
/**
|
||||||
* Returns an {@link AmqpErrorHandlerChain}
|
* Returns an {@link AmqpErrorHandlerChain}
|
||||||
*
|
*
|
||||||
* @param errorHandlers
|
* @param errorHandlers {@link List} of error handlers
|
||||||
* {@link List} of error handlers
|
* @param defaultHandler the default error handler
|
||||||
* @param defaultHandler
|
|
||||||
* the default error handler
|
|
||||||
* @return an {@link AmqpErrorHandlerChain}
|
* @return an {@link AmqpErrorHandlerChain}
|
||||||
*/
|
*/
|
||||||
public static AmqpErrorHandlerChain getHandlerChain(final List<AmqpErrorHandler> errorHandlers, final ErrorHandler defaultHandler) {
|
public static AmqpErrorHandlerChain getHandlerChain(final List<AmqpErrorHandler> errorHandlers, final ErrorHandler defaultHandler) {
|
||||||
@@ -50,8 +47,7 @@ public final class AmqpErrorHandlerChain {
|
|||||||
/**
|
/**
|
||||||
* Handles the error based on the type of exception
|
* Handles the error based on the type of exception
|
||||||
*
|
*
|
||||||
* @param error
|
* @param error the throwable containing the cause of exception
|
||||||
* the throwable containing the cause of exception
|
|
||||||
*/
|
*/
|
||||||
public void handle(final Throwable error) {
|
public void handle(final Throwable error) {
|
||||||
if (iterator.hasNext()) {
|
if (iterator.hasNext()) {
|
||||||
|
|||||||
@@ -27,10 +27,8 @@ public final class AmqpErrorMessageComposer {
|
|||||||
/**
|
/**
|
||||||
* Constructs an error message based on failed message content
|
* Constructs an error message based on failed message content
|
||||||
*
|
*
|
||||||
* @param throwable
|
* @param throwable the throwable containing failed message content
|
||||||
* the throwable containing failed message content
|
* @return meaningful error message
|
||||||
* @return
|
|
||||||
* meaningful error message
|
|
||||||
*/
|
*/
|
||||||
public static String constructErrorMessage(final Throwable throwable) {
|
public static String constructErrorMessage(final Throwable throwable) {
|
||||||
StringBuilder completeErrorMessage = new StringBuilder();
|
StringBuilder completeErrorMessage = new StringBuilder();
|
||||||
|
|||||||
@@ -106,26 +106,16 @@ public class AmqpMessageDispatcherService extends BaseAmqpService {
|
|||||||
/**
|
/**
|
||||||
* Constructor.
|
* Constructor.
|
||||||
*
|
*
|
||||||
* @param rabbitTemplate
|
* @param rabbitTemplate the rabbitTemplate
|
||||||
* the rabbitTemplate
|
* @param amqpSenderService to send AMQP message
|
||||||
* @param amqpSenderService
|
* @param artifactUrlHandler for generating download URLs
|
||||||
* to send AMQP message
|
* @param systemSecurityContext for execution with system permissions
|
||||||
* @param artifactUrlHandler
|
* @param systemManagement the systemManagement
|
||||||
* for generating download URLs
|
* @param targetManagement to access target information
|
||||||
* @param systemSecurityContext
|
* @param serviceMatcher to check in cluster case if the message is from the same
|
||||||
* for execution with system permissions
|
* cluster node
|
||||||
* @param systemManagement
|
* @param distributionSetManagement to retrieve modules
|
||||||
* the systemManagement
|
* @param tenantConfigurationManagement to access tenant configuration
|
||||||
* @param targetManagement
|
|
||||||
* to access target information
|
|
||||||
* @param serviceMatcher
|
|
||||||
* to check in cluster case if the message is from the same
|
|
||||||
* cluster node
|
|
||||||
* @param distributionSetManagement
|
|
||||||
* to retrieve modules
|
|
||||||
* @param tenantConfigurationManagement
|
|
||||||
* to access tenant configuration
|
|
||||||
*
|
|
||||||
*/
|
*/
|
||||||
protected AmqpMessageDispatcherService(final RabbitTemplate rabbitTemplate,
|
protected AmqpMessageDispatcherService(final RabbitTemplate rabbitTemplate,
|
||||||
final AmqpMessageSenderService amqpSenderService, final ArtifactUrlHandler artifactUrlHandler,
|
final AmqpMessageSenderService amqpSenderService, final ArtifactUrlHandler artifactUrlHandler,
|
||||||
@@ -147,12 +137,16 @@ public class AmqpMessageDispatcherService extends BaseAmqpService {
|
|||||||
this.tenantConfigurationManagement = tenantConfigurationManagement;
|
this.tenantConfigurationManagement = tenantConfigurationManagement;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public boolean isBatchAssignmentsEnabled() {
|
||||||
|
return systemSecurityContext.runAsSystem(() -> tenantConfigurationManagement
|
||||||
|
.getConfigurationValue(BATCH_ASSIGNMENTS_ENABLED, Boolean.class).getValue());
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Method to send a message to a RabbitMQ Exchange after the Distribution
|
* Method to send a message to a RabbitMQ Exchange after the Distribution
|
||||||
* set has been assign to a Target.
|
* set has been assign to a Target.
|
||||||
*
|
*
|
||||||
* @param assignedEvent
|
* @param assignedEvent the object to be send.
|
||||||
* the object to be send.
|
|
||||||
*/
|
*/
|
||||||
@EventListener(classes = TargetAssignDistributionSetEvent.class)
|
@EventListener(classes = TargetAssignDistributionSetEvent.class)
|
||||||
protected void targetAssignDistributionSet(final TargetAssignDistributionSetEvent assignedEvent) {
|
protected void targetAssignDistributionSet(final TargetAssignDistributionSetEvent assignedEvent) {
|
||||||
@@ -173,8 +167,7 @@ public class AmqpMessageDispatcherService extends BaseAmqpService {
|
|||||||
/**
|
/**
|
||||||
* Listener for Multi-Action events.
|
* Listener for Multi-Action events.
|
||||||
*
|
*
|
||||||
* @param multiActionEvent
|
* @param multiActionEvent the Multi-Action event to be processed
|
||||||
* the Multi-Action event to be processed
|
|
||||||
*/
|
*/
|
||||||
@EventListener(classes = MultiActionEvent.class)
|
@EventListener(classes = MultiActionEvent.class)
|
||||||
protected void onMultiAction(final MultiActionEvent multiActionEvent) {
|
protected void onMultiAction(final MultiActionEvent multiActionEvent) {
|
||||||
@@ -185,6 +178,261 @@ public class AmqpMessageDispatcherService extends BaseAmqpService {
|
|||||||
sendMultiActionRequestMessages(multiActionEvent.getTenant(), multiActionEvent.getControllerIds());
|
sendMultiActionRequestMessages(multiActionEvent.getTenant(), multiActionEvent.getControllerIds());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
protected void sendUpdateMessageToTarget(final ActionProperties actionsProps, final Target target,
|
||||||
|
final Map<SoftwareModule, List<SoftwareModuleMetadata>> softwareModules) {
|
||||||
|
final Map<String, ActionProperties> actionProp = new HashMap<>();
|
||||||
|
actionProp.put(target.getControllerId(), actionsProps);
|
||||||
|
sendUpdateMessageToTargets(actionProp, Collections.singletonList(target), softwareModules);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected void sendMultiActionRequestToTarget(final String tenant, final Target target, final List<Action> actions,
|
||||||
|
final Function<Action, Map<SoftwareModule, List<SoftwareModuleMetadata>>> getSoftwareModuleMetaData) {
|
||||||
|
|
||||||
|
final URI targetAddress = target.getAddress();
|
||||||
|
if (!IpUtil.isAmqpUri(targetAddress) || CollectionUtils.isEmpty(actions)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
final DmfMultiActionRequest multiActionRequest = new DmfMultiActionRequest();
|
||||||
|
actions.forEach(action -> {
|
||||||
|
final DmfActionRequest actionRequest = createDmfActionRequest(target, action,
|
||||||
|
getSoftwareModuleMetaData.apply(action));
|
||||||
|
final int weight = deploymentManagement.getWeightConsideringDefault(action);
|
||||||
|
multiActionRequest.addElement(getEventTypeForAction(action), actionRequest, weight);
|
||||||
|
});
|
||||||
|
|
||||||
|
final Message message = getMessageConverter().toMessage(multiActionRequest,
|
||||||
|
createConnectorMessagePropertiesEvent(tenant, target.getControllerId(), EventTopic.MULTI_ACTION));
|
||||||
|
amqpSenderService.sendMessage(message, targetAddress);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected DmfDownloadAndUpdateRequest createDownloadAndUpdateRequest(final Target target, final Long actionId,
|
||||||
|
final Map<SoftwareModule, List<SoftwareModuleMetadata>> softwareModules) {
|
||||||
|
final DmfDownloadAndUpdateRequest request = new DmfDownloadAndUpdateRequest();
|
||||||
|
request.setActionId(actionId);
|
||||||
|
request.setTargetSecurityToken(systemSecurityContext.runAsSystem(target::getSecurityToken));
|
||||||
|
|
||||||
|
if (softwareModules != null) {
|
||||||
|
softwareModules.entrySet()
|
||||||
|
.forEach(entry -> request.addSoftwareModule(convertToAmqpSoftwareModule(target, entry)));
|
||||||
|
}
|
||||||
|
return request;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Method to send a message to a RabbitMQ Exchange after the assignment of
|
||||||
|
* the Distribution set to a Target has been canceled.
|
||||||
|
*
|
||||||
|
* @param cancelEvent that is to be converted to a DMF message
|
||||||
|
*/
|
||||||
|
@EventListener(classes = CancelTargetAssignmentEvent.class)
|
||||||
|
protected void targetCancelAssignmentToDistributionSet(final CancelTargetAssignmentEvent cancelEvent) {
|
||||||
|
if (!shouldBeProcessed(cancelEvent)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
final List<Target> eventTargets = partitionedParallelExecution(cancelEvent.getActions().keySet(),
|
||||||
|
targetManagement::getByControllerID);
|
||||||
|
|
||||||
|
eventTargets.forEach(target -> {
|
||||||
|
cancelEvent.getActionPropertiesForController(target.getControllerId()).map(ActionProperties::getId)
|
||||||
|
.ifPresent(actionId -> {
|
||||||
|
sendCancelMessageToTarget(cancelEvent.getTenant(), target.getControllerId(), actionId,
|
||||||
|
target.getAddress());
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Method to send a message to a RabbitMQ Exchange after a Target was
|
||||||
|
* deleted.
|
||||||
|
*
|
||||||
|
* @param deleteEvent the TargetDeletedEvent which holds the necessary data for
|
||||||
|
* sending a target delete message.
|
||||||
|
*/
|
||||||
|
@EventListener(classes = TargetDeletedEvent.class)
|
||||||
|
protected void targetDelete(final TargetDeletedEvent deleteEvent) {
|
||||||
|
if (!shouldBeProcessed(deleteEvent)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
sendDeleteMessage(deleteEvent.getTenant(), deleteEvent.getControllerId(), deleteEvent.getTargetAddress());
|
||||||
|
}
|
||||||
|
|
||||||
|
@EventListener(classes = TargetAttributesRequestedEvent.class)
|
||||||
|
protected void targetTriggerUpdateAttributes(final TargetAttributesRequestedEvent updateAttributesEvent) {
|
||||||
|
sendUpdateAttributesMessageToTarget(updateAttributesEvent.getTenant(), updateAttributesEvent.getControllerId(),
|
||||||
|
updateAttributesEvent.getTargetAddress());
|
||||||
|
}
|
||||||
|
|
||||||
|
protected void sendPingReponseToDmfReceiver(final Message ping, final String tenant, final String virtualHost) {
|
||||||
|
final Message message = MessageBuilder.withBody(String.valueOf(System.currentTimeMillis()).getBytes())
|
||||||
|
.setContentType(MessageProperties.CONTENT_TYPE_TEXT_PLAIN)
|
||||||
|
.setCorrelationId(ping.getMessageProperties().getCorrelationId())
|
||||||
|
.setHeader(MessageHeaderKey.TYPE, MessageType.PING_RESPONSE).setHeader(MessageHeaderKey.TENANT, tenant)
|
||||||
|
.build();
|
||||||
|
|
||||||
|
amqpSenderService.sendMessage(message,
|
||||||
|
IpUtil.createAmqpUri(virtualHost, ping.getMessageProperties().getReplyTo()));
|
||||||
|
}
|
||||||
|
|
||||||
|
protected boolean shouldBeProcessed(final RemoteApplicationEvent event) {
|
||||||
|
return isFromSelf(event);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected void sendCancelMessageToTarget(final String tenant, final String controllerId, final Long actionId,
|
||||||
|
final URI address) {
|
||||||
|
if (!IpUtil.isAmqpUri(address)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
final DmfActionRequest actionRequest = new DmfActionRequest();
|
||||||
|
actionRequest.setActionId(actionId);
|
||||||
|
|
||||||
|
final Message message = getMessageConverter().toMessage(actionRequest,
|
||||||
|
createConnectorMessagePropertiesEvent(tenant, controllerId, EventTopic.CANCEL_DOWNLOAD));
|
||||||
|
|
||||||
|
amqpSenderService.sendMessage(message, address);
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
protected DmfTarget convertToDmfTarget(final Target target, final Long actionId) {
|
||||||
|
final DmfTarget dmfTarget = new DmfTarget();
|
||||||
|
dmfTarget.setActionId(actionId);
|
||||||
|
dmfTarget.setControllerId(target.getControllerId());
|
||||||
|
dmfTarget.setTargetSecurityToken(systemSecurityContext.runAsSystem(target::getSecurityToken));
|
||||||
|
return dmfTarget;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates a Confirmation request.
|
||||||
|
*
|
||||||
|
* @param target the target
|
||||||
|
* @param actionId the actionId
|
||||||
|
* @param softwareModules the software modules
|
||||||
|
* @return
|
||||||
|
*/
|
||||||
|
protected DmfConfirmRequest createConfirmRequest(final Target target, final Long actionId, final Map<SoftwareModule,
|
||||||
|
List<SoftwareModuleMetadata>> softwareModules) {
|
||||||
|
final DmfConfirmRequest request = new DmfConfirmRequest();
|
||||||
|
request.setActionId(actionId);
|
||||||
|
request.setTargetSecurityToken(systemSecurityContext.runAsSystem(target::getSecurityToken));
|
||||||
|
|
||||||
|
//Software modules can be filtered in the future exposing only the needed.
|
||||||
|
if (softwareModules != null) {
|
||||||
|
softwareModules.entrySet()
|
||||||
|
.forEach(entry -> request.addSoftwareModule(convertToAmqpSoftwareModule(target, entry)));
|
||||||
|
}
|
||||||
|
return request;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static DmfActionRequest createPlainActionRequest(final Action action) {
|
||||||
|
final DmfActionRequest actionRequest = new DmfActionRequest();
|
||||||
|
actionRequest.setActionId(action.getId());
|
||||||
|
return actionRequest;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Method to get the type of event depending on whether the action is a
|
||||||
|
* DOWNLOAD_ONLY action or if it has a valid maintenance window available or
|
||||||
|
* not based on defined maintenance schedule. In case of no maintenance
|
||||||
|
* schedule or if there is a valid window available, the topic
|
||||||
|
* {@link EventTopic#DOWNLOAD_AND_INSTALL} is returned else
|
||||||
|
* {@link EventTopic#DOWNLOAD} is returned.
|
||||||
|
*
|
||||||
|
* @param action current action properties.
|
||||||
|
* @return {@link EventTopic} to use for message.
|
||||||
|
*/
|
||||||
|
private static EventTopic getEventTypeForTarget(final ActionProperties action) {
|
||||||
|
if (action.isWaitingConfirmation()) {
|
||||||
|
return EventTopic.CONFIRM;
|
||||||
|
}
|
||||||
|
return (Action.ActionType.DOWNLOAD_ONLY == action.getActionType() || !action.isMaintenanceWindowAvailable())
|
||||||
|
? EventTopic.DOWNLOAD
|
||||||
|
: EventTopic.DOWNLOAD_AND_INSTALL;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Determines the {@link EventTopic} for the given {@link Action}, depending
|
||||||
|
* on its action type.
|
||||||
|
*
|
||||||
|
* @param action to obtain the corresponding {@link EventTopic} for
|
||||||
|
* @return the {@link EventTopic} for this action
|
||||||
|
*/
|
||||||
|
private static EventTopic getEventTypeForAction(final Action action) {
|
||||||
|
if (action.isCancelingOrCanceled()) {
|
||||||
|
return EventTopic.CANCEL_DOWNLOAD;
|
||||||
|
}
|
||||||
|
return getEventTypeForTarget(new ActionProperties(action));
|
||||||
|
}
|
||||||
|
|
||||||
|
private static <T, R> List<R> partitionedParallelExecution(final Collection<T> controllerIds,
|
||||||
|
final Function<Collection<T>, List<R>> loadingFunction) {
|
||||||
|
// Ensure not exceeding the max value of MAX_PROCESSING_SIZE
|
||||||
|
if (controllerIds.size() > MAX_PROCESSING_SIZE) {
|
||||||
|
// Split the provided collection
|
||||||
|
final Iterable<List<T>> partitions = ListUtils.partition(IterableUtils.toList(controllerIds), MAX_PROCESSING_SIZE);
|
||||||
|
// Preserve the security context because it gets lost when executing
|
||||||
|
// loading calls in new threads
|
||||||
|
final SecurityContext context = SecurityContextHolder.getContext();
|
||||||
|
// Handling remote request in parallel streams
|
||||||
|
return StreamSupport.stream(partitions.spliterator(), true) //
|
||||||
|
.flatMap(partition -> withSecurityContext(() -> loadingFunction.apply(partition), context).stream())
|
||||||
|
.collect(Collectors.toList());
|
||||||
|
}
|
||||||
|
return loadingFunction.apply(controllerIds);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static <T> T withSecurityContext(final Supplier<T> callable, final SecurityContext securityContext) {
|
||||||
|
final SecurityContext oldContext = SecurityContextHolder.getContext();
|
||||||
|
try {
|
||||||
|
SecurityContextHolder.setContext(securityContext);
|
||||||
|
return callable.get();
|
||||||
|
} finally {
|
||||||
|
SecurityContextHolder.setContext(oldContext);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static MessageProperties createConnectorMessagePropertiesEvent(final String tenant,
|
||||||
|
final String controllerId, final EventTopic topic) {
|
||||||
|
final MessageProperties messageProperties = createConnectorMessageProperties(tenant, controllerId);
|
||||||
|
messageProperties.setHeader(MessageHeaderKey.TOPIC, topic);
|
||||||
|
messageProperties.setHeader(MessageHeaderKey.TYPE, MessageType.EVENT);
|
||||||
|
return messageProperties;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static MessageProperties createConnectorMessagePropertiesDeleteThing(final String tenant,
|
||||||
|
final String controllerId) {
|
||||||
|
final MessageProperties messageProperties = createConnectorMessageProperties(tenant, controllerId);
|
||||||
|
messageProperties.setHeader(MessageHeaderKey.TYPE, MessageType.THING_DELETED);
|
||||||
|
return messageProperties;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static MessageProperties createConnectorMessageProperties(final String tenant, final String controllerId) {
|
||||||
|
final MessageProperties messageProperties = new MessageProperties();
|
||||||
|
messageProperties.setContentType(MessageProperties.CONTENT_TYPE_JSON);
|
||||||
|
messageProperties.setHeader(MessageHeaderKey.CONTENT_TYPE, MessageProperties.CONTENT_TYPE_JSON);
|
||||||
|
messageProperties.setHeader(MessageHeaderKey.THING_ID, controllerId);
|
||||||
|
messageProperties.setHeader(MessageHeaderKey.TENANT, tenant);
|
||||||
|
return messageProperties;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static MessageProperties createMessagePropertiesBatch(final String tenant, final EventTopic topic) {
|
||||||
|
final MessageProperties messageProperties = new MessageProperties();
|
||||||
|
messageProperties.setContentType(MessageProperties.CONTENT_TYPE_JSON);
|
||||||
|
messageProperties.setHeader(MessageHeaderKey.CONTENT_TYPE, MessageProperties.CONTENT_TYPE_JSON);
|
||||||
|
messageProperties.setHeader(MessageHeaderKey.TENANT, tenant);
|
||||||
|
|
||||||
|
messageProperties.setHeader(MessageHeaderKey.TOPIC, topic);
|
||||||
|
messageProperties.setHeader(MessageHeaderKey.TYPE, MessageType.EVENT);
|
||||||
|
return messageProperties;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static EventTopic getBatchEventTopicForAction(final ActionProperties action) {
|
||||||
|
return (Action.ActionType.DOWNLOAD_ONLY == action.getActionType() || !action.isMaintenanceWindowAvailable())
|
||||||
|
? EventTopic.BATCH_DOWNLOAD
|
||||||
|
: EventTopic.BATCH_DOWNLOAD_AND_INSTALL;
|
||||||
|
}
|
||||||
|
|
||||||
private List<Target> getTargetsWithoutPendingCancellations(final Set<String> controllerIds) {
|
private List<Target> getTargetsWithoutPendingCancellations(final Set<String> controllerIds) {
|
||||||
return partitionedParallelExecution(controllerIds, partition -> {
|
return partitionedParallelExecution(controllerIds, partition -> {
|
||||||
return targetManagement.getByControllerID(partition).stream().filter(target -> {
|
return targetManagement.getByControllerID(partition).stream().filter(target -> {
|
||||||
@@ -207,13 +455,6 @@ public class AmqpMessageDispatcherService extends BaseAmqpService {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
protected void sendUpdateMessageToTarget(final ActionProperties actionsProps, final Target target,
|
|
||||||
final Map<SoftwareModule, List<SoftwareModuleMetadata>> softwareModules) {
|
|
||||||
final Map<String, ActionProperties> actionProp = new HashMap<>();
|
|
||||||
actionProp.put(target.getControllerId(), actionsProps);
|
|
||||||
sendUpdateMessageToTargets(actionProp, Collections.singletonList(target), softwareModules);
|
|
||||||
}
|
|
||||||
|
|
||||||
private void sendUpdateMessageToTargets(final Map<String, ActionProperties> actionsPropsByTargetId,
|
private void sendUpdateMessageToTargets(final Map<String, ActionProperties> actionsPropsByTargetId,
|
||||||
final List<Target> targets, final Map<SoftwareModule, List<SoftwareModuleMetadata>> softwareModules) {
|
final List<Target> targets, final Map<SoftwareModule, List<SoftwareModuleMetadata>> softwareModules) {
|
||||||
|
|
||||||
@@ -248,27 +489,6 @@ public class AmqpMessageDispatcherService extends BaseAmqpService {
|
|||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
protected void sendMultiActionRequestToTarget(final String tenant, final Target target, final List<Action> actions,
|
|
||||||
final Function<Action, Map<SoftwareModule, List<SoftwareModuleMetadata>>> getSoftwareModuleMetaData) {
|
|
||||||
|
|
||||||
final URI targetAddress = target.getAddress();
|
|
||||||
if (!IpUtil.isAmqpUri(targetAddress) || CollectionUtils.isEmpty(actions)) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
final DmfMultiActionRequest multiActionRequest = new DmfMultiActionRequest();
|
|
||||||
actions.forEach(action -> {
|
|
||||||
final DmfActionRequest actionRequest = createDmfActionRequest(target, action,
|
|
||||||
getSoftwareModuleMetaData.apply(action));
|
|
||||||
final int weight = deploymentManagement.getWeightConsideringDefault(action);
|
|
||||||
multiActionRequest.addElement(getEventTypeForAction(action), actionRequest, weight);
|
|
||||||
});
|
|
||||||
|
|
||||||
final Message message = getMessageConverter().toMessage(multiActionRequest,
|
|
||||||
createConnectorMessagePropertiesEvent(tenant, target.getControllerId(), EventTopic.MULTI_ACTION));
|
|
||||||
amqpSenderService.sendMessage(message, targetAddress);
|
|
||||||
}
|
|
||||||
|
|
||||||
private DmfActionRequest createDmfActionRequest(final Target target, final Action action,
|
private DmfActionRequest createDmfActionRequest(final Target target, final Action action,
|
||||||
final Map<SoftwareModule, List<SoftwareModuleMetadata>> softwareModules) {
|
final Map<SoftwareModule, List<SoftwareModuleMetadata>> softwareModules) {
|
||||||
if (action.isCancelingOrCanceled()) {
|
if (action.isCancelingOrCanceled()) {
|
||||||
@@ -279,137 +499,6 @@ public class AmqpMessageDispatcherService extends BaseAmqpService {
|
|||||||
return createDownloadAndUpdateRequest(target, action.getId(), softwareModules);
|
return createDownloadAndUpdateRequest(target, action.getId(), softwareModules);
|
||||||
}
|
}
|
||||||
|
|
||||||
private static DmfActionRequest createPlainActionRequest(final Action action) {
|
|
||||||
final DmfActionRequest actionRequest = new DmfActionRequest();
|
|
||||||
actionRequest.setActionId(action.getId());
|
|
||||||
return actionRequest;
|
|
||||||
}
|
|
||||||
|
|
||||||
protected DmfDownloadAndUpdateRequest createDownloadAndUpdateRequest(final Target target, final Long actionId,
|
|
||||||
final Map<SoftwareModule, List<SoftwareModuleMetadata>> softwareModules) {
|
|
||||||
final DmfDownloadAndUpdateRequest request = new DmfDownloadAndUpdateRequest();
|
|
||||||
request.setActionId(actionId);
|
|
||||||
request.setTargetSecurityToken(systemSecurityContext.runAsSystem(target::getSecurityToken));
|
|
||||||
|
|
||||||
if (softwareModules != null) {
|
|
||||||
softwareModules.entrySet()
|
|
||||||
.forEach(entry -> request.addSoftwareModule(convertToAmqpSoftwareModule(target, entry)));
|
|
||||||
}
|
|
||||||
return request;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Method to get the type of event depending on whether the action is a
|
|
||||||
* DOWNLOAD_ONLY action or if it has a valid maintenance window available or
|
|
||||||
* not based on defined maintenance schedule. In case of no maintenance
|
|
||||||
* schedule or if there is a valid window available, the topic
|
|
||||||
* {@link EventTopic#DOWNLOAD_AND_INSTALL} is returned else
|
|
||||||
* {@link EventTopic#DOWNLOAD} is returned.
|
|
||||||
*
|
|
||||||
* @param action
|
|
||||||
* current action properties.
|
|
||||||
*
|
|
||||||
* @return {@link EventTopic} to use for message.
|
|
||||||
*/
|
|
||||||
private static EventTopic getEventTypeForTarget(final ActionProperties action) {
|
|
||||||
if (action.isWaitingConfirmation()) {
|
|
||||||
return EventTopic.CONFIRM;
|
|
||||||
}
|
|
||||||
return (Action.ActionType.DOWNLOAD_ONLY == action.getActionType() || !action.isMaintenanceWindowAvailable())
|
|
||||||
? EventTopic.DOWNLOAD
|
|
||||||
: EventTopic.DOWNLOAD_AND_INSTALL;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Determines the {@link EventTopic} for the given {@link Action}, depending
|
|
||||||
* on its action type.
|
|
||||||
*
|
|
||||||
* @param action
|
|
||||||
* to obtain the corresponding {@link EventTopic} for
|
|
||||||
*
|
|
||||||
* @return the {@link EventTopic} for this action
|
|
||||||
*/
|
|
||||||
private static EventTopic getEventTypeForAction(final Action action) {
|
|
||||||
if (action.isCancelingOrCanceled()) {
|
|
||||||
return EventTopic.CANCEL_DOWNLOAD;
|
|
||||||
}
|
|
||||||
return getEventTypeForTarget(new ActionProperties(action));
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Method to send a message to a RabbitMQ Exchange after the assignment of
|
|
||||||
* the Distribution set to a Target has been canceled.
|
|
||||||
*
|
|
||||||
* @param cancelEvent
|
|
||||||
* that is to be converted to a DMF message
|
|
||||||
*/
|
|
||||||
@EventListener(classes = CancelTargetAssignmentEvent.class)
|
|
||||||
protected void targetCancelAssignmentToDistributionSet(final CancelTargetAssignmentEvent cancelEvent) {
|
|
||||||
if (!shouldBeProcessed(cancelEvent)) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
final List<Target> eventTargets = partitionedParallelExecution(cancelEvent.getActions().keySet(),
|
|
||||||
targetManagement::getByControllerID);
|
|
||||||
|
|
||||||
eventTargets.forEach(target -> {
|
|
||||||
cancelEvent.getActionPropertiesForController(target.getControllerId()).map(ActionProperties::getId)
|
|
||||||
.ifPresent(actionId -> {
|
|
||||||
sendCancelMessageToTarget(cancelEvent.getTenant(), target.getControllerId(), actionId,
|
|
||||||
target.getAddress());
|
|
||||||
});
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
private static <T, R> List<R> partitionedParallelExecution(final Collection<T> controllerIds,
|
|
||||||
final Function<Collection<T>, List<R>> loadingFunction) {
|
|
||||||
// Ensure not exceeding the max value of MAX_PROCESSING_SIZE
|
|
||||||
if (controllerIds.size() > MAX_PROCESSING_SIZE) {
|
|
||||||
// Split the provided collection
|
|
||||||
final Iterable<List<T>> partitions = ListUtils.partition(IterableUtils.toList(controllerIds), MAX_PROCESSING_SIZE);
|
|
||||||
// Preserve the security context because it gets lost when executing
|
|
||||||
// loading calls in new threads
|
|
||||||
final SecurityContext context = SecurityContextHolder.getContext();
|
|
||||||
// Handling remote request in parallel streams
|
|
||||||
return StreamSupport.stream(partitions.spliterator(), true) //
|
|
||||||
.flatMap(partition -> withSecurityContext(() -> loadingFunction.apply(partition), context).stream())
|
|
||||||
.collect(Collectors.toList());
|
|
||||||
}
|
|
||||||
return loadingFunction.apply(controllerIds);
|
|
||||||
}
|
|
||||||
|
|
||||||
private static <T> T withSecurityContext(final Supplier<T> callable, final SecurityContext securityContext) {
|
|
||||||
final SecurityContext oldContext = SecurityContextHolder.getContext();
|
|
||||||
try {
|
|
||||||
SecurityContextHolder.setContext(securityContext);
|
|
||||||
return callable.get();
|
|
||||||
} finally {
|
|
||||||
SecurityContextHolder.setContext(oldContext);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Method to send a message to a RabbitMQ Exchange after a Target was
|
|
||||||
* deleted.
|
|
||||||
*
|
|
||||||
* @param deleteEvent
|
|
||||||
* the TargetDeletedEvent which holds the necessary data for
|
|
||||||
* sending a target delete message.
|
|
||||||
*/
|
|
||||||
@EventListener(classes = TargetDeletedEvent.class)
|
|
||||||
protected void targetDelete(final TargetDeletedEvent deleteEvent) {
|
|
||||||
if (!shouldBeProcessed(deleteEvent)) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
sendDeleteMessage(deleteEvent.getTenant(), deleteEvent.getControllerId(), deleteEvent.getTargetAddress());
|
|
||||||
}
|
|
||||||
|
|
||||||
@EventListener(classes = TargetAttributesRequestedEvent.class)
|
|
||||||
protected void targetTriggerUpdateAttributes(final TargetAttributesRequestedEvent updateAttributesEvent) {
|
|
||||||
sendUpdateAttributesMessageToTarget(updateAttributesEvent.getTenant(), updateAttributesEvent.getControllerId(),
|
|
||||||
updateAttributesEvent.getTargetAddress());
|
|
||||||
}
|
|
||||||
|
|
||||||
private void sendSingleUpdateMessage(final ActionProperties action, final Target target,
|
private void sendSingleUpdateMessage(final ActionProperties action, final Target target,
|
||||||
final Map<SoftwareModule, List<SoftwareModuleMetadata>> modules) {
|
final Map<SoftwareModule, List<SoftwareModuleMetadata>> modules) {
|
||||||
|
|
||||||
@@ -434,17 +523,6 @@ public class AmqpMessageDispatcherService extends BaseAmqpService {
|
|||||||
amqpSenderService.sendMessage(message, targetAddress);
|
amqpSenderService.sendMessage(message, targetAddress);
|
||||||
}
|
}
|
||||||
|
|
||||||
protected void sendPingReponseToDmfReceiver(final Message ping, final String tenant, final String virtualHost) {
|
|
||||||
final Message message = MessageBuilder.withBody(String.valueOf(System.currentTimeMillis()).getBytes())
|
|
||||||
.setContentType(MessageProperties.CONTENT_TYPE_TEXT_PLAIN)
|
|
||||||
.setCorrelationId(ping.getMessageProperties().getCorrelationId())
|
|
||||||
.setHeader(MessageHeaderKey.TYPE, MessageType.PING_RESPONSE).setHeader(MessageHeaderKey.TENANT, tenant)
|
|
||||||
.build();
|
|
||||||
|
|
||||||
amqpSenderService.sendMessage(message,
|
|
||||||
IpUtil.createAmqpUri(virtualHost, ping.getMessageProperties().getReplyTo()));
|
|
||||||
}
|
|
||||||
|
|
||||||
private void sendDeleteMessage(final String tenant, final String controllerId, final String targetAddress) {
|
private void sendDeleteMessage(final String tenant, final String controllerId, final String targetAddress) {
|
||||||
if (!hasValidAddress(targetAddress)) {
|
if (!hasValidAddress(targetAddress)) {
|
||||||
return;
|
return;
|
||||||
@@ -459,10 +537,6 @@ public class AmqpMessageDispatcherService extends BaseAmqpService {
|
|||||||
return targetAddress != null && IpUtil.isAmqpUri(URI.create(targetAddress));
|
return targetAddress != null && IpUtil.isAmqpUri(URI.create(targetAddress));
|
||||||
}
|
}
|
||||||
|
|
||||||
protected boolean shouldBeProcessed(final RemoteApplicationEvent event) {
|
|
||||||
return isFromSelf(event);
|
|
||||||
}
|
|
||||||
|
|
||||||
private boolean isFromSelf(final RemoteApplicationEvent event) {
|
private boolean isFromSelf(final RemoteApplicationEvent event) {
|
||||||
return serviceMatcher == null || serviceMatcher.isFromSelf(event);
|
return serviceMatcher == null || serviceMatcher.isFromSelf(event);
|
||||||
}
|
}
|
||||||
@@ -471,22 +545,6 @@ public class AmqpMessageDispatcherService extends BaseAmqpService {
|
|||||||
return deploymentManagement.hasPendingCancellations(targetId);
|
return deploymentManagement.hasPendingCancellations(targetId);
|
||||||
}
|
}
|
||||||
|
|
||||||
protected void sendCancelMessageToTarget(final String tenant, final String controllerId, final Long actionId,
|
|
||||||
final URI address) {
|
|
||||||
if (!IpUtil.isAmqpUri(address)) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
final DmfActionRequest actionRequest = new DmfActionRequest();
|
|
||||||
actionRequest.setActionId(actionId);
|
|
||||||
|
|
||||||
final Message message = getMessageConverter().toMessage(actionRequest,
|
|
||||||
createConnectorMessagePropertiesEvent(tenant, controllerId, EventTopic.CANCEL_DOWNLOAD));
|
|
||||||
|
|
||||||
amqpSenderService.sendMessage(message, address);
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
private void sendUpdateAttributesMessageToTarget(final String tenant, final String controllerId,
|
private void sendUpdateAttributesMessageToTarget(final String tenant, final String controllerId,
|
||||||
final String targetAddress) {
|
final String targetAddress) {
|
||||||
if (!hasValidAddress(targetAddress)) {
|
if (!hasValidAddress(targetAddress)) {
|
||||||
@@ -499,30 +557,6 @@ public class AmqpMessageDispatcherService extends BaseAmqpService {
|
|||||||
amqpSenderService.sendMessage(message, URI.create(targetAddress));
|
amqpSenderService.sendMessage(message, URI.create(targetAddress));
|
||||||
}
|
}
|
||||||
|
|
||||||
private static MessageProperties createConnectorMessagePropertiesEvent(final String tenant,
|
|
||||||
final String controllerId, final EventTopic topic) {
|
|
||||||
final MessageProperties messageProperties = createConnectorMessageProperties(tenant, controllerId);
|
|
||||||
messageProperties.setHeader(MessageHeaderKey.TOPIC, topic);
|
|
||||||
messageProperties.setHeader(MessageHeaderKey.TYPE, MessageType.EVENT);
|
|
||||||
return messageProperties;
|
|
||||||
}
|
|
||||||
|
|
||||||
private static MessageProperties createConnectorMessagePropertiesDeleteThing(final String tenant,
|
|
||||||
final String controllerId) {
|
|
||||||
final MessageProperties messageProperties = createConnectorMessageProperties(tenant, controllerId);
|
|
||||||
messageProperties.setHeader(MessageHeaderKey.TYPE, MessageType.THING_DELETED);
|
|
||||||
return messageProperties;
|
|
||||||
}
|
|
||||||
|
|
||||||
private static MessageProperties createConnectorMessageProperties(final String tenant, final String controllerId) {
|
|
||||||
final MessageProperties messageProperties = new MessageProperties();
|
|
||||||
messageProperties.setContentType(MessageProperties.CONTENT_TYPE_JSON);
|
|
||||||
messageProperties.setHeader(MessageHeaderKey.CONTENT_TYPE, MessageProperties.CONTENT_TYPE_JSON);
|
|
||||||
messageProperties.setHeader(MessageHeaderKey.THING_ID, controllerId);
|
|
||||||
messageProperties.setHeader(MessageHeaderKey.TENANT, tenant);
|
|
||||||
return messageProperties;
|
|
||||||
}
|
|
||||||
|
|
||||||
private DmfSoftwareModule convertToAmqpSoftwareModule(final Target target,
|
private DmfSoftwareModule convertToAmqpSoftwareModule(final Target target,
|
||||||
final Entry<SoftwareModule, List<SoftwareModuleMetadata>> entry) {
|
final Entry<SoftwareModule, List<SoftwareModuleMetadata>> entry) {
|
||||||
final DmfSoftwareModule amqpSoftwareModule = new DmfSoftwareModule();
|
final DmfSoftwareModule amqpSoftwareModule = new DmfSoftwareModule();
|
||||||
@@ -558,9 +592,9 @@ public class AmqpMessageDispatcherService extends BaseAmqpService {
|
|||||||
final TenantMetaData metaData = systemManagement.getTenantMetadata();
|
final TenantMetaData metaData = systemManagement.getTenantMetadata();
|
||||||
artifact.setUrls(artifactUrlHandler
|
artifact.setUrls(artifactUrlHandler
|
||||||
.getUrls(new URLPlaceholder(metaData.getTenant(),
|
.getUrls(new URLPlaceholder(metaData.getTenant(),
|
||||||
metaData.getId(), target.getControllerId(), target.getId(),
|
metaData.getId(), target.getControllerId(), target.getId(),
|
||||||
new SoftwareData(localArtifact.getSoftwareModule().getId(), localArtifact.getFilename(),
|
new SoftwareData(localArtifact.getSoftwareModule().getId(), localArtifact.getFilename(),
|
||||||
localArtifact.getId(), localArtifact.getSha1Hash())),
|
localArtifact.getId(), localArtifact.getSha1Hash())),
|
||||||
ApiType.DMF)
|
ApiType.DMF)
|
||||||
.stream().collect(Collectors.toMap(ArtifactUrl::getProtocol, ArtifactUrl::getRef)));
|
.stream().collect(Collectors.toMap(ArtifactUrl::getProtocol, ArtifactUrl::getRef)));
|
||||||
|
|
||||||
@@ -608,55 +642,4 @@ public class AmqpMessageDispatcherService extends BaseAmqpService {
|
|||||||
createMessagePropertiesBatch(firstAction.getTenant(), getBatchEventTopicForAction(firstAction)));
|
createMessagePropertiesBatch(firstAction.getTenant(), getBatchEventTopicForAction(firstAction)));
|
||||||
amqpSenderService.sendMessage(message, firstTarget.getAddress());
|
amqpSenderService.sendMessage(message, firstTarget.getAddress());
|
||||||
}
|
}
|
||||||
|
|
||||||
protected DmfTarget convertToDmfTarget(final Target target, final Long actionId) {
|
|
||||||
final DmfTarget dmfTarget = new DmfTarget();
|
|
||||||
dmfTarget.setActionId(actionId);
|
|
||||||
dmfTarget.setControllerId(target.getControllerId());
|
|
||||||
dmfTarget.setTargetSecurityToken(systemSecurityContext.runAsSystem(target::getSecurityToken));
|
|
||||||
return dmfTarget;
|
|
||||||
}
|
|
||||||
|
|
||||||
private static MessageProperties createMessagePropertiesBatch(final String tenant, final EventTopic topic) {
|
|
||||||
final MessageProperties messageProperties = new MessageProperties();
|
|
||||||
messageProperties.setContentType(MessageProperties.CONTENT_TYPE_JSON);
|
|
||||||
messageProperties.setHeader(MessageHeaderKey.CONTENT_TYPE, MessageProperties.CONTENT_TYPE_JSON);
|
|
||||||
messageProperties.setHeader(MessageHeaderKey.TENANT, tenant);
|
|
||||||
|
|
||||||
messageProperties.setHeader(MessageHeaderKey.TOPIC, topic);
|
|
||||||
messageProperties.setHeader(MessageHeaderKey.TYPE, MessageType.EVENT);
|
|
||||||
return messageProperties;
|
|
||||||
}
|
|
||||||
|
|
||||||
public boolean isBatchAssignmentsEnabled() {
|
|
||||||
return systemSecurityContext.runAsSystem(() -> tenantConfigurationManagement
|
|
||||||
.getConfigurationValue(BATCH_ASSIGNMENTS_ENABLED, Boolean.class).getValue());
|
|
||||||
}
|
|
||||||
|
|
||||||
private static EventTopic getBatchEventTopicForAction(final ActionProperties action) {
|
|
||||||
return (Action.ActionType.DOWNLOAD_ONLY == action.getActionType() || !action.isMaintenanceWindowAvailable())
|
|
||||||
? EventTopic.BATCH_DOWNLOAD
|
|
||||||
: EventTopic.BATCH_DOWNLOAD_AND_INSTALL;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Creates a Confirmation request.
|
|
||||||
* @param target the target
|
|
||||||
* @param actionId the actionId
|
|
||||||
* @param softwareModules the software modules
|
|
||||||
* @return
|
|
||||||
*/
|
|
||||||
protected DmfConfirmRequest createConfirmRequest(final Target target, final Long actionId, final Map<SoftwareModule,
|
|
||||||
List<SoftwareModuleMetadata>> softwareModules) {
|
|
||||||
final DmfConfirmRequest request = new DmfConfirmRequest();
|
|
||||||
request.setActionId(actionId);
|
|
||||||
request.setTargetSecurityToken(systemSecurityContext.runAsSystem(target::getSecurityToken));
|
|
||||||
|
|
||||||
//Software modules can be filtered in the future exposing only the needed.
|
|
||||||
if (softwareModules != null) {
|
|
||||||
softwareModules.entrySet()
|
|
||||||
.forEach(entry -> request.addSoftwareModule(convertToAmqpSoftwareModule(target, entry)));
|
|
||||||
}
|
|
||||||
return request;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -73,38 +73,25 @@ import org.springframework.util.StringUtils;
|
|||||||
@Slf4j
|
@Slf4j
|
||||||
public class AmqpMessageHandlerService extends BaseAmqpService {
|
public class AmqpMessageHandlerService extends BaseAmqpService {
|
||||||
|
|
||||||
private final AmqpMessageDispatcherService amqpMessageDispatcherService;
|
|
||||||
|
|
||||||
private ControllerManagement controllerManagement;
|
|
||||||
private final ConfirmationManagement confirmationManagement;
|
|
||||||
|
|
||||||
private final EntityFactory entityFactory;
|
|
||||||
|
|
||||||
private final TenantConfigurationManagement tenantConfigurationManagement;
|
|
||||||
|
|
||||||
private final SystemSecurityContext systemSecurityContext;
|
|
||||||
|
|
||||||
private static final String THING_ID_NULL = "ThingId is null";
|
private static final String THING_ID_NULL = "ThingId is null";
|
||||||
|
|
||||||
private static final String EMPTY_MESSAGE_BODY = "\"\"";
|
private static final String EMPTY_MESSAGE_BODY = "\"\"";
|
||||||
|
private final AmqpMessageDispatcherService amqpMessageDispatcherService;
|
||||||
|
private final ConfirmationManagement confirmationManagement;
|
||||||
|
private final EntityFactory entityFactory;
|
||||||
|
private final TenantConfigurationManagement tenantConfigurationManagement;
|
||||||
|
private final SystemSecurityContext systemSecurityContext;
|
||||||
|
private ControllerManagement controllerManagement;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Constructor.
|
* Constructor.
|
||||||
*
|
*
|
||||||
* @param rabbitTemplate
|
* @param rabbitTemplate for converting messages
|
||||||
* for converting messages
|
* @param amqpMessageDispatcherService to sending events to DMF client
|
||||||
* @param amqpMessageDispatcherService
|
* @param controllerManagement for target repo access
|
||||||
* to sending events to DMF client
|
* @param entityFactory to create entities
|
||||||
* @param controllerManagement
|
* @param systemSecurityContext the system Security Context
|
||||||
* for target repo access
|
* @param tenantConfigurationManagement the tenant configuration Management
|
||||||
* @param entityFactory
|
* @param confirmationManagement the confirmation management
|
||||||
* to create entities
|
|
||||||
* @param systemSecurityContext
|
|
||||||
* the system Security Context
|
|
||||||
* @param tenantConfigurationManagement
|
|
||||||
* the tenant configuration Management
|
|
||||||
* @param confirmationManagement
|
|
||||||
* the confirmation management
|
|
||||||
*/
|
*/
|
||||||
public AmqpMessageHandlerService(final RabbitTemplate rabbitTemplate,
|
public AmqpMessageHandlerService(final RabbitTemplate rabbitTemplate,
|
||||||
final AmqpMessageDispatcherService amqpMessageDispatcherService,
|
final AmqpMessageDispatcherService amqpMessageDispatcherService,
|
||||||
@@ -123,12 +110,9 @@ public class AmqpMessageHandlerService extends BaseAmqpService {
|
|||||||
/**
|
/**
|
||||||
* Method to handle all incoming DMF amqp messages.
|
* Method to handle all incoming DMF amqp messages.
|
||||||
*
|
*
|
||||||
* @param message
|
* @param message incoming message
|
||||||
* incoming message
|
* @param type the message type
|
||||||
* @param type
|
* @param tenant the contentType of the message
|
||||||
* the message type
|
|
||||||
* @param tenant
|
|
||||||
* the contentType of the message
|
|
||||||
* @return a message if <null> no message is send back to sender
|
* @return a message if <null> no message is send back to sender
|
||||||
*/
|
*/
|
||||||
@RabbitListener(queues = "${hawkbit.dmf.rabbitmq.receiverQueue:dmf_receiver}", containerFactory = "listenerContainerFactory")
|
@RabbitListener(queues = "${hawkbit.dmf.rabbitmq.receiverQueue:dmf_receiver}", containerFactory = "listenerContainerFactory")
|
||||||
@@ -140,15 +124,11 @@ public class AmqpMessageHandlerService extends BaseAmqpService {
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* * Executed if a amqp message arrives.
|
* * Executed if a amqp message arrives.
|
||||||
*
|
*
|
||||||
* @param message
|
* @param message the message
|
||||||
* the message
|
* @param type the type
|
||||||
* @param type
|
* @param tenant the tenant
|
||||||
* the type
|
* @param virtualHost the virtual host
|
||||||
* @param tenant
|
|
||||||
* the tenant
|
|
||||||
* @param virtualHost
|
|
||||||
* the virtual host
|
|
||||||
* @return the rpc message back to supplier.
|
* @return the rpc message back to supplier.
|
||||||
*/
|
*/
|
||||||
public Message onMessage(final Message message, final String type, final String tenant, final String virtualHost) {
|
public Message onMessage(final Message message, final String type, final String tenant, final String virtualHost) {
|
||||||
@@ -160,28 +140,28 @@ public class AmqpMessageHandlerService extends BaseAmqpService {
|
|||||||
try {
|
try {
|
||||||
final MessageType messageType = MessageType.valueOf(type);
|
final MessageType messageType = MessageType.valueOf(type);
|
||||||
switch (messageType) {
|
switch (messageType) {
|
||||||
case THING_CREATED:
|
case THING_CREATED:
|
||||||
setTenantSecurityContext(tenant);
|
setTenantSecurityContext(tenant);
|
||||||
registerTarget(message, virtualHost);
|
registerTarget(message, virtualHost);
|
||||||
break;
|
break;
|
||||||
case THING_REMOVED:
|
case THING_REMOVED:
|
||||||
setTenantSecurityContext(tenant);
|
setTenantSecurityContext(tenant);
|
||||||
deleteTarget(message);
|
deleteTarget(message);
|
||||||
break;
|
break;
|
||||||
case EVENT:
|
case EVENT:
|
||||||
checkContentTypeJson(message);
|
checkContentTypeJson(message);
|
||||||
setTenantSecurityContext(tenant);
|
setTenantSecurityContext(tenant);
|
||||||
handleIncomingEvent(message);
|
handleIncomingEvent(message);
|
||||||
break;
|
break;
|
||||||
case PING:
|
case PING:
|
||||||
if (isCorrelationIdNotEmpty(message)) {
|
if (isCorrelationIdNotEmpty(message)) {
|
||||||
amqpMessageDispatcherService.sendPingReponseToDmfReceiver(message, tenant, virtualHost);
|
amqpMessageDispatcherService.sendPingReponseToDmfReceiver(message, tenant, virtualHost);
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
default:
|
default:
|
||||||
logAndThrowMessageError(message, "No handle method was found for the given message type.");
|
logAndThrowMessageError(message, "No handle method was found for the given message type.");
|
||||||
}
|
}
|
||||||
} catch(AssignmentQuotaExceededException ex) {
|
} catch (AssignmentQuotaExceededException ex) {
|
||||||
throw new AmqpRejectAndDontRequeueException("Could not handle message due to quota violation!", ex);
|
throw new AmqpRejectAndDontRequeueException("Could not handle message due to quota violation!", ex);
|
||||||
} catch (final IllegalArgumentException ex) {
|
} catch (final IllegalArgumentException ex) {
|
||||||
throw new AmqpRejectAndDontRequeueException("Invalid message!", ex);
|
throw new AmqpRejectAndDontRequeueException("Invalid message!", ex);
|
||||||
@@ -191,6 +171,11 @@ public class AmqpMessageHandlerService extends BaseAmqpService {
|
|||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// for testing
|
||||||
|
public void setControllerManagement(final ControllerManagement controllerManagement) {
|
||||||
|
this.controllerManagement = controllerManagement;
|
||||||
|
}
|
||||||
|
|
||||||
private static void setSecurityContext(final Authentication authentication) {
|
private static void setSecurityContext(final Authentication authentication) {
|
||||||
final SecurityContextImpl securityContextImpl = new SecurityContextImpl();
|
final SecurityContextImpl securityContextImpl = new SecurityContextImpl();
|
||||||
securityContextImpl.setAuthentication(authentication);
|
securityContextImpl.setAuthentication(authentication);
|
||||||
@@ -205,15 +190,92 @@ public class AmqpMessageHandlerService extends BaseAmqpService {
|
|||||||
setSecurityContext(authenticationToken);
|
setSecurityContext(authenticationToken);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private static boolean isOptionalMessageBodyEmpty(final Message message) {
|
||||||
|
// empty byte array message body is serialized to double-quoted string
|
||||||
|
// by message converter and should also be considered as empty
|
||||||
|
return isMessageBodyEmpty(message) || EMPTY_MESSAGE_BODY.equals(new String(message.getBody()));
|
||||||
|
}
|
||||||
|
|
||||||
|
private static boolean shouldTargetProceed(final Action action) {
|
||||||
|
return !action.isActive() || (action.hasMaintenanceSchedule() && action.isMaintenanceWindowAvailable());
|
||||||
|
}
|
||||||
|
|
||||||
|
private static boolean isCorrelationIdNotEmpty(final Message message) {
|
||||||
|
return StringUtils.hasLength(message.getMessageProperties().getCorrelationId());
|
||||||
|
}
|
||||||
|
|
||||||
|
// Exception squid:MethodCyclomaticComplexity - false positive, is a simple
|
||||||
|
// mapping
|
||||||
|
@SuppressWarnings("squid:MethodCyclomaticComplexity")
|
||||||
|
private static Status mapStatus(final Message message, final DmfActionUpdateStatus actionUpdateStatus,
|
||||||
|
final Action action) {
|
||||||
|
Status status = null;
|
||||||
|
switch (actionUpdateStatus.getActionStatus()) {
|
||||||
|
case DOWNLOAD:
|
||||||
|
status = Status.DOWNLOAD;
|
||||||
|
break;
|
||||||
|
case RETRIEVED:
|
||||||
|
status = Status.RETRIEVED;
|
||||||
|
break;
|
||||||
|
case RUNNING:
|
||||||
|
case CONFIRMED:
|
||||||
|
status = Status.RUNNING;
|
||||||
|
break;
|
||||||
|
case CANCELED:
|
||||||
|
status = Status.CANCELED;
|
||||||
|
break;
|
||||||
|
case FINISHED:
|
||||||
|
status = Status.FINISHED;
|
||||||
|
break;
|
||||||
|
case ERROR:
|
||||||
|
status = Status.ERROR;
|
||||||
|
break;
|
||||||
|
case WARNING:
|
||||||
|
status = Status.WARNING;
|
||||||
|
break;
|
||||||
|
case DOWNLOADED:
|
||||||
|
status = Status.DOWNLOADED;
|
||||||
|
break;
|
||||||
|
case CANCEL_REJECTED:
|
||||||
|
status = handleCancelRejectedState(message, action);
|
||||||
|
break;
|
||||||
|
case DENIED:
|
||||||
|
status = Status.WAIT_FOR_CONFIRMATION;
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
logAndThrowMessageError(message, "Status for action does not exisit.");
|
||||||
|
}
|
||||||
|
|
||||||
|
return status;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static Status handleCancelRejectedState(final Message message, final Action action) {
|
||||||
|
if (action.isCancelingOrCanceled()) {
|
||||||
|
return Status.CANCEL_REJECTED;
|
||||||
|
}
|
||||||
|
logAndThrowMessageError(message,
|
||||||
|
"Cancel rejected message is not allowed, if action is on state: " + action.getStatus());
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Retrieve the update mode from the given update message.
|
||||||
|
*/
|
||||||
|
private static UpdateMode getUpdateMode(final DmfAttributeUpdate update) {
|
||||||
|
final DmfUpdateMode mode = update.getMode();
|
||||||
|
if (mode != null) {
|
||||||
|
return UpdateMode.valueOf(mode.name());
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Method to create a new target or to find the target if it already exists
|
* Method to create a new target or to find the target if it already exists
|
||||||
* and update its poll time, status and optionally its name and attributes.
|
* and update its poll time, status and optionally its name and attributes.
|
||||||
*
|
*
|
||||||
* @param message
|
* @param message the message that contains replyTo property and optionally the
|
||||||
* the message that contains replyTo property and optionally the
|
* name and attributes in body
|
||||||
* name and attributes in body
|
* @param virtualHost the virtual host
|
||||||
* @param virtualHost
|
|
||||||
* the virtual host
|
|
||||||
*/
|
*/
|
||||||
private void registerTarget(final Message message, final String virtualHost) {
|
private void registerTarget(final Message message, final String virtualHost) {
|
||||||
final String thingId = getStringHeaderKey(message, MessageHeaderKey.THING_ID, THING_ID_NULL);
|
final String thingId = getStringHeaderKey(message, MessageHeaderKey.THING_ID, THING_ID_NULL);
|
||||||
@@ -235,7 +297,7 @@ public class AmqpMessageHandlerService extends BaseAmqpService {
|
|||||||
final DmfAttributeUpdate thingAttributeUpdateBody = thingCreateBody.getAttributeUpdate();
|
final DmfAttributeUpdate thingAttributeUpdateBody = thingCreateBody.getAttributeUpdate();
|
||||||
|
|
||||||
log.debug("Received \"THING_CREATED\" AMQP message for thing \"{}\" with target name \"{}\" and type " +
|
log.debug("Received \"THING_CREATED\" AMQP message for thing \"{}\" with target name \"{}\" and type " +
|
||||||
"\"{}\".", thingId, thingCreateBody.getName(), thingCreateBody.getType());
|
"\"{}\".", thingId, thingCreateBody.getName(), thingCreateBody.getType());
|
||||||
|
|
||||||
target = controllerManagement.findOrRegisterTargetIfItDoesNotExist(thingId, amqpUri,
|
target = controllerManagement.findOrRegisterTargetIfItDoesNotExist(thingId, amqpUri,
|
||||||
thingCreateBody.getName(), thingCreateBody.getType());
|
thingCreateBody.getName(), thingCreateBody.getType());
|
||||||
@@ -253,12 +315,6 @@ public class AmqpMessageHandlerService extends BaseAmqpService {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private static boolean isOptionalMessageBodyEmpty(final Message message) {
|
|
||||||
// empty byte array message body is serialized to double-quoted string
|
|
||||||
// by message converter and should also be considered as empty
|
|
||||||
return isMessageBodyEmpty(message) || EMPTY_MESSAGE_BODY.equals(new String(message.getBody()));
|
|
||||||
}
|
|
||||||
|
|
||||||
private void sendUpdateCommandToTarget(final Target target) {
|
private void sendUpdateCommandToTarget(final Target target) {
|
||||||
if (isMultiAssignmentsEnabled()) {
|
if (isMultiAssignmentsEnabled()) {
|
||||||
sendCurrentActionsAsMultiActionToTarget(target);
|
sendCurrentActionsAsMultiActionToTarget(target);
|
||||||
@@ -314,23 +370,22 @@ public class AmqpMessageHandlerService extends BaseAmqpService {
|
|||||||
/**
|
/**
|
||||||
* Method to handle the different topics to an event.
|
* Method to handle the different topics to an event.
|
||||||
*
|
*
|
||||||
* @param message
|
* @param message the incoming event message.
|
||||||
* the incoming event message.
|
|
||||||
*/
|
*/
|
||||||
private void handleIncomingEvent(final Message message) {
|
private void handleIncomingEvent(final Message message) {
|
||||||
switch (EventTopic.valueOf(getStringHeaderKey(message, MessageHeaderKey.TOPIC, "EventTopic is null"))) {
|
switch (EventTopic.valueOf(getStringHeaderKey(message, MessageHeaderKey.TOPIC, "EventTopic is null"))) {
|
||||||
case UPDATE_ACTION_STATUS:
|
case UPDATE_ACTION_STATUS:
|
||||||
updateActionStatus(message);
|
updateActionStatus(message);
|
||||||
break;
|
break;
|
||||||
case UPDATE_ATTRIBUTES:
|
case UPDATE_ATTRIBUTES:
|
||||||
updateAttributes(message);
|
updateAttributes(message);
|
||||||
break;
|
break;
|
||||||
case UPDATE_AUTO_CONFIRM:
|
case UPDATE_AUTO_CONFIRM:
|
||||||
setAutoConfirmationState(message);
|
setAutoConfirmationState(message);
|
||||||
break;
|
break;
|
||||||
default:
|
default:
|
||||||
logAndThrowMessageError(message, "Got event without appropriate topic.");
|
logAndThrowMessageError(message, "Got event without appropriate topic.");
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
@@ -345,7 +400,7 @@ public class AmqpMessageHandlerService extends BaseAmqpService {
|
|||||||
final String thingId = getStringHeaderKey(message, MessageHeaderKey.THING_ID, THING_ID_NULL);
|
final String thingId = getStringHeaderKey(message, MessageHeaderKey.THING_ID, THING_ID_NULL);
|
||||||
|
|
||||||
controllerManagement.updateControllerAttributes(thingId, attributeUpdate.getAttributes(),
|
controllerManagement.updateControllerAttributes(thingId, attributeUpdate.getAttributes(),
|
||||||
getUpdateMode(attributeUpdate));
|
getUpdateMode(attributeUpdate));
|
||||||
}
|
}
|
||||||
|
|
||||||
private void setAutoConfirmationState(final Message message) {
|
private void setAutoConfirmationState(final Message message) {
|
||||||
@@ -367,8 +422,7 @@ public class AmqpMessageHandlerService extends BaseAmqpService {
|
|||||||
/**
|
/**
|
||||||
* Method to update the action status of an action through the event.
|
* Method to update the action status of an action through the event.
|
||||||
*
|
*
|
||||||
* @param message
|
* @param message the object form the ampq message
|
||||||
* the object form the ampq message
|
|
||||||
*/
|
*/
|
||||||
private void updateActionStatus(final Message message) {
|
private void updateActionStatus(final Message message) {
|
||||||
final DmfActionUpdateStatus actionUpdateStatus = convertMessage(message, DmfActionUpdateStatus.class);
|
final DmfActionUpdateStatus actionUpdateStatus = convertMessage(message, DmfActionUpdateStatus.class);
|
||||||
@@ -408,68 +462,6 @@ public class AmqpMessageHandlerService extends BaseAmqpService {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private static boolean shouldTargetProceed(final Action action) {
|
|
||||||
return !action.isActive() || (action.hasMaintenanceSchedule() && action.isMaintenanceWindowAvailable());
|
|
||||||
}
|
|
||||||
|
|
||||||
private static boolean isCorrelationIdNotEmpty(final Message message) {
|
|
||||||
return StringUtils.hasLength(message.getMessageProperties().getCorrelationId());
|
|
||||||
}
|
|
||||||
|
|
||||||
// Exception squid:MethodCyclomaticComplexity - false positive, is a simple
|
|
||||||
// mapping
|
|
||||||
@SuppressWarnings("squid:MethodCyclomaticComplexity")
|
|
||||||
private static Status mapStatus(final Message message, final DmfActionUpdateStatus actionUpdateStatus,
|
|
||||||
final Action action) {
|
|
||||||
Status status = null;
|
|
||||||
switch (actionUpdateStatus.getActionStatus()) {
|
|
||||||
case DOWNLOAD:
|
|
||||||
status = Status.DOWNLOAD;
|
|
||||||
break;
|
|
||||||
case RETRIEVED:
|
|
||||||
status = Status.RETRIEVED;
|
|
||||||
break;
|
|
||||||
case RUNNING:
|
|
||||||
case CONFIRMED:
|
|
||||||
status = Status.RUNNING;
|
|
||||||
break;
|
|
||||||
case CANCELED:
|
|
||||||
status = Status.CANCELED;
|
|
||||||
break;
|
|
||||||
case FINISHED:
|
|
||||||
status = Status.FINISHED;
|
|
||||||
break;
|
|
||||||
case ERROR:
|
|
||||||
status = Status.ERROR;
|
|
||||||
break;
|
|
||||||
case WARNING:
|
|
||||||
status = Status.WARNING;
|
|
||||||
break;
|
|
||||||
case DOWNLOADED:
|
|
||||||
status = Status.DOWNLOADED;
|
|
||||||
break;
|
|
||||||
case CANCEL_REJECTED:
|
|
||||||
status = handleCancelRejectedState(message, action);
|
|
||||||
break;
|
|
||||||
case DENIED:
|
|
||||||
status = Status.WAIT_FOR_CONFIRMATION;
|
|
||||||
break;
|
|
||||||
default:
|
|
||||||
logAndThrowMessageError(message, "Status for action does not exisit.");
|
|
||||||
}
|
|
||||||
|
|
||||||
return status;
|
|
||||||
}
|
|
||||||
|
|
||||||
private static Status handleCancelRejectedState(final Message message, final Action action) {
|
|
||||||
if (action.isCancelingOrCanceled()) {
|
|
||||||
return Status.CANCEL_REJECTED;
|
|
||||||
}
|
|
||||||
logAndThrowMessageError(message,
|
|
||||||
"Cancel rejected message is not allowed, if action is on state: " + action.getStatus());
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Exception squid:S3655 - logAndThrowMessageError throws exception, i.e.
|
// Exception squid:S3655 - logAndThrowMessageError throws exception, i.e.
|
||||||
// get will not be called
|
// get will not be called
|
||||||
@SuppressWarnings("squid:S3655")
|
@SuppressWarnings("squid:S3655")
|
||||||
@@ -488,17 +480,6 @@ public class AmqpMessageHandlerService extends BaseAmqpService {
|
|||||||
return findActionWithDetails.get();
|
return findActionWithDetails.get();
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Retrieve the update mode from the given update message.
|
|
||||||
*/
|
|
||||||
private static UpdateMode getUpdateMode(final DmfAttributeUpdate update) {
|
|
||||||
final DmfUpdateMode mode = update.getMode();
|
|
||||||
if (mode != null) {
|
|
||||||
return UpdateMode.valueOf(mode.name());
|
|
||||||
}
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
private boolean isMultiAssignmentsEnabled() {
|
private boolean isMultiAssignmentsEnabled() {
|
||||||
return getConfigValue(MULTI_ASSIGNMENTS_ENABLED, Boolean.class);
|
return getConfigValue(MULTI_ASSIGNMENTS_ENABLED, Boolean.class);
|
||||||
}
|
}
|
||||||
@@ -507,9 +488,4 @@ public class AmqpMessageHandlerService extends BaseAmqpService {
|
|||||||
return systemSecurityContext
|
return systemSecurityContext
|
||||||
.runAsSystem(() -> tenantConfigurationManagement.getConfigurationValue(key, valueType).getValue());
|
.runAsSystem(() -> tenantConfigurationManagement.getConfigurationValue(key, valueType).getValue());
|
||||||
}
|
}
|
||||||
|
|
||||||
// for testing
|
|
||||||
public void setControllerManagement(final ControllerManagement controllerManagement) {
|
|
||||||
this.controllerManagement = controllerManagement;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -24,11 +24,9 @@ public interface AmqpMessageSenderService {
|
|||||||
/**
|
/**
|
||||||
* Send the given message to the given uri. The uri contains the (virtual)
|
* Send the given message to the given uri. The uri contains the (virtual)
|
||||||
* host and exchange e.g amqp://host/exchange.
|
* host and exchange e.g amqp://host/exchange.
|
||||||
*
|
*
|
||||||
* @param message
|
* @param message the amqp message
|
||||||
* the amqp message
|
* @param replyTo the reply to uri
|
||||||
* @param replyTo
|
|
||||||
* the reply to uri
|
|
||||||
*/
|
*/
|
||||||
void sendMessage(@NotNull final Message message, @NotNull final URI replyTo);
|
void sendMessage(@NotNull final Message message, @NotNull final URI replyTo);
|
||||||
}
|
}
|
||||||
@@ -15,7 +15,6 @@ import org.springframework.boot.context.properties.ConfigurationProperties;
|
|||||||
/**
|
/**
|
||||||
* Bean which holds the necessary properties for configuring the AMQP
|
* Bean which holds the necessary properties for configuring the AMQP
|
||||||
* connection.
|
* connection.
|
||||||
*
|
|
||||||
*/
|
*/
|
||||||
@Data
|
@Data
|
||||||
@ConfigurationProperties("hawkbit.dmf.rabbitmq")
|
@ConfigurationProperties("hawkbit.dmf.rabbitmq")
|
||||||
|
|||||||
@@ -32,29 +32,18 @@ public class BaseAmqpService {
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* Constructor.
|
* Constructor.
|
||||||
*
|
*
|
||||||
* @param rabbitTemplate
|
* @param rabbitTemplate the rabbit template.
|
||||||
* the rabbit template.
|
|
||||||
*/
|
*/
|
||||||
public BaseAmqpService(final RabbitTemplate rabbitTemplate) {
|
public BaseAmqpService(final RabbitTemplate rabbitTemplate) {
|
||||||
this.rabbitTemplate = rabbitTemplate;
|
this.rabbitTemplate = rabbitTemplate;
|
||||||
}
|
}
|
||||||
|
|
||||||
protected static void checkContentTypeJson(final Message message) {
|
|
||||||
final MessageProperties messageProperties = message.getMessageProperties();
|
|
||||||
if (messageProperties.getContentType() != null && messageProperties.getContentType().contains("json")) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
throw new AmqpRejectAndDontRequeueException("Content-Type is not JSON compatible");
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Is needed to convert a incoming message to is originally object type.
|
* Is needed to convert a incoming message to is originally object type.
|
||||||
*
|
*
|
||||||
* @param message
|
* @param message the message to convert.
|
||||||
* the message to convert.
|
* @param clazz the class of the originally object.
|
||||||
* @param clazz
|
|
||||||
* the class of the originally object.
|
|
||||||
* @return the converted object
|
* @return the converted object
|
||||||
*/
|
*/
|
||||||
@SuppressWarnings("unchecked")
|
@SuppressWarnings("unchecked")
|
||||||
@@ -65,14 +54,27 @@ public class BaseAmqpService {
|
|||||||
return (T) rabbitTemplate.getMessageConverter().fromMessage(message);
|
return (T) rabbitTemplate.getMessageConverter().fromMessage(message);
|
||||||
}
|
}
|
||||||
|
|
||||||
protected MessageConverter getMessageConverter() {
|
protected static void checkContentTypeJson(final Message message) {
|
||||||
return rabbitTemplate.getMessageConverter();
|
final MessageProperties messageProperties = message.getMessageProperties();
|
||||||
|
if (messageProperties.getContentType() != null && messageProperties.getContentType().contains("json")) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
throw new AmqpRejectAndDontRequeueException("Content-Type is not JSON compatible");
|
||||||
}
|
}
|
||||||
|
|
||||||
protected static boolean isMessageBodyEmpty(final Message message) {
|
protected static boolean isMessageBodyEmpty(final Message message) {
|
||||||
return message.getBody() == null || message.getBody().length == 0;
|
return message.getBody() == null || message.getBody().length == 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
protected static final void logAndThrowMessageError(final Message message, final String error) {
|
||||||
|
log.debug("Warning! \"{}\" reported by message: {}", error, message);
|
||||||
|
throw new AmqpRejectAndDontRequeueException(error);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected MessageConverter getMessageConverter() {
|
||||||
|
return rabbitTemplate.getMessageConverter();
|
||||||
|
}
|
||||||
|
|
||||||
protected void checkMessageBody(@NotNull final Message message) {
|
protected void checkMessageBody(@NotNull final Message message) {
|
||||||
if (isMessageBodyEmpty(message)) {
|
if (isMessageBodyEmpty(message)) {
|
||||||
throw new MessageConversionException("Message body cannot be null");
|
throw new MessageConversionException("Message body cannot be null");
|
||||||
@@ -89,20 +91,14 @@ public class BaseAmqpService {
|
|||||||
return value.toString();
|
return value.toString();
|
||||||
}
|
}
|
||||||
|
|
||||||
protected static final void logAndThrowMessageError(final Message message, final String error) {
|
|
||||||
log.debug("Warning! \"{}\" reported by message: {}", error, message);
|
|
||||||
throw new AmqpRejectAndDontRequeueException(error);
|
|
||||||
}
|
|
||||||
|
|
||||||
protected RabbitTemplate getRabbitTemplate() {
|
protected RabbitTemplate getRabbitTemplate() {
|
||||||
return rabbitTemplate;
|
return rabbitTemplate;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Clean message properties before sending a message.
|
* Clean message properties before sending a message.
|
||||||
*
|
*
|
||||||
* @param message
|
* @param message the message to cleaned up
|
||||||
* the message to cleaned up
|
|
||||||
*/
|
*/
|
||||||
protected void cleanMessageHeaderProperties(final Message message) {
|
protected void cleanMessageHeaderProperties(final Message message) {
|
||||||
message.getMessageProperties().getHeaders().remove(AbstractJavaTypeMapper.DEFAULT_CLASSID_FIELD_NAME);
|
message.getMessageProperties().getHeaders().remove(AbstractJavaTypeMapper.DEFAULT_CLASSID_FIELD_NAME);
|
||||||
|
|||||||
@@ -18,7 +18,6 @@ import org.springframework.util.ErrorHandler;
|
|||||||
/**
|
/**
|
||||||
* {@link RabbitListenerContainerFactory} that can be configured through
|
* {@link RabbitListenerContainerFactory} that can be configured through
|
||||||
* hawkBit's {@link AmqpProperties}.
|
* hawkBit's {@link AmqpProperties}.
|
||||||
*
|
|
||||||
*/
|
*/
|
||||||
public class ConfigurableRabbitListenerContainerFactory extends SimpleRabbitListenerContainerFactory {
|
public class ConfigurableRabbitListenerContainerFactory extends SimpleRabbitListenerContainerFactory {
|
||||||
|
|
||||||
@@ -26,14 +25,11 @@ public class ConfigurableRabbitListenerContainerFactory extends SimpleRabbitList
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* Constructor.
|
* Constructor.
|
||||||
*
|
*
|
||||||
* @param missingQueuesFatal
|
* @param missingQueuesFatal the missingQueuesFatal to set.
|
||||||
* the missingQueuesFatal to set.
|
* @param declarationRetries The number of retries
|
||||||
|
* @param errorHandler the error handler which should be use
|
||||||
* @see SimpleMessageListenerContainer#setMissingQueuesFatal
|
* @see SimpleMessageListenerContainer#setMissingQueuesFatal
|
||||||
* @param declarationRetries
|
|
||||||
* The number of retries
|
|
||||||
* @param errorHandler
|
|
||||||
* the error handler which should be use
|
|
||||||
*/
|
*/
|
||||||
public ConfigurableRabbitListenerContainerFactory(final boolean missingQueuesFatal, final int declarationRetries,
|
public ConfigurableRabbitListenerContainerFactory(final boolean missingQueuesFatal, final int declarationRetries,
|
||||||
final ErrorHandler errorHandler) {
|
final ErrorHandler errorHandler) {
|
||||||
|
|||||||
@@ -29,7 +29,7 @@ public class DefaultAmqpMessageSenderService extends BaseAmqpService implements
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* Constructor.
|
* Constructor.
|
||||||
*
|
*
|
||||||
* @param rabbitTemplate the AMQP template
|
* @param rabbitTemplate the AMQP template
|
||||||
*/
|
*/
|
||||||
public DefaultAmqpMessageSenderService(final RabbitTemplate rabbitTemplate) {
|
public DefaultAmqpMessageSenderService(final RabbitTemplate rabbitTemplate) {
|
||||||
|
|||||||
@@ -14,11 +14,11 @@ import java.util.concurrent.TimeUnit;
|
|||||||
import jakarta.validation.ConstraintViolationException;
|
import jakarta.validation.ConstraintViolationException;
|
||||||
|
|
||||||
import lombok.extern.slf4j.Slf4j;
|
import lombok.extern.slf4j.Slf4j;
|
||||||
|
import org.eclipse.hawkbit.repository.exception.AssignmentQuotaExceededException;
|
||||||
import org.eclipse.hawkbit.repository.exception.CancelActionNotAllowedException;
|
import org.eclipse.hawkbit.repository.exception.CancelActionNotAllowedException;
|
||||||
import org.eclipse.hawkbit.repository.exception.EntityNotFoundException;
|
import org.eclipse.hawkbit.repository.exception.EntityNotFoundException;
|
||||||
import org.eclipse.hawkbit.repository.exception.InvalidTargetAddressException;
|
import org.eclipse.hawkbit.repository.exception.InvalidTargetAddressException;
|
||||||
import org.eclipse.hawkbit.repository.exception.InvalidTargetAttributeException;
|
import org.eclipse.hawkbit.repository.exception.InvalidTargetAttributeException;
|
||||||
import org.eclipse.hawkbit.repository.exception.AssignmentQuotaExceededException;
|
|
||||||
import org.eclipse.hawkbit.repository.exception.TenantNotExistException;
|
import org.eclipse.hawkbit.repository.exception.TenantNotExistException;
|
||||||
import org.springframework.amqp.rabbit.listener.ConditionalRejectingErrorHandler;
|
import org.springframework.amqp.rabbit.listener.ConditionalRejectingErrorHandler;
|
||||||
import org.springframework.amqp.rabbit.listener.FatalExceptionStrategy;
|
import org.springframework.amqp.rabbit.listener.FatalExceptionStrategy;
|
||||||
|
|||||||
@@ -10,6 +10,7 @@
|
|||||||
package org.eclipse.hawkbit.amqp;
|
package org.eclipse.hawkbit.amqp;
|
||||||
|
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
|
||||||
import jakarta.validation.constraints.NotNull;
|
import jakarta.validation.constraints.NotNull;
|
||||||
|
|
||||||
import lombok.extern.slf4j.Slf4j;
|
import lombok.extern.slf4j.Slf4j;
|
||||||
@@ -21,16 +22,15 @@ import org.springframework.util.ErrorHandler;
|
|||||||
*/
|
*/
|
||||||
@Slf4j
|
@Slf4j
|
||||||
public class DelegatingConditionalErrorHandler implements ErrorHandler {
|
public class DelegatingConditionalErrorHandler implements ErrorHandler {
|
||||||
|
|
||||||
private final List<AmqpErrorHandler> handlers;
|
private final List<AmqpErrorHandler> handlers;
|
||||||
private final ErrorHandler defaultHandler;
|
private final ErrorHandler defaultHandler;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Constructor
|
* Constructor
|
||||||
*
|
*
|
||||||
* @param handlers
|
* @param handlers {@link List} of error handlers
|
||||||
* {@link List} of error handlers
|
* @param defaultHandler the default error handler
|
||||||
* @param defaultHandler
|
|
||||||
* the default error handler
|
|
||||||
*/
|
*/
|
||||||
public DelegatingConditionalErrorHandler(final List<AmqpErrorHandler> handlers, @NotNull final ErrorHandler defaultHandler) {
|
public DelegatingConditionalErrorHandler(final List<AmqpErrorHandler> handlers, @NotNull final ErrorHandler defaultHandler) {
|
||||||
this.handlers = handlers;
|
this.handlers = handlers;
|
||||||
@@ -53,7 +53,7 @@ public class DelegatingConditionalErrorHandler implements ErrorHandler {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private boolean includesAmqpRejectException(final Throwable t) {
|
private boolean includesAmqpRejectException(final Throwable t) {
|
||||||
if (t instanceof AmqpRejectAndDontRequeueException){
|
if (t instanceof AmqpRejectAndDontRequeueException) {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
if (t.getCause() != null) {
|
if (t.getCause() != null) {
|
||||||
|
|||||||
@@ -30,11 +30,11 @@ public class MessageConversionExceptionHandler extends AbstractAmqpErrorHandler<
|
|||||||
//since the detailed error message lies in the first parent of current throwable we retrieve it
|
//since the detailed error message lies in the first parent of current throwable we retrieve it
|
||||||
// and append it to the errorMessage
|
// and append it to the errorMessage
|
||||||
final Optional<String> detailedErrorMessage = getFirstAncestralErrorMessage(throwable.getCause());
|
final Optional<String> detailedErrorMessage = getFirstAncestralErrorMessage(throwable.getCause());
|
||||||
return detailedErrorMessage.isPresent()? (detailedErrorMessage.get() + errorMessage) : errorMessage;
|
return detailedErrorMessage.isPresent() ? (detailedErrorMessage.get() + errorMessage) : errorMessage;
|
||||||
}
|
}
|
||||||
|
|
||||||
private Optional<String> getFirstAncestralErrorMessage(final Throwable throwable) {
|
private Optional<String> getFirstAncestralErrorMessage(final Throwable throwable) {
|
||||||
if(throwable.getCause() instanceof InvalidFormatException) {
|
if (throwable.getCause() instanceof InvalidFormatException) {
|
||||||
return Optional.of(throwable.getCause().getMessage());
|
return Optional.of(throwable.getCause().getMessage());
|
||||||
}
|
}
|
||||||
return Optional.empty();
|
return Optional.empty();
|
||||||
|
|||||||
@@ -23,6 +23,9 @@ import java.util.List;
|
|||||||
import java.util.Optional;
|
import java.util.Optional;
|
||||||
import java.util.UUID;
|
import java.util.UUID;
|
||||||
|
|
||||||
|
import io.qameta.allure.Description;
|
||||||
|
import io.qameta.allure.Feature;
|
||||||
|
import io.qameta.allure.Story;
|
||||||
import org.eclipse.hawkbit.api.ArtifactUrl;
|
import org.eclipse.hawkbit.api.ArtifactUrl;
|
||||||
import org.eclipse.hawkbit.api.ArtifactUrlHandler;
|
import org.eclipse.hawkbit.api.ArtifactUrlHandler;
|
||||||
import org.eclipse.hawkbit.artifact.repository.ArtifactFilesystem;
|
import org.eclipse.hawkbit.artifact.repository.ArtifactFilesystem;
|
||||||
@@ -33,7 +36,6 @@ import org.eclipse.hawkbit.dmf.amqp.api.MessageHeaderKey;
|
|||||||
import org.eclipse.hawkbit.dmf.amqp.api.MessageType;
|
import org.eclipse.hawkbit.dmf.amqp.api.MessageType;
|
||||||
import org.eclipse.hawkbit.dmf.json.model.DmfActionRequest;
|
import org.eclipse.hawkbit.dmf.json.model.DmfActionRequest;
|
||||||
import org.eclipse.hawkbit.dmf.json.model.DmfDownloadAndUpdateRequest;
|
import org.eclipse.hawkbit.dmf.json.model.DmfDownloadAndUpdateRequest;
|
||||||
import org.eclipse.hawkbit.dmf.json.model.DmfMetadata;
|
|
||||||
import org.eclipse.hawkbit.dmf.json.model.DmfSoftwareModule;
|
import org.eclipse.hawkbit.dmf.json.model.DmfSoftwareModule;
|
||||||
import org.eclipse.hawkbit.repository.SystemManagement;
|
import org.eclipse.hawkbit.repository.SystemManagement;
|
||||||
import org.eclipse.hawkbit.repository.event.remote.CancelTargetAssignmentEvent;
|
import org.eclipse.hawkbit.repository.event.remote.CancelTargetAssignmentEvent;
|
||||||
@@ -62,10 +64,6 @@ import org.springframework.amqp.support.converter.Jackson2JsonMessageConverter;
|
|||||||
import org.springframework.boot.test.context.SpringBootTest;
|
import org.springframework.boot.test.context.SpringBootTest;
|
||||||
import org.springframework.test.context.ActiveProfiles;
|
import org.springframework.test.context.ActiveProfiles;
|
||||||
|
|
||||||
import io.qameta.allure.Description;
|
|
||||||
import io.qameta.allure.Feature;
|
|
||||||
import io.qameta.allure.Story;
|
|
||||||
|
|
||||||
@ActiveProfiles({ "test" })
|
@ActiveProfiles({ "test" })
|
||||||
@Feature("Component Tests - Device Management Federation API")
|
@Feature("Component Tests - Device Management Federation API")
|
||||||
@Story("AmqpMessage Dispatcher Service Test")
|
@Story("AmqpMessage Dispatcher Service Test")
|
||||||
@@ -117,14 +115,10 @@ class AmqpMessageDispatcherServiceTest extends AbstractIntegrationTest {
|
|||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private Message getCaptureAddressEvent(final TargetAssignDistributionSetEvent targetAssignDistributionSetEvent) {
|
protected Message createArgumentCapture(final URI uri) {
|
||||||
final Target target = targetManagement
|
final ArgumentCaptor<Message> argumentCaptor = ArgumentCaptor.forClass(Message.class);
|
||||||
.getByControllerID(targetAssignDistributionSetEvent.getActions().keySet().iterator().next()).get();
|
Mockito.verify(senderService).sendMessage(argumentCaptor.capture(), eq(uri));
|
||||||
return createArgumentCapture(target.getAddress());
|
return argumentCaptor.getValue();
|
||||||
}
|
|
||||||
|
|
||||||
private Action createAction(final DistributionSet testDs) {
|
|
||||||
return getFirstAssignedAction(assignDistributionSet(testDs, testTarget));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
@@ -295,6 +289,16 @@ class AmqpMessageDispatcherServiceTest extends AbstractIntegrationTest {
|
|||||||
Mockito.verifyNoInteractions(senderService);
|
Mockito.verifyNoInteractions(senderService);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private Message getCaptureAddressEvent(final TargetAssignDistributionSetEvent targetAssignDistributionSetEvent) {
|
||||||
|
final Target target = targetManagement
|
||||||
|
.getByControllerID(targetAssignDistributionSetEvent.getActions().keySet().iterator().next()).get();
|
||||||
|
return createArgumentCapture(target.getAddress());
|
||||||
|
}
|
||||||
|
|
||||||
|
private Action createAction(final DistributionSet testDs) {
|
||||||
|
return getFirstAssignedAction(assignDistributionSet(testDs, testTarget));
|
||||||
|
}
|
||||||
|
|
||||||
private void assertCancelMessage(final Message sendMessage) {
|
private void assertCancelMessage(final Message sendMessage) {
|
||||||
assertEventMessage(sendMessage);
|
assertEventMessage(sendMessage);
|
||||||
final DmfActionRequest actionId = convertMessage(sendMessage, DmfActionRequest.class);
|
final DmfActionRequest actionId = convertMessage(sendMessage, DmfActionRequest.class);
|
||||||
@@ -313,7 +317,7 @@ class AmqpMessageDispatcherServiceTest extends AbstractIntegrationTest {
|
|||||||
assertThat(sendMessage.getMessageProperties().getHeaders())
|
assertThat(sendMessage.getMessageProperties().getHeaders())
|
||||||
.containsEntry(MessageHeaderKey.TENANT, TENANT);
|
.containsEntry(MessageHeaderKey.TENANT, TENANT);
|
||||||
assertThat(sendMessage.getMessageProperties().getHeaders())
|
assertThat(sendMessage.getMessageProperties().getHeaders())
|
||||||
.containsEntry(MessageHeaderKey.TYPE,MessageType.THING_DELETED);
|
.containsEntry(MessageHeaderKey.TYPE, MessageType.THING_DELETED);
|
||||||
}
|
}
|
||||||
|
|
||||||
private DmfDownloadAndUpdateRequest assertDownloadAndInstallMessage(final Message sendMessage, final Long action) {
|
private DmfDownloadAndUpdateRequest assertDownloadAndInstallMessage(final Message sendMessage, final Long action) {
|
||||||
@@ -323,7 +327,7 @@ class AmqpMessageDispatcherServiceTest extends AbstractIntegrationTest {
|
|||||||
assertThat(downloadAndUpdateRequest.getActionId()).isEqualTo(action);
|
assertThat(downloadAndUpdateRequest.getActionId()).isEqualTo(action);
|
||||||
assertThat(sendMessage.getMessageProperties().getHeaders())
|
assertThat(sendMessage.getMessageProperties().getHeaders())
|
||||||
.as("The topic of the event should contain DOWNLOAD_AND_INSTALL")
|
.as("The topic of the event should contain DOWNLOAD_AND_INSTALL")
|
||||||
.containsEntry(MessageHeaderKey.TOPIC,EventTopic.DOWNLOAD_AND_INSTALL);
|
.containsEntry(MessageHeaderKey.TOPIC, EventTopic.DOWNLOAD_AND_INSTALL);
|
||||||
assertThat(downloadAndUpdateRequest.getTargetSecurityToken())
|
assertThat(downloadAndUpdateRequest.getTargetSecurityToken())
|
||||||
.as("Security token of target")
|
.as("Security token of target")
|
||||||
.isEqualTo(TEST_TOKEN);
|
.isEqualTo(TEST_TOKEN);
|
||||||
@@ -346,18 +350,12 @@ class AmqpMessageDispatcherServiceTest extends AbstractIntegrationTest {
|
|||||||
.containsEntry(MessageHeaderKey.THING_ID, CONTROLLER_ID);
|
.containsEntry(MessageHeaderKey.THING_ID, CONTROLLER_ID);
|
||||||
assertThat(sendMessage.getMessageProperties().getHeaders())
|
assertThat(sendMessage.getMessageProperties().getHeaders())
|
||||||
.as("The value of the message header TYPE should be EVENT")
|
.as("The value of the message header TYPE should be EVENT")
|
||||||
.containsEntry(MessageHeaderKey.TYPE,MessageType.EVENT);
|
.containsEntry(MessageHeaderKey.TYPE, MessageType.EVENT);
|
||||||
assertThat(sendMessage.getMessageProperties().getContentType())
|
assertThat(sendMessage.getMessageProperties().getContentType())
|
||||||
.as("The content type message should be " + MessageProperties.CONTENT_TYPE_JSON)
|
.as("The content type message should be " + MessageProperties.CONTENT_TYPE_JSON)
|
||||||
.isEqualTo(MessageProperties.CONTENT_TYPE_JSON);
|
.isEqualTo(MessageProperties.CONTENT_TYPE_JSON);
|
||||||
}
|
}
|
||||||
|
|
||||||
protected Message createArgumentCapture(final URI uri) {
|
|
||||||
final ArgumentCaptor<Message> argumentCaptor = ArgumentCaptor.forClass(Message.class);
|
|
||||||
Mockito.verify(senderService).sendMessage(argumentCaptor.capture(), eq(uri));
|
|
||||||
return argumentCaptor.getValue();
|
|
||||||
}
|
|
||||||
|
|
||||||
@SuppressWarnings("unchecked")
|
@SuppressWarnings("unchecked")
|
||||||
private <T> T convertMessage(final Message message, final Class<T> clazz) {
|
private <T> T convertMessage(final Message message, final Class<T> clazz) {
|
||||||
message.getMessageProperties().getHeaders().put(AbstractJavaTypeMapper.DEFAULT_CLASSID_FIELD_NAME,
|
message.getMessageProperties().getHeaders().put(AbstractJavaTypeMapper.DEFAULT_CLASSID_FIELD_NAME,
|
||||||
|
|||||||
@@ -26,6 +26,10 @@ import java.net.URI;
|
|||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
import java.util.Optional;
|
import java.util.Optional;
|
||||||
|
|
||||||
|
import io.qameta.allure.Description;
|
||||||
|
import io.qameta.allure.Feature;
|
||||||
|
import io.qameta.allure.Step;
|
||||||
|
import io.qameta.allure.Story;
|
||||||
import org.eclipse.hawkbit.dmf.amqp.api.EventTopic;
|
import org.eclipse.hawkbit.dmf.amqp.api.EventTopic;
|
||||||
import org.eclipse.hawkbit.dmf.amqp.api.MessageHeaderKey;
|
import org.eclipse.hawkbit.dmf.amqp.api.MessageHeaderKey;
|
||||||
import org.eclipse.hawkbit.dmf.amqp.api.MessageType;
|
import org.eclipse.hawkbit.dmf.amqp.api.MessageType;
|
||||||
@@ -71,11 +75,6 @@ import org.springframework.amqp.support.converter.Jackson2JsonMessageConverter;
|
|||||||
import org.springframework.amqp.support.converter.MessageConversionException;
|
import org.springframework.amqp.support.converter.MessageConversionException;
|
||||||
import org.springframework.amqp.support.converter.MessageConverter;
|
import org.springframework.amqp.support.converter.MessageConverter;
|
||||||
|
|
||||||
import io.qameta.allure.Description;
|
|
||||||
import io.qameta.allure.Feature;
|
|
||||||
import io.qameta.allure.Step;
|
|
||||||
import io.qameta.allure.Story;
|
|
||||||
|
|
||||||
@ExtendWith(MockitoExtension.class)
|
@ExtendWith(MockitoExtension.class)
|
||||||
@Feature("Component Tests - Device Management Federation API")
|
@Feature("Component Tests - Device Management Federation API")
|
||||||
@Story("AmqpMessage Handler Service Test")
|
@Story("AmqpMessage Handler Service Test")
|
||||||
@@ -168,49 +167,6 @@ public class AmqpMessageHandlerServiceTest {
|
|||||||
assertReplyToCapturedField("MyTest");
|
assertReplyToCapturedField("MyTest");
|
||||||
}
|
}
|
||||||
|
|
||||||
@Step
|
|
||||||
private void processThingCreatedMessage(final String thingId, final DmfCreateThing payload) {
|
|
||||||
final MessageProperties messageProperties = getThingCreatedMessageProperties(thingId);
|
|
||||||
final Message message = createMessage(payload != null ? payload : new byte[0], messageProperties);
|
|
||||||
|
|
||||||
final Target targetMock = mock(Target.class);
|
|
||||||
if (payload == null) {
|
|
||||||
when(controllerManagementMock.findOrRegisterTargetIfItDoesNotExist(targetIdCaptor.capture(),
|
|
||||||
uriCaptor.capture())).thenReturn(targetMock);
|
|
||||||
} else {
|
|
||||||
when(controllerManagementMock.findOrRegisterTargetIfItDoesNotExist(targetIdCaptor.capture(),
|
|
||||||
uriCaptor.capture(), targetNameCaptor.capture(), targetTypeNameCaptor.capture()))
|
|
||||||
.thenReturn(targetMock);
|
|
||||||
if (payload.getAttributeUpdate() != null) {
|
|
||||||
when(controllerManagementMock.updateControllerAttributes(targetIdCaptor.capture(),
|
|
||||||
attributesCaptor.capture(), modeCaptor.capture())).thenReturn(null);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
when(controllerManagementMock.findActiveActionWithHighestWeight(any())).thenReturn(Optional.empty());
|
|
||||||
|
|
||||||
amqpMessageHandlerService.onMessage(message, MessageType.THING_CREATED.name(), TENANT, VIRTUAL_HOST);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Step
|
|
||||||
private void assertThingIdCapturedField(final String thingId) {
|
|
||||||
assertThat(targetIdCaptor.getValue()).as("Thing id is wrong").isEqualTo(thingId);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Step
|
|
||||||
private void assertReplyToCapturedField(final String replyTo) {
|
|
||||||
assertThat(uriCaptor.getValue()).as("Uri is not right").hasToString("amqp://" + VIRTUAL_HOST + "/" + replyTo);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Step
|
|
||||||
private void assertInitiatorCapturedField(final String initiator) {
|
|
||||||
assertThat(initiatorCaptor.getValue()).as("Initiator is wrong").isEqualTo(initiator);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Step
|
|
||||||
private void assertRemarkCapturedField(final String remark) {
|
|
||||||
assertThat(remarkCaptor.getValue()).as("Remark is wrong").isEqualTo(remark);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
@Description("Tests the creation of a target/thing with specified name by calling the same method that incoming RabbitMQ messages would access.")
|
@Description("Tests the creation of a target/thing with specified name by calling the same method that incoming RabbitMQ messages would access.")
|
||||||
public void createThingWithName() {
|
public void createThingWithName() {
|
||||||
@@ -227,11 +183,6 @@ public class AmqpMessageHandlerServiceTest {
|
|||||||
assertThingNameCapturedField(knownThingName);
|
assertThingNameCapturedField(knownThingName);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Step
|
|
||||||
private void assertThingNameCapturedField(final String thingName) {
|
|
||||||
assertThat(targetNameCaptor.getValue()).as("Thing name is wrong").isEqualTo(thingName);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
@Description("Tests the creation of a target/thing with specified type name by calling the same method that incoming RabbitMQ messages would access.")
|
@Description("Tests the creation of a target/thing with specified type name by calling the same method that incoming RabbitMQ messages would access.")
|
||||||
public void createThingWithType() {
|
public void createThingWithType() {
|
||||||
@@ -248,11 +199,6 @@ public class AmqpMessageHandlerServiceTest {
|
|||||||
assertThingTypeCapturedField(knownThingTypeName);
|
assertThingTypeCapturedField(knownThingTypeName);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Step
|
|
||||||
private void assertThingTypeCapturedField(final String thingType) {
|
|
||||||
assertThat(targetTypeNameCaptor.getValue()).as("Thing type is wrong").isEqualTo(thingType);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
@Description("Tests not allowed body in message")
|
@Description("Tests not allowed body in message")
|
||||||
public void createThingWithWrongBody() {
|
public void createThingWithWrongBody() {
|
||||||
@@ -284,11 +230,6 @@ public class AmqpMessageHandlerServiceTest {
|
|||||||
assertThingAttributesCapturedField(attributeUpdate.getAttributes());
|
assertThingAttributesCapturedField(attributeUpdate.getAttributes());
|
||||||
}
|
}
|
||||||
|
|
||||||
@Step
|
|
||||||
private void assertThingAttributesCapturedField(final Map<String, String> attributes) {
|
|
||||||
assertThat(attributesCaptor.getValue()).as("Attributes is not right").isEqualTo(attributes);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
@Description("Tests the creation of a target/thing with specified name and attributes by calling the same method that incoming RabbitMQ messages would access.")
|
@Description("Tests the creation of a target/thing with specified name and attributes by calling the same method that incoming RabbitMQ messages would access.")
|
||||||
public void createThingWithNameAndAttributes() {
|
public void createThingWithNameAndAttributes() {
|
||||||
@@ -312,11 +253,6 @@ public class AmqpMessageHandlerServiceTest {
|
|||||||
assertThingAttributesModeCapturedField(UpdateMode.REPLACE);
|
assertThingAttributesModeCapturedField(UpdateMode.REPLACE);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Step
|
|
||||||
private void assertThingAttributesModeCapturedField(final UpdateMode attributesUpdateMode) {
|
|
||||||
assertThat(modeCaptor.getValue()).as("Attributes update mode is not right").isEqualTo(attributesUpdateMode);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
@Description("Tests the target attribute update by calling the same method that incoming RabbitMQ messages would access.")
|
@Description("Tests the target attribute update by calling the same method that incoming RabbitMQ messages would access.")
|
||||||
public void updateAttributes() {
|
public void updateAttributes() {
|
||||||
@@ -571,6 +507,35 @@ public class AmqpMessageHandlerServiceTest {
|
|||||||
.contains("Device reported status code: 12");
|
.contains("Device reported status code: 12");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
@Description("Tests the deletion of a target/thing, requested by the target itself.")
|
||||||
|
public void deleteThing() {
|
||||||
|
// prepare valid message
|
||||||
|
final String knownThingId = "1";
|
||||||
|
final MessageProperties messageProperties = createMessageProperties(MessageType.THING_REMOVED);
|
||||||
|
messageProperties.setHeader(MessageHeaderKey.THING_ID, knownThingId);
|
||||||
|
final Message message = createMessage(new byte[0], messageProperties);
|
||||||
|
|
||||||
|
// test
|
||||||
|
amqpMessageHandlerService.onMessage(message, MessageType.THING_REMOVED.name(), TENANT, VIRTUAL_HOST);
|
||||||
|
|
||||||
|
// verify
|
||||||
|
verify(controllerManagementMock).deleteExistingTarget(knownThingId);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
@Description("Tests the deletion of a target/thing with missing thingId")
|
||||||
|
public void deleteThingWithoutThingId() {
|
||||||
|
// prepare invalid message
|
||||||
|
final MessageProperties messageProperties = createMessageProperties(MessageType.THING_REMOVED);
|
||||||
|
final Message message = createMessage(new byte[0], messageProperties);
|
||||||
|
|
||||||
|
assertThatExceptionOfType(AmqpRejectAndDontRequeueException.class)
|
||||||
|
.as(FAIL_MESSAGE_AMQP_REJECT_REASON + "since no thingId was set")
|
||||||
|
.isThrownBy(() -> amqpMessageHandlerService.onMessage(message, MessageType.THING_REMOVED.name(), TENANT,
|
||||||
|
VIRTUAL_HOST));
|
||||||
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
@Description("Tests activating auto-confirmation on a target.")
|
@Description("Tests activating auto-confirmation on a target.")
|
||||||
void setAutoConfirmationStateActive() {
|
void setAutoConfirmationStateActive() {
|
||||||
@@ -589,7 +554,7 @@ public class AmqpMessageHandlerServiceTest {
|
|||||||
final Message message = createMessage(autoConfirmation, messageProperties);
|
final Message message = createMessage(autoConfirmation, messageProperties);
|
||||||
|
|
||||||
when(controllerManagementMock.activateAutoConfirmation(targetIdCaptor.capture(), initiatorCaptor.capture(),
|
when(controllerManagementMock.activateAutoConfirmation(targetIdCaptor.capture(), initiatorCaptor.capture(),
|
||||||
remarkCaptor.capture())).thenReturn(null);
|
remarkCaptor.capture())).thenReturn(null);
|
||||||
|
|
||||||
amqpMessageHandlerService.onMessage(message, MessageType.EVENT.name(), TENANT, VIRTUAL_HOST);
|
amqpMessageHandlerService.onMessage(message, MessageType.EVENT.name(), TENANT, VIRTUAL_HOST);
|
||||||
|
|
||||||
@@ -600,7 +565,6 @@ public class AmqpMessageHandlerServiceTest {
|
|||||||
assertRemarkCapturedField(remark);
|
assertRemarkCapturedField(remark);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
@Description("Tests deactivating auto-confirmation on a target.")
|
@Description("Tests deactivating auto-confirmation on a target.")
|
||||||
void setAutoConfirmationStateDeactivated() {
|
void setAutoConfirmationStateDeactivated() {
|
||||||
@@ -621,6 +585,69 @@ public class AmqpMessageHandlerServiceTest {
|
|||||||
assertThingIdCapturedField(knownThingId);
|
assertThingIdCapturedField(knownThingId);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Step
|
||||||
|
private void processThingCreatedMessage(final String thingId, final DmfCreateThing payload) {
|
||||||
|
final MessageProperties messageProperties = getThingCreatedMessageProperties(thingId);
|
||||||
|
final Message message = createMessage(payload != null ? payload : new byte[0], messageProperties);
|
||||||
|
|
||||||
|
final Target targetMock = mock(Target.class);
|
||||||
|
if (payload == null) {
|
||||||
|
when(controllerManagementMock.findOrRegisterTargetIfItDoesNotExist(targetIdCaptor.capture(),
|
||||||
|
uriCaptor.capture())).thenReturn(targetMock);
|
||||||
|
} else {
|
||||||
|
when(controllerManagementMock.findOrRegisterTargetIfItDoesNotExist(targetIdCaptor.capture(),
|
||||||
|
uriCaptor.capture(), targetNameCaptor.capture(), targetTypeNameCaptor.capture()))
|
||||||
|
.thenReturn(targetMock);
|
||||||
|
if (payload.getAttributeUpdate() != null) {
|
||||||
|
when(controllerManagementMock.updateControllerAttributes(targetIdCaptor.capture(),
|
||||||
|
attributesCaptor.capture(), modeCaptor.capture())).thenReturn(null);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
when(controllerManagementMock.findActiveActionWithHighestWeight(any())).thenReturn(Optional.empty());
|
||||||
|
|
||||||
|
amqpMessageHandlerService.onMessage(message, MessageType.THING_CREATED.name(), TENANT, VIRTUAL_HOST);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Step
|
||||||
|
private void assertThingIdCapturedField(final String thingId) {
|
||||||
|
assertThat(targetIdCaptor.getValue()).as("Thing id is wrong").isEqualTo(thingId);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Step
|
||||||
|
private void assertReplyToCapturedField(final String replyTo) {
|
||||||
|
assertThat(uriCaptor.getValue()).as("Uri is not right").hasToString("amqp://" + VIRTUAL_HOST + "/" + replyTo);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Step
|
||||||
|
private void assertInitiatorCapturedField(final String initiator) {
|
||||||
|
assertThat(initiatorCaptor.getValue()).as("Initiator is wrong").isEqualTo(initiator);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Step
|
||||||
|
private void assertRemarkCapturedField(final String remark) {
|
||||||
|
assertThat(remarkCaptor.getValue()).as("Remark is wrong").isEqualTo(remark);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Step
|
||||||
|
private void assertThingNameCapturedField(final String thingName) {
|
||||||
|
assertThat(targetNameCaptor.getValue()).as("Thing name is wrong").isEqualTo(thingName);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Step
|
||||||
|
private void assertThingTypeCapturedField(final String thingType) {
|
||||||
|
assertThat(targetTypeNameCaptor.getValue()).as("Thing type is wrong").isEqualTo(thingType);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Step
|
||||||
|
private void assertThingAttributesCapturedField(final Map<String, String> attributes) {
|
||||||
|
assertThat(attributesCaptor.getValue()).as("Attributes is not right").isEqualTo(attributes);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Step
|
||||||
|
private void assertThingAttributesModeCapturedField(final UpdateMode attributesUpdateMode) {
|
||||||
|
assertThat(modeCaptor.getValue()).as("Attributes update mode is not right").isEqualTo(attributesUpdateMode);
|
||||||
|
}
|
||||||
|
|
||||||
private DmfActionUpdateStatus createActionUpdateStatus(final DmfActionStatus status) {
|
private DmfActionUpdateStatus createActionUpdateStatus(final DmfActionStatus status) {
|
||||||
return createActionUpdateStatus(status, 2L);
|
return createActionUpdateStatus(status, 2L);
|
||||||
}
|
}
|
||||||
@@ -675,35 +702,6 @@ public class AmqpMessageHandlerServiceTest {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
|
||||||
@Description("Tests the deletion of a target/thing, requested by the target itself.")
|
|
||||||
public void deleteThing() {
|
|
||||||
// prepare valid message
|
|
||||||
final String knownThingId = "1";
|
|
||||||
final MessageProperties messageProperties = createMessageProperties(MessageType.THING_REMOVED);
|
|
||||||
messageProperties.setHeader(MessageHeaderKey.THING_ID, knownThingId);
|
|
||||||
final Message message = createMessage(new byte[0], messageProperties);
|
|
||||||
|
|
||||||
// test
|
|
||||||
amqpMessageHandlerService.onMessage(message, MessageType.THING_REMOVED.name(), TENANT, VIRTUAL_HOST);
|
|
||||||
|
|
||||||
// verify
|
|
||||||
verify(controllerManagementMock).deleteExistingTarget(knownThingId);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
@Description("Tests the deletion of a target/thing with missing thingId")
|
|
||||||
public void deleteThingWithoutThingId() {
|
|
||||||
// prepare invalid message
|
|
||||||
final MessageProperties messageProperties = createMessageProperties(MessageType.THING_REMOVED);
|
|
||||||
final Message message = createMessage(new byte[0], messageProperties);
|
|
||||||
|
|
||||||
assertThatExceptionOfType(AmqpRejectAndDontRequeueException.class)
|
|
||||||
.as(FAIL_MESSAGE_AMQP_REJECT_REASON + "since no thingId was set")
|
|
||||||
.isThrownBy(() -> amqpMessageHandlerService.onMessage(message, MessageType.THING_REMOVED.name(), TENANT,
|
|
||||||
VIRTUAL_HOST));
|
|
||||||
}
|
|
||||||
|
|
||||||
private MessageProperties getThingCreatedMessageProperties(final String thingId) {
|
private MessageProperties getThingCreatedMessageProperties(final String thingId) {
|
||||||
final MessageProperties messageProperties = createMessageProperties(MessageType.THING_CREATED);
|
final MessageProperties messageProperties = createMessageProperties(MessageType.THING_CREATED);
|
||||||
messageProperties.setHeader(MessageHeaderKey.THING_ID, thingId);
|
messageProperties.setHeader(MessageHeaderKey.THING_ID, thingId);
|
||||||
|
|||||||
@@ -13,6 +13,9 @@ import static org.assertj.core.api.Assertions.assertThat;
|
|||||||
import static org.assertj.core.api.Assertions.assertThatExceptionOfType;
|
import static org.assertj.core.api.Assertions.assertThatExceptionOfType;
|
||||||
import static org.mockito.Mockito.when;
|
import static org.mockito.Mockito.when;
|
||||||
|
|
||||||
|
import io.qameta.allure.Description;
|
||||||
|
import io.qameta.allure.Feature;
|
||||||
|
import io.qameta.allure.Story;
|
||||||
import org.eclipse.hawkbit.dmf.json.model.DmfActionStatus;
|
import org.eclipse.hawkbit.dmf.json.model.DmfActionStatus;
|
||||||
import org.eclipse.hawkbit.dmf.json.model.DmfActionUpdateStatus;
|
import org.eclipse.hawkbit.dmf.json.model.DmfActionUpdateStatus;
|
||||||
import org.eclipse.hawkbit.repository.event.remote.entity.TargetCreatedEvent;
|
import org.eclipse.hawkbit.repository.event.remote.entity.TargetCreatedEvent;
|
||||||
@@ -29,10 +32,6 @@ import org.springframework.amqp.rabbit.core.RabbitTemplate;
|
|||||||
import org.springframework.amqp.support.converter.Jackson2JsonMessageConverter;
|
import org.springframework.amqp.support.converter.Jackson2JsonMessageConverter;
|
||||||
import org.springframework.amqp.support.converter.MessageConversionException;
|
import org.springframework.amqp.support.converter.MessageConversionException;
|
||||||
|
|
||||||
import io.qameta.allure.Description;
|
|
||||||
import io.qameta.allure.Feature;
|
|
||||||
import io.qameta.allure.Story;
|
|
||||||
|
|
||||||
@ExtendWith(MockitoExtension.class)
|
@ExtendWith(MockitoExtension.class)
|
||||||
@Feature("Component Tests - Device Management Federation API")
|
@Feature("Component Tests - Device Management Federation API")
|
||||||
@Story("Base Amqp Service Test")
|
@Story("Base Amqp Service Test")
|
||||||
|
|||||||
@@ -11,35 +11,34 @@ package org.eclipse.hawkbit.amqp;
|
|||||||
|
|
||||||
import static org.assertj.core.api.Assertions.assertThatExceptionOfType;
|
import static org.assertj.core.api.Assertions.assertThatExceptionOfType;
|
||||||
|
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
import io.qameta.allure.Description;
|
import io.qameta.allure.Description;
|
||||||
import io.qameta.allure.Feature;
|
import io.qameta.allure.Feature;
|
||||||
import io.qameta.allure.Story;
|
import io.qameta.allure.Story;
|
||||||
|
|
||||||
import org.junit.jupiter.api.Test;
|
import org.junit.jupiter.api.Test;
|
||||||
import org.springframework.util.ErrorHandler;
|
import org.springframework.util.ErrorHandler;
|
||||||
|
|
||||||
import java.util.ArrayList;
|
|
||||||
import java.util.List;
|
|
||||||
|
|
||||||
@Feature("Unit Tests - Delegating Conditional Error Handler")
|
@Feature("Unit Tests - Delegating Conditional Error Handler")
|
||||||
@Story("Delegating Conditional Error Handler")
|
@Story("Delegating Conditional Error Handler")
|
||||||
public class DelegatingAmqpErrorHandlerTest {
|
public class DelegatingAmqpErrorHandlerTest {
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
@Description("Verifies that with a list of conditional error handlers, the error is delegated to specific handler.")
|
@Description("Verifies that with a list of conditional error handlers, the error is delegated to specific handler.")
|
||||||
public void verifyDelegationHandling(){
|
public void verifyDelegationHandling() {
|
||||||
List<AmqpErrorHandler> handlers = new ArrayList<>();
|
List<AmqpErrorHandler> handlers = new ArrayList<>();
|
||||||
handlers.add(new IllegalArgumentExceptionHandler());
|
handlers.add(new IllegalArgumentExceptionHandler());
|
||||||
handlers.add(new IndexOutOfBoundsExceptionHandler());
|
handlers.add(new IndexOutOfBoundsExceptionHandler());
|
||||||
assertThatExceptionOfType(IllegalArgumentException.class)
|
assertThatExceptionOfType(IllegalArgumentException.class)
|
||||||
.as("Expected handled exception to be of type IllegalArgumentException")
|
.as("Expected handled exception to be of type IllegalArgumentException")
|
||||||
.isThrownBy(() -> new DelegatingConditionalErrorHandler(handlers, new DefaultErrorHandler())
|
.isThrownBy(() -> new DelegatingConditionalErrorHandler(handlers, new DefaultErrorHandler())
|
||||||
.handleError(new Throwable(new IllegalArgumentException())));
|
.handleError(new Throwable(new IllegalArgumentException())));
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
@Description("Verifies that default handler is used if no handlers are defined for the specific exception.")
|
@Description("Verifies that default handler is used if no handlers are defined for the specific exception.")
|
||||||
public void verifyDefaultDelegationHandling(){
|
public void verifyDefaultDelegationHandling() {
|
||||||
List<AmqpErrorHandler> handlers = new ArrayList<>();
|
List<AmqpErrorHandler> handlers = new ArrayList<>();
|
||||||
handlers.add(new IllegalArgumentExceptionHandler());
|
handlers.add(new IllegalArgumentExceptionHandler());
|
||||||
handlers.add(new IndexOutOfBoundsExceptionHandler());
|
handlers.add(new IndexOutOfBoundsExceptionHandler());
|
||||||
|
|||||||
@@ -21,6 +21,8 @@ import java.util.Set;
|
|||||||
import java.util.UUID;
|
import java.util.UUID;
|
||||||
import java.util.concurrent.Callable;
|
import java.util.concurrent.Callable;
|
||||||
|
|
||||||
|
import com.cronutils.utils.StringUtils;
|
||||||
|
import io.qameta.allure.Step;
|
||||||
import org.assertj.core.api.HamcrestCondition;
|
import org.assertj.core.api.HamcrestCondition;
|
||||||
import org.eclipse.hawkbit.amqp.DmfApiConfiguration;
|
import org.eclipse.hawkbit.amqp.DmfApiConfiguration;
|
||||||
import org.eclipse.hawkbit.dmf.amqp.api.AmqpSettings;
|
import org.eclipse.hawkbit.dmf.amqp.api.AmqpSettings;
|
||||||
@@ -48,9 +50,8 @@ import org.eclipse.hawkbit.repository.model.DistributionSetAssignmentResult;
|
|||||||
import org.eclipse.hawkbit.repository.model.SoftwareModule;
|
import org.eclipse.hawkbit.repository.model.SoftwareModule;
|
||||||
import org.eclipse.hawkbit.repository.model.Target;
|
import org.eclipse.hawkbit.repository.model.Target;
|
||||||
import org.eclipse.hawkbit.repository.model.TargetUpdateStatus;
|
import org.eclipse.hawkbit.repository.model.TargetUpdateStatus;
|
||||||
import org.eclipse.hawkbit.repository.test.TestConfiguration;
|
|
||||||
import org.eclipse.hawkbit.repository.test.util.TestdataFactory;
|
|
||||||
import org.eclipse.hawkbit.repository.test.util.SecurityContextSwitch;
|
import org.eclipse.hawkbit.repository.test.util.SecurityContextSwitch;
|
||||||
|
import org.eclipse.hawkbit.repository.test.util.TestdataFactory;
|
||||||
import org.eclipse.hawkbit.util.IpUtil;
|
import org.eclipse.hawkbit.util.IpUtil;
|
||||||
import org.junit.jupiter.api.BeforeEach;
|
import org.junit.jupiter.api.BeforeEach;
|
||||||
import org.mockito.Mockito;
|
import org.mockito.Mockito;
|
||||||
@@ -63,12 +64,7 @@ import org.springframework.context.annotation.Import;
|
|||||||
import org.springframework.test.context.ContextConfiguration;
|
import org.springframework.test.context.ContextConfiguration;
|
||||||
import org.springframework.util.CollectionUtils;
|
import org.springframework.util.CollectionUtils;
|
||||||
|
|
||||||
import com.cronutils.utils.StringUtils;
|
|
||||||
|
|
||||||
import io.qameta.allure.Step;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
*
|
|
||||||
* Common class for {@link AmqpMessageHandlerServiceIntegrationTest} and
|
* Common class for {@link AmqpMessageHandlerServiceIntegrationTest} and
|
||||||
* {@link AmqpMessageDispatcherServiceIntegrationTest}.
|
* {@link AmqpMessageDispatcherServiceIntegrationTest}.
|
||||||
*/
|
*/
|
||||||
@@ -131,7 +127,7 @@ public abstract class AbstractAmqpServiceIntegrationTest extends AbstractAmqpInt
|
|||||||
testdataFactory.addSoftwareModuleMetadata(distributionSet);
|
testdataFactory.addSoftwareModuleMetadata(distributionSet);
|
||||||
|
|
||||||
return registerTargetAndAssignDistributionSet(distributionSet.getId(), TargetUpdateStatus.REGISTERED,
|
return registerTargetAndAssignDistributionSet(distributionSet.getId(), TargetUpdateStatus.REGISTERED,
|
||||||
distributionSet.getModules(), controllerId);
|
distributionSet.getModules(), controllerId);
|
||||||
}
|
}
|
||||||
|
|
||||||
protected DistributionSetAssignmentResult prepareDistributionSetAndAssign(final String controllerId) {
|
protected DistributionSetAssignmentResult prepareDistributionSetAndAssign(final String controllerId) {
|
||||||
@@ -196,17 +192,6 @@ public abstract class AbstractAmqpServiceIntegrationTest extends AbstractAmqpInt
|
|||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private void assertAssignmentMessage(final Set<SoftwareModule> dsModules, final String controllerId,
|
|
||||||
final EventTopic topic) {
|
|
||||||
final Message replyMessage = assertReplyMessageHeader(topic, controllerId);
|
|
||||||
assertAllTargetsCount(1);
|
|
||||||
|
|
||||||
final DmfDownloadAndUpdateRequest downloadAndUpdateRequest = (DmfDownloadAndUpdateRequest) getDmfClient()
|
|
||||||
.getMessageConverter().fromMessage(replyMessage);
|
|
||||||
|
|
||||||
assertDmfDownloadAndUpdateRequest(downloadAndUpdateRequest, dsModules, controllerId);
|
|
||||||
}
|
|
||||||
|
|
||||||
protected void assertDmfDownloadAndUpdateRequest(final DmfDownloadAndUpdateRequest request,
|
protected void assertDmfDownloadAndUpdateRequest(final DmfDownloadAndUpdateRequest request,
|
||||||
final Set<SoftwareModule> softwareModules, final String controllerId) {
|
final Set<SoftwareModule> softwareModules, final String controllerId) {
|
||||||
assertSoftwareModules(softwareModules, request.getSoftwareModules());
|
assertSoftwareModules(softwareModules, request.getSoftwareModules());
|
||||||
@@ -294,18 +279,6 @@ public abstract class AbstractAmqpServiceIntegrationTest extends AbstractAmqpInt
|
|||||||
expectedTargetStatus, createdBy, attributes, () -> targetManagement.getByControllerID(controllerId));
|
expectedTargetStatus, createdBy, attributes, () -> targetManagement.getByControllerID(controllerId));
|
||||||
}
|
}
|
||||||
|
|
||||||
private void registerAndAssertTargetWithExistingTenant(final String controllerId, final String name,
|
|
||||||
final int existingTargetsAfterCreation, final TargetUpdateStatus expectedTargetStatus,
|
|
||||||
final String createdBy, final Map<String, String> attributes,
|
|
||||||
final Callable<Optional<Target>> fetchTarget) {
|
|
||||||
createAndSendThingCreated(controllerId, name, attributes);
|
|
||||||
final Target registeredTarget = waitUntilIsPresent(fetchTarget::call);
|
|
||||||
assertAllTargetsCount(existingTargetsAfterCreation);
|
|
||||||
assertThat(registeredTarget).isNotNull();
|
|
||||||
assertTarget(registeredTarget, name != null ? name : controllerId, expectedTargetStatus, createdBy,
|
|
||||||
attributes != null ? attributes : Collections.emptyMap());
|
|
||||||
}
|
|
||||||
|
|
||||||
protected void registerSameTargetAndAssertBasedOnVersion(final String controllerId,
|
protected void registerSameTargetAndAssertBasedOnVersion(final String controllerId,
|
||||||
final int existingTargetsAfterCreation, final TargetUpdateStatus expectedTargetStatus) {
|
final int existingTargetsAfterCreation, final TargetUpdateStatus expectedTargetStatus) {
|
||||||
registerSameTargetAndAssertBasedOnVersion(controllerId, null, existingTargetsAfterCreation,
|
registerSameTargetAndAssertBasedOnVersion(controllerId, null, existingTargetsAfterCreation,
|
||||||
@@ -320,27 +293,6 @@ public abstract class AbstractAmqpServiceIntegrationTest extends AbstractAmqpInt
|
|||||||
expectedTargetStatus, CREATED_BY, attributes, () -> findTargetBasedOnNewVersion(controllerId, version));
|
expectedTargetStatus, CREATED_BY, attributes, () -> findTargetBasedOnNewVersion(controllerId, version));
|
||||||
}
|
}
|
||||||
|
|
||||||
private Optional<Target> findTargetBasedOnNewVersion(final String controllerId, final int version) {
|
|
||||||
final Optional<Target> target2 = controllerManagement.getByControllerId(controllerId);
|
|
||||||
if (version < target2.get().getOptLockRevision()) {
|
|
||||||
return target2;
|
|
||||||
}
|
|
||||||
return Optional.empty();
|
|
||||||
}
|
|
||||||
|
|
||||||
private void assertTarget(final Target target, final String name, final TargetUpdateStatus updateStatus,
|
|
||||||
final String createdBy, final Map<String, String> attributes) {
|
|
||||||
assertThat(target.getTenant()).isEqualTo(TENANT_EXIST);
|
|
||||||
assertThat(target.getName()).isEqualTo(name);
|
|
||||||
assertThat(target.getDescription()).contains("Plug and Play");
|
|
||||||
assertThat(target.getDescription()).contains(target.getControllerId());
|
|
||||||
assertThat(target.getCreatedBy()).isEqualTo(createdBy);
|
|
||||||
assertThat(target.getUpdateStatus()).isEqualTo(updateStatus);
|
|
||||||
assertThat(target.getAddress())
|
|
||||||
.isEqualTo(IpUtil.createAmqpUri(getVirtualHost(), DmfTestConfiguration.REPLY_TO_EXCHANGE));
|
|
||||||
assertThat(targetManagement.getControllerAttributes(target.getControllerId())).isEqualTo(attributes);
|
|
||||||
}
|
|
||||||
|
|
||||||
protected Message createTargetMessage(final String controllerId, final String tenant) {
|
protected Message createTargetMessage(final String controllerId, final String tenant) {
|
||||||
return createTargetMessage(controllerId, null, null, tenant);
|
return createTargetMessage(controllerId, null, null, tenant);
|
||||||
}
|
}
|
||||||
@@ -458,7 +410,7 @@ public abstract class AbstractAmqpServiceIntegrationTest extends AbstractAmqpInt
|
|||||||
}
|
}
|
||||||
|
|
||||||
protected void assertSoftwareModules(final Set<SoftwareModule> expectedSoftwareModules,
|
protected void assertSoftwareModules(final Set<SoftwareModule> expectedSoftwareModules,
|
||||||
final List<DmfSoftwareModule> softwareModules) {
|
final List<DmfSoftwareModule> softwareModules) {
|
||||||
assertThat(expectedSoftwareModules)
|
assertThat(expectedSoftwareModules)
|
||||||
.is(new HamcrestCondition<>(SoftwareModuleJsonMatcher.containsExactly(softwareModules)));
|
.is(new HamcrestCondition<>(SoftwareModuleJsonMatcher.containsExactly(softwareModules)));
|
||||||
softwareModules.forEach(dmfModule -> assertThat(dmfModule.getMetadata()).containsExactly(
|
softwareModules.forEach(dmfModule -> assertThat(dmfModule.getMetadata()).containsExactly(
|
||||||
@@ -484,4 +436,48 @@ public abstract class AbstractAmqpServiceIntegrationTest extends AbstractAmqpInt
|
|||||||
assertThat(updatedTarget.getSecurityToken()).isEqualTo(request.getTargetSecurityToken());
|
assertThat(updatedTarget.getSecurityToken()).isEqualTo(request.getTargetSecurityToken());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void assertAssignmentMessage(final Set<SoftwareModule> dsModules, final String controllerId,
|
||||||
|
final EventTopic topic) {
|
||||||
|
final Message replyMessage = assertReplyMessageHeader(topic, controllerId);
|
||||||
|
assertAllTargetsCount(1);
|
||||||
|
|
||||||
|
final DmfDownloadAndUpdateRequest downloadAndUpdateRequest = (DmfDownloadAndUpdateRequest) getDmfClient()
|
||||||
|
.getMessageConverter().fromMessage(replyMessage);
|
||||||
|
|
||||||
|
assertDmfDownloadAndUpdateRequest(downloadAndUpdateRequest, dsModules, controllerId);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void registerAndAssertTargetWithExistingTenant(final String controllerId, final String name,
|
||||||
|
final int existingTargetsAfterCreation, final TargetUpdateStatus expectedTargetStatus,
|
||||||
|
final String createdBy, final Map<String, String> attributes,
|
||||||
|
final Callable<Optional<Target>> fetchTarget) {
|
||||||
|
createAndSendThingCreated(controllerId, name, attributes);
|
||||||
|
final Target registeredTarget = waitUntilIsPresent(fetchTarget::call);
|
||||||
|
assertAllTargetsCount(existingTargetsAfterCreation);
|
||||||
|
assertThat(registeredTarget).isNotNull();
|
||||||
|
assertTarget(registeredTarget, name != null ? name : controllerId, expectedTargetStatus, createdBy,
|
||||||
|
attributes != null ? attributes : Collections.emptyMap());
|
||||||
|
}
|
||||||
|
|
||||||
|
private Optional<Target> findTargetBasedOnNewVersion(final String controllerId, final int version) {
|
||||||
|
final Optional<Target> target2 = controllerManagement.getByControllerId(controllerId);
|
||||||
|
if (version < target2.get().getOptLockRevision()) {
|
||||||
|
return target2;
|
||||||
|
}
|
||||||
|
return Optional.empty();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void assertTarget(final Target target, final String name, final TargetUpdateStatus updateStatus,
|
||||||
|
final String createdBy, final Map<String, String> attributes) {
|
||||||
|
assertThat(target.getTenant()).isEqualTo(TENANT_EXIST);
|
||||||
|
assertThat(target.getName()).isEqualTo(name);
|
||||||
|
assertThat(target.getDescription()).contains("Plug and Play");
|
||||||
|
assertThat(target.getDescription()).contains(target.getControllerId());
|
||||||
|
assertThat(target.getCreatedBy()).isEqualTo(createdBy);
|
||||||
|
assertThat(target.getUpdateStatus()).isEqualTo(updateStatus);
|
||||||
|
assertThat(target.getAddress())
|
||||||
|
.isEqualTo(IpUtil.createAmqpUri(getVirtualHost(), DmfTestConfiguration.REPLY_TO_EXCHANGE));
|
||||||
|
assertThat(targetManagement.getControllerAttributes(target.getControllerId())).isEqualTo(attributes);
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -30,6 +30,9 @@ import java.util.UUID;
|
|||||||
import java.util.concurrent.Callable;
|
import java.util.concurrent.Callable;
|
||||||
import java.util.stream.Collectors;
|
import java.util.stream.Collectors;
|
||||||
|
|
||||||
|
import io.qameta.allure.Description;
|
||||||
|
import io.qameta.allure.Feature;
|
||||||
|
import io.qameta.allure.Story;
|
||||||
import org.eclipse.hawkbit.dmf.amqp.api.EventTopic;
|
import org.eclipse.hawkbit.dmf.amqp.api.EventTopic;
|
||||||
import org.eclipse.hawkbit.dmf.amqp.api.MessageHeaderKey;
|
import org.eclipse.hawkbit.dmf.amqp.api.MessageHeaderKey;
|
||||||
import org.eclipse.hawkbit.dmf.json.model.DmfActionRequest;
|
import org.eclipse.hawkbit.dmf.json.model.DmfActionRequest;
|
||||||
@@ -80,13 +83,10 @@ import org.junit.jupiter.params.provider.EnumSource;
|
|||||||
import org.mockito.Mockito;
|
import org.mockito.Mockito;
|
||||||
import org.springframework.amqp.core.Message;
|
import org.springframework.amqp.core.Message;
|
||||||
|
|
||||||
import io.qameta.allure.Description;
|
|
||||||
import io.qameta.allure.Feature;
|
|
||||||
import io.qameta.allure.Story;
|
|
||||||
|
|
||||||
@Feature("Component Tests - Device Management Federation API")
|
@Feature("Component Tests - Device Management Federation API")
|
||||||
@Story("Amqp Message Dispatcher Service")
|
@Story("Amqp Message Dispatcher Service")
|
||||||
public class AmqpMessageDispatcherServiceIntegrationTest extends AbstractAmqpServiceIntegrationTest {
|
public class AmqpMessageDispatcherServiceIntegrationTest extends AbstractAmqpServiceIntegrationTest {
|
||||||
|
|
||||||
private static final String TARGET_PREFIX = "Dmf_disp_";
|
private static final String TARGET_PREFIX = "Dmf_disp_";
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
@@ -140,7 +140,7 @@ public class AmqpMessageDispatcherServiceIntegrationTest extends AbstractAmqpSer
|
|||||||
@Expect(type = DistributionSetUpdatedEvent.class, count = 1), // implicit lock
|
@Expect(type = DistributionSetUpdatedEvent.class, count = 1), // implicit lock
|
||||||
@Expect(type = SoftwareModuleUpdatedEvent.class, count = 9), // implicit lock
|
@Expect(type = SoftwareModuleUpdatedEvent.class, count = 9), // implicit lock
|
||||||
@Expect(type = TargetUpdatedEvent.class, count = 1), @Expect(type = TargetPollEvent.class, count = 1) })
|
@Expect(type = TargetUpdatedEvent.class, count = 1), @Expect(type = TargetPollEvent.class, count = 1) })
|
||||||
public void sendDownloadAndInstallStatusMessageDuringMaintenanceWindow() {
|
public void sendDownloadAndInstallStatusMessageDuringMaintenanceWindow() {
|
||||||
final String controllerId = TARGET_PREFIX + "sendDAndIStatusMessageDuringWindow";
|
final String controllerId = TARGET_PREFIX + "sendDAndIStatusMessageDuringWindow";
|
||||||
|
|
||||||
registerAndAssertTargetWithExistingTenant(controllerId);
|
registerAndAssertTargetWithExistingTenant(controllerId);
|
||||||
@@ -242,7 +242,7 @@ public class AmqpMessageDispatcherServiceIntegrationTest extends AbstractAmqpSer
|
|||||||
.setActionType(ActionType.DOWNLOAD_ONLY).setWeight(weight1).build()).getAssignedEntity().get(0).getId();
|
.setActionType(ActionType.DOWNLOAD_ONLY).setWeight(weight1).build()).getAssignedEntity().get(0).getId();
|
||||||
final Long cancelActionId = makeAssignment(
|
final Long cancelActionId = makeAssignment(
|
||||||
DeploymentManagement.deploymentRequest(controllerId, ds.getId()).setWeight(DEFAULT_TEST_WEIGHT).build())
|
DeploymentManagement.deploymentRequest(controllerId, ds.getId()).setWeight(DEFAULT_TEST_WEIGHT).build())
|
||||||
.getAssignedEntity().get(0).getId();
|
.getAssignedEntity().get(0).getId();
|
||||||
// make sure the latest message in the queue is the one triggered by the
|
// make sure the latest message in the queue is the one triggered by the
|
||||||
// cancellation
|
// cancellation
|
||||||
waitUntilEventMessagesAreDispatchedToTarget(EventTopic.DOWNLOAD_AND_INSTALL, EventTopic.MULTI_ACTION,
|
waitUntilEventMessagesAreDispatchedToTarget(EventTopic.DOWNLOAD_AND_INSTALL, EventTopic.MULTI_ACTION,
|
||||||
@@ -274,14 +274,6 @@ public class AmqpMessageDispatcherServiceIntegrationTest extends AbstractAmqpSer
|
|||||||
controllerId);
|
controllerId);
|
||||||
}
|
}
|
||||||
|
|
||||||
private List<DmfMultiActionElement> getLatestMultiActionMessages(final String expectedControllerId) {
|
|
||||||
final Message multiactionMessage = replyToListener.getLatestEventMessage(EventTopic.MULTI_ACTION);
|
|
||||||
assertThat(multiactionMessage.getMessageProperties().getHeaders().get(MessageHeaderKey.THING_ID))
|
|
||||||
.isEqualTo(expectedControllerId);
|
|
||||||
return ((DmfMultiActionRequest) getDmfClient().getMessageConverter().fromMessage(multiactionMessage))
|
|
||||||
.getElements();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
@Description("Handle cancelation process of an action in multi assignment mode.")
|
@Description("Handle cancelation process of an action in multi assignment mode.")
|
||||||
@ExpectEvents({ @Expect(type = TargetCreatedEvent.class, count = 1),
|
@ExpectEvents({ @Expect(type = TargetCreatedEvent.class, count = 1),
|
||||||
@@ -376,24 +368,6 @@ public class AmqpMessageDispatcherServiceIntegrationTest extends AbstractAmqpSer
|
|||||||
assertLatestMultiActionMessage(controllerId, Arrays.asList(action1Install, action2Install));
|
assertLatestMultiActionMessage(controllerId, Arrays.asList(action1Install, action2Install));
|
||||||
}
|
}
|
||||||
|
|
||||||
private void updateActionViaDmfClient(final String controllerId, final long actionId,
|
|
||||||
final DmfActionStatus status) {
|
|
||||||
createAndSendActionStatusUpdateMessage(controllerId, actionId, status);
|
|
||||||
}
|
|
||||||
|
|
||||||
private Long assignNewDsToTarget(final String controllerId) {
|
|
||||||
return assignNewDsToTarget(controllerId, null);
|
|
||||||
}
|
|
||||||
|
|
||||||
private Long assignNewDsToTarget(final String controllerId, final Integer weight) {
|
|
||||||
final DistributionSet ds = testdataFactory.createDistributionSet();
|
|
||||||
final Long actionId = getFirstAssignedActionId(
|
|
||||||
assignDistributionSet(ds.getId(), Collections.singletonList(controllerId), ActionType.FORCED,
|
|
||||||
RepositoryModelConstants.NO_FORCE_TIME, weight));
|
|
||||||
waitUntilTargetHasStatus(controllerId, TargetUpdateStatus.PENDING);
|
|
||||||
return actionId;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
@Description("If multi assignment is enabled multiple rollouts with the same DS lead to multiple actions.")
|
@Description("If multi assignment is enabled multiple rollouts with the same DS lead to multiple actions.")
|
||||||
@ExpectEvents({ @Expect(type = TargetCreatedEvent.class, count = 1),
|
@ExpectEvents({ @Expect(type = TargetCreatedEvent.class, count = 1),
|
||||||
@@ -474,18 +448,6 @@ public class AmqpMessageDispatcherServiceIntegrationTest extends AbstractAmqpSer
|
|||||||
assertLatestMultiActionMessageContainsInstallMessages(controllerId, Collections.singletonList(smIds1));
|
assertLatestMultiActionMessageContainsInstallMessages(controllerId, Collections.singletonList(smIds1));
|
||||||
}
|
}
|
||||||
|
|
||||||
private Set<Long> getSoftwareModuleIds(final DistributionSet ds) {
|
|
||||||
return ds.getModules().stream().map(SoftwareModule::getId).collect(Collectors.toSet());
|
|
||||||
}
|
|
||||||
|
|
||||||
private Rollout createAndStartRollout(final DistributionSet ds, final String filterQuery, final Integer weight) {
|
|
||||||
final Rollout rollout = testdataFactory.createRolloutByVariables(UUID.randomUUID().toString(), "", 1,
|
|
||||||
filterQuery, ds, "50", "5", ActionType.FORCED, weight, false);
|
|
||||||
rolloutManagement.start(rollout.getId());
|
|
||||||
rolloutHandler.handleAll();
|
|
||||||
return rollout;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
@Description("Verify that a cancel assignment send a cancel message.")
|
@Description("Verify that a cancel assignment send a cancel message.")
|
||||||
@ExpectEvents({ @Expect(type = TargetCreatedEvent.class, count = 1),
|
@ExpectEvents({ @Expect(type = TargetCreatedEvent.class, count = 1),
|
||||||
@@ -577,6 +539,169 @@ public class AmqpMessageDispatcherServiceIntegrationTest extends AbstractAmqpSer
|
|||||||
assertThat(assignedDistributionSet.getId()).isEqualTo(distributionSet.getId());
|
assertThat(assignedDistributionSet.getId()).isEqualTo(distributionSet.getId());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
@Description("Verify payload of batch assignment download and install message.")
|
||||||
|
public void assertBatchAssignmentsDownloadAndInstall() {
|
||||||
|
assertBatchAssignmentsMessagePayload(BATCH_DOWNLOAD_AND_INSTALL);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
@Description("Verify payload of batch assignments download only message.")
|
||||||
|
public void assertBatchAssignmentsDownloadOnly() {
|
||||||
|
assertBatchAssignmentsMessagePayload(BATCH_DOWNLOAD);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected void assertDmfBatchDownloadAndUpdateRequest(final DmfBatchDownloadAndUpdateRequest request,
|
||||||
|
final Set<SoftwareModule> softwareModules,
|
||||||
|
final List<String> controllerIds) {
|
||||||
|
assertSoftwareModules(softwareModules, request.getSoftwareModules());
|
||||||
|
|
||||||
|
final List<Object> tokens = controllerIds.stream().map(controllerId -> {
|
||||||
|
final Optional<Target> target = controllerManagement.getByControllerId(controllerId);
|
||||||
|
assertThat(target).isPresent();
|
||||||
|
return target.get().getSecurityToken();
|
||||||
|
}).collect(Collectors.toList());
|
||||||
|
|
||||||
|
final List<DmfTarget> requestTargets = request.getTargets();
|
||||||
|
|
||||||
|
assertThat(requestTargets).hasSameSizeAs(controllerIds);
|
||||||
|
requestTargets.forEach(requestTarget -> {
|
||||||
|
assertThat(requestTarget).isNotNull();
|
||||||
|
assertThat(tokens.contains(requestTarget.getTargetSecurityToken()));
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
protected void assertEventMessageNotPresent(final EventTopic eventTopic) {
|
||||||
|
assertThat(replyToListener.getLatestEventMessage(eventTopic)).isNull();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
@Description("Verify that batch and multi-assignments can't be activated at the same time.")
|
||||||
|
void assertBatchAndMultiAssignmentsNotCompatible() {
|
||||||
|
enableBatchAssignments();
|
||||||
|
assertThatExceptionOfType(TenantConfigurationValueChangeNotAllowedException.class)
|
||||||
|
.isThrownBy(() -> enableMultiAssignments());
|
||||||
|
disableBatchAssignments();
|
||||||
|
|
||||||
|
enableMultiAssignments();
|
||||||
|
assertThatExceptionOfType(TenantConfigurationValueChangeNotAllowedException.class)
|
||||||
|
.isThrownBy(() -> enableBatchAssignments());
|
||||||
|
}
|
||||||
|
|
||||||
|
@ParameterizedTest
|
||||||
|
@EnumSource(names = { "BATCH_DOWNLOAD_AND_INSTALL", "BATCH_DOWNLOAD" })
|
||||||
|
@Description("Verify payload of batch assignments.")
|
||||||
|
@ExpectEvents({ @Expect(type = TargetCreatedEvent.class, count = 3),
|
||||||
|
@Expect(type = TargetAssignDistributionSetEvent.class, count = 1),
|
||||||
|
@Expect(type = ActionCreatedEvent.class, count = 3), @Expect(type = ActionUpdatedEvent.class, count = 0),
|
||||||
|
@Expect(type = DistributionSetCreatedEvent.class, count = 1),
|
||||||
|
@Expect(type = SoftwareModuleCreatedEvent.class, count = 3),
|
||||||
|
@Expect(type = DistributionSetUpdatedEvent.class, count = 1), // implicit lock
|
||||||
|
@Expect(type = SoftwareModuleUpdatedEvent.class, count = 9), // implicit lock
|
||||||
|
@Expect(type = TargetUpdatedEvent.class, count = 3), @Expect(type = TargetPollEvent.class, count = 3),
|
||||||
|
@Expect(type = TenantConfigurationCreatedEvent.class, count = 1) })
|
||||||
|
void assertBatchAssignmentsMessagePayload(final EventTopic topic) {
|
||||||
|
enableBatchAssignments();
|
||||||
|
|
||||||
|
final List<String> targets = Arrays.asList("batchCtrlID1", "batchCtrlID2", "batchCtrlID3");
|
||||||
|
for (int i = 0; i < targets.size(); i++) {
|
||||||
|
registerAndAssertTargetWithExistingTenant(targets.get(i), i + 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
final DistributionSet ds = testdataFactory.createDistributionSet();
|
||||||
|
testdataFactory.addSoftwareModuleMetadata(ds);
|
||||||
|
|
||||||
|
assignDistributionSet(ds.getId(), targets, topic == BATCH_DOWNLOAD ? DOWNLOAD_ONLY : FORCED);
|
||||||
|
|
||||||
|
waitUntilEventMessagesAreDispatchedToTarget(topic);
|
||||||
|
|
||||||
|
final Message message = replyToListener.getLatestEventMessage(topic);
|
||||||
|
final Map<String, Object> headers = message.getMessageProperties().getHeaders();
|
||||||
|
assertThat(headers).containsEntry("type", EVENT.toString());
|
||||||
|
assertThat(headers).containsEntry("topic", topic.toString());
|
||||||
|
|
||||||
|
final DmfBatchDownloadAndUpdateRequest batchRequest = (DmfBatchDownloadAndUpdateRequest) getDmfClient()
|
||||||
|
.getMessageConverter().fromMessage(message);
|
||||||
|
|
||||||
|
assertThat(batchRequest).isExactlyInstanceOf(DmfBatchDownloadAndUpdateRequest.class);
|
||||||
|
assertDmfBatchDownloadAndUpdateRequest(batchRequest, ds.getModules(), targets);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
@Description("Verify that a distribution assignment send a confirm message.")
|
||||||
|
@ExpectEvents({ @Expect(type = TargetCreatedEvent.class, count = 1),
|
||||||
|
@Expect(type = TargetAssignDistributionSetEvent.class, count = 1),
|
||||||
|
@Expect(type = ActionCreatedEvent.class, count = 1),
|
||||||
|
@Expect(type = DistributionSetCreatedEvent.class, count = 1),
|
||||||
|
@Expect(type = SoftwareModuleCreatedEvent.class, count = 3),
|
||||||
|
@Expect(type = DistributionSetUpdatedEvent.class, count = 1), // implicit lock
|
||||||
|
@Expect(type = SoftwareModuleUpdatedEvent.class, count = 9), // implicit lock
|
||||||
|
@Expect(type = TargetUpdatedEvent.class, count = 1),
|
||||||
|
@Expect(type = TargetPollEvent.class, count = 1),
|
||||||
|
@Expect(type = TenantConfigurationCreatedEvent.class, count = 1) })
|
||||||
|
void sendConfirmStatus() {
|
||||||
|
final String controllerId = TARGET_PREFIX + "sendConfirmStatus";
|
||||||
|
enableConfirmationFlow();
|
||||||
|
registerTargetAndAssignDistributionSet(controllerId);
|
||||||
|
|
||||||
|
waitUntilTargetHasStatus(controllerId, TargetUpdateStatus.PENDING);
|
||||||
|
assertConfirmMessage(getDistributionSet().getModules(), controllerId);
|
||||||
|
assertEventMessageNotPresent(EventTopic.DOWNLOAD_AND_INSTALL);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static Set<Long> getSmIds(final DmfDownloadAndUpdateRequest request) {
|
||||||
|
return request.getSoftwareModules().stream().map(DmfSoftwareModule::getModuleId).collect(Collectors.toSet());
|
||||||
|
}
|
||||||
|
|
||||||
|
private static List<DmfDownloadAndUpdateRequest> getDownloadAndUpdateRequests(final DmfMultiActionRequest request) {
|
||||||
|
return request.getElements().stream()
|
||||||
|
.filter(AmqpMessageDispatcherServiceIntegrationTest::isDownloadAndUpdateRequest)
|
||||||
|
.map(multiAction -> (DmfDownloadAndUpdateRequest) multiAction.getAction()).collect(Collectors.toList());
|
||||||
|
}
|
||||||
|
|
||||||
|
private static boolean isDownloadAndUpdateRequest(final DmfMultiActionElement multiActionElement) {
|
||||||
|
return multiActionElement.getTopic().equals(EventTopic.DOWNLOAD)
|
||||||
|
|| multiActionElement.getTopic().equals(EventTopic.DOWNLOAD_AND_INSTALL);
|
||||||
|
}
|
||||||
|
|
||||||
|
private List<DmfMultiActionElement> getLatestMultiActionMessages(final String expectedControllerId) {
|
||||||
|
final Message multiactionMessage = replyToListener.getLatestEventMessage(EventTopic.MULTI_ACTION);
|
||||||
|
assertThat(multiactionMessage.getMessageProperties().getHeaders().get(MessageHeaderKey.THING_ID))
|
||||||
|
.isEqualTo(expectedControllerId);
|
||||||
|
return ((DmfMultiActionRequest) getDmfClient().getMessageConverter().fromMessage(multiactionMessage))
|
||||||
|
.getElements();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void updateActionViaDmfClient(final String controllerId, final long actionId,
|
||||||
|
final DmfActionStatus status) {
|
||||||
|
createAndSendActionStatusUpdateMessage(controllerId, actionId, status);
|
||||||
|
}
|
||||||
|
|
||||||
|
private Long assignNewDsToTarget(final String controllerId) {
|
||||||
|
return assignNewDsToTarget(controllerId, null);
|
||||||
|
}
|
||||||
|
|
||||||
|
private Long assignNewDsToTarget(final String controllerId, final Integer weight) {
|
||||||
|
final DistributionSet ds = testdataFactory.createDistributionSet();
|
||||||
|
final Long actionId = getFirstAssignedActionId(
|
||||||
|
assignDistributionSet(ds.getId(), Collections.singletonList(controllerId), ActionType.FORCED,
|
||||||
|
RepositoryModelConstants.NO_FORCE_TIME, weight));
|
||||||
|
waitUntilTargetHasStatus(controllerId, TargetUpdateStatus.PENDING);
|
||||||
|
return actionId;
|
||||||
|
}
|
||||||
|
|
||||||
|
private Set<Long> getSoftwareModuleIds(final DistributionSet ds) {
|
||||||
|
return ds.getModules().stream().map(SoftwareModule::getId).collect(Collectors.toSet());
|
||||||
|
}
|
||||||
|
|
||||||
|
private Rollout createAndStartRollout(final DistributionSet ds, final String filterQuery, final Integer weight) {
|
||||||
|
final Rollout rollout = testdataFactory.createRolloutByVariables(UUID.randomUUID().toString(), "", 1,
|
||||||
|
filterQuery, ds, "50", "5", ActionType.FORCED, weight, false);
|
||||||
|
rolloutManagement.start(rollout.getId());
|
||||||
|
rolloutHandler.handleAll();
|
||||||
|
return rollout;
|
||||||
|
}
|
||||||
|
|
||||||
private void waitUntilTargetHasStatus(final String controllerId, final TargetUpdateStatus status) {
|
private void waitUntilTargetHasStatus(final String controllerId, final TargetUpdateStatus status) {
|
||||||
waitUntil(() -> {
|
waitUntil(() -> {
|
||||||
final Optional<Target> findTargetByControllerID = targetManagement.getByControllerID(controllerId);
|
final Optional<Target> findTargetByControllerID = targetManagement.getByControllerID(controllerId);
|
||||||
@@ -614,129 +739,4 @@ public class AmqpMessageDispatcherServiceIntegrationTest extends AbstractAmqpSer
|
|||||||
.map(request -> new SimpleEntry<>(request.getAction().getActionId(), request.getTopic()))
|
.map(request -> new SimpleEntry<>(request.getAction().getActionId(), request.getTopic()))
|
||||||
.collect(Collectors.toList());
|
.collect(Collectors.toList());
|
||||||
}
|
}
|
||||||
|
|
||||||
private static Set<Long> getSmIds(final DmfDownloadAndUpdateRequest request) {
|
|
||||||
return request.getSoftwareModules().stream().map(DmfSoftwareModule::getModuleId).collect(Collectors.toSet());
|
|
||||||
}
|
|
||||||
|
|
||||||
private static List<DmfDownloadAndUpdateRequest> getDownloadAndUpdateRequests(final DmfMultiActionRequest request) {
|
|
||||||
return request.getElements().stream()
|
|
||||||
.filter(AmqpMessageDispatcherServiceIntegrationTest::isDownloadAndUpdateRequest)
|
|
||||||
.map(multiAction -> (DmfDownloadAndUpdateRequest) multiAction.getAction()).collect(Collectors.toList());
|
|
||||||
}
|
|
||||||
|
|
||||||
private static boolean isDownloadAndUpdateRequest(final DmfMultiActionElement multiActionElement) {
|
|
||||||
return multiActionElement.getTopic().equals(EventTopic.DOWNLOAD)
|
|
||||||
|| multiActionElement.getTopic().equals(EventTopic.DOWNLOAD_AND_INSTALL);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
@Description("Verify payload of batch assignment download and install message.")
|
|
||||||
public void assertBatchAssignmentsDownloadAndInstall() {
|
|
||||||
assertBatchAssignmentsMessagePayload(BATCH_DOWNLOAD_AND_INSTALL);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
@Description("Verify payload of batch assignments download only message.")
|
|
||||||
public void assertBatchAssignmentsDownloadOnly() {
|
|
||||||
assertBatchAssignmentsMessagePayload(BATCH_DOWNLOAD);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
@Description("Verify that batch and multi-assignments can't be activated at the same time.")
|
|
||||||
void assertBatchAndMultiAssignmentsNotCompatible() {
|
|
||||||
enableBatchAssignments();
|
|
||||||
assertThatExceptionOfType(TenantConfigurationValueChangeNotAllowedException.class)
|
|
||||||
.isThrownBy(() -> enableMultiAssignments());
|
|
||||||
disableBatchAssignments();
|
|
||||||
|
|
||||||
enableMultiAssignments();
|
|
||||||
assertThatExceptionOfType(TenantConfigurationValueChangeNotAllowedException.class)
|
|
||||||
.isThrownBy(() -> enableBatchAssignments());
|
|
||||||
}
|
|
||||||
|
|
||||||
@ParameterizedTest
|
|
||||||
@EnumSource(names = { "BATCH_DOWNLOAD_AND_INSTALL", "BATCH_DOWNLOAD" })
|
|
||||||
@Description("Verify payload of batch assignments.")
|
|
||||||
@ExpectEvents({ @Expect(type = TargetCreatedEvent.class, count = 3),
|
|
||||||
@Expect(type = TargetAssignDistributionSetEvent.class, count = 1),
|
|
||||||
@Expect(type = ActionCreatedEvent.class, count = 3), @Expect(type = ActionUpdatedEvent.class, count = 0),
|
|
||||||
@Expect(type = DistributionSetCreatedEvent.class, count = 1),
|
|
||||||
@Expect(type = SoftwareModuleCreatedEvent.class, count = 3),
|
|
||||||
@Expect(type = DistributionSetUpdatedEvent.class, count = 1), // implicit lock
|
|
||||||
@Expect(type = SoftwareModuleUpdatedEvent.class, count = 9), // implicit lock
|
|
||||||
@Expect(type = TargetUpdatedEvent.class, count = 3), @Expect(type = TargetPollEvent.class, count = 3),
|
|
||||||
@Expect(type = TenantConfigurationCreatedEvent.class, count = 1) })
|
|
||||||
void assertBatchAssignmentsMessagePayload(final EventTopic topic) {
|
|
||||||
enableBatchAssignments();
|
|
||||||
|
|
||||||
final List<String> targets = Arrays.asList("batchCtrlID1", "batchCtrlID2", "batchCtrlID3");
|
|
||||||
for (int i = 0; i < targets.size(); i++) {
|
|
||||||
registerAndAssertTargetWithExistingTenant(targets.get(i), i+1);
|
|
||||||
}
|
|
||||||
|
|
||||||
final DistributionSet ds = testdataFactory.createDistributionSet();
|
|
||||||
testdataFactory.addSoftwareModuleMetadata(ds);
|
|
||||||
|
|
||||||
assignDistributionSet(ds.getId(), targets, topic == BATCH_DOWNLOAD?DOWNLOAD_ONLY:FORCED);
|
|
||||||
|
|
||||||
waitUntilEventMessagesAreDispatchedToTarget(topic);
|
|
||||||
|
|
||||||
final Message message = replyToListener.getLatestEventMessage(topic);
|
|
||||||
final Map<String, Object> headers = message.getMessageProperties().getHeaders();
|
|
||||||
assertThat(headers).containsEntry("type", EVENT.toString());
|
|
||||||
assertThat(headers).containsEntry("topic",topic.toString());
|
|
||||||
|
|
||||||
final DmfBatchDownloadAndUpdateRequest batchRequest = (DmfBatchDownloadAndUpdateRequest) getDmfClient()
|
|
||||||
.getMessageConverter().fromMessage(message);
|
|
||||||
|
|
||||||
assertThat(batchRequest).isExactlyInstanceOf(DmfBatchDownloadAndUpdateRequest.class);
|
|
||||||
assertDmfBatchDownloadAndUpdateRequest(batchRequest, ds.getModules(), targets);
|
|
||||||
}
|
|
||||||
|
|
||||||
protected void assertDmfBatchDownloadAndUpdateRequest(final DmfBatchDownloadAndUpdateRequest request,
|
|
||||||
final Set<SoftwareModule> softwareModules,
|
|
||||||
final List<String> controllerIds) {
|
|
||||||
assertSoftwareModules(softwareModules, request.getSoftwareModules());
|
|
||||||
|
|
||||||
final List<Object> tokens = controllerIds.stream().map(controllerId -> {
|
|
||||||
final Optional<Target> target = controllerManagement.getByControllerId(controllerId);
|
|
||||||
assertThat(target).isPresent();
|
|
||||||
return target.get().getSecurityToken();
|
|
||||||
}).collect(Collectors.toList());
|
|
||||||
|
|
||||||
|
|
||||||
final List<DmfTarget> requestTargets = request.getTargets();
|
|
||||||
|
|
||||||
assertThat(requestTargets).hasSameSizeAs(controllerIds);
|
|
||||||
requestTargets.forEach(requestTarget -> {
|
|
||||||
assertThat(requestTarget).isNotNull();
|
|
||||||
assertThat(tokens.contains(requestTarget.getTargetSecurityToken()));
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
@Description("Verify that a distribution assignment send a confirm message.")
|
|
||||||
@ExpectEvents({ @Expect(type = TargetCreatedEvent.class, count = 1),
|
|
||||||
@Expect(type = TargetAssignDistributionSetEvent.class, count = 1),
|
|
||||||
@Expect(type = ActionCreatedEvent.class, count = 1),
|
|
||||||
@Expect(type = DistributionSetCreatedEvent.class, count = 1),
|
|
||||||
@Expect(type = SoftwareModuleCreatedEvent.class, count = 3),
|
|
||||||
@Expect(type = DistributionSetUpdatedEvent.class, count = 1), // implicit lock
|
|
||||||
@Expect(type = SoftwareModuleUpdatedEvent.class, count = 9), // implicit lock
|
|
||||||
@Expect(type = TargetUpdatedEvent.class, count = 1),
|
|
||||||
@Expect(type = TargetPollEvent.class, count = 1),
|
|
||||||
@Expect(type = TenantConfigurationCreatedEvent.class, count = 1) })
|
|
||||||
void sendConfirmStatus() {
|
|
||||||
final String controllerId = TARGET_PREFIX + "sendConfirmStatus";
|
|
||||||
enableConfirmationFlow();
|
|
||||||
registerTargetAndAssignDistributionSet(controllerId);
|
|
||||||
|
|
||||||
waitUntilTargetHasStatus(controllerId, TargetUpdateStatus.PENDING);
|
|
||||||
assertConfirmMessage(getDistributionSet().getModules(), controllerId);
|
|
||||||
assertEventMessageNotPresent(EventTopic.DOWNLOAD_AND_INSTALL);
|
|
||||||
}
|
|
||||||
protected void assertEventMessageNotPresent(final EventTopic eventTopic) {
|
|
||||||
assertThat(replyToListener.getLatestEventMessage(eventTopic)).isNull();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -26,6 +26,12 @@ import java.util.Optional;
|
|||||||
import java.util.UUID;
|
import java.util.UUID;
|
||||||
import java.util.stream.Collectors;
|
import java.util.stream.Collectors;
|
||||||
|
|
||||||
|
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||||
|
import com.fasterxml.jackson.databind.node.ObjectNode;
|
||||||
|
import io.qameta.allure.Description;
|
||||||
|
import io.qameta.allure.Feature;
|
||||||
|
import io.qameta.allure.Step;
|
||||||
|
import io.qameta.allure.Story;
|
||||||
import org.apache.commons.lang3.RandomStringUtils;
|
import org.apache.commons.lang3.RandomStringUtils;
|
||||||
import org.eclipse.hawkbit.amqp.AmqpMessageHandlerService;
|
import org.eclipse.hawkbit.amqp.AmqpMessageHandlerService;
|
||||||
import org.eclipse.hawkbit.amqp.AmqpProperties;
|
import org.eclipse.hawkbit.amqp.AmqpProperties;
|
||||||
@@ -37,12 +43,12 @@ import org.eclipse.hawkbit.dmf.json.model.DmfAttributeUpdate;
|
|||||||
import org.eclipse.hawkbit.dmf.json.model.DmfUpdateMode;
|
import org.eclipse.hawkbit.dmf.json.model.DmfUpdateMode;
|
||||||
import org.eclipse.hawkbit.repository.ControllerManagement;
|
import org.eclipse.hawkbit.repository.ControllerManagement;
|
||||||
import org.eclipse.hawkbit.repository.RepositoryConstants;
|
import org.eclipse.hawkbit.repository.RepositoryConstants;
|
||||||
|
import org.eclipse.hawkbit.repository.event.remote.CancelTargetAssignmentEvent;
|
||||||
import org.eclipse.hawkbit.repository.event.remote.TargetAssignDistributionSetEvent;
|
import org.eclipse.hawkbit.repository.event.remote.TargetAssignDistributionSetEvent;
|
||||||
import org.eclipse.hawkbit.repository.event.remote.TargetAttributesRequestedEvent;
|
import org.eclipse.hawkbit.repository.event.remote.TargetAttributesRequestedEvent;
|
||||||
import org.eclipse.hawkbit.repository.event.remote.TargetPollEvent;
|
import org.eclipse.hawkbit.repository.event.remote.TargetPollEvent;
|
||||||
import org.eclipse.hawkbit.repository.event.remote.entity.ActionCreatedEvent;
|
import org.eclipse.hawkbit.repository.event.remote.entity.ActionCreatedEvent;
|
||||||
import org.eclipse.hawkbit.repository.event.remote.entity.ActionUpdatedEvent;
|
import org.eclipse.hawkbit.repository.event.remote.entity.ActionUpdatedEvent;
|
||||||
import org.eclipse.hawkbit.repository.event.remote.CancelTargetAssignmentEvent;
|
|
||||||
import org.eclipse.hawkbit.repository.event.remote.entity.DistributionSetCreatedEvent;
|
import org.eclipse.hawkbit.repository.event.remote.entity.DistributionSetCreatedEvent;
|
||||||
import org.eclipse.hawkbit.repository.event.remote.entity.DistributionSetUpdatedEvent;
|
import org.eclipse.hawkbit.repository.event.remote.entity.DistributionSetUpdatedEvent;
|
||||||
import org.eclipse.hawkbit.repository.event.remote.entity.SoftwareModuleCreatedEvent;
|
import org.eclipse.hawkbit.repository.event.remote.entity.SoftwareModuleCreatedEvent;
|
||||||
@@ -63,8 +69,8 @@ import org.eclipse.hawkbit.repository.model.Target;
|
|||||||
import org.eclipse.hawkbit.repository.model.TargetUpdateStatus;
|
import org.eclipse.hawkbit.repository.model.TargetUpdateStatus;
|
||||||
import org.eclipse.hawkbit.repository.test.matcher.Expect;
|
import org.eclipse.hawkbit.repository.test.matcher.Expect;
|
||||||
import org.eclipse.hawkbit.repository.test.matcher.ExpectEvents;
|
import org.eclipse.hawkbit.repository.test.matcher.ExpectEvents;
|
||||||
import org.eclipse.hawkbit.repository.test.util.TargetTestData;
|
|
||||||
import org.eclipse.hawkbit.repository.test.util.SecurityContextSwitch;
|
import org.eclipse.hawkbit.repository.test.util.SecurityContextSwitch;
|
||||||
|
import org.eclipse.hawkbit.repository.test.util.TargetTestData;
|
||||||
import org.junit.jupiter.api.Test;
|
import org.junit.jupiter.api.Test;
|
||||||
import org.junit.jupiter.params.ParameterizedTest;
|
import org.junit.jupiter.params.ParameterizedTest;
|
||||||
import org.junit.jupiter.params.provider.NullSource;
|
import org.junit.jupiter.params.provider.NullSource;
|
||||||
@@ -74,22 +80,14 @@ import org.springframework.amqp.core.Message;
|
|||||||
import org.springframework.amqp.rabbit.core.RabbitAdmin;
|
import org.springframework.amqp.rabbit.core.RabbitAdmin;
|
||||||
import org.springframework.beans.factory.annotation.Autowired;
|
import org.springframework.beans.factory.annotation.Autowired;
|
||||||
|
|
||||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
|
||||||
import com.fasterxml.jackson.databind.node.ObjectNode;
|
|
||||||
|
|
||||||
import io.qameta.allure.Description;
|
|
||||||
import io.qameta.allure.Feature;
|
|
||||||
import io.qameta.allure.Step;
|
|
||||||
import io.qameta.allure.Story;
|
|
||||||
|
|
||||||
@Feature("Component Tests - Device Management Federation API")
|
@Feature("Component Tests - Device Management Federation API")
|
||||||
@Story("Amqp Message Handler Service")
|
@Story("Amqp Message Handler Service")
|
||||||
class AmqpMessageHandlerServiceIntegrationTest extends AbstractAmqpServiceIntegrationTest {
|
class AmqpMessageHandlerServiceIntegrationTest extends AbstractAmqpServiceIntegrationTest {
|
||||||
private static final String TARGET_PREFIX = "Dmf_hand_";
|
|
||||||
public static final String DMF_REGISTER_TEST_CONTROLLER_ID = "Dmf_hand_registerTargets_1";
|
public static final String DMF_REGISTER_TEST_CONTROLLER_ID = "Dmf_hand_registerTargets_1";
|
||||||
public static final String DMF_ATTR_TEST_CONTROLLER_ID = "Dmf_hand_updateAttributes";
|
public static final String DMF_ATTR_TEST_CONTROLLER_ID = "Dmf_hand_updateAttributes";
|
||||||
public static final String UPDATE_ATTR_TEST_CONTROLLER_ID = "ControllerAttributeTestTarget";
|
public static final String UPDATE_ATTR_TEST_CONTROLLER_ID = "ControllerAttributeTestTarget";
|
||||||
|
private static final String TARGET_PREFIX = "Dmf_hand_";
|
||||||
@Autowired
|
@Autowired
|
||||||
private AmqpProperties amqpProperties;
|
private AmqpProperties amqpProperties;
|
||||||
|
|
||||||
@@ -179,7 +177,7 @@ class AmqpMessageHandlerServiceIntegrationTest extends AbstractAmqpServiceIntegr
|
|||||||
}
|
}
|
||||||
|
|
||||||
@ParameterizedTest
|
@ParameterizedTest
|
||||||
@ValueSource(strings = {"", "Invalid Invalid"})
|
@ValueSource(strings = { "", "Invalid Invalid" })
|
||||||
@NullSource
|
@NullSource
|
||||||
@Description("Tests register invalid target with empty controller id.")
|
@Description("Tests register invalid target with empty controller id.")
|
||||||
@ExpectEvents({ @Expect(type = TargetCreatedEvent.class) })
|
@ExpectEvents({ @Expect(type = TargetCreatedEvent.class) })
|
||||||
@@ -309,7 +307,7 @@ class AmqpMessageHandlerServiceIntegrationTest extends AbstractAmqpServiceIntegr
|
|||||||
}
|
}
|
||||||
|
|
||||||
@ParameterizedTest
|
@ParameterizedTest
|
||||||
@ValueSource(strings={"", "NotExist"})
|
@ValueSource(strings = { "", "NotExist" })
|
||||||
@NullSource
|
@NullSource
|
||||||
@Description("Tests null type message header. This message should forwarded to the deadletter queue")
|
@Description("Tests null type message header. This message should forwarded to the deadletter queue")
|
||||||
@ExpectEvents({ @Expect(type = TargetCreatedEvent.class) })
|
@ExpectEvents({ @Expect(type = TargetCreatedEvent.class) })
|
||||||
@@ -323,7 +321,7 @@ class AmqpMessageHandlerServiceIntegrationTest extends AbstractAmqpServiceIntegr
|
|||||||
}
|
}
|
||||||
|
|
||||||
@ParameterizedTest
|
@ParameterizedTest
|
||||||
@ValueSource(strings = {"", "NotExist"})
|
@ValueSource(strings = { "", "NotExist" })
|
||||||
@NullSource
|
@NullSource
|
||||||
@Description("Tests null topic message header. This message should forwarded to the deadletter queue")
|
@Description("Tests null topic message header. This message should forwarded to the deadletter queue")
|
||||||
@ExpectEvents({ @Expect(type = TargetCreatedEvent.class) })
|
@ExpectEvents({ @Expect(type = TargetCreatedEvent.class) })
|
||||||
@@ -347,7 +345,7 @@ class AmqpMessageHandlerServiceIntegrationTest extends AbstractAmqpServiceIntegr
|
|||||||
}
|
}
|
||||||
|
|
||||||
@ParameterizedTest
|
@ParameterizedTest
|
||||||
@ValueSource(strings = { "", "Invalid Content"})
|
@ValueSource(strings = { "", "Invalid Content" })
|
||||||
@NullSource
|
@NullSource
|
||||||
@Description("Tests invalid null message content. This message should forwarded to the deadletter queue")
|
@Description("Tests invalid null message content. This message should forwarded to the deadletter queue")
|
||||||
@ExpectEvents({ @Expect(type = TargetCreatedEvent.class) })
|
@ExpectEvents({ @Expect(type = TargetCreatedEvent.class) })
|
||||||
@@ -688,84 +686,6 @@ class AmqpMessageHandlerServiceIntegrationTest extends AbstractAmqpServiceIntegr
|
|||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Step
|
|
||||||
private void updateAttributesWithUpdateModeRemove() {
|
|
||||||
|
|
||||||
// assemble the expected attributes
|
|
||||||
final Map<String, String> expectedAttributes = targetManagement
|
|
||||||
.getControllerAttributes(DMF_ATTR_TEST_CONTROLLER_ID);
|
|
||||||
expectedAttributes.remove("k1");
|
|
||||||
expectedAttributes.remove("k3");
|
|
||||||
|
|
||||||
// send an update message with update mode
|
|
||||||
final Map<String, String> removeAttributes = new HashMap<>();
|
|
||||||
removeAttributes.put("k1", "foo");
|
|
||||||
removeAttributes.put("k3", "bar");
|
|
||||||
|
|
||||||
final DmfAttributeUpdate remove = new DmfAttributeUpdate();
|
|
||||||
remove.setMode(DmfUpdateMode.REMOVE);
|
|
||||||
remove.getAttributes().putAll(removeAttributes);
|
|
||||||
sendUpdateAttributeMessage(remove);
|
|
||||||
|
|
||||||
// validate
|
|
||||||
assertUpdateAttributes(DMF_ATTR_TEST_CONTROLLER_ID, expectedAttributes);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Step
|
|
||||||
private void updateAttributesWithUpdateModeMerge() {
|
|
||||||
// get the current attributes
|
|
||||||
final Map<String, String> attributes = new HashMap<>(
|
|
||||||
targetManagement.getControllerAttributes(DMF_ATTR_TEST_CONTROLLER_ID));
|
|
||||||
|
|
||||||
// send an update message with update mode MERGE
|
|
||||||
final Map<String, String> mergeAttributes = new HashMap<>();
|
|
||||||
mergeAttributes.put("k1", "v1_modified_again");
|
|
||||||
mergeAttributes.put("k4", "v4");
|
|
||||||
|
|
||||||
final DmfAttributeUpdate merge = new DmfAttributeUpdate();
|
|
||||||
merge.setMode(DmfUpdateMode.MERGE);
|
|
||||||
merge.getAttributes().putAll(mergeAttributes);
|
|
||||||
sendUpdateAttributeMessage(merge);
|
|
||||||
|
|
||||||
// validate
|
|
||||||
final Map<String, String> expectedAttributes = new HashMap<>();
|
|
||||||
expectedAttributes.putAll(attributes);
|
|
||||||
expectedAttributes.putAll(mergeAttributes);
|
|
||||||
assertUpdateAttributes(DMF_ATTR_TEST_CONTROLLER_ID, expectedAttributes);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Step
|
|
||||||
private void updateAttributesWithUpdateModeReplace() {
|
|
||||||
// send an update message with update mode REPLACE
|
|
||||||
final Map<String, String> expectedAttributes = new HashMap<>();
|
|
||||||
expectedAttributes.put("k1", "v1_modified");
|
|
||||||
expectedAttributes.put("k2", "v2");
|
|
||||||
expectedAttributes.put("k3", "v3");
|
|
||||||
|
|
||||||
final DmfAttributeUpdate replace = new DmfAttributeUpdate();
|
|
||||||
replace.setMode(DmfUpdateMode.REPLACE);
|
|
||||||
replace.getAttributes().putAll(expectedAttributes);
|
|
||||||
sendUpdateAttributeMessage(replace);
|
|
||||||
|
|
||||||
// validate
|
|
||||||
assertUpdateAttributes(DMF_ATTR_TEST_CONTROLLER_ID, expectedAttributes);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Step
|
|
||||||
private void updateAttributesWithoutUpdateMode() {
|
|
||||||
// send an update message which does not specify an update mode
|
|
||||||
final Map<String, String> expectedAttributes = new HashMap<>();
|
|
||||||
expectedAttributes.put("k0", "v0");
|
|
||||||
expectedAttributes.put("k1", "v1");
|
|
||||||
|
|
||||||
final DmfAttributeUpdate defaultUpdate = new DmfAttributeUpdate();
|
|
||||||
defaultUpdate.getAttributes().putAll(expectedAttributes);
|
|
||||||
sendUpdateAttributeMessage(defaultUpdate);
|
|
||||||
|
|
||||||
// validate
|
|
||||||
assertUpdateAttributes(DMF_ATTR_TEST_CONTROLLER_ID, expectedAttributes);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
@Description("Verify that sending an update controller attribute message with no thingid header to an existing target does not work.")
|
@Description("Verify that sending an update controller attribute message with no thingid header to an existing target does not work.")
|
||||||
@ExpectEvents({ @Expect(type = TargetCreatedEvent.class, count = 1),
|
@ExpectEvents({ @Expect(type = TargetCreatedEvent.class, count = 1),
|
||||||
@@ -917,6 +837,234 @@ class AmqpMessageHandlerServiceIntegrationTest extends AbstractAmqpServiceIntegr
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
@Description("Register a target and send a update action status (confirmed). Verify if the updated action status is correct.")
|
||||||
|
@ExpectEvents({ @Expect(type = TargetCreatedEvent.class, count = 1),
|
||||||
|
@Expect(type = TargetAssignDistributionSetEvent.class, count = 1),
|
||||||
|
@Expect(type = ActionUpdatedEvent.class, count = 1),
|
||||||
|
@Expect(type = ActionCreatedEvent.class, count = 1),
|
||||||
|
@Expect(type = DistributionSetCreatedEvent.class, count = 1),
|
||||||
|
@Expect(type = SoftwareModuleCreatedEvent.class, count = 3),
|
||||||
|
@Expect(type = DistributionSetUpdatedEvent.class, count = 1), // implicit lock
|
||||||
|
@Expect(type = SoftwareModuleUpdatedEvent.class, count = 9), // implicit lock
|
||||||
|
@Expect(type = TargetUpdatedEvent.class, count = 1),
|
||||||
|
@Expect(type = TargetPollEvent.class, count = 1),
|
||||||
|
@Expect(type = TenantConfigurationCreatedEvent.class, count = 1) })
|
||||||
|
void confirmedActionStatus() {
|
||||||
|
enableConfirmationFlow();
|
||||||
|
final String controllerId = TARGET_PREFIX + "confirmedActionStatus";
|
||||||
|
|
||||||
|
final DistributionSetAssignmentResult assignmentResult = registerTargetAndAssignDistributionSet(controllerId);
|
||||||
|
final Long actionId = getFirstAssignedActionId(assignmentResult);
|
||||||
|
createAndSendActionStatusUpdateMessage(controllerId, actionId, DmfActionStatus.CONFIRMED);
|
||||||
|
assertActionStatusList(actionId, 2, Status.WAIT_FOR_CONFIRMATION, Status.RUNNING);
|
||||||
|
|
||||||
|
// assert download and install message
|
||||||
|
waitUntilEventMessagesAreDispatchedToTarget(EventTopic.CONFIRM, EventTopic.DOWNLOAD_AND_INSTALL);
|
||||||
|
assertDownloadAndInstallMessage(assignmentResult.getDistributionSet().getModules(), controllerId);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
@Description("Verify the DMF confirmed feedback can be provided if confirmation flow is disabled")
|
||||||
|
@ExpectEvents({ @Expect(type = TargetCreatedEvent.class, count = 1),
|
||||||
|
@Expect(type = TargetAssignDistributionSetEvent.class, count = 1),
|
||||||
|
@Expect(type = ActionUpdatedEvent.class, count = 1),
|
||||||
|
@Expect(type = ActionCreatedEvent.class, count = 1),
|
||||||
|
@Expect(type = DistributionSetCreatedEvent.class, count = 1),
|
||||||
|
@Expect(type = SoftwareModuleCreatedEvent.class, count = 3),
|
||||||
|
@Expect(type = DistributionSetUpdatedEvent.class, count = 1), // implicit lock
|
||||||
|
@Expect(type = SoftwareModuleUpdatedEvent.class, count = 9), // implicit lock
|
||||||
|
@Expect(type = TargetUpdatedEvent.class, count = 1),
|
||||||
|
@Expect(type = TargetPollEvent.class, count = 1),
|
||||||
|
@Expect(type = TenantConfigurationCreatedEvent.class, count = 1),
|
||||||
|
@Expect(type = TenantConfigurationUpdatedEvent.class, count = 1) })
|
||||||
|
void verifyActionCanBeConfirmedOnDisabledConfirmationFlow() {
|
||||||
|
enableConfirmationFlow();
|
||||||
|
final String controllerId = TARGET_PREFIX + "confirmedActionStatus";
|
||||||
|
|
||||||
|
final DistributionSetAssignmentResult assignmentResult = registerTargetAndAssignDistributionSet(controllerId);
|
||||||
|
final Long actionId = getFirstAssignedActionId(assignmentResult);
|
||||||
|
// verify action status is in WAIT_FOR_CONFIRMATION
|
||||||
|
assertActionStatusList(actionId, 1, Status.WAIT_FOR_CONFIRMATION);
|
||||||
|
|
||||||
|
disableConfirmationFlow();
|
||||||
|
|
||||||
|
createAndSendActionStatusUpdateMessage(controllerId, actionId, DmfActionStatus.CONFIRMED);
|
||||||
|
assertActionStatusList(actionId, 2, Status.WAIT_FOR_CONFIRMATION, Status.RUNNING);
|
||||||
|
|
||||||
|
// assert download and install message
|
||||||
|
waitUntilEventMessagesAreDispatchedToTarget(EventTopic.CONFIRM, EventTopic.DOWNLOAD_AND_INSTALL);
|
||||||
|
assertDownloadAndInstallMessage(assignmentResult.getDistributionSet().getModules(), controllerId);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
@Description("Verify the DMF confirmed feedback can be provided if confirmation flow is disabled")
|
||||||
|
@ExpectEvents({ @Expect(type = TargetCreatedEvent.class, count = 1),
|
||||||
|
@Expect(type = TargetAssignDistributionSetEvent.class, count = 1),
|
||||||
|
@Expect(type = ActionUpdatedEvent.class, count = 0),
|
||||||
|
@Expect(type = ActionCreatedEvent.class, count = 1),
|
||||||
|
@Expect(type = DistributionSetCreatedEvent.class, count = 1),
|
||||||
|
@Expect(type = SoftwareModuleCreatedEvent.class, count = 3),
|
||||||
|
@Expect(type = DistributionSetUpdatedEvent.class, count = 1), // implicit lock
|
||||||
|
@Expect(type = SoftwareModuleUpdatedEvent.class, count = 9), // implicit lock
|
||||||
|
@Expect(type = TargetUpdatedEvent.class, count = 1),
|
||||||
|
@Expect(type = TargetPollEvent.class, count = 1),
|
||||||
|
@Expect(type = TenantConfigurationCreatedEvent.class, count = 1),
|
||||||
|
@Expect(type = TenantConfigurationUpdatedEvent.class, count = 1) })
|
||||||
|
void verifyActionCanBeDeniedOnDisabledConfirmationFlow() {
|
||||||
|
enableConfirmationFlow();
|
||||||
|
final String controllerId = TARGET_PREFIX + "confirmedActionStatus";
|
||||||
|
|
||||||
|
final DistributionSetAssignmentResult assignmentResult = registerTargetAndAssignDistributionSet(controllerId);
|
||||||
|
final Long actionId = getFirstAssignedActionId(assignmentResult);
|
||||||
|
// verify action status is in WAIT_FOR_CONFIRMATION
|
||||||
|
assertActionStatusList(actionId, 1, Status.WAIT_FOR_CONFIRMATION);
|
||||||
|
|
||||||
|
disableConfirmationFlow();
|
||||||
|
|
||||||
|
createAndSendActionStatusUpdateMessage(controllerId, actionId, DmfActionStatus.DENIED);
|
||||||
|
assertActionStatusList(actionId, 2, Status.WAIT_FOR_CONFIRMATION);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
@Description("Verify the DMF download and install message is send directly if auto-confirmation is active")
|
||||||
|
@ExpectEvents({ @Expect(type = TargetCreatedEvent.class, count = 1),
|
||||||
|
@Expect(type = TargetAssignDistributionSetEvent.class, count = 1),
|
||||||
|
@Expect(type = ActionUpdatedEvent.class, count = 0),
|
||||||
|
@Expect(type = ActionCreatedEvent.class, count = 1),
|
||||||
|
@Expect(type = DistributionSetCreatedEvent.class, count = 1),
|
||||||
|
@Expect(type = SoftwareModuleCreatedEvent.class, count = 3),
|
||||||
|
@Expect(type = DistributionSetUpdatedEvent.class, count = 1), // implicit lock
|
||||||
|
@Expect(type = SoftwareModuleUpdatedEvent.class, count = 9), // implicit lock
|
||||||
|
@Expect(type = DistributionSetCreatedEvent.class, count = 1),
|
||||||
|
@Expect(type = TargetUpdatedEvent.class, count = 2),
|
||||||
|
@Expect(type = TargetPollEvent.class, count = 1),
|
||||||
|
@Expect(type = TenantConfigurationCreatedEvent.class, count = 1) })
|
||||||
|
void verifyDownloadAndInstallDirectlySendOnAutoConfirmationEnabled() {
|
||||||
|
enableConfirmationFlow();
|
||||||
|
final String controllerId = TARGET_PREFIX + "confirmedActionStatus";
|
||||||
|
|
||||||
|
registerAndAssertTargetWithExistingTenant(controllerId);
|
||||||
|
|
||||||
|
confirmationManagement.activateAutoConfirmation(controllerId, null, null);
|
||||||
|
|
||||||
|
final DistributionSetAssignmentResult assignmentResult = prepareDistributionSetAndAssign(controllerId);
|
||||||
|
final Long actionId = getFirstAssignedActionId(assignmentResult);
|
||||||
|
// verify action status is in WAIT_FOR_CONFIRMATION
|
||||||
|
assertActionStatusList(actionId, 1, Status.WAIT_FOR_CONFIRMATION);
|
||||||
|
|
||||||
|
// assert download and install message
|
||||||
|
waitUntilEventMessagesAreDispatchedToTarget(EventTopic.DOWNLOAD_AND_INSTALL);
|
||||||
|
assertDownloadAndInstallMessage(assignmentResult.getDistributionSet().getModules(), controllerId);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
@Description("Register a target and send a update action status (denied). Verify if the updated action status is correct.")
|
||||||
|
@ExpectEvents({ @Expect(type = TargetCreatedEvent.class, count = 1),
|
||||||
|
@Expect(type = TargetAssignDistributionSetEvent.class, count = 1),
|
||||||
|
@Expect(type = ActionUpdatedEvent.class), @Expect(type = ActionCreatedEvent.class, count = 1),
|
||||||
|
@Expect(type = DistributionSetCreatedEvent.class, count = 1),
|
||||||
|
@Expect(type = SoftwareModuleCreatedEvent.class, count = 3),
|
||||||
|
@Expect(type = DistributionSetUpdatedEvent.class, count = 1), // implicit lock
|
||||||
|
@Expect(type = SoftwareModuleUpdatedEvent.class, count = 9), // implicit lock
|
||||||
|
@Expect(type = TargetUpdatedEvent.class, count = 1),
|
||||||
|
@Expect(type = TargetPollEvent.class, count = 1),
|
||||||
|
@Expect(type = TenantConfigurationCreatedEvent.class, count = 1) })
|
||||||
|
void deniedActionStatus() {
|
||||||
|
enableConfirmationFlow();
|
||||||
|
final String controllerId = TARGET_PREFIX + "deniedActionStatus";
|
||||||
|
final DistributionSetAssignmentResult assignmentResult = registerTargetAndAssignDistributionSet(controllerId);
|
||||||
|
final Long actionId = getFirstAssignedActionId(assignmentResult);
|
||||||
|
assertActionStatusList(actionId, 1, Status.WAIT_FOR_CONFIRMATION);
|
||||||
|
createAndSendActionStatusUpdateMessage(controllerId, actionId, DmfActionStatus.DENIED);
|
||||||
|
assertActionStatusList(actionId, 2, Status.WAIT_FOR_CONFIRMATION);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static String getActionIdFromBody(final byte[] body) throws IOException {
|
||||||
|
final ObjectMapper objectMapper = new ObjectMapper();
|
||||||
|
final ObjectNode node = objectMapper.readValue(new String(body, Charset.defaultCharset()), ObjectNode.class);
|
||||||
|
assertThat(node.has("actionId")).isTrue();
|
||||||
|
return node.get("actionId").asText();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Step
|
||||||
|
private void updateAttributesWithUpdateModeRemove() {
|
||||||
|
|
||||||
|
// assemble the expected attributes
|
||||||
|
final Map<String, String> expectedAttributes = targetManagement
|
||||||
|
.getControllerAttributes(DMF_ATTR_TEST_CONTROLLER_ID);
|
||||||
|
expectedAttributes.remove("k1");
|
||||||
|
expectedAttributes.remove("k3");
|
||||||
|
|
||||||
|
// send an update message with update mode
|
||||||
|
final Map<String, String> removeAttributes = new HashMap<>();
|
||||||
|
removeAttributes.put("k1", "foo");
|
||||||
|
removeAttributes.put("k3", "bar");
|
||||||
|
|
||||||
|
final DmfAttributeUpdate remove = new DmfAttributeUpdate();
|
||||||
|
remove.setMode(DmfUpdateMode.REMOVE);
|
||||||
|
remove.getAttributes().putAll(removeAttributes);
|
||||||
|
sendUpdateAttributeMessage(remove);
|
||||||
|
|
||||||
|
// validate
|
||||||
|
assertUpdateAttributes(DMF_ATTR_TEST_CONTROLLER_ID, expectedAttributes);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Step
|
||||||
|
private void updateAttributesWithUpdateModeMerge() {
|
||||||
|
// get the current attributes
|
||||||
|
final Map<String, String> attributes = new HashMap<>(
|
||||||
|
targetManagement.getControllerAttributes(DMF_ATTR_TEST_CONTROLLER_ID));
|
||||||
|
|
||||||
|
// send an update message with update mode MERGE
|
||||||
|
final Map<String, String> mergeAttributes = new HashMap<>();
|
||||||
|
mergeAttributes.put("k1", "v1_modified_again");
|
||||||
|
mergeAttributes.put("k4", "v4");
|
||||||
|
|
||||||
|
final DmfAttributeUpdate merge = new DmfAttributeUpdate();
|
||||||
|
merge.setMode(DmfUpdateMode.MERGE);
|
||||||
|
merge.getAttributes().putAll(mergeAttributes);
|
||||||
|
sendUpdateAttributeMessage(merge);
|
||||||
|
|
||||||
|
// validate
|
||||||
|
final Map<String, String> expectedAttributes = new HashMap<>();
|
||||||
|
expectedAttributes.putAll(attributes);
|
||||||
|
expectedAttributes.putAll(mergeAttributes);
|
||||||
|
assertUpdateAttributes(DMF_ATTR_TEST_CONTROLLER_ID, expectedAttributes);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Step
|
||||||
|
private void updateAttributesWithUpdateModeReplace() {
|
||||||
|
// send an update message with update mode REPLACE
|
||||||
|
final Map<String, String> expectedAttributes = new HashMap<>();
|
||||||
|
expectedAttributes.put("k1", "v1_modified");
|
||||||
|
expectedAttributes.put("k2", "v2");
|
||||||
|
expectedAttributes.put("k3", "v3");
|
||||||
|
|
||||||
|
final DmfAttributeUpdate replace = new DmfAttributeUpdate();
|
||||||
|
replace.setMode(DmfUpdateMode.REPLACE);
|
||||||
|
replace.getAttributes().putAll(expectedAttributes);
|
||||||
|
sendUpdateAttributeMessage(replace);
|
||||||
|
|
||||||
|
// validate
|
||||||
|
assertUpdateAttributes(DMF_ATTR_TEST_CONTROLLER_ID, expectedAttributes);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Step
|
||||||
|
private void updateAttributesWithoutUpdateMode() {
|
||||||
|
// send an update message which does not specify an update mode
|
||||||
|
final Map<String, String> expectedAttributes = new HashMap<>();
|
||||||
|
expectedAttributes.put("k0", "v0");
|
||||||
|
expectedAttributes.put("k1", "v1");
|
||||||
|
|
||||||
|
final DmfAttributeUpdate defaultUpdate = new DmfAttributeUpdate();
|
||||||
|
defaultUpdate.getAttributes().putAll(expectedAttributes);
|
||||||
|
sendUpdateAttributeMessage(defaultUpdate);
|
||||||
|
|
||||||
|
// validate
|
||||||
|
assertUpdateAttributes(DMF_ATTR_TEST_CONTROLLER_ID, expectedAttributes);
|
||||||
|
}
|
||||||
|
|
||||||
@Step
|
@Step
|
||||||
private void verifyAssignedDsAndInstalledDs(final Long assignedDsId, final Long installedDsId) {
|
private void verifyAssignedDsAndInstalledDs(final Long assignedDsId, final Long installedDsId) {
|
||||||
final Optional<Target> target = controllerManagement.getByControllerId(DMF_REGISTER_TEST_CONTROLLER_ID);
|
final Optional<Target> target = controllerManagement.getByControllerId(DMF_REGISTER_TEST_CONTROLLER_ID);
|
||||||
@@ -1014,6 +1162,7 @@ class AmqpMessageHandlerServiceIntegrationTest extends AbstractAmqpServiceIntegr
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
private int getAuthenticationMessageCount() {
|
private int getAuthenticationMessageCount() {
|
||||||
return Integer
|
return Integer
|
||||||
.parseInt(Objects.requireNonNull(getRabbitAdmin().getQueueProperties(amqpProperties.getReceiverQueue()))
|
.parseInt(Objects.requireNonNull(getRabbitAdmin().getQueueProperties(amqpProperties.getReceiverQueue()))
|
||||||
@@ -1034,155 +1183,4 @@ class AmqpMessageHandlerServiceIntegrationTest extends AbstractAmqpServiceIntegr
|
|||||||
.verify(getDeadletterListener(), Mockito.times(numberOfInvocations)).handleMessage(Mockito.any()));
|
.verify(getDeadletterListener(), Mockito.times(numberOfInvocations)).handleMessage(Mockito.any()));
|
||||||
Mockito.reset(getDeadletterListener());
|
Mockito.reset(getDeadletterListener());
|
||||||
}
|
}
|
||||||
|
|
||||||
private static String getActionIdFromBody(final byte[] body) throws IOException {
|
|
||||||
final ObjectMapper objectMapper = new ObjectMapper();
|
|
||||||
final ObjectNode node = objectMapper.readValue(new String(body, Charset.defaultCharset()), ObjectNode.class);
|
|
||||||
assertThat(node.has("actionId")).isTrue();
|
|
||||||
return node.get("actionId").asText();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
@Description("Register a target and send a update action status (confirmed). Verify if the updated action status is correct.")
|
|
||||||
@ExpectEvents({ @Expect(type = TargetCreatedEvent.class, count = 1),
|
|
||||||
@Expect(type = TargetAssignDistributionSetEvent.class, count = 1),
|
|
||||||
@Expect(type = ActionUpdatedEvent.class, count = 1),
|
|
||||||
@Expect(type = ActionCreatedEvent.class, count = 1),
|
|
||||||
@Expect(type = DistributionSetCreatedEvent.class, count = 1),
|
|
||||||
@Expect(type = SoftwareModuleCreatedEvent.class, count = 3),
|
|
||||||
@Expect(type = DistributionSetUpdatedEvent.class, count = 1), // implicit lock
|
|
||||||
@Expect(type = SoftwareModuleUpdatedEvent.class, count = 9), // implicit lock
|
|
||||||
@Expect(type = TargetUpdatedEvent.class, count = 1),
|
|
||||||
@Expect(type = TargetPollEvent.class, count = 1),
|
|
||||||
@Expect(type = TenantConfigurationCreatedEvent.class, count = 1) })
|
|
||||||
void confirmedActionStatus() {
|
|
||||||
enableConfirmationFlow();
|
|
||||||
final String controllerId = TARGET_PREFIX + "confirmedActionStatus";
|
|
||||||
|
|
||||||
final DistributionSetAssignmentResult assignmentResult = registerTargetAndAssignDistributionSet(controllerId);
|
|
||||||
final Long actionId = getFirstAssignedActionId(assignmentResult);
|
|
||||||
createAndSendActionStatusUpdateMessage(controllerId, actionId, DmfActionStatus.CONFIRMED);
|
|
||||||
assertActionStatusList(actionId, 2, Status.WAIT_FOR_CONFIRMATION, Status.RUNNING);
|
|
||||||
|
|
||||||
// assert download and install message
|
|
||||||
waitUntilEventMessagesAreDispatchedToTarget(EventTopic.CONFIRM, EventTopic.DOWNLOAD_AND_INSTALL);
|
|
||||||
assertDownloadAndInstallMessage(assignmentResult.getDistributionSet().getModules(), controllerId);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
@Description("Verify the DMF confirmed feedback can be provided if confirmation flow is disabled")
|
|
||||||
@ExpectEvents({ @Expect(type = TargetCreatedEvent.class, count = 1),
|
|
||||||
@Expect(type = TargetAssignDistributionSetEvent.class, count = 1),
|
|
||||||
@Expect(type = ActionUpdatedEvent.class, count = 1),
|
|
||||||
@Expect(type = ActionCreatedEvent.class, count = 1),
|
|
||||||
@Expect(type = DistributionSetCreatedEvent.class, count = 1),
|
|
||||||
@Expect(type = SoftwareModuleCreatedEvent.class, count = 3),
|
|
||||||
@Expect(type = DistributionSetUpdatedEvent.class, count = 1), // implicit lock
|
|
||||||
@Expect(type = SoftwareModuleUpdatedEvent.class, count = 9), // implicit lock
|
|
||||||
@Expect(type = TargetUpdatedEvent.class, count = 1),
|
|
||||||
@Expect(type = TargetPollEvent.class, count = 1),
|
|
||||||
@Expect(type = TenantConfigurationCreatedEvent.class, count = 1),
|
|
||||||
@Expect(type = TenantConfigurationUpdatedEvent.class, count = 1) })
|
|
||||||
void verifyActionCanBeConfirmedOnDisabledConfirmationFlow() {
|
|
||||||
enableConfirmationFlow();
|
|
||||||
final String controllerId = TARGET_PREFIX + "confirmedActionStatus";
|
|
||||||
|
|
||||||
final DistributionSetAssignmentResult assignmentResult = registerTargetAndAssignDistributionSet(controllerId);
|
|
||||||
final Long actionId = getFirstAssignedActionId(assignmentResult);
|
|
||||||
// verify action status is in WAIT_FOR_CONFIRMATION
|
|
||||||
assertActionStatusList(actionId, 1, Status.WAIT_FOR_CONFIRMATION);
|
|
||||||
|
|
||||||
disableConfirmationFlow();
|
|
||||||
|
|
||||||
createAndSendActionStatusUpdateMessage(controllerId, actionId, DmfActionStatus.CONFIRMED);
|
|
||||||
assertActionStatusList(actionId, 2, Status.WAIT_FOR_CONFIRMATION, Status.RUNNING);
|
|
||||||
|
|
||||||
// assert download and install message
|
|
||||||
waitUntilEventMessagesAreDispatchedToTarget(EventTopic.CONFIRM, EventTopic.DOWNLOAD_AND_INSTALL);
|
|
||||||
assertDownloadAndInstallMessage(assignmentResult.getDistributionSet().getModules(), controllerId);
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
@Test
|
|
||||||
@Description("Verify the DMF confirmed feedback can be provided if confirmation flow is disabled")
|
|
||||||
@ExpectEvents({ @Expect(type = TargetCreatedEvent.class, count = 1),
|
|
||||||
@Expect(type = TargetAssignDistributionSetEvent.class, count = 1),
|
|
||||||
@Expect(type = ActionUpdatedEvent.class, count = 0),
|
|
||||||
@Expect(type = ActionCreatedEvent.class, count = 1),
|
|
||||||
@Expect(type = DistributionSetCreatedEvent.class, count = 1),
|
|
||||||
@Expect(type = SoftwareModuleCreatedEvent.class, count = 3),
|
|
||||||
@Expect(type = DistributionSetUpdatedEvent.class, count = 1), // implicit lock
|
|
||||||
@Expect(type = SoftwareModuleUpdatedEvent.class, count = 9), // implicit lock
|
|
||||||
@Expect(type = TargetUpdatedEvent.class, count = 1),
|
|
||||||
@Expect(type = TargetPollEvent.class, count = 1),
|
|
||||||
@Expect(type = TenantConfigurationCreatedEvent.class, count = 1),
|
|
||||||
@Expect(type = TenantConfigurationUpdatedEvent.class, count = 1)})
|
|
||||||
void verifyActionCanBeDeniedOnDisabledConfirmationFlow() {
|
|
||||||
enableConfirmationFlow();
|
|
||||||
final String controllerId = TARGET_PREFIX + "confirmedActionStatus";
|
|
||||||
|
|
||||||
final DistributionSetAssignmentResult assignmentResult = registerTargetAndAssignDistributionSet(controllerId);
|
|
||||||
final Long actionId = getFirstAssignedActionId(assignmentResult);
|
|
||||||
// verify action status is in WAIT_FOR_CONFIRMATION
|
|
||||||
assertActionStatusList(actionId, 1, Status.WAIT_FOR_CONFIRMATION);
|
|
||||||
|
|
||||||
disableConfirmationFlow();
|
|
||||||
|
|
||||||
createAndSendActionStatusUpdateMessage(controllerId, actionId, DmfActionStatus.DENIED);
|
|
||||||
assertActionStatusList(actionId, 2, Status.WAIT_FOR_CONFIRMATION);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
@Description("Verify the DMF download and install message is send directly if auto-confirmation is active")
|
|
||||||
@ExpectEvents({ @Expect(type = TargetCreatedEvent.class, count = 1),
|
|
||||||
@Expect(type = TargetAssignDistributionSetEvent.class, count = 1),
|
|
||||||
@Expect(type = ActionUpdatedEvent.class, count = 0),
|
|
||||||
@Expect(type = ActionCreatedEvent.class, count = 1),
|
|
||||||
@Expect(type = DistributionSetCreatedEvent.class, count = 1),
|
|
||||||
@Expect(type = SoftwareModuleCreatedEvent.class, count = 3),
|
|
||||||
@Expect(type = DistributionSetUpdatedEvent.class, count = 1), // implicit lock
|
|
||||||
@Expect(type = SoftwareModuleUpdatedEvent.class, count = 9), // implicit lock
|
|
||||||
@Expect(type = DistributionSetCreatedEvent.class, count = 1),
|
|
||||||
@Expect(type = TargetUpdatedEvent.class, count = 2),
|
|
||||||
@Expect(type = TargetPollEvent.class, count = 1),
|
|
||||||
@Expect(type = TenantConfigurationCreatedEvent.class, count = 1) })
|
|
||||||
void verifyDownloadAndInstallDirectlySendOnAutoConfirmationEnabled() {
|
|
||||||
enableConfirmationFlow();
|
|
||||||
final String controllerId = TARGET_PREFIX + "confirmedActionStatus";
|
|
||||||
|
|
||||||
registerAndAssertTargetWithExistingTenant(controllerId);
|
|
||||||
|
|
||||||
confirmationManagement.activateAutoConfirmation(controllerId, null, null);
|
|
||||||
|
|
||||||
final DistributionSetAssignmentResult assignmentResult = prepareDistributionSetAndAssign(controllerId);
|
|
||||||
final Long actionId = getFirstAssignedActionId(assignmentResult);
|
|
||||||
// verify action status is in WAIT_FOR_CONFIRMATION
|
|
||||||
assertActionStatusList(actionId, 1, Status.WAIT_FOR_CONFIRMATION);
|
|
||||||
|
|
||||||
// assert download and install message
|
|
||||||
waitUntilEventMessagesAreDispatchedToTarget(EventTopic.DOWNLOAD_AND_INSTALL);
|
|
||||||
assertDownloadAndInstallMessage(assignmentResult.getDistributionSet().getModules(), controllerId);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
@Description("Register a target and send a update action status (denied). Verify if the updated action status is correct.")
|
|
||||||
@ExpectEvents({ @Expect(type = TargetCreatedEvent.class, count = 1),
|
|
||||||
@Expect(type = TargetAssignDistributionSetEvent.class, count = 1),
|
|
||||||
@Expect(type = ActionUpdatedEvent.class), @Expect(type = ActionCreatedEvent.class, count = 1),
|
|
||||||
@Expect(type = DistributionSetCreatedEvent.class, count = 1),
|
|
||||||
@Expect(type = SoftwareModuleCreatedEvent.class, count = 3),
|
|
||||||
@Expect(type = DistributionSetUpdatedEvent.class, count = 1), // implicit lock
|
|
||||||
@Expect(type = SoftwareModuleUpdatedEvent.class, count = 9), // implicit lock
|
|
||||||
@Expect(type = TargetUpdatedEvent.class, count = 1),
|
|
||||||
@Expect(type = TargetPollEvent.class, count = 1),
|
|
||||||
@Expect(type = TenantConfigurationCreatedEvent.class, count = 1) })
|
|
||||||
void deniedActionStatus() {
|
|
||||||
enableConfirmationFlow();
|
|
||||||
final String controllerId = TARGET_PREFIX + "deniedActionStatus";
|
|
||||||
final DistributionSetAssignmentResult assignmentResult = registerTargetAndAssignDistributionSet(controllerId);
|
|
||||||
final Long actionId = getFirstAssignedActionId(assignmentResult);
|
|
||||||
assertActionStatusList(actionId, 1, Status.WAIT_FOR_CONFIRMATION);
|
|
||||||
createAndSendActionStatusUpdateMessage(controllerId, actionId, DmfActionStatus.DENIED);
|
|
||||||
assertActionStatusList(actionId, 2, Status.WAIT_FOR_CONFIRMATION);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -33,16 +33,15 @@ public final class SoftwareModuleJsonMatcher {
|
|||||||
* <code>null</code>
|
* <code>null</code>
|
||||||
* <p>
|
* <p>
|
||||||
* For example:
|
* For example:
|
||||||
*
|
*
|
||||||
* <pre>
|
* <pre>
|
||||||
* List<SoftwareModule> modules;
|
* List<SoftwareModule> modules;
|
||||||
* List<org.eclipse.hawkbit.dmf.json.model.SoftwareModule> expectedModules;
|
* List<org.eclipse.hawkbit.dmf.json.model.SoftwareModule> expectedModules;
|
||||||
*
|
*
|
||||||
* assertThat(modules, containsExactly(expectedModules));
|
* assertThat(modules, containsExactly(expectedModules));
|
||||||
* </pre>
|
* </pre>
|
||||||
*
|
*
|
||||||
* @param expectedModules
|
* @param expectedModules the json sofware modules.
|
||||||
* the json sofware modules.
|
|
||||||
*/
|
*/
|
||||||
public static SoftwareModulesMatcher containsExactly(final List<DmfSoftwareModule> expectedModules) {
|
public static SoftwareModulesMatcher containsExactly(final List<DmfSoftwareModule> expectedModules) {
|
||||||
return new SoftwareModulesMatcher(expectedModules);
|
return new SoftwareModulesMatcher(expectedModules);
|
||||||
@@ -56,13 +55,22 @@ public final class SoftwareModuleJsonMatcher {
|
|||||||
this.expectedModules = expectedModules;
|
this.expectedModules = expectedModules;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean matches(final Object actualValue) {
|
||||||
|
return containsExactly(actualValue);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void describeTo(final Description description) {
|
||||||
|
description.appendValue(expectedModules);
|
||||||
|
}
|
||||||
|
|
||||||
boolean containsExactly(final Object actual) {
|
boolean containsExactly(final Object actual) {
|
||||||
if (actual == null) {
|
if (actual == null) {
|
||||||
return expectedModules == null;
|
return expectedModules == null;
|
||||||
}
|
}
|
||||||
|
|
||||||
@SuppressWarnings("unchecked")
|
@SuppressWarnings("unchecked") final Collection<SoftwareModule> modules = (Collection<SoftwareModule>) actual;
|
||||||
final Collection<SoftwareModule> modules = (Collection<SoftwareModule>) actual;
|
|
||||||
|
|
||||||
return expectedModules.stream().allMatch(e -> existsIn(e, modules));
|
return expectedModules.stream().allMatch(e -> existsIn(e, modules));
|
||||||
}
|
}
|
||||||
@@ -73,16 +81,6 @@ public final class SoftwareModuleJsonMatcher {
|
|||||||
&& module.getModuleVersion().equals(e.getVersion())
|
&& module.getModuleVersion().equals(e.getVersion())
|
||||||
&& module.getArtifacts().size() == e.getArtifacts().size());
|
&& module.getArtifacts().size() == e.getArtifacts().size());
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
|
||||||
public boolean matches(final Object actualValue) {
|
|
||||||
return containsExactly(actualValue);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void describeTo(final Description description) {
|
|
||||||
description.appendValue(expectedModules);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user