Renamed new DMF parent folder to be consistent with other parents. (#499)
* Fix DMF folder name. Signed-off-by: kaizimmerm <kai.zimmermann@bosch-si.com> * Alligned name. Signed-off-by: kaizimmerm <kai.zimmermann@bosch-si.com> * Delete old gitgnore Signed-off-by: kaizimmerm <kai.zimmermann@bosch-si.com>
This commit is contained in:
@@ -0,0 +1,236 @@
|
||||
/**
|
||||
* Copyright (c) 2015 Bosch Software Innovations GmbH and others.
|
||||
*
|
||||
* All rights reserved. This program and the accompanying materials
|
||||
* are made available under the terms of the Eclipse Public License v1.0
|
||||
* which accompanies this distribution, and is available at
|
||||
* http://www.eclipse.org/legal/epl-v10.html
|
||||
*/
|
||||
package org.eclipse.hawkbit.amqp;
|
||||
|
||||
import java.net.URISyntaxException;
|
||||
import java.util.Optional;
|
||||
import java.util.UUID;
|
||||
|
||||
import org.eclipse.hawkbit.api.HostnameResolver;
|
||||
import org.eclipse.hawkbit.artifact.repository.model.DbArtifact;
|
||||
import org.eclipse.hawkbit.artifact.repository.model.DbArtifactHash;
|
||||
import org.eclipse.hawkbit.cache.DownloadArtifactCache;
|
||||
import org.eclipse.hawkbit.cache.DownloadIdCache;
|
||||
import org.eclipse.hawkbit.cache.DownloadType;
|
||||
import org.eclipse.hawkbit.dmf.json.model.Artifact;
|
||||
import org.eclipse.hawkbit.dmf.json.model.ArtifactHash;
|
||||
import org.eclipse.hawkbit.dmf.json.model.DownloadResponse;
|
||||
import org.eclipse.hawkbit.dmf.json.model.TenantSecurityToken;
|
||||
import org.eclipse.hawkbit.dmf.json.model.TenantSecurityToken.FileResource;
|
||||
import org.eclipse.hawkbit.repository.ArtifactManagement;
|
||||
import org.eclipse.hawkbit.repository.ControllerManagement;
|
||||
import org.eclipse.hawkbit.repository.exception.EntityNotFoundException;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import org.springframework.amqp.AmqpRejectAndDontRequeueException;
|
||||
import org.springframework.amqp.core.Message;
|
||||
import org.springframework.amqp.rabbit.annotation.RabbitListener;
|
||||
import org.springframework.amqp.rabbit.core.RabbitTemplate;
|
||||
import org.springframework.http.HttpStatus;
|
||||
import org.springframework.security.authentication.AuthenticationServiceException;
|
||||
import org.springframework.security.authentication.BadCredentialsException;
|
||||
import org.springframework.security.authentication.CredentialsExpiredException;
|
||||
import org.springframework.security.core.context.SecurityContext;
|
||||
import org.springframework.security.core.context.SecurityContextHolder;
|
||||
import org.springframework.web.util.UriComponentsBuilder;
|
||||
|
||||
/**
|
||||
*
|
||||
* {@link AmqpMessageHandlerService} handles all incoming target authentication
|
||||
* AMQP messages that can be used by 3rd party CDN services to check if a target
|
||||
* is permitted to download certain artifact. This is handled by the queue that
|
||||
* is configured for the property
|
||||
* hawkbit.dmf.rabbitmq.authenticationReceiverQueue.
|
||||
*
|
||||
*/
|
||||
public class AmqpAuthenticationMessageHandler extends BaseAmqpService {
|
||||
private static final Logger LOG = LoggerFactory.getLogger(AmqpAuthenticationMessageHandler.class);
|
||||
|
||||
private final AmqpControllerAuthentication authenticationManager;
|
||||
|
||||
private final ArtifactManagement artifactManagement;
|
||||
|
||||
private final DownloadIdCache cache;
|
||||
|
||||
private final HostnameResolver hostnameResolver;
|
||||
|
||||
private final ControllerManagement controllerManagement;
|
||||
|
||||
/**
|
||||
* @param rabbitTemplate
|
||||
* the configured amqp template.
|
||||
* @param artifactManagement
|
||||
* for artifact URI generation
|
||||
* @param cache
|
||||
* for download Ids
|
||||
* @param hostnameResolver
|
||||
* for resolving the host for downloads
|
||||
* @param authenticationManager
|
||||
* for target authentication
|
||||
* @param controllerManagement
|
||||
* for target repo access
|
||||
*/
|
||||
public AmqpAuthenticationMessageHandler(final RabbitTemplate rabbitTemplate,
|
||||
final AmqpControllerAuthentication authenticationManager, final ArtifactManagement artifactManagement,
|
||||
final DownloadIdCache cache, final HostnameResolver hostnameResolver,
|
||||
final ControllerManagement controllerManagement) {
|
||||
super(rabbitTemplate);
|
||||
this.authenticationManager = authenticationManager;
|
||||
this.artifactManagement = artifactManagement;
|
||||
this.cache = cache;
|
||||
this.hostnameResolver = hostnameResolver;
|
||||
this.controllerManagement = controllerManagement;
|
||||
}
|
||||
|
||||
/**
|
||||
* Executed on an authentication request.
|
||||
*
|
||||
* @param message
|
||||
* the amqp message
|
||||
* @return the rpc message back to supplier.
|
||||
*/
|
||||
@RabbitListener(queues = "${hawkbit.dmf.rabbitmq.authenticationReceiverQueue:authentication_receiver}", containerFactory = "listenerContainerFactory")
|
||||
public Message onAuthenticationRequest(final Message message) {
|
||||
checkContentTypeJson(message);
|
||||
final SecurityContext oldContext = SecurityContextHolder.getContext();
|
||||
try {
|
||||
return handleAuthenticationMessage(message);
|
||||
} catch (final RuntimeException ex) {
|
||||
throw new AmqpRejectAndDontRequeueException(ex);
|
||||
} finally {
|
||||
SecurityContextHolder.setContext(oldContext);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* check action for this download purposes, the method will throw an
|
||||
* EntityNotFoundException in case the controller is not allowed to download
|
||||
* this file because it's not assigned to an action and not assigned to this
|
||||
* controller. Otherwise no controllerId is set = anonymous download
|
||||
*
|
||||
* @param secruityToken
|
||||
* the security token which holds the target ID to check on
|
||||
* @param sha1Hash
|
||||
* of the artifact to verify if the given target is allowed to
|
||||
* download it
|
||||
*/
|
||||
private void checkIfArtifactIsAssignedToTarget(final TenantSecurityToken secruityToken, final String sha1Hash) {
|
||||
|
||||
if (secruityToken.getControllerId() != null) {
|
||||
checkByControllerId(sha1Hash, secruityToken.getControllerId());
|
||||
} else if (secruityToken.getTargetId() != null) {
|
||||
checkByTargetId(sha1Hash, secruityToken.getTargetId());
|
||||
} else {
|
||||
LOG.info("anonymous download no authentication check for artifact {}", sha1Hash);
|
||||
return;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
private void checkByTargetId(final String sha1Hash, final Long targetId) {
|
||||
LOG.debug("no anonymous download request, doing authentication check for target {} and artifact {}", targetId,
|
||||
sha1Hash);
|
||||
if (!controllerManagement.hasTargetArtifactAssigned(targetId, sha1Hash)) {
|
||||
LOG.info("target {} tried to download artifact {} which is not assigned to the target", targetId, sha1Hash);
|
||||
throw new EntityNotFoundException();
|
||||
}
|
||||
LOG.info("download security check for target {} and artifact {} granted", targetId, sha1Hash);
|
||||
}
|
||||
|
||||
private void checkByControllerId(final String sha1Hash, final String controllerId) {
|
||||
LOG.debug("no anonymous download request, doing authentication check for target {} and artifact {}",
|
||||
controllerId, sha1Hash);
|
||||
if (!controllerManagement.hasTargetArtifactAssigned(controllerId, sha1Hash)) {
|
||||
LOG.info("target {} tried to download artifact {} which is not assigned to the target", controllerId,
|
||||
sha1Hash);
|
||||
throw new EntityNotFoundException();
|
||||
}
|
||||
LOG.info("download security check for target {} and artifact {} granted", controllerId, sha1Hash);
|
||||
}
|
||||
|
||||
private Optional<org.eclipse.hawkbit.repository.model.Artifact> findArtifactByFileResource(
|
||||
final FileResource fileResource) {
|
||||
|
||||
if (fileResource == null) {
|
||||
return Optional.empty();
|
||||
}
|
||||
|
||||
if (fileResource.getSha1() != null) {
|
||||
return artifactManagement.findFirstArtifactBySHA1(fileResource.getSha1());
|
||||
}
|
||||
|
||||
if (fileResource.getFilename() != null) {
|
||||
return artifactManagement.findArtifactByFilename(fileResource.getFilename());
|
||||
}
|
||||
|
||||
if (fileResource.getArtifactId() != null) {
|
||||
return artifactManagement.findArtifact(fileResource.getArtifactId());
|
||||
}
|
||||
|
||||
if (fileResource.getSoftwareModuleFilenameResource() != null) {
|
||||
return artifactManagement.findByFilenameAndSoftwareModule(
|
||||
fileResource.getSoftwareModuleFilenameResource().getFilename(),
|
||||
fileResource.getSoftwareModuleFilenameResource().getSoftwareModuleId());
|
||||
}
|
||||
|
||||
return Optional.empty();
|
||||
}
|
||||
|
||||
private static Artifact convertDbArtifact(final DbArtifact dbArtifact) {
|
||||
final Artifact artifact = new Artifact();
|
||||
artifact.setSize(dbArtifact.getSize());
|
||||
final DbArtifactHash dbArtifactHash = dbArtifact.getHashes();
|
||||
artifact.setHashes(new ArtifactHash(dbArtifactHash.getSha1(), dbArtifactHash.getMd5()));
|
||||
return artifact;
|
||||
}
|
||||
|
||||
private Message handleAuthenticationMessage(final Message message) {
|
||||
final DownloadResponse authentificationResponse = new DownloadResponse();
|
||||
final TenantSecurityToken secruityToken = convertMessage(message, TenantSecurityToken.class);
|
||||
final FileResource fileResource = secruityToken.getFileResource();
|
||||
try {
|
||||
SecurityContextHolder.getContext().setAuthentication(authenticationManager.doAuthenticate(secruityToken));
|
||||
|
||||
final String sha1Hash = findArtifactByFileResource(fileResource)
|
||||
.map(org.eclipse.hawkbit.repository.model.Artifact::getSha1Hash)
|
||||
.orElseThrow(() -> new EntityNotFoundException());
|
||||
|
||||
checkIfArtifactIsAssignedToTarget(secruityToken, sha1Hash);
|
||||
|
||||
final Artifact artifact = convertDbArtifact(artifactManagement.loadArtifactBinary(sha1Hash).orElseThrow(
|
||||
() -> new EntityNotFoundException(org.eclipse.hawkbit.repository.model.Artifact.class, sha1Hash)));
|
||||
|
||||
authentificationResponse.setArtifact(artifact);
|
||||
final String downloadId = UUID.randomUUID().toString();
|
||||
// SHA1 key is set, download by SHA1
|
||||
final DownloadArtifactCache downloadCache = new DownloadArtifactCache(DownloadType.BY_SHA1, sha1Hash);
|
||||
cache.put(downloadId, downloadCache);
|
||||
authentificationResponse
|
||||
.setDownloadUrl(UriComponentsBuilder.fromUri(hostnameResolver.resolveHostname().toURI())
|
||||
.path("/api/v1/downloadserver/downloadId/").path(downloadId).build().toUriString());
|
||||
authentificationResponse.setResponseCode(HttpStatus.OK.value());
|
||||
} catch (final BadCredentialsException | AuthenticationServiceException | CredentialsExpiredException e) {
|
||||
LOG.error("Login failed", e);
|
||||
authentificationResponse.setResponseCode(HttpStatus.FORBIDDEN.value());
|
||||
authentificationResponse.setMessage("Login failed");
|
||||
} catch (final URISyntaxException e) {
|
||||
LOG.error("URI build exception", e);
|
||||
authentificationResponse.setResponseCode(HttpStatus.INTERNAL_SERVER_ERROR.value());
|
||||
authentificationResponse.setMessage("Building download URI failed");
|
||||
} catch (final EntityNotFoundException e) {
|
||||
final String errorMessage = "Artifact for resource " + fileResource + "not found ";
|
||||
LOG.warn(errorMessage, e);
|
||||
authentificationResponse.setResponseCode(HttpStatus.NOT_FOUND.value());
|
||||
authentificationResponse.setMessage(errorMessage);
|
||||
}
|
||||
|
||||
return getMessageConverter().toMessage(authentificationResponse, message.getMessageProperties());
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,419 @@
|
||||
/**
|
||||
* Copyright (c) 2015 Bosch Software Innovations GmbH and others.
|
||||
*
|
||||
* All rights reserved. This program and the accompanying materials
|
||||
* are made available under the terms of the Eclipse Public License v1.0
|
||||
* which accompanies this distribution, and is available at
|
||||
* http://www.eclipse.org/legal/epl-v10.html
|
||||
*/
|
||||
package org.eclipse.hawkbit.amqp;
|
||||
|
||||
import java.security.GeneralSecurityException;
|
||||
import java.time.Duration;
|
||||
import java.util.Map;
|
||||
import java.util.concurrent.Executor;
|
||||
import java.util.concurrent.ScheduledExecutorService;
|
||||
|
||||
import org.eclipse.hawkbit.api.ArtifactUrlHandler;
|
||||
import org.eclipse.hawkbit.api.HostnameResolver;
|
||||
import org.eclipse.hawkbit.cache.DownloadIdCache;
|
||||
import org.eclipse.hawkbit.dmf.amqp.api.AmqpSettings;
|
||||
import org.eclipse.hawkbit.repository.ArtifactManagement;
|
||||
import org.eclipse.hawkbit.repository.ControllerManagement;
|
||||
import org.eclipse.hawkbit.repository.EntityFactory;
|
||||
import org.eclipse.hawkbit.repository.SystemManagement;
|
||||
import org.eclipse.hawkbit.repository.TargetManagement;
|
||||
import org.eclipse.hawkbit.repository.TenantConfigurationManagement;
|
||||
import org.eclipse.hawkbit.security.DdiSecurityProperties;
|
||||
import org.eclipse.hawkbit.security.SystemSecurityContext;
|
||||
import org.eclipse.hawkbit.tenancy.TenantAware;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import org.springframework.amqp.core.Binding;
|
||||
import org.springframework.amqp.core.BindingBuilder;
|
||||
import org.springframework.amqp.core.FanoutExchange;
|
||||
import org.springframework.amqp.core.Queue;
|
||||
import org.springframework.amqp.core.QueueBuilder;
|
||||
import org.springframework.amqp.rabbit.connection.CachingConnectionFactory;
|
||||
import org.springframework.amqp.rabbit.connection.ConnectionFactory;
|
||||
import org.springframework.amqp.rabbit.core.RabbitAdmin;
|
||||
import org.springframework.amqp.rabbit.core.RabbitTemplate;
|
||||
import org.springframework.amqp.rabbit.listener.ConditionalRejectingErrorHandler;
|
||||
import org.springframework.amqp.rabbit.listener.RabbitListenerContainerFactory;
|
||||
import org.springframework.amqp.rabbit.listener.SimpleMessageListenerContainer;
|
||||
import org.springframework.amqp.support.converter.Jackson2JsonMessageConverter;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.beans.factory.annotation.Qualifier;
|
||||
import org.springframework.boot.autoconfigure.amqp.RabbitProperties;
|
||||
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
|
||||
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
|
||||
import org.springframework.boot.context.properties.EnableConfigurationProperties;
|
||||
import org.springframework.cloud.bus.ServiceMatcher;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
import org.springframework.retry.backoff.ExponentialBackOffPolicy;
|
||||
import org.springframework.retry.support.RetryTemplate;
|
||||
import org.springframework.util.ErrorHandler;
|
||||
|
||||
import com.google.common.collect.Maps;
|
||||
|
||||
/**
|
||||
* Spring configuration for AMQP based DMF communication for indirect device
|
||||
* integration.
|
||||
*
|
||||
*/
|
||||
@EnableConfigurationProperties({ AmqpProperties.class, AmqpDeadletterProperties.class })
|
||||
@ConditionalOnProperty(prefix = "hawkbit.dmf.rabbitmq", name = "enabled", matchIfMissing = true)
|
||||
public class AmqpConfiguration {
|
||||
|
||||
private static final Logger LOGGER = LoggerFactory.getLogger(AmqpConfiguration.class);
|
||||
|
||||
@Autowired
|
||||
private AmqpProperties amqpProperties;
|
||||
|
||||
@Autowired
|
||||
private AmqpDeadletterProperties amqpDeadletterProperties;
|
||||
|
||||
@Autowired
|
||||
private ConnectionFactory rabbitConnectionFactory;
|
||||
|
||||
@Autowired(required = false)
|
||||
private ServiceMatcher serviceMatcher;
|
||||
|
||||
/**
|
||||
* Register the bean for the custom error handler.
|
||||
*
|
||||
* @return custom error handler
|
||||
*/
|
||||
@Bean
|
||||
@ConditionalOnMissingBean(ErrorHandler.class)
|
||||
public ErrorHandler errorHandler() {
|
||||
return new ConditionalRejectingErrorHandler(
|
||||
new DelayedRequeueExceptionStrategy(amqpProperties.getRequeueDelay()));
|
||||
}
|
||||
|
||||
@Configuration
|
||||
@ConditionalOnMissingBean(ConnectionFactory.class)
|
||||
@ConditionalOnProperty(prefix = "hawkbit.dmf.rabbitmq", name = "enabled", matchIfMissing = true)
|
||||
protected static class RabbitConnectionFactoryCreator {
|
||||
|
||||
@Autowired
|
||||
private AmqpProperties amqpProperties;
|
||||
|
||||
@Autowired
|
||||
@Qualifier("asyncExecutor")
|
||||
private Executor threadPoolExecutor;
|
||||
|
||||
@Autowired
|
||||
private ScheduledExecutorService scheduledExecutorService;
|
||||
|
||||
/**
|
||||
* {@link ConnectionFactory} with enabled publisher confirms and
|
||||
* heartbeat.
|
||||
*
|
||||
* @param config
|
||||
* with standard {@link RabbitProperties}
|
||||
* @return {@link ConnectionFactory}
|
||||
* @throws GeneralSecurityException
|
||||
* in case of problems with enabled SSL connections
|
||||
*/
|
||||
@Bean
|
||||
public ConnectionFactory rabbitConnectionFactory(final RabbitProperties config)
|
||||
throws GeneralSecurityException {
|
||||
final CachingConnectionFactory factory = new CachingConnectionFactory();
|
||||
factory.setRequestedHeartBeat(amqpProperties.getRequestedHeartBeat());
|
||||
factory.setExecutor(threadPoolExecutor);
|
||||
factory.getRabbitConnectionFactory().setHeartbeatExecutor(scheduledExecutorService);
|
||||
factory.setPublisherConfirms(true);
|
||||
|
||||
if (config.getSsl().isEnabled()) {
|
||||
factory.getRabbitConnectionFactory().useSslProtocol();
|
||||
}
|
||||
|
||||
final String addresses = config.getAddresses();
|
||||
factory.setAddresses(addresses);
|
||||
if (config.getHost() != null) {
|
||||
factory.setHost(config.getHost());
|
||||
factory.setPort(config.getPort());
|
||||
}
|
||||
if (config.getUsername() != null) {
|
||||
factory.setUsername(config.getUsername());
|
||||
}
|
||||
if (config.getPassword() != null) {
|
||||
factory.setPassword(config.getPassword());
|
||||
}
|
||||
if (config.getVirtualHost() != null) {
|
||||
factory.setVirtualHost(config.getVirtualHost());
|
||||
}
|
||||
return factory;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a {@link RabbitAdmin} and ignore declaration exceptions.
|
||||
* {@link RabbitAdmin#setIgnoreDeclarationExceptions(boolean)}
|
||||
*
|
||||
* @return the bean
|
||||
*/
|
||||
@Bean
|
||||
public RabbitAdmin rabbitAdmin() {
|
||||
final RabbitAdmin rabbitAdmin = new RabbitAdmin(rabbitConnectionFactory);
|
||||
rabbitAdmin.setIgnoreDeclarationExceptions(true);
|
||||
return rabbitAdmin;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return {@link RabbitTemplate} with automatic retry, published confirms
|
||||
* and {@link Jackson2JsonMessageConverter}.
|
||||
*/
|
||||
@Bean
|
||||
public RabbitTemplate rabbitTemplate() {
|
||||
final RabbitTemplate rabbitTemplate = new RabbitTemplate(rabbitConnectionFactory);
|
||||
rabbitTemplate.setMessageConverter(new Jackson2JsonMessageConverter());
|
||||
|
||||
final RetryTemplate retryTemplate = new RetryTemplate();
|
||||
retryTemplate.setBackOffPolicy(new ExponentialBackOffPolicy());
|
||||
rabbitTemplate.setRetryTemplate(retryTemplate);
|
||||
|
||||
rabbitTemplate.setConfirmCallback((correlationData, ack, cause) -> {
|
||||
if (ack) {
|
||||
LOGGER.debug("Message with {} confirmed by broker.", correlationData);
|
||||
} else {
|
||||
LOGGER.error("Broker is unable to handle message with {} : {}", correlationData, cause);
|
||||
}
|
||||
});
|
||||
|
||||
return rabbitTemplate;
|
||||
}
|
||||
|
||||
/**
|
||||
* Create the DMF API receiver queue for retrieving DMF messages.
|
||||
*
|
||||
* @return the receiver queue
|
||||
*/
|
||||
@Bean
|
||||
public Queue dmfReceiverQueue() {
|
||||
return new Queue(amqpProperties.getReceiverQueue(), true, false, false,
|
||||
amqpDeadletterProperties.getDeadLetterExchangeArgs(amqpProperties.getDeadLetterExchange()));
|
||||
}
|
||||
|
||||
/**
|
||||
* Create the DMF API receiver queue for authentication requests called by
|
||||
* 3rd party artifact storages for download authorization by devices.
|
||||
*
|
||||
* @return the receiver queue
|
||||
*/
|
||||
@Bean
|
||||
public Queue authenticationReceiverQueue() {
|
||||
return QueueBuilder.nonDurable(amqpProperties.getAuthenticationReceiverQueue()).autoDelete()
|
||||
.withArguments(getTTLMaxArgsAuthenticationQueue()).build();
|
||||
}
|
||||
|
||||
/**
|
||||
* Create DMF exchange.
|
||||
*
|
||||
* @return the fanout exchange
|
||||
*/
|
||||
@Bean
|
||||
public FanoutExchange dmfSenderExchange() {
|
||||
return new FanoutExchange(AmqpSettings.DMF_EXCHANGE);
|
||||
}
|
||||
|
||||
/**
|
||||
* Create the Binding {@link AmqpConfiguration#dmfReceiverQueue()} to
|
||||
* {@link AmqpConfiguration#dmfSenderExchange()}.
|
||||
*
|
||||
* @return the binding and create the queue and exchange
|
||||
*/
|
||||
@Bean
|
||||
public Binding bindDmfSenderExchangeToDmfQueue() {
|
||||
return BindingBuilder.bind(dmfReceiverQueue()).to(dmfSenderExchange());
|
||||
}
|
||||
|
||||
/**
|
||||
* Create authentication exchange.
|
||||
*
|
||||
* @return the fanout exchange
|
||||
*/
|
||||
@Bean
|
||||
public FanoutExchange authenticationExchange() {
|
||||
return new FanoutExchange(AmqpSettings.AUTHENTICATION_EXCHANGE, false, true);
|
||||
}
|
||||
|
||||
/**
|
||||
* Create the Binding
|
||||
* {@link AmqpConfiguration#authenticationReceiverQueue()} to
|
||||
* {@link AmqpConfiguration#authenticationExchange()}.
|
||||
*
|
||||
* @return the binding and create the queue and exchange
|
||||
*/
|
||||
@Bean
|
||||
public Binding bindAuthenticationSenderExchangeToAuthenticationQueue() {
|
||||
return BindingBuilder.bind(authenticationReceiverQueue()).to(authenticationExchange());
|
||||
}
|
||||
|
||||
/**
|
||||
* Create dead letter queue.
|
||||
*
|
||||
* @return the queue
|
||||
*/
|
||||
@Bean
|
||||
public Queue deadLetterQueue() {
|
||||
return amqpDeadletterProperties.createDeadletterQueue(amqpProperties.getDeadLetterQueue());
|
||||
}
|
||||
|
||||
/**
|
||||
* Create the dead letter fanout exchange.
|
||||
*
|
||||
* @return the fanout exchange
|
||||
*/
|
||||
@Bean
|
||||
public FanoutExchange deadLetterExchange() {
|
||||
return new FanoutExchange(amqpProperties.getDeadLetterExchange());
|
||||
}
|
||||
|
||||
/**
|
||||
* Create the Binding deadLetterQueue to deadLetterExchange.
|
||||
*
|
||||
* @return the binding
|
||||
*/
|
||||
@Bean
|
||||
public Binding bindDeadLetterQueueToDeadLetterExchange() {
|
||||
return BindingBuilder.bind(deadLetterQueue()).to(deadLetterExchange());
|
||||
}
|
||||
|
||||
/**
|
||||
* Create AMQP handler service bean.
|
||||
*
|
||||
* @param rabbitTemplate
|
||||
* for converting messages
|
||||
* @param amqpMessageDispatcherService
|
||||
* to sending events to DMF client
|
||||
* @param controllerManagement
|
||||
* for target repo access
|
||||
* @param entityFactory
|
||||
* to create entities
|
||||
*
|
||||
* @return handler service bean
|
||||
*/
|
||||
@Bean
|
||||
public AmqpMessageHandlerService amqpMessageHandlerService(final RabbitTemplate rabbitTemplate,
|
||||
final AmqpMessageDispatcherService amqpMessageDispatcherService,
|
||||
final ControllerManagement controllerManagement, final EntityFactory entityFactory) {
|
||||
return new AmqpMessageHandlerService(rabbitTemplate, amqpMessageDispatcherService, controllerManagement,
|
||||
entityFactory);
|
||||
}
|
||||
|
||||
/**
|
||||
* Create AMQP handler service bean for authentication messages.
|
||||
*
|
||||
* @param rabbitTemplate
|
||||
* for converting messages
|
||||
* @param authenticationManager
|
||||
* for target authentication
|
||||
* @param artifactManagement
|
||||
* for artifact URI generation
|
||||
* @param downloadIdCache
|
||||
* for download IDs
|
||||
* @param hostnameResolver
|
||||
* for resolving the host for downloads
|
||||
* @param controllerManagement
|
||||
* for target repo access
|
||||
* @return handler service bean
|
||||
*/
|
||||
@Bean
|
||||
public AmqpAuthenticationMessageHandler amqpAuthenticationMessageHandler(final RabbitTemplate rabbitTemplate,
|
||||
final AmqpControllerAuthentication authenticationManager, final ArtifactManagement artifactManagement,
|
||||
final DownloadIdCache downloadIdCache, final HostnameResolver hostnameResolver,
|
||||
final ControllerManagement controllerManagement) {
|
||||
return new AmqpAuthenticationMessageHandler(rabbitTemplate, authenticationManager, artifactManagement,
|
||||
downloadIdCache, hostnameResolver, controllerManagement);
|
||||
}
|
||||
|
||||
/**
|
||||
* Create default amqp sender service bean.
|
||||
*
|
||||
* @return the default amqp sender service bean
|
||||
*/
|
||||
@Bean
|
||||
@ConditionalOnMissingBean
|
||||
public AmqpSenderService amqpSenderServiceBean() {
|
||||
return new DefaultAmqpSenderService(rabbitTemplate());
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the Listener factory.
|
||||
*
|
||||
* @param errorHandler
|
||||
* the error hander
|
||||
* @return the {@link SimpleMessageListenerContainer} that gets used receive
|
||||
* AMQP messages
|
||||
*/
|
||||
@Bean(name = { "listenerContainerFactory" })
|
||||
public RabbitListenerContainerFactory<SimpleMessageListenerContainer> listenerContainerFactory(
|
||||
final ErrorHandler errorHandler) {
|
||||
return new ConfigurableRabbitListenerContainerFactory(amqpProperties, rabbitConnectionFactory, errorHandler);
|
||||
}
|
||||
|
||||
/**
|
||||
* create the authentication bean for controller over amqp.
|
||||
*
|
||||
* @param systemManagement
|
||||
* the systemManagement
|
||||
* @param controllerManagement
|
||||
* the controllerManagement
|
||||
* @param tenantConfigurationManagement
|
||||
* the tenantConfigurationManagement
|
||||
* @param tenantAware
|
||||
* the tenantAware
|
||||
* @param ddiSecruityProperties
|
||||
* the ddiSecruityProperties
|
||||
* @param systemSecurityContext
|
||||
* the systemSecurityContext
|
||||
* @return the bean
|
||||
*/
|
||||
@Bean
|
||||
@ConditionalOnMissingBean(AmqpControllerAuthentication.class)
|
||||
public AmqpControllerAuthentication amqpControllerAuthentication(final SystemManagement systemManagement,
|
||||
final ControllerManagement controllerManagement,
|
||||
final TenantConfigurationManagement tenantConfigurationManagement, final TenantAware tenantAware,
|
||||
final DdiSecurityProperties ddiSecruityProperties, final SystemSecurityContext systemSecurityContext) {
|
||||
return new AmqpControllerAuthentication(systemManagement, controllerManagement, tenantConfigurationManagement,
|
||||
tenantAware, ddiSecruityProperties, systemSecurityContext);
|
||||
}
|
||||
|
||||
/**
|
||||
* Create the dispatcher bean.
|
||||
*
|
||||
* @param rabbitTemplate
|
||||
* the rabbitTemplate
|
||||
* @param amqpSenderService
|
||||
* to send AMQP message
|
||||
* @param artifactUrlHandler
|
||||
* for generating download URLs
|
||||
* @param systemSecurityContext
|
||||
* for execution with system permissions
|
||||
* @param systemManagement
|
||||
* the systemManagement
|
||||
* @param targetManagement
|
||||
* to access target information
|
||||
* @return the bean
|
||||
*/
|
||||
@Bean
|
||||
@ConditionalOnMissingBean(AmqpMessageDispatcherService.class)
|
||||
public AmqpMessageDispatcherService amqpMessageDispatcherService(final RabbitTemplate rabbitTemplate,
|
||||
final AmqpSenderService amqpSenderService, final ArtifactUrlHandler artifactUrlHandler,
|
||||
final SystemSecurityContext systemSecurityContext, final SystemManagement systemManagement,
|
||||
final TargetManagement targetManagement) {
|
||||
return new AmqpMessageDispatcherService(rabbitTemplate, amqpSenderService, artifactUrlHandler,
|
||||
systemSecurityContext, systemManagement, targetManagement, serviceMatcher);
|
||||
}
|
||||
|
||||
private static Map<String, Object> getTTLMaxArgsAuthenticationQueue() {
|
||||
final Map<String, Object> args = Maps.newHashMapWithExpectedSize(2);
|
||||
args.put("x-message-ttl", Duration.ofSeconds(30).toMillis());
|
||||
args.put("x-max-length", 1_000);
|
||||
return args;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,167 @@
|
||||
/**
|
||||
* Copyright (c) 2015 Bosch Software Innovations GmbH and others.
|
||||
*
|
||||
* All rights reserved. This program and the accompanying materials
|
||||
* are made available under the terms of the Eclipse Public License v1.0
|
||||
* which accompanies this distribution, and is available at
|
||||
* http://www.eclipse.org/legal/epl-v10.html
|
||||
*/
|
||||
package org.eclipse.hawkbit.amqp;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
import javax.annotation.PostConstruct;
|
||||
|
||||
import org.eclipse.hawkbit.dmf.json.model.TenantSecurityToken;
|
||||
import org.eclipse.hawkbit.im.authentication.TenantAwareAuthenticationDetails;
|
||||
import org.eclipse.hawkbit.repository.ControllerManagement;
|
||||
import org.eclipse.hawkbit.repository.SystemManagement;
|
||||
import org.eclipse.hawkbit.repository.TenantConfigurationManagement;
|
||||
import org.eclipse.hawkbit.security.ControllerPreAuthenticateSecurityTokenFilter;
|
||||
import org.eclipse.hawkbit.security.ControllerPreAuthenticatedAnonymousDownload;
|
||||
import org.eclipse.hawkbit.security.ControllerPreAuthenticatedAnonymousFilter;
|
||||
import org.eclipse.hawkbit.security.ControllerPreAuthenticatedGatewaySecurityTokenFilter;
|
||||
import org.eclipse.hawkbit.security.ControllerPreAuthenticatedSecurityHeaderFilter;
|
||||
import org.eclipse.hawkbit.security.DdiSecurityProperties;
|
||||
import org.eclipse.hawkbit.security.PreAuthTokenSourceTrustAuthenticationProvider;
|
||||
import org.eclipse.hawkbit.security.PreAuthentificationFilter;
|
||||
import org.eclipse.hawkbit.security.SystemSecurityContext;
|
||||
import org.eclipse.hawkbit.tenancy.TenantAware;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import org.springframework.security.core.Authentication;
|
||||
import org.springframework.security.web.authentication.preauth.PreAuthenticatedAuthenticationToken;
|
||||
|
||||
import com.google.common.collect.Lists;
|
||||
|
||||
/**
|
||||
*
|
||||
* A controller which handles the DMF AMQP authentication.
|
||||
*/
|
||||
public class AmqpControllerAuthentication {
|
||||
|
||||
private static final Logger LOGGER = LoggerFactory.getLogger(AmqpControllerAuthentication.class);
|
||||
|
||||
private final PreAuthTokenSourceTrustAuthenticationProvider preAuthenticatedAuthenticationProvider = new PreAuthTokenSourceTrustAuthenticationProvider();
|
||||
|
||||
private List<PreAuthentificationFilter> filterChain;
|
||||
|
||||
private final ControllerManagement controllerManagement;
|
||||
|
||||
private final SystemManagement systemManagement;
|
||||
|
||||
private final TenantConfigurationManagement tenantConfigurationManagement;
|
||||
|
||||
private final TenantAware tenantAware;
|
||||
|
||||
private final DdiSecurityProperties ddiSecruityProperties;
|
||||
|
||||
private final SystemSecurityContext systemSecurityContext;
|
||||
|
||||
/**
|
||||
* Constructor.
|
||||
*
|
||||
* @param systemManagement
|
||||
* @param controllerManagement
|
||||
* @param tenantConfigurationManagement
|
||||
* @param tenantAware
|
||||
* current tenant
|
||||
* @param ddiSecruityProperties
|
||||
* security configurations
|
||||
* @param systemSecurityContext
|
||||
* security context
|
||||
*/
|
||||
public AmqpControllerAuthentication(final SystemManagement systemManagement,
|
||||
final ControllerManagement controllerManagement,
|
||||
final TenantConfigurationManagement tenantConfigurationManagement, final TenantAware tenantAware,
|
||||
final DdiSecurityProperties ddiSecruityProperties, final SystemSecurityContext systemSecurityContext) {
|
||||
this.controllerManagement = controllerManagement;
|
||||
this.systemManagement = systemManagement;
|
||||
this.tenantConfigurationManagement = tenantConfigurationManagement;
|
||||
this.tenantAware = tenantAware;
|
||||
this.ddiSecruityProperties = ddiSecruityProperties;
|
||||
this.systemSecurityContext = systemSecurityContext;
|
||||
}
|
||||
|
||||
/**
|
||||
* Called by spring when bean instantiated and autowired.
|
||||
*/
|
||||
@PostConstruct
|
||||
public void postConstruct() {
|
||||
addFilter();
|
||||
}
|
||||
|
||||
private void addFilter() {
|
||||
filterChain = Lists.newArrayListWithExpectedSize(5);
|
||||
|
||||
final ControllerPreAuthenticatedGatewaySecurityTokenFilter gatewaySecurityTokenFilter = new ControllerPreAuthenticatedGatewaySecurityTokenFilter(
|
||||
tenantConfigurationManagement, tenantAware, systemSecurityContext);
|
||||
filterChain.add(gatewaySecurityTokenFilter);
|
||||
|
||||
final ControllerPreAuthenticatedSecurityHeaderFilter securityHeaderFilter = new ControllerPreAuthenticatedSecurityHeaderFilter(
|
||||
ddiSecruityProperties.getRp().getCnHeader(), ddiSecruityProperties.getRp().getSslIssuerHashHeader(),
|
||||
tenantConfigurationManagement, tenantAware, systemSecurityContext);
|
||||
filterChain.add(securityHeaderFilter);
|
||||
|
||||
final ControllerPreAuthenticateSecurityTokenFilter securityTokenFilter = new ControllerPreAuthenticateSecurityTokenFilter(
|
||||
tenantConfigurationManagement, controllerManagement, tenantAware, systemSecurityContext);
|
||||
filterChain.add(securityTokenFilter);
|
||||
|
||||
final ControllerPreAuthenticatedAnonymousDownload anonymousDownloadFilter = new ControllerPreAuthenticatedAnonymousDownload(
|
||||
tenantConfigurationManagement, tenantAware, systemSecurityContext);
|
||||
filterChain.add(anonymousDownloadFilter);
|
||||
|
||||
filterChain.add(new ControllerPreAuthenticatedAnonymousFilter(ddiSecruityProperties));
|
||||
}
|
||||
|
||||
/**
|
||||
* Performs authentication with the security token.
|
||||
*
|
||||
* @param securityToken
|
||||
* the authentication request object
|
||||
* @return the authentication object
|
||||
*/
|
||||
public Authentication doAuthenticate(final TenantSecurityToken securityToken) {
|
||||
resolveTenant(securityToken);
|
||||
PreAuthenticatedAuthenticationToken authentication = new PreAuthenticatedAuthenticationToken(null, null);
|
||||
for (final PreAuthentificationFilter filter : filterChain) {
|
||||
final PreAuthenticatedAuthenticationToken authenticationRest = createAuthentication(filter, securityToken);
|
||||
if (authenticationRest != null) {
|
||||
authentication = authenticationRest;
|
||||
authentication.setDetails(new TenantAwareAuthenticationDetails(securityToken.getTenant(), true));
|
||||
break;
|
||||
}
|
||||
}
|
||||
return preAuthenticatedAuthenticationProvider.authenticate(authentication);
|
||||
|
||||
}
|
||||
|
||||
private void resolveTenant(final TenantSecurityToken securityToken) {
|
||||
if (securityToken.getTenant() == null) {
|
||||
securityToken.setTenant(systemSecurityContext
|
||||
.runAsSystem(() -> systemManagement.getTenantMetadata(securityToken.getTenantId()).getTenant()));
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
private static PreAuthenticatedAuthenticationToken createAuthentication(final PreAuthentificationFilter filter,
|
||||
final TenantSecurityToken secruityToken) {
|
||||
|
||||
if (!filter.isEnable(secruityToken)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
final Object principal = filter.getPreAuthenticatedPrincipal(secruityToken);
|
||||
final Object credentials = filter.getPreAuthenticatedCredentials(secruityToken);
|
||||
|
||||
if (principal == null) {
|
||||
LOGGER.debug("No pre-authenticated principal found in message");
|
||||
return null;
|
||||
}
|
||||
|
||||
LOGGER.debug("preAuthenticatedPrincipal = {} trying to authenticate", principal);
|
||||
|
||||
return new PreAuthenticatedAuthenticationToken(principal, credentials);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,71 @@
|
||||
/**
|
||||
* Copyright (c) 2015 Bosch Software Innovations GmbH and others.
|
||||
*
|
||||
* All rights reserved. This program and the accompanying materials
|
||||
* are made available under the terms of the Eclipse Public License v1.0
|
||||
* which accompanies this distribution, and is available at
|
||||
* http://www.eclipse.org/legal/epl-v10.html
|
||||
*/
|
||||
package org.eclipse.hawkbit.amqp;
|
||||
|
||||
import java.time.Duration;
|
||||
import java.util.Map;
|
||||
|
||||
import org.springframework.amqp.core.Queue;
|
||||
import org.springframework.boot.context.properties.ConfigurationProperties;
|
||||
|
||||
import com.google.common.collect.Maps;
|
||||
|
||||
/**
|
||||
* Bean which holds the necessary properties for configuring the AMQP deadletter
|
||||
* queue.
|
||||
*/
|
||||
@ConfigurationProperties("hawkbit.dmf.rabbitmq.deadLetter")
|
||||
public class AmqpDeadletterProperties {
|
||||
private static final int THREE_WEEKS = 21;
|
||||
|
||||
/**
|
||||
* Message time to live (ttl) for the deadletter queue. Default ttl is 3
|
||||
* weeks.
|
||||
*/
|
||||
private long ttl = Duration.ofDays(THREE_WEEKS).toMillis();
|
||||
|
||||
/**
|
||||
* Return the deadletter arguments.
|
||||
*
|
||||
* @param exchange
|
||||
* the deadletter exchange
|
||||
* @return map which holds the properties
|
||||
*/
|
||||
public Map<String, Object> getDeadLetterExchangeArgs(final String exchange) {
|
||||
final Map<String, Object> args = Maps.newHashMapWithExpectedSize(1);
|
||||
args.put("x-dead-letter-exchange", exchange);
|
||||
return args;
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a deadletter queue with ttl for messages
|
||||
*
|
||||
* @param queueName
|
||||
* the deadlette queue name
|
||||
* @return the deadletter queue
|
||||
*/
|
||||
public Queue createDeadletterQueue(final String queueName) {
|
||||
return new Queue(queueName, true, false, false, getTTLArgs());
|
||||
}
|
||||
|
||||
private Map<String, Object> getTTLArgs() {
|
||||
final Map<String, Object> args = Maps.newHashMapWithExpectedSize(1);
|
||||
args.put("x-message-ttl", getTtl());
|
||||
return args;
|
||||
}
|
||||
|
||||
public long getTtl() {
|
||||
return ttl;
|
||||
}
|
||||
|
||||
public void setTtl(final long ttl) {
|
||||
this.ttl = ttl;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,271 @@
|
||||
/**
|
||||
* Copyright (c) 2015 Bosch Software Innovations GmbH and others.
|
||||
*
|
||||
* All rights reserved. This program and the accompanying materials
|
||||
* are made available under the terms of the Eclipse Public License v1.0
|
||||
* which accompanies this distribution, and is available at
|
||||
* http://www.eclipse.org/legal/epl-v10.html
|
||||
*/
|
||||
package org.eclipse.hawkbit.amqp;
|
||||
|
||||
import java.net.URI;
|
||||
import java.util.Collection;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
import org.eclipse.hawkbit.api.ApiType;
|
||||
import org.eclipse.hawkbit.api.ArtifactUrl;
|
||||
import org.eclipse.hawkbit.api.ArtifactUrlHandler;
|
||||
import org.eclipse.hawkbit.api.URLPlaceholder;
|
||||
import org.eclipse.hawkbit.api.URLPlaceholder.SoftwareData;
|
||||
import org.eclipse.hawkbit.dmf.amqp.api.EventTopic;
|
||||
import org.eclipse.hawkbit.dmf.amqp.api.MessageHeaderKey;
|
||||
import org.eclipse.hawkbit.dmf.amqp.api.MessageType;
|
||||
import org.eclipse.hawkbit.dmf.json.model.Artifact;
|
||||
import org.eclipse.hawkbit.dmf.json.model.ArtifactHash;
|
||||
import org.eclipse.hawkbit.dmf.json.model.DownloadAndUpdateRequest;
|
||||
import org.eclipse.hawkbit.dmf.json.model.SoftwareModule;
|
||||
import org.eclipse.hawkbit.repository.SystemManagement;
|
||||
import org.eclipse.hawkbit.repository.TargetManagement;
|
||||
import org.eclipse.hawkbit.repository.event.remote.TargetAssignDistributionSetEvent;
|
||||
import org.eclipse.hawkbit.repository.event.remote.TargetDeletedEvent;
|
||||
import org.eclipse.hawkbit.repository.event.remote.entity.CancelTargetAssignmentEvent;
|
||||
import org.eclipse.hawkbit.repository.model.Target;
|
||||
import org.eclipse.hawkbit.security.SystemSecurityContext;
|
||||
import org.eclipse.hawkbit.util.IpUtil;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import org.springframework.amqp.core.Message;
|
||||
import org.springframework.amqp.core.MessageProperties;
|
||||
import org.springframework.amqp.rabbit.core.RabbitTemplate;
|
||||
import org.springframework.cloud.bus.ServiceMatcher;
|
||||
import org.springframework.cloud.bus.event.RemoteApplicationEvent;
|
||||
import org.springframework.context.event.EventListener;
|
||||
|
||||
/**
|
||||
* {@link AmqpMessageDispatcherService} create all outgoing AMQP messages and
|
||||
* delegate the messages to a {@link AmqpSenderService}.
|
||||
*
|
||||
* Additionally the dispatcher listener/subscribe for some target events e.g.
|
||||
* assignment.
|
||||
*
|
||||
*/
|
||||
public class AmqpMessageDispatcherService extends BaseAmqpService {
|
||||
|
||||
private static final Logger LOG = LoggerFactory.getLogger(AmqpMessageDispatcherService.class);
|
||||
|
||||
private final ArtifactUrlHandler artifactUrlHandler;
|
||||
private final AmqpSenderService amqpSenderService;
|
||||
private final SystemSecurityContext systemSecurityContext;
|
||||
private final SystemManagement systemManagement;
|
||||
private final TargetManagement targetManagement;
|
||||
private final ServiceMatcher serviceMatcher;
|
||||
|
||||
/**
|
||||
* Constructor.
|
||||
*
|
||||
* @param rabbitTemplate
|
||||
* the rabbitTemplate
|
||||
* @param amqpSenderService
|
||||
* to send AMQP message
|
||||
* @param artifactUrlHandler
|
||||
* for generating download URLs
|
||||
* @param systemSecurityContext
|
||||
* for execution with system permissions
|
||||
* @param systemManagement
|
||||
* the systemManagement
|
||||
* @param targetManagement
|
||||
* to access target information
|
||||
* @param serviceMatcher
|
||||
* to check in cluster case if the message is from the same
|
||||
* cluster node
|
||||
*/
|
||||
public AmqpMessageDispatcherService(final RabbitTemplate rabbitTemplate, final AmqpSenderService amqpSenderService,
|
||||
final ArtifactUrlHandler artifactUrlHandler, final SystemSecurityContext systemSecurityContext,
|
||||
final SystemManagement systemManagement, final TargetManagement targetManagement,
|
||||
final ServiceMatcher serviceMatcher) {
|
||||
super(rabbitTemplate);
|
||||
this.artifactUrlHandler = artifactUrlHandler;
|
||||
this.amqpSenderService = amqpSenderService;
|
||||
this.systemSecurityContext = systemSecurityContext;
|
||||
this.systemManagement = systemManagement;
|
||||
this.targetManagement = targetManagement;
|
||||
this.serviceMatcher = serviceMatcher;
|
||||
}
|
||||
|
||||
/**
|
||||
* Method to send a message to a RabbitMQ Exchange after the Distribution
|
||||
* set has been assign to a Target.
|
||||
*
|
||||
* @param assignedEvent
|
||||
* the object to be send.
|
||||
*/
|
||||
@EventListener(classes = TargetAssignDistributionSetEvent.class)
|
||||
public void targetAssignDistributionSet(final TargetAssignDistributionSetEvent assignedEvent) {
|
||||
if (isNotFromSelf(assignedEvent)) {
|
||||
return;
|
||||
}
|
||||
|
||||
LOG.debug("targetAssignDistributionSet retrieved for controller {}. I will forward it to DMF broker.",
|
||||
assignedEvent.getControllerId());
|
||||
|
||||
targetManagement.findTargetByControllerID(assignedEvent.getControllerId())
|
||||
.ifPresent(target -> sendUpdateMessageToTarget(assignedEvent.getTenant(), target,
|
||||
assignedEvent.getActionId(), assignedEvent.getModules()));
|
||||
|
||||
}
|
||||
|
||||
void sendUpdateMessageToTarget(final String tenant, final Target target, final Long actionId,
|
||||
final Collection<org.eclipse.hawkbit.repository.model.SoftwareModule> modules) {
|
||||
|
||||
final URI targetAdress = target.getAddress();
|
||||
if (!IpUtil.isAmqpUri(targetAdress)) {
|
||||
return;
|
||||
}
|
||||
|
||||
final DownloadAndUpdateRequest downloadAndUpdateRequest = new DownloadAndUpdateRequest();
|
||||
downloadAndUpdateRequest.setActionId(actionId);
|
||||
|
||||
final String targetSecurityToken = systemSecurityContext.runAsSystem(target::getSecurityToken);
|
||||
downloadAndUpdateRequest.setTargetSecurityToken(targetSecurityToken);
|
||||
|
||||
for (final org.eclipse.hawkbit.repository.model.SoftwareModule softwareModule : modules) {
|
||||
final SoftwareModule amqpSoftwareModule = convertToAmqpSoftwareModule(target, softwareModule);
|
||||
downloadAndUpdateRequest.addSoftwareModule(amqpSoftwareModule);
|
||||
}
|
||||
|
||||
final Message message = getMessageConverter().toMessage(downloadAndUpdateRequest,
|
||||
createConnectorMessagePropertiesEvent(tenant, target.getControllerId(),
|
||||
EventTopic.DOWNLOAD_AND_INSTALL));
|
||||
amqpSenderService.sendMessage(message, targetAdress);
|
||||
}
|
||||
|
||||
/**
|
||||
* Method to send a message to a RabbitMQ Exchange after the assignment of
|
||||
* the Distribution set to a Target has been canceled.
|
||||
*
|
||||
* @param cancelEvent
|
||||
* the object to be send.
|
||||
*/
|
||||
@EventListener(classes = CancelTargetAssignmentEvent.class)
|
||||
public void targetCancelAssignmentToDistributionSet(final CancelTargetAssignmentEvent cancelEvent) {
|
||||
if (isNotFromSelf(cancelEvent)) {
|
||||
return;
|
||||
}
|
||||
|
||||
sendCancelMessageToTarget(cancelEvent.getTenant(), cancelEvent.getEntity().getControllerId(),
|
||||
cancelEvent.getActionId(), cancelEvent.getEntity().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)
|
||||
public void targetDelete(final TargetDeletedEvent deleteEvent) {
|
||||
if (isNotFromSelf(deleteEvent)) {
|
||||
return;
|
||||
}
|
||||
sendDeleteMessage(deleteEvent.getTenant(), deleteEvent.getControllerId(), deleteEvent.getTargetAddress());
|
||||
}
|
||||
|
||||
void sendDeleteMessage(final String tenant, final String controllerId, final String targetAddress) {
|
||||
|
||||
if (!hasValidAddress(targetAddress)) {
|
||||
return;
|
||||
}
|
||||
|
||||
final Message message = new Message(null, createConnectorMessagePropertiesDeleteThing(tenant, controllerId));
|
||||
amqpSenderService.sendMessage(message, URI.create(targetAddress));
|
||||
}
|
||||
|
||||
private boolean hasValidAddress(final String targetAddress) {
|
||||
return targetAddress != null && IpUtil.isAmqpUri(URI.create(targetAddress));
|
||||
}
|
||||
|
||||
private boolean isNotFromSelf(final RemoteApplicationEvent event) {
|
||||
return serviceMatcher != null && !serviceMatcher.isFromSelf(event);
|
||||
}
|
||||
|
||||
void sendCancelMessageToTarget(final String tenant, final String controllerId, final Long actionId,
|
||||
final URI address) {
|
||||
if (!IpUtil.isAmqpUri(address)) {
|
||||
return;
|
||||
}
|
||||
final Message message = getMessageConverter().toMessage(actionId,
|
||||
createConnectorMessagePropertiesEvent(tenant, controllerId, EventTopic.CANCEL_DOWNLOAD));
|
||||
|
||||
amqpSenderService.sendMessage(message, address);
|
||||
|
||||
}
|
||||
|
||||
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 SoftwareModule convertToAmqpSoftwareModule(final Target target,
|
||||
final org.eclipse.hawkbit.repository.model.SoftwareModule softwareModule) {
|
||||
final SoftwareModule amqpSoftwareModule = new SoftwareModule();
|
||||
amqpSoftwareModule.setModuleId(softwareModule.getId());
|
||||
amqpSoftwareModule.setModuleType(softwareModule.getType().getKey());
|
||||
amqpSoftwareModule.setModuleVersion(softwareModule.getVersion());
|
||||
|
||||
final List<Artifact> artifacts = convertArtifacts(target, softwareModule.getArtifacts());
|
||||
amqpSoftwareModule.setArtifacts(artifacts);
|
||||
return amqpSoftwareModule;
|
||||
}
|
||||
|
||||
private List<Artifact> convertArtifacts(final Target target,
|
||||
final List<org.eclipse.hawkbit.repository.model.Artifact> localArtifacts) {
|
||||
if (localArtifacts.isEmpty()) {
|
||||
return Collections.emptyList();
|
||||
}
|
||||
|
||||
return localArtifacts.stream().map(localArtifact -> convertArtifact(target, localArtifact))
|
||||
.collect(Collectors.toList());
|
||||
}
|
||||
|
||||
private Artifact convertArtifact(final Target target,
|
||||
final org.eclipse.hawkbit.repository.model.Artifact localArtifact) {
|
||||
final Artifact artifact = new Artifact();
|
||||
|
||||
artifact.setUrls(artifactUrlHandler
|
||||
.getUrls(new URLPlaceholder(systemManagement.getTenantMetadata().getTenant(),
|
||||
systemManagement.getTenantMetadata().getId(), target.getControllerId(), target.getId(),
|
||||
new SoftwareData(localArtifact.getSoftwareModule().getId(), localArtifact.getFilename(),
|
||||
localArtifact.getId(), localArtifact.getSha1Hash())),
|
||||
ApiType.DMF)
|
||||
.stream().collect(Collectors.toMap(ArtifactUrl::getProtocol, ArtifactUrl::getRef)));
|
||||
|
||||
artifact.setFilename(localArtifact.getFilename());
|
||||
artifact.setHashes(new ArtifactHash(localArtifact.getSha1Hash(), localArtifact.getMd5Hash()));
|
||||
artifact.setSize(localArtifact.getSize());
|
||||
return artifact;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,331 @@
|
||||
/**
|
||||
* Copyright (c) 2015 Bosch Software Innovations GmbH and others.
|
||||
*
|
||||
* All rights reserved. This program and the accompanying materials
|
||||
* are made available under the terms of the Eclipse Public License v1.0
|
||||
* which accompanies this distribution, and is available at
|
||||
* http://www.eclipse.org/legal/epl-v10.html
|
||||
*/
|
||||
package org.eclipse.hawkbit.amqp;
|
||||
|
||||
import java.net.URI;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
import java.util.Optional;
|
||||
import java.util.UUID;
|
||||
|
||||
import org.apache.commons.lang3.ArrayUtils;
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
import org.eclipse.hawkbit.dmf.amqp.api.EventTopic;
|
||||
import org.eclipse.hawkbit.dmf.amqp.api.MessageHeaderKey;
|
||||
import org.eclipse.hawkbit.dmf.amqp.api.MessageType;
|
||||
import org.eclipse.hawkbit.dmf.json.model.ActionUpdateStatus;
|
||||
import org.eclipse.hawkbit.dmf.json.model.AttributeUpdate;
|
||||
import org.eclipse.hawkbit.im.authentication.SpPermission.SpringEvalExpressions;
|
||||
import org.eclipse.hawkbit.im.authentication.TenantAwareAuthenticationDetails;
|
||||
import org.eclipse.hawkbit.repository.ControllerManagement;
|
||||
import org.eclipse.hawkbit.repository.EntityFactory;
|
||||
import org.eclipse.hawkbit.repository.RepositoryConstants;
|
||||
import org.eclipse.hawkbit.repository.builder.ActionStatusCreate;
|
||||
import org.eclipse.hawkbit.repository.model.Action;
|
||||
import org.eclipse.hawkbit.repository.model.Action.Status;
|
||||
import org.eclipse.hawkbit.repository.model.Target;
|
||||
import org.eclipse.hawkbit.util.IpUtil;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import org.springframework.amqp.AmqpRejectAndDontRequeueException;
|
||||
import org.springframework.amqp.core.Message;
|
||||
import org.springframework.amqp.rabbit.annotation.RabbitListener;
|
||||
import org.springframework.amqp.rabbit.core.RabbitTemplate;
|
||||
import org.springframework.messaging.handler.annotation.Header;
|
||||
import org.springframework.security.authentication.AnonymousAuthenticationToken;
|
||||
import org.springframework.security.core.Authentication;
|
||||
import org.springframework.security.core.authority.SimpleGrantedAuthority;
|
||||
import org.springframework.security.core.context.SecurityContext;
|
||||
import org.springframework.security.core.context.SecurityContextHolder;
|
||||
import org.springframework.security.core.context.SecurityContextImpl;
|
||||
|
||||
/**
|
||||
*
|
||||
* {@link AmqpMessageHandlerService} handles all incoming target interaction
|
||||
* AMQP messages (e.g. create target, check for updates etc.) for the queue
|
||||
* which is configured for the property hawkbit.dmf.rabbitmq.receiverQueue.
|
||||
*
|
||||
*/
|
||||
public class AmqpMessageHandlerService extends BaseAmqpService {
|
||||
|
||||
private static final Logger LOG = LoggerFactory.getLogger(AmqpMessageHandlerService.class);
|
||||
|
||||
private final AmqpMessageDispatcherService amqpMessageDispatcherService;
|
||||
|
||||
private final ControllerManagement controllerManagement;
|
||||
|
||||
private final EntityFactory entityFactory;
|
||||
|
||||
/**
|
||||
* Constructor.
|
||||
*
|
||||
* @param rabbitTemplate
|
||||
* for converting messages
|
||||
* @param amqpMessageDispatcherService
|
||||
* to sending events to DMF client
|
||||
* @param controllerManagement
|
||||
* for target repo access
|
||||
* @param entityFactory
|
||||
* to create entities
|
||||
*/
|
||||
public AmqpMessageHandlerService(final RabbitTemplate rabbitTemplate,
|
||||
final AmqpMessageDispatcherService amqpMessageDispatcherService,
|
||||
final ControllerManagement controllerManagement, final EntityFactory entityFactory) {
|
||||
super(rabbitTemplate);
|
||||
this.amqpMessageDispatcherService = amqpMessageDispatcherService;
|
||||
this.controllerManagement = controllerManagement;
|
||||
this.entityFactory = entityFactory;
|
||||
}
|
||||
|
||||
/**
|
||||
* Method to handle all incoming DMF amqp messages.
|
||||
*
|
||||
* @param message
|
||||
* incoming message
|
||||
* @param type
|
||||
* the message type
|
||||
* @param tenant
|
||||
* the contentType of the message
|
||||
*
|
||||
* @return a message if <null> no message is send back to sender
|
||||
*/
|
||||
@RabbitListener(queues = "${hawkbit.dmf.rabbitmq.receiverQueue:dmf_receiver}", containerFactory = "listenerContainerFactory")
|
||||
public Message onMessage(final Message message, @Header(MessageHeaderKey.TYPE) final String type,
|
||||
@Header(MessageHeaderKey.TENANT) final String tenant) {
|
||||
return onMessage(message, type, tenant, getRabbitTemplate().getConnectionFactory().getVirtualHost());
|
||||
}
|
||||
|
||||
/**
|
||||
* * Executed if a amqp message arrives.
|
||||
*
|
||||
* @param message
|
||||
* the message
|
||||
* @param type
|
||||
* the type
|
||||
* @param tenant
|
||||
* the tenant
|
||||
* @param virtualHost
|
||||
* the virtual host
|
||||
* @return the rpc message back to supplier.
|
||||
*/
|
||||
public Message onMessage(final Message message, final String type, final String tenant, final String virtualHost) {
|
||||
checkContentTypeJson(message);
|
||||
final SecurityContext oldContext = SecurityContextHolder.getContext();
|
||||
try {
|
||||
final MessageType messageType = MessageType.valueOf(type);
|
||||
switch (messageType) {
|
||||
case THING_CREATED:
|
||||
setTenantSecurityContext(tenant);
|
||||
registerTarget(message, virtualHost);
|
||||
break;
|
||||
case EVENT:
|
||||
setTenantSecurityContext(tenant);
|
||||
final String topicValue = getStringHeaderKey(message, MessageHeaderKey.TOPIC, "EventTopic is null");
|
||||
final EventTopic eventTopic = EventTopic.valueOf(topicValue);
|
||||
handleIncomingEvent(message, eventTopic);
|
||||
break;
|
||||
default:
|
||||
logAndThrowMessageError(message, "No handle method was found for the given message type.");
|
||||
}
|
||||
} catch (final IllegalArgumentException ex) {
|
||||
throw new AmqpRejectAndDontRequeueException("Invalid message!", ex);
|
||||
} finally {
|
||||
SecurityContextHolder.setContext(oldContext);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
private static void setSecurityContext(final Authentication authentication) {
|
||||
final SecurityContextImpl securityContextImpl = new SecurityContextImpl();
|
||||
securityContextImpl.setAuthentication(authentication);
|
||||
SecurityContextHolder.setContext(securityContextImpl);
|
||||
}
|
||||
|
||||
private static void setTenantSecurityContext(final String tenantId) {
|
||||
final AnonymousAuthenticationToken authenticationToken = new AnonymousAuthenticationToken(
|
||||
UUID.randomUUID().toString(), "AMQP-Controller",
|
||||
Collections.singletonList(new SimpleGrantedAuthority(SpringEvalExpressions.CONTROLLER_ROLE_ANONYMOUS)));
|
||||
authenticationToken.setDetails(new TenantAwareAuthenticationDetails(tenantId, true));
|
||||
setSecurityContext(authenticationToken);
|
||||
}
|
||||
|
||||
/**
|
||||
* Method to create a new target or to find the target if it already exists.
|
||||
*
|
||||
* @param targetID
|
||||
* the ID of the target/thing
|
||||
* @param ip
|
||||
* the ip of the target/thing
|
||||
*/
|
||||
private void registerTarget(final Message message, final String virtualHost) {
|
||||
final String thingId = getStringHeaderKey(message, MessageHeaderKey.THING_ID, "ThingId is null");
|
||||
final String replyTo = message.getMessageProperties().getReplyTo();
|
||||
|
||||
if (StringUtils.isEmpty(replyTo)) {
|
||||
logAndThrowMessageError(message, "No ReplyTo was set for the createThing message.");
|
||||
}
|
||||
|
||||
final URI amqpUri = IpUtil.createAmqpUri(virtualHost, replyTo);
|
||||
final Target target = controllerManagement.findOrRegisterTargetIfItDoesNotexist(thingId, amqpUri);
|
||||
LOG.debug("Target {} reported online state.", thingId);
|
||||
|
||||
lookIfUpdateAvailable(target);
|
||||
}
|
||||
|
||||
private void lookIfUpdateAvailable(final Target target) {
|
||||
|
||||
final Optional<Action> actionOptional = controllerManagement
|
||||
.findOldestActiveActionByTarget(target.getControllerId());
|
||||
|
||||
if (!actionOptional.isPresent()) {
|
||||
return;
|
||||
}
|
||||
|
||||
final Action action = actionOptional.get();
|
||||
if (action.isCancelingOrCanceled()) {
|
||||
amqpMessageDispatcherService.sendCancelMessageToTarget(target.getTenant(), target.getControllerId(),
|
||||
action.getId(), target.getAddress());
|
||||
return;
|
||||
}
|
||||
|
||||
amqpMessageDispatcherService.sendUpdateMessageToTarget(action.getTenant(), action.getTarget(), action.getId(),
|
||||
action.getDistributionSet().getModules());
|
||||
}
|
||||
|
||||
/**
|
||||
* Method to handle the different topics to an event.
|
||||
*
|
||||
* @param message
|
||||
* the incoming event message.
|
||||
* @param topic
|
||||
* the topic of the event.
|
||||
*/
|
||||
private void handleIncomingEvent(final Message message, final EventTopic topic) {
|
||||
switch (topic) {
|
||||
case UPDATE_ACTION_STATUS:
|
||||
updateActionStatus(message);
|
||||
break;
|
||||
case UPDATE_ATTRIBUTES:
|
||||
updateAttributes(message);
|
||||
break;
|
||||
default:
|
||||
logAndThrowMessageError(message, "Got event without appropriate topic.");
|
||||
break;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
private void updateAttributes(final Message message) {
|
||||
final AttributeUpdate attributeUpdate = convertMessage(message, AttributeUpdate.class);
|
||||
final String thingId = getStringHeaderKey(message, MessageHeaderKey.THING_ID, "ThingId is null");
|
||||
|
||||
controllerManagement.updateControllerAttributes(thingId, attributeUpdate.getAttributes());
|
||||
}
|
||||
|
||||
/**
|
||||
* Method to update the action status of an action through the event.
|
||||
*
|
||||
* @param actionUpdateStatus
|
||||
* the object form the ampq message
|
||||
*/
|
||||
private void updateActionStatus(final Message message) {
|
||||
final ActionUpdateStatus actionUpdateStatus = convertMessage(message, ActionUpdateStatus.class);
|
||||
final Action action = checkActionExist(message, actionUpdateStatus);
|
||||
|
||||
final List<String> messages = actionUpdateStatus.getMessage();
|
||||
if (ArrayUtils.isNotEmpty(message.getMessageProperties().getCorrelationId())) {
|
||||
messages.add(RepositoryConstants.SERVER_MESSAGE_PREFIX + "DMF message correlation-id "
|
||||
+ convertCorrelationId(message));
|
||||
}
|
||||
|
||||
final Status status = mapStatus(message, actionUpdateStatus, action);
|
||||
final ActionStatusCreate actionStatus = entityFactory.actionStatus().create(action.getId()).status(status)
|
||||
.messages(messages);
|
||||
|
||||
final Action addUpdateActionStatus = getUpdateActionStatus(status, actionStatus);
|
||||
|
||||
if (!addUpdateActionStatus.isActive()) {
|
||||
lookIfUpdateAvailable(action.getTarget());
|
||||
}
|
||||
}
|
||||
|
||||
private Status mapStatus(final Message message, final ActionUpdateStatus actionUpdateStatus, final Action action) {
|
||||
Status status = null;
|
||||
switch (actionUpdateStatus.getActionStatus()) {
|
||||
case DOWNLOAD:
|
||||
status = Status.DOWNLOAD;
|
||||
break;
|
||||
case RETRIEVED:
|
||||
status = Status.RETRIEVED;
|
||||
break;
|
||||
case RUNNING:
|
||||
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 CANCEL_REJECTED:
|
||||
status = hanldeCancelRejectedState(message, action);
|
||||
break;
|
||||
default:
|
||||
logAndThrowMessageError(message, "Status for action does not exisit.");
|
||||
}
|
||||
|
||||
return status;
|
||||
}
|
||||
|
||||
private Status hanldeCancelRejectedState(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;
|
||||
|
||||
}
|
||||
|
||||
private static String convertCorrelationId(final Message message) {
|
||||
return new String(message.getMessageProperties().getCorrelationId(), StandardCharsets.UTF_8);
|
||||
}
|
||||
|
||||
private Action getUpdateActionStatus(final Status status, final ActionStatusCreate actionStatus) {
|
||||
if (Status.CANCELED.equals(status)) {
|
||||
return controllerManagement.addCancelActionStatus(actionStatus);
|
||||
}
|
||||
return controllerManagement.addUpdateActionStatus(actionStatus);
|
||||
}
|
||||
|
||||
// Exception squid:S3655 - logAndThrowMessageError throws exception, i.e.
|
||||
// get will not be called
|
||||
@SuppressWarnings("squid:S3655")
|
||||
private Action checkActionExist(final Message message, final ActionUpdateStatus actionUpdateStatus) {
|
||||
final Long actionId = actionUpdateStatus.getActionId();
|
||||
|
||||
LOG.debug("Target notifies intermediate about action {} with status {}.", actionId,
|
||||
actionUpdateStatus.getActionStatus());
|
||||
|
||||
final Optional<Action> findActionWithDetails = controllerManagement.findActionWithDetails(actionId);
|
||||
if (!findActionWithDetails.isPresent()) {
|
||||
logAndThrowMessageError(message,
|
||||
"Got intermediate notification about action " + actionId + " but action does not exist");
|
||||
}
|
||||
|
||||
return findActionWithDetails.get();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,196 @@
|
||||
/**
|
||||
* Copyright (c) 2015 Bosch Software Innovations GmbH and others.
|
||||
*
|
||||
* All rights reserved. This program and the accompanying materials
|
||||
* are made available under the terms of the Eclipse Public License v1.0
|
||||
* which accompanies this distribution, and is available at
|
||||
* http://www.eclipse.org/legal/epl-v10.html
|
||||
*/
|
||||
package org.eclipse.hawkbit.amqp;
|
||||
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
import org.springframework.boot.context.properties.ConfigurationProperties;
|
||||
|
||||
/**
|
||||
* Bean which holds the necessary properties for configuring the AMQP
|
||||
* connection.
|
||||
*
|
||||
*/
|
||||
@ConfigurationProperties("hawkbit.dmf.rabbitmq")
|
||||
public class AmqpProperties {
|
||||
|
||||
private static final int ONE_MINUTE = 60;
|
||||
|
||||
private static final int DEFAULT_QUEUE_DECLARATION_RETRIES = 50;
|
||||
|
||||
private static final int DEFAULT_INITIAL_CONSUMERS = 3;
|
||||
|
||||
private static final int DEFAULT_PREFETCH_COUNT = 10;
|
||||
|
||||
private static final int DEFAULT_MAX_CONSUMERS = 10;
|
||||
|
||||
private static final long DEFAULT_REQUEUE_DELAY = 0;
|
||||
|
||||
/**
|
||||
* Enable DMF API based on AMQP 0.9
|
||||
*/
|
||||
private boolean enabled = true;
|
||||
|
||||
/**
|
||||
* DMF API dead letter queue.
|
||||
*/
|
||||
private String deadLetterQueue = "dmf_connector_deadletter_ttl";
|
||||
|
||||
/**
|
||||
* DMF API dead letter exchange.
|
||||
*/
|
||||
private String deadLetterExchange = "dmf.connector.deadletter";
|
||||
|
||||
/**
|
||||
* DMF API receiving queue for EVENT or THING_CREATED message.
|
||||
*/
|
||||
private String receiverQueue = "dmf_receiver";
|
||||
|
||||
/**
|
||||
* Authentication request called by 3rd party artifact storages for download
|
||||
* authorizations.
|
||||
*/
|
||||
private String authenticationReceiverQueue = "authentication_receiver";
|
||||
|
||||
/**
|
||||
* Missing queue fatal.
|
||||
*/
|
||||
private boolean missingQueuesFatal;
|
||||
|
||||
/**
|
||||
* Requested heartbeat interval from broker in {@link TimeUnit#SECONDS}.
|
||||
*/
|
||||
private int requestedHeartBeat = (int) TimeUnit.SECONDS.toSeconds(ONE_MINUTE);
|
||||
|
||||
/**
|
||||
* Sets an upper limit to the number of consumers.
|
||||
*/
|
||||
private int maxConcurrentConsumers = DEFAULT_MAX_CONSUMERS;
|
||||
|
||||
/**
|
||||
* Tells the broker how many messages to send to each consumer in a single
|
||||
* request. Often this can be set quite high to improve throughput.
|
||||
*/
|
||||
private int prefetchCount = DEFAULT_PREFETCH_COUNT;
|
||||
|
||||
/**
|
||||
* Initial number of consumers. Is scaled up if necessary up to
|
||||
* {@link #maxConcurrentConsumers}.
|
||||
*/
|
||||
private int initialConcurrentConsumers = DEFAULT_INITIAL_CONSUMERS;
|
||||
|
||||
/**
|
||||
* The number of retry attempts when passive queue declaration fails.
|
||||
* Passive queue declaration occurs when the consumer starts or, when
|
||||
* consuming from multiple queues, when not all queues were available during
|
||||
* initialization.
|
||||
*/
|
||||
private int declarationRetries = DEFAULT_QUEUE_DECLARATION_RETRIES;
|
||||
|
||||
/**
|
||||
* Delay for messages that are requeued in milliseconds.
|
||||
*/
|
||||
private long requeueDelay = DEFAULT_REQUEUE_DELAY;
|
||||
|
||||
public long getRequeueDelay() {
|
||||
return requeueDelay;
|
||||
}
|
||||
|
||||
public void setRequeueDelay(final long requeueDelay) {
|
||||
this.requeueDelay = requeueDelay;
|
||||
}
|
||||
|
||||
public int getDeclarationRetries() {
|
||||
return declarationRetries;
|
||||
}
|
||||
|
||||
public void setDeclarationRetries(final int declarationRetries) {
|
||||
this.declarationRetries = declarationRetries;
|
||||
}
|
||||
|
||||
public String getAuthenticationReceiverQueue() {
|
||||
return authenticationReceiverQueue;
|
||||
}
|
||||
|
||||
public void setAuthenticationReceiverQueue(final String authenticationReceiverQueue) {
|
||||
this.authenticationReceiverQueue = authenticationReceiverQueue;
|
||||
}
|
||||
|
||||
public int getPrefetchCount() {
|
||||
return prefetchCount;
|
||||
}
|
||||
|
||||
public void setPrefetchCount(final int prefetchCount) {
|
||||
this.prefetchCount = prefetchCount;
|
||||
}
|
||||
|
||||
public int getInitialConcurrentConsumers() {
|
||||
return initialConcurrentConsumers;
|
||||
}
|
||||
|
||||
public void setInitialConcurrentConsumers(final int initialConcurrentConsumers) {
|
||||
this.initialConcurrentConsumers = initialConcurrentConsumers;
|
||||
}
|
||||
|
||||
public int getMaxConcurrentConsumers() {
|
||||
return maxConcurrentConsumers;
|
||||
}
|
||||
|
||||
public void setMaxConcurrentConsumers(final int maxConcurrentConsumers) {
|
||||
this.maxConcurrentConsumers = maxConcurrentConsumers;
|
||||
}
|
||||
|
||||
public boolean isMissingQueuesFatal() {
|
||||
return missingQueuesFatal;
|
||||
}
|
||||
|
||||
public void setMissingQueuesFatal(final boolean missingQueuesFatal) {
|
||||
this.missingQueuesFatal = missingQueuesFatal;
|
||||
}
|
||||
|
||||
public String getDeadLetterExchange() {
|
||||
return deadLetterExchange;
|
||||
}
|
||||
|
||||
public void setDeadLetterExchange(final String deadLetterExchange) {
|
||||
this.deadLetterExchange = deadLetterExchange;
|
||||
}
|
||||
|
||||
public String getDeadLetterQueue() {
|
||||
return deadLetterQueue;
|
||||
}
|
||||
|
||||
public void setDeadLetterQueue(final String deadLetterQueue) {
|
||||
this.deadLetterQueue = deadLetterQueue;
|
||||
}
|
||||
|
||||
public String getReceiverQueue() {
|
||||
return receiverQueue;
|
||||
}
|
||||
|
||||
public int getRequestedHeartBeat() {
|
||||
return requestedHeartBeat;
|
||||
}
|
||||
|
||||
public void setRequestedHeartBeat(final int requestedHeartBeat) {
|
||||
this.requestedHeartBeat = requestedHeartBeat;
|
||||
}
|
||||
|
||||
public void setReceiverQueue(final String receiverQueue) {
|
||||
this.receiverQueue = receiverQueue;
|
||||
}
|
||||
|
||||
public boolean isEnabled() {
|
||||
return enabled;
|
||||
}
|
||||
|
||||
public void setEnabled(final boolean enabled) {
|
||||
this.enabled = enabled;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,46 @@
|
||||
/**
|
||||
* Copyright (c) 2015 Bosch Software Innovations GmbH and others.
|
||||
*
|
||||
* All rights reserved. This program and the accompanying materials
|
||||
* are made available under the terms of the Eclipse Public License v1.0
|
||||
* which accompanies this distribution, and is available at
|
||||
* http://www.eclipse.org/legal/epl-v10.html
|
||||
*/
|
||||
package org.eclipse.hawkbit.amqp;
|
||||
|
||||
import java.net.URI;
|
||||
|
||||
import javax.validation.constraints.NotNull;
|
||||
|
||||
import org.springframework.amqp.core.Message;
|
||||
|
||||
/**
|
||||
* Interface to send a amqp message.
|
||||
*/
|
||||
@FunctionalInterface
|
||||
public interface AmqpSenderService {
|
||||
|
||||
/**
|
||||
* Send the given message to the given uri. The uri contains the (virtual)
|
||||
* host and exchange e.g amqp://host/exchange.
|
||||
*
|
||||
* @param message
|
||||
* the amqp message
|
||||
* @param replyTo
|
||||
* the reply to uri
|
||||
*/
|
||||
void sendMessage(@NotNull Message message, @NotNull URI replyTo);
|
||||
|
||||
/**
|
||||
* Extract the exchange from the uri. Default implementation removes the
|
||||
* first /.
|
||||
*
|
||||
* @param amqpUri
|
||||
* the amqp uri
|
||||
* @return the exchange.
|
||||
*/
|
||||
default String extractExchange(final URI amqpUri) {
|
||||
return amqpUri.getPath().substring(1);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,111 @@
|
||||
/**
|
||||
* Copyright (c) 2015 Bosch Software Innovations GmbH and others.
|
||||
*
|
||||
* All rights reserved. This program and the accompanying materials
|
||||
* are made available under the terms of the Eclipse Public License v1.0
|
||||
* which accompanies this distribution, and is available at
|
||||
* http://www.eclipse.org/legal/epl-v10.html
|
||||
*/
|
||||
package org.eclipse.hawkbit.amqp;
|
||||
|
||||
import java.util.Map;
|
||||
|
||||
import javax.validation.constraints.NotNull;
|
||||
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import org.springframework.amqp.AmqpRejectAndDontRequeueException;
|
||||
import org.springframework.amqp.core.Message;
|
||||
import org.springframework.amqp.core.MessageProperties;
|
||||
import org.springframework.amqp.rabbit.core.RabbitTemplate;
|
||||
import org.springframework.amqp.support.converter.AbstractJavaTypeMapper;
|
||||
import org.springframework.amqp.support.converter.MessageConversionException;
|
||||
import org.springframework.amqp.support.converter.MessageConverter;
|
||||
|
||||
/**
|
||||
* A base class which provide basis amqp staff.
|
||||
*/
|
||||
public class BaseAmqpService {
|
||||
|
||||
private static final Logger LOGGER = LoggerFactory.getLogger(BaseAmqpService.class);
|
||||
private final RabbitTemplate rabbitTemplate;
|
||||
|
||||
/**
|
||||
* Constructor.
|
||||
*
|
||||
* @param rabbitTemplate
|
||||
* the rabbit template.
|
||||
*/
|
||||
public BaseAmqpService(final 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.
|
||||
*
|
||||
* @param message
|
||||
* the message to convert.
|
||||
* @param clazz
|
||||
* the class of the originally object.
|
||||
* @return the converted object
|
||||
*/
|
||||
@SuppressWarnings("unchecked")
|
||||
public <T> T convertMessage(@NotNull final Message message, final Class<T> clazz) {
|
||||
checkMessageBody(message);
|
||||
message.getMessageProperties().getHeaders().put(AbstractJavaTypeMapper.DEFAULT_CLASSID_FIELD_NAME,
|
||||
clazz.getName());
|
||||
return (T) rabbitTemplate.getMessageConverter().fromMessage(message);
|
||||
}
|
||||
|
||||
protected MessageConverter getMessageConverter() {
|
||||
return rabbitTemplate.getMessageConverter();
|
||||
}
|
||||
|
||||
private static boolean isMessageBodyEmpty(final Message message) {
|
||||
return message.getBody() == null || message.getBody().length == 0;
|
||||
}
|
||||
|
||||
protected void checkMessageBody(@NotNull final Message message) {
|
||||
if (isMessageBodyEmpty(message)) {
|
||||
throw new MessageConversionException("Message body cannot be null");
|
||||
}
|
||||
}
|
||||
|
||||
protected String getStringHeaderKey(final Message message, final String key, final String errorMessageIfNull) {
|
||||
final Map<String, Object> header = message.getMessageProperties().getHeaders();
|
||||
final Object value = header.get(key);
|
||||
if (value == null) {
|
||||
logAndThrowMessageError(message, errorMessageIfNull);
|
||||
return null;
|
||||
}
|
||||
return value.toString();
|
||||
}
|
||||
|
||||
protected static final void logAndThrowMessageError(final Message message, final String error) {
|
||||
LOGGER.warn("Warning! \"{}\" reported by message: {}", error, message);
|
||||
throw new AmqpRejectAndDontRequeueException(error);
|
||||
}
|
||||
|
||||
protected RabbitTemplate getRabbitTemplate() {
|
||||
return rabbitTemplate;
|
||||
}
|
||||
|
||||
/**
|
||||
* Clean message properties before sending a message.
|
||||
*
|
||||
* @param message
|
||||
* the message to cleaned up
|
||||
*/
|
||||
protected void cleanMessageHeaderProperties(final Message message) {
|
||||
message.getMessageProperties().getHeaders().remove(AbstractJavaTypeMapper.DEFAULT_CLASSID_FIELD_NAME);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,56 @@
|
||||
/**
|
||||
* Copyright (c) 2015 Bosch Software Innovations GmbH and others.
|
||||
*
|
||||
* All rights reserved. This program and the accompanying materials
|
||||
* are made available under the terms of the Eclipse Public License v1.0
|
||||
* which accompanies this distribution, and is available at
|
||||
* http://www.eclipse.org/legal/epl-v10.html
|
||||
*/
|
||||
package org.eclipse.hawkbit.amqp;
|
||||
|
||||
import org.springframework.amqp.rabbit.config.SimpleRabbitListenerContainerFactory;
|
||||
import org.springframework.amqp.rabbit.connection.ConnectionFactory;
|
||||
import org.springframework.amqp.rabbit.listener.RabbitListenerContainerFactory;
|
||||
import org.springframework.amqp.rabbit.listener.SimpleMessageListenerContainer;
|
||||
import org.springframework.util.ErrorHandler;
|
||||
|
||||
/**
|
||||
* {@link RabbitListenerContainerFactory} that can be configured through
|
||||
* hawkBit's {@link AmqpProperties}.
|
||||
*
|
||||
*/
|
||||
public class ConfigurableRabbitListenerContainerFactory extends SimpleRabbitListenerContainerFactory {
|
||||
private final AmqpProperties amqpProperties;
|
||||
|
||||
/**
|
||||
* Constructor.
|
||||
*
|
||||
* @param rabbitConnectionFactory
|
||||
* for the container factory
|
||||
* @param amqpProperties
|
||||
* to configure the container factory
|
||||
* @param errorHandler
|
||||
* the error handler which should be use
|
||||
*/
|
||||
public ConfigurableRabbitListenerContainerFactory(final AmqpProperties amqpProperties,
|
||||
final ConnectionFactory rabbitConnectionFactory, final ErrorHandler errorHandler) {
|
||||
this.amqpProperties = amqpProperties;
|
||||
setErrorHandler(errorHandler);
|
||||
setDefaultRequeueRejected(true);
|
||||
setConnectionFactory(rabbitConnectionFactory);
|
||||
setMissingQueuesFatal(amqpProperties.isMissingQueuesFatal());
|
||||
setConcurrentConsumers(amqpProperties.getInitialConcurrentConsumers());
|
||||
setMaxConcurrentConsumers(amqpProperties.getMaxConcurrentConsumers());
|
||||
setPrefetchCount(amqpProperties.getPrefetchCount());
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
// Exception squid:UnusedProtectedMethod - called by
|
||||
// AbstractRabbitListenerContainerFactory
|
||||
@SuppressWarnings("squid:UnusedProtectedMethod")
|
||||
protected void initializeContainer(final SimpleMessageListenerContainer instance) {
|
||||
super.initializeContainer(instance);
|
||||
instance.setDeclarationRetries(amqpProperties.getDeclarationRetries());
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,61 @@
|
||||
/**
|
||||
* Copyright (c) 2015 Bosch Software Innovations GmbH and others.
|
||||
*
|
||||
* All rights reserved. This program and the accompanying materials
|
||||
* are made available under the terms of the Eclipse Public License v1.0
|
||||
* which accompanies this distribution, and is available at
|
||||
* http://www.eclipse.org/legal/epl-v10.html
|
||||
*/
|
||||
package org.eclipse.hawkbit.amqp;
|
||||
|
||||
import java.net.URI;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.util.UUID;
|
||||
|
||||
import org.eclipse.hawkbit.util.IpUtil;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import org.springframework.amqp.core.Message;
|
||||
import org.springframework.amqp.rabbit.core.RabbitTemplate;
|
||||
import org.springframework.amqp.rabbit.support.CorrelationData;
|
||||
|
||||
/**
|
||||
* A default implementation for the sender service. The service sends all amqp
|
||||
* message to the configured spring rabbitmq connections. The exchange is
|
||||
* extracted from the uri.
|
||||
*/
|
||||
public class DefaultAmqpSenderService implements AmqpSenderService {
|
||||
private static final Logger LOGGER = LoggerFactory.getLogger(DefaultAmqpSenderService.class);
|
||||
|
||||
private final RabbitTemplate internalAmqpTemplate;
|
||||
|
||||
/**
|
||||
* Constructor.
|
||||
*
|
||||
* @param internalAmqpTemplate
|
||||
* the amqp template
|
||||
*/
|
||||
public DefaultAmqpSenderService(final RabbitTemplate internalAmqpTemplate) {
|
||||
this.internalAmqpTemplate = internalAmqpTemplate;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void sendMessage(final Message message, final URI replyTo) {
|
||||
if (!IpUtil.isAmqpUri(replyTo)) {
|
||||
return;
|
||||
}
|
||||
|
||||
final String correlationId = UUID.randomUUID().toString();
|
||||
final String exchange = extractExchange(replyTo);
|
||||
message.getMessageProperties().setCorrelationId(correlationId.getBytes(StandardCharsets.UTF_8));
|
||||
|
||||
if (LOGGER.isTraceEnabled()) {
|
||||
LOGGER.trace("Sending message {} to exchange {} with correlationId {}", message, exchange, correlationId);
|
||||
} else {
|
||||
LOGGER.debug("Sending message to exchange {} with correlationId {}", exchange, correlationId);
|
||||
}
|
||||
|
||||
internalAmqpTemplate.send(exchange, null, message, new CorrelationData(correlationId));
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,86 @@
|
||||
/**
|
||||
* Copyright (c) 2015 Bosch Software Innovations GmbH and others.
|
||||
*
|
||||
* All rights reserved. This program and the accompanying materials
|
||||
* are made available under the terms of the Eclipse Public License v1.0
|
||||
* which accompanies this distribution, and is available at
|
||||
* http://www.eclipse.org/legal/epl-v10.html
|
||||
*/
|
||||
package org.eclipse.hawkbit.amqp;
|
||||
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
import javax.validation.ConstraintViolationException;
|
||||
|
||||
import org.eclipse.hawkbit.repository.exception.CancelActionNotAllowedException;
|
||||
import org.eclipse.hawkbit.repository.exception.EntityNotFoundException;
|
||||
import org.eclipse.hawkbit.repository.exception.InvalidTargetAddressException;
|
||||
import org.eclipse.hawkbit.repository.exception.TenantNotExistException;
|
||||
import org.eclipse.hawkbit.repository.exception.ToManyAttributeEntriesException;
|
||||
import org.eclipse.hawkbit.repository.exception.TooManyStatusEntriesException;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import org.springframework.amqp.rabbit.listener.ConditionalRejectingErrorHandler;
|
||||
import org.springframework.amqp.rabbit.listener.FatalExceptionStrategy;
|
||||
import org.springframework.amqp.support.converter.MessageConversionException;
|
||||
import org.springframework.messaging.MessageHandlingException;
|
||||
|
||||
/**
|
||||
* Custom {@link FatalExceptionStrategy} that markes defined hawkBit internal
|
||||
* exceptions not to be requeued. In addition it throttles in case of a requeue
|
||||
* by means of blocking the processing thread for a certain amount of time. That
|
||||
* avoids a back and forth between broker and hawkBit at maximum speed.
|
||||
*
|
||||
*/
|
||||
public class DelayedRequeueExceptionStrategy extends ConditionalRejectingErrorHandler.DefaultExceptionStrategy {
|
||||
private static final Logger LOG = LoggerFactory.getLogger(DelayedRequeueExceptionStrategy.class);
|
||||
|
||||
private final long delay;
|
||||
|
||||
/**
|
||||
* @param delay
|
||||
* in {@link TimeUnit#MILLISECONDS} before requeue.
|
||||
*/
|
||||
public DelayedRequeueExceptionStrategy(final long delay) {
|
||||
this.delay = delay;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected boolean isUserCauseFatal(final Throwable cause) {
|
||||
if (invalidMessage(cause)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
LOG.error("Found a message that has to be requeued. Processing with delay of {}ms: ", delay, cause);
|
||||
|
||||
try {
|
||||
TimeUnit.MILLISECONDS.sleep(delay);
|
||||
} catch (final InterruptedException e) {
|
||||
LOG.error("Delay interrupted!", e);
|
||||
Thread.currentThread().interrupt();
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
private boolean invalidMessage(final Throwable cause) {
|
||||
return doesNotExist(cause) || quotaHit(cause) || invalidContent(cause) || invalidState(cause);
|
||||
}
|
||||
|
||||
private boolean invalidState(final Throwable cause) {
|
||||
return cause instanceof CancelActionNotAllowedException;
|
||||
}
|
||||
|
||||
private boolean quotaHit(final Throwable cause) {
|
||||
return cause instanceof TooManyStatusEntriesException || cause instanceof ToManyAttributeEntriesException;
|
||||
}
|
||||
|
||||
private boolean doesNotExist(final Throwable cause) {
|
||||
return cause instanceof TenantNotExistException || cause instanceof EntityNotFoundException;
|
||||
}
|
||||
|
||||
private boolean invalidContent(final Throwable cause) {
|
||||
return cause instanceof ConstraintViolationException || cause instanceof InvalidTargetAddressException
|
||||
|| cause instanceof MessageConversionException || cause instanceof MessageHandlingException;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,23 @@
|
||||
/**
|
||||
* Copyright (c) 2015 Bosch Software Innovations GmbH and others.
|
||||
*
|
||||
* All rights reserved. This program and the accompanying materials
|
||||
* are made available under the terms of the Eclipse Public License v1.0
|
||||
* which accompanies this distribution, and is available at
|
||||
* http://www.eclipse.org/legal/epl-v10.html
|
||||
*/
|
||||
package org.eclipse.hawkbit.amqp;
|
||||
|
||||
import org.springframework.context.annotation.ComponentScan;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
import org.springframework.context.annotation.Import;
|
||||
|
||||
/**
|
||||
* Enable Device Management Federation API.
|
||||
*/
|
||||
@Configuration
|
||||
@ComponentScan
|
||||
@Import(AmqpConfiguration.class)
|
||||
public class DmfApiConfiguration {
|
||||
|
||||
}
|
||||
@@ -0,0 +1,366 @@
|
||||
/**
|
||||
* Copyright (c) 2015 Bosch Software Innovations GmbH and others.
|
||||
*
|
||||
* All rights reserved. This program and the accompanying materials
|
||||
* are made available under the terms of the Eclipse Public License v1.0
|
||||
* which accompanies this distribution, and is available at
|
||||
* http://www.eclipse.org/legal/epl-v10.html
|
||||
*/
|
||||
package org.eclipse.hawkbit.amqp;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
import static org.junit.Assert.fail;
|
||||
import static org.mockito.Matchers.any;
|
||||
import static org.mockito.Matchers.anyString;
|
||||
import static org.mockito.Matchers.eq;
|
||||
import static org.mockito.Mockito.mock;
|
||||
import static org.mockito.Mockito.when;
|
||||
|
||||
import java.net.URL;
|
||||
import java.util.Optional;
|
||||
|
||||
import org.eclipse.hawkbit.api.HostnameResolver;
|
||||
import org.eclipse.hawkbit.artifact.repository.model.DbArtifact;
|
||||
import org.eclipse.hawkbit.artifact.repository.model.DbArtifactHash;
|
||||
import org.eclipse.hawkbit.cache.DownloadIdCache;
|
||||
import org.eclipse.hawkbit.dmf.amqp.api.MessageHeaderKey;
|
||||
import org.eclipse.hawkbit.dmf.amqp.api.MessageType;
|
||||
import org.eclipse.hawkbit.dmf.json.model.DownloadResponse;
|
||||
import org.eclipse.hawkbit.dmf.json.model.TenantSecurityToken;
|
||||
import org.eclipse.hawkbit.dmf.json.model.TenantSecurityToken.FileResource;
|
||||
import org.eclipse.hawkbit.repository.ArtifactManagement;
|
||||
import org.eclipse.hawkbit.repository.ControllerManagement;
|
||||
import org.eclipse.hawkbit.repository.SystemManagement;
|
||||
import org.eclipse.hawkbit.repository.TenantConfigurationManagement;
|
||||
import org.eclipse.hawkbit.repository.jpa.JpaEntityFactory;
|
||||
import org.eclipse.hawkbit.repository.jpa.model.JpaArtifact;
|
||||
import org.eclipse.hawkbit.repository.jpa.model.JpaSoftwareModule;
|
||||
import org.eclipse.hawkbit.repository.jpa.model.JpaSoftwareModuleType;
|
||||
import org.eclipse.hawkbit.repository.model.Target;
|
||||
import org.eclipse.hawkbit.repository.model.TenantConfigurationValue;
|
||||
import org.eclipse.hawkbit.repository.model.TenantMetaData;
|
||||
import org.eclipse.hawkbit.security.DdiSecurityProperties;
|
||||
import org.eclipse.hawkbit.security.DdiSecurityProperties.Authentication.Anonymous;
|
||||
import org.eclipse.hawkbit.security.DdiSecurityProperties.Rp;
|
||||
import org.eclipse.hawkbit.security.SecurityContextTenantAware;
|
||||
import org.eclipse.hawkbit.security.SystemSecurityContext;
|
||||
import org.eclipse.hawkbit.tenancy.configuration.TenantConfigurationProperties.TenantConfigurationKey;
|
||||
import org.junit.Before;
|
||||
import org.junit.Test;
|
||||
import org.junit.runner.RunWith;
|
||||
import org.mockito.Mock;
|
||||
import org.mockito.runners.MockitoJUnitRunner;
|
||||
import org.springframework.amqp.core.Message;
|
||||
import org.springframework.amqp.core.MessageProperties;
|
||||
import org.springframework.amqp.rabbit.core.RabbitTemplate;
|
||||
import org.springframework.amqp.support.converter.Jackson2JsonMessageConverter;
|
||||
import org.springframework.amqp.support.converter.MessageConverter;
|
||||
import org.springframework.http.HttpStatus;
|
||||
import org.springframework.security.authentication.BadCredentialsException;
|
||||
import org.springframework.security.core.Authentication;
|
||||
import org.springframework.security.core.context.SecurityContextHolder;
|
||||
import org.springframework.security.web.authentication.preauth.PreAuthenticatedAuthenticationToken;
|
||||
|
||||
import ru.yandex.qatools.allure.annotations.Description;
|
||||
import ru.yandex.qatools.allure.annotations.Features;
|
||||
import ru.yandex.qatools.allure.annotations.Stories;
|
||||
|
||||
/**
|
||||
*
|
||||
* Test Amqp controller authentication.
|
||||
*/
|
||||
@Features("Component Tests - Device Management Federation API")
|
||||
@Stories("AmqpController Authentication Test")
|
||||
@RunWith(MockitoJUnitRunner.class)
|
||||
public class AmqpControllerAuthenticationTest {
|
||||
|
||||
private static final String SHA1 = "12345";
|
||||
private static final Long ARTIFACT_ID = 1123L;
|
||||
private static final Long ARTIFACT_SIZE = 6666L;
|
||||
private static final String TENANT = "DEFAULT";
|
||||
private static final Long TENANT_ID = 123L;
|
||||
private static final String CONTROLLER_ID = "123";
|
||||
private static final Long TARGET_ID = 123L;
|
||||
private AmqpMessageHandlerService amqpMessageHandlerService;
|
||||
private AmqpAuthenticationMessageHandler amqpAuthenticationMessageHandlerService;
|
||||
|
||||
private MessageConverter messageConverter;
|
||||
|
||||
private AmqpControllerAuthentication authenticationManager;
|
||||
|
||||
@Mock
|
||||
private TenantConfigurationManagement tenantConfigurationManagementMock;
|
||||
|
||||
@Mock
|
||||
private SystemManagement systemManagement;
|
||||
|
||||
@Mock
|
||||
private DownloadIdCache cacheMock;
|
||||
|
||||
@Mock
|
||||
private HostnameResolver hostnameResolverMock;
|
||||
|
||||
@Mock
|
||||
private ArtifactManagement artifactManagementMock;
|
||||
|
||||
@Mock
|
||||
private ControllerManagement controllerManagementMock;
|
||||
|
||||
@Mock
|
||||
private Target targteMock;
|
||||
|
||||
private static final TenantConfigurationValue<Boolean> CONFIG_VALUE_FALSE = TenantConfigurationValue
|
||||
.<Boolean> builder().value(Boolean.FALSE).build();
|
||||
|
||||
private static final TenantConfigurationValue<Boolean> CONFIG_VALUE_TRUE = TenantConfigurationValue
|
||||
.<Boolean> builder().value(Boolean.TRUE).build();
|
||||
|
||||
@Before
|
||||
public void before() throws Exception {
|
||||
messageConverter = new Jackson2JsonMessageConverter();
|
||||
final RabbitTemplate rabbitTemplate = mock(RabbitTemplate.class);
|
||||
when(rabbitTemplate.getMessageConverter()).thenReturn(messageConverter);
|
||||
|
||||
final DdiSecurityProperties secruityProperties = mock(DdiSecurityProperties.class);
|
||||
final Rp rp = mock(Rp.class);
|
||||
final DdiSecurityProperties.Authentication ddiAuthentication = mock(DdiSecurityProperties.Authentication.class);
|
||||
final Anonymous anonymous = mock(Anonymous.class);
|
||||
when(secruityProperties.getRp()).thenReturn(rp);
|
||||
when(rp.getSslIssuerHashHeader()).thenReturn("X-Ssl-Issuer-Hash-%d");
|
||||
when(secruityProperties.getAuthentication()).thenReturn(ddiAuthentication);
|
||||
when(ddiAuthentication.getAnonymous()).thenReturn(anonymous);
|
||||
when(anonymous.isEnabled()).thenReturn(false);
|
||||
|
||||
when(tenantConfigurationManagementMock.getConfigurationValue(any(), eq(Boolean.class)))
|
||||
.thenReturn(CONFIG_VALUE_FALSE);
|
||||
|
||||
final ControllerManagement controllerManagement = mock(ControllerManagement.class);
|
||||
when(controllerManagement.findByControllerId(anyString())).thenReturn(Optional.of(targteMock));
|
||||
when(controllerManagement.findByTargetId(any(Long.class))).thenReturn(Optional.of(targteMock));
|
||||
|
||||
when(targteMock.getSecurityToken()).thenReturn(CONTROLLER_ID);
|
||||
when(targteMock.getControllerId()).thenReturn(CONTROLLER_ID);
|
||||
|
||||
final SecurityContextTenantAware tenantAware = new SecurityContextTenantAware();
|
||||
final SystemSecurityContext systemSecurityContext = new SystemSecurityContext(tenantAware);
|
||||
|
||||
final TenantMetaData tenantMetaData = mock(TenantMetaData.class);
|
||||
when(tenantMetaData.getTenant()).thenReturn(TENANT);
|
||||
when(systemManagement.getTenantMetadata(TENANT_ID)).thenReturn(tenantMetaData);
|
||||
|
||||
authenticationManager = new AmqpControllerAuthentication(systemManagement, controllerManagement,
|
||||
tenantConfigurationManagementMock, tenantAware, secruityProperties, systemSecurityContext);
|
||||
|
||||
authenticationManager.postConstruct();
|
||||
|
||||
final JpaArtifact testArtifact = new JpaArtifact(SHA1, "afilename", new JpaSoftwareModule(
|
||||
new JpaSoftwareModuleType("a key", "a name", null, 1), "a name", null, null, null));
|
||||
testArtifact.setId(1L);
|
||||
|
||||
when(artifactManagementMock.findArtifact(ARTIFACT_ID)).thenReturn(Optional.of(testArtifact));
|
||||
when(artifactManagementMock.findFirstArtifactBySHA1(SHA1)).thenReturn(Optional.of(testArtifact));
|
||||
|
||||
final DbArtifact artifact = new DbArtifact();
|
||||
artifact.setSize(ARTIFACT_SIZE);
|
||||
artifact.setHashes(new DbArtifactHash(SHA1, "md5 test"));
|
||||
when(artifactManagementMock.loadArtifactBinary(SHA1)).thenReturn(Optional.of(artifact));
|
||||
|
||||
amqpMessageHandlerService = new AmqpMessageHandlerService(rabbitTemplate,
|
||||
mock(AmqpMessageDispatcherService.class), controllerManagementMock, new JpaEntityFactory());
|
||||
|
||||
amqpAuthenticationMessageHandlerService = new AmqpAuthenticationMessageHandler(rabbitTemplate,
|
||||
authenticationManager, artifactManagementMock, cacheMock, hostnameResolverMock,
|
||||
controllerManagementMock);
|
||||
|
||||
when(hostnameResolverMock.resolveHostname()).thenReturn(new URL("http://localhost"));
|
||||
|
||||
when(controllerManagementMock.hasTargetArtifactAssigned(TARGET_ID, SHA1)).thenReturn(true);
|
||||
when(controllerManagementMock.hasTargetArtifactAssigned(CONTROLLER_ID, SHA1)).thenReturn(true);
|
||||
}
|
||||
|
||||
@Test
|
||||
@Description("Tests authentication manager without principal")
|
||||
public void testAuthenticationeBadCredantialsWithoutPricipal() {
|
||||
final TenantSecurityToken securityToken = new TenantSecurityToken(TENANT, TENANT_ID, CONTROLLER_ID, TARGET_ID,
|
||||
FileResource.createFileResourceBySha1(SHA1));
|
||||
try {
|
||||
authenticationManager.doAuthenticate(securityToken);
|
||||
fail("BadCredentialsException was excepeted since principal was missing");
|
||||
} catch (final BadCredentialsException exception) {
|
||||
// test ok - exception was excepted
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@Test
|
||||
@Description("Tests authentication manager without wrong credential")
|
||||
public void testAuthenticationBadCredantialsWithWrongCredential() {
|
||||
final TenantSecurityToken securityToken = new TenantSecurityToken(TENANT, TENANT_ID, CONTROLLER_ID, TARGET_ID,
|
||||
FileResource.createFileResourceBySha1(SHA1));
|
||||
when(tenantConfigurationManagementMock.getConfigurationValue(
|
||||
eq(TenantConfigurationKey.AUTHENTICATION_MODE_TARGET_SECURITY_TOKEN_ENABLED), eq(Boolean.class)))
|
||||
.thenReturn(CONFIG_VALUE_TRUE);
|
||||
securityToken.putHeader(TenantSecurityToken.AUTHORIZATION_HEADER, "TargetToken 12" + CONTROLLER_ID);
|
||||
try {
|
||||
authenticationManager.doAuthenticate(securityToken);
|
||||
fail("BadCredentialsException was excepeted due to wrong credential");
|
||||
} catch (final BadCredentialsException exception) {
|
||||
// test ok - exception was excepted
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@Test
|
||||
@Description("Tests authentication successfull")
|
||||
public void testSuccessfullAuthentication() {
|
||||
final TenantSecurityToken securityToken = new TenantSecurityToken(TENANT, TENANT_ID, CONTROLLER_ID, TARGET_ID,
|
||||
FileResource.createFileResourceBySha1(SHA1));
|
||||
when(tenantConfigurationManagementMock.getConfigurationValue(
|
||||
eq(TenantConfigurationKey.AUTHENTICATION_MODE_TARGET_SECURITY_TOKEN_ENABLED), eq(Boolean.class)))
|
||||
.thenReturn(CONFIG_VALUE_TRUE);
|
||||
securityToken.putHeader(TenantSecurityToken.AUTHORIZATION_HEADER, "TargetToken " + CONTROLLER_ID);
|
||||
final Authentication authentication = authenticationManager.doAuthenticate(securityToken);
|
||||
assertThat(authentication).isNotNull();
|
||||
}
|
||||
|
||||
@Test
|
||||
@Description("Tests authentication message without principal")
|
||||
public void testAuthenticationMessageBadCredantialsWithoutPricipal() {
|
||||
final MessageProperties messageProperties = createMessageProperties(null);
|
||||
|
||||
final TenantSecurityToken securityToken = new TenantSecurityToken(TENANT, TENANT_ID, CONTROLLER_ID, TARGET_ID,
|
||||
FileResource.createFileResourceBySha1(SHA1));
|
||||
final Message message = amqpMessageHandlerService.getMessageConverter().toMessage(securityToken,
|
||||
messageProperties);
|
||||
|
||||
// test
|
||||
final Message onMessage = amqpAuthenticationMessageHandlerService.onAuthenticationRequest(message);
|
||||
|
||||
// verify
|
||||
final DownloadResponse downloadResponse = (DownloadResponse) messageConverter.fromMessage(onMessage);
|
||||
assertThat(downloadResponse).isNotNull();
|
||||
assertThat(downloadResponse.getResponseCode()).isEqualTo(HttpStatus.FORBIDDEN.value());
|
||||
}
|
||||
|
||||
@Test
|
||||
@Description("Tests authentication message without wrong credential")
|
||||
public void testAuthenticationMessageBadCredantialsWithWrongCredential() {
|
||||
final MessageProperties messageProperties = createMessageProperties(null);
|
||||
final TenantSecurityToken securityToken = new TenantSecurityToken(TENANT, TENANT_ID, CONTROLLER_ID, TARGET_ID,
|
||||
FileResource.createFileResourceBySha1(SHA1));
|
||||
when(tenantConfigurationManagementMock.getConfigurationValue(
|
||||
eq(TenantConfigurationKey.AUTHENTICATION_MODE_TARGET_SECURITY_TOKEN_ENABLED), eq(Boolean.class)))
|
||||
.thenReturn(CONFIG_VALUE_TRUE);
|
||||
securityToken.putHeader(TenantSecurityToken.AUTHORIZATION_HEADER, "TargetToken 12" + CONTROLLER_ID);
|
||||
final Message message = amqpMessageHandlerService.getMessageConverter().toMessage(securityToken,
|
||||
messageProperties);
|
||||
|
||||
// test
|
||||
final Message onMessage = amqpAuthenticationMessageHandlerService.onAuthenticationRequest(message);
|
||||
|
||||
// verify
|
||||
final DownloadResponse downloadResponse = (DownloadResponse) messageConverter.fromMessage(onMessage);
|
||||
assertThat(downloadResponse).isNotNull();
|
||||
assertThat(downloadResponse.getResponseCode()).isEqualTo(HttpStatus.FORBIDDEN.value());
|
||||
}
|
||||
|
||||
@Test
|
||||
@Description("Tests authentication message successfull")
|
||||
public void successfullMessageAuthentication() {
|
||||
final MessageProperties messageProperties = createMessageProperties(null);
|
||||
final TenantSecurityToken securityToken = new TenantSecurityToken(TENANT, null, CONTROLLER_ID, null,
|
||||
FileResource.createFileResourceBySha1(SHA1));
|
||||
when(tenantConfigurationManagementMock.getConfigurationValue(
|
||||
eq(TenantConfigurationKey.AUTHENTICATION_MODE_TARGET_SECURITY_TOKEN_ENABLED), eq(Boolean.class)))
|
||||
.thenReturn(CONFIG_VALUE_TRUE);
|
||||
securityToken.putHeader(TenantSecurityToken.AUTHORIZATION_HEADER, "TargetToken " + CONTROLLER_ID);
|
||||
final Message message = amqpMessageHandlerService.getMessageConverter().toMessage(securityToken,
|
||||
messageProperties);
|
||||
|
||||
// test
|
||||
final Message onMessage = amqpAuthenticationMessageHandlerService.onAuthenticationRequest(message);
|
||||
|
||||
// verify
|
||||
final DownloadResponse downloadResponse = (DownloadResponse) messageConverter.fromMessage(onMessage);
|
||||
assertThat(downloadResponse).isNotNull();
|
||||
assertThat(downloadResponse.getDownloadUrl()).isNotNull();
|
||||
assertThat(downloadResponse.getResponseCode()).isEqualTo(HttpStatus.OK.value());
|
||||
assertThat(SecurityContextHolder.getContext()).isNotNull();
|
||||
assertThat(SecurityContextHolder.getContext().getAuthentication()).isNotNull();
|
||||
assertThat(SecurityContextHolder.getContext().getAuthentication().getClass().getName())
|
||||
.isEqualTo(PreAuthenticatedAuthenticationToken.class.getName());
|
||||
|
||||
}
|
||||
|
||||
@Test
|
||||
@Description("Tests authentication message successfull with targetId intead of controllerId provided and artifactId instead of SHA1.")
|
||||
public void successfullMessageAuthenticationWithTargetIdAndArtifactId() {
|
||||
final MessageProperties messageProperties = createMessageProperties(null);
|
||||
final TenantSecurityToken securityToken = new TenantSecurityToken(TENANT, null, null, TARGET_ID,
|
||||
FileResource.createFileResourceByArtifactId(ARTIFACT_ID));
|
||||
when(tenantConfigurationManagementMock.getConfigurationValue(
|
||||
eq(TenantConfigurationKey.AUTHENTICATION_MODE_TARGET_SECURITY_TOKEN_ENABLED), eq(Boolean.class)))
|
||||
.thenReturn(CONFIG_VALUE_TRUE);
|
||||
securityToken.putHeader(TenantSecurityToken.AUTHORIZATION_HEADER, "TargetToken " + CONTROLLER_ID);
|
||||
final Message message = amqpMessageHandlerService.getMessageConverter().toMessage(securityToken,
|
||||
messageProperties);
|
||||
|
||||
// test
|
||||
final Message onMessage = amqpAuthenticationMessageHandlerService.onAuthenticationRequest(message);
|
||||
|
||||
// verify
|
||||
final DownloadResponse downloadResponse = (DownloadResponse) messageConverter.fromMessage(onMessage);
|
||||
assertThat(downloadResponse).isNotNull();
|
||||
assertThat(downloadResponse.getDownloadUrl()).isNotNull();
|
||||
assertThat(downloadResponse.getResponseCode()).isEqualTo(HttpStatus.OK.value());
|
||||
assertThat(SecurityContextHolder.getContext()).isNotNull();
|
||||
assertThat(SecurityContextHolder.getContext().getAuthentication()).isNotNull();
|
||||
assertThat(SecurityContextHolder.getContext().getAuthentication().getClass().getName())
|
||||
.isEqualTo(PreAuthenticatedAuthenticationToken.class.getName());
|
||||
|
||||
}
|
||||
|
||||
@Test
|
||||
@Description("Tests authentication message successfull")
|
||||
public void successfullMessageAuthenticationWithTenantid() {
|
||||
final MessageProperties messageProperties = createMessageProperties(null);
|
||||
final TenantSecurityToken securityToken = new TenantSecurityToken(null, TENANT_ID, CONTROLLER_ID, TARGET_ID,
|
||||
FileResource.createFileResourceBySha1(SHA1));
|
||||
when(tenantConfigurationManagementMock.getConfigurationValue(
|
||||
eq(TenantConfigurationKey.AUTHENTICATION_MODE_TARGET_SECURITY_TOKEN_ENABLED), eq(Boolean.class)))
|
||||
.thenReturn(CONFIG_VALUE_TRUE);
|
||||
securityToken.putHeader(TenantSecurityToken.AUTHORIZATION_HEADER, "TargetToken " + CONTROLLER_ID);
|
||||
final Message message = amqpMessageHandlerService.getMessageConverter().toMessage(securityToken,
|
||||
messageProperties);
|
||||
|
||||
// test
|
||||
final Message onMessage = amqpAuthenticationMessageHandlerService.onAuthenticationRequest(message);
|
||||
|
||||
// verify
|
||||
final DownloadResponse downloadResponse = (DownloadResponse) messageConverter.fromMessage(onMessage);
|
||||
assertThat(downloadResponse).isNotNull();
|
||||
assertThat(downloadResponse.getDownloadUrl()).isNotNull();
|
||||
assertThat(downloadResponse.getResponseCode()).isEqualTo(HttpStatus.OK.value());
|
||||
assertThat(SecurityContextHolder.getContext()).isNotNull();
|
||||
assertThat(SecurityContextHolder.getContext().getAuthentication()).isNotNull();
|
||||
assertThat(SecurityContextHolder.getContext().getAuthentication().getClass().getName())
|
||||
.isEqualTo(PreAuthenticatedAuthenticationToken.class.getName());
|
||||
|
||||
}
|
||||
|
||||
private MessageProperties createMessageProperties(final MessageType type) {
|
||||
return createMessageProperties(type, "MyTest");
|
||||
}
|
||||
|
||||
private MessageProperties createMessageProperties(final MessageType type, final String replyTo) {
|
||||
final MessageProperties messageProperties = new MessageProperties();
|
||||
if (type != null) {
|
||||
messageProperties.setHeader(MessageHeaderKey.TYPE, type.name());
|
||||
}
|
||||
messageProperties.setHeader(MessageHeaderKey.TENANT, TENANT);
|
||||
messageProperties.setContentType(MessageProperties.CONTENT_TYPE_JSON);
|
||||
messageProperties.setReplyTo(replyTo);
|
||||
return messageProperties;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,336 @@
|
||||
/**
|
||||
* Copyright (c) 2015 Bosch Software Innovations GmbH and others.
|
||||
*
|
||||
* All rights reserved. This program and the accompanying materials
|
||||
* are made available under the terms of the Eclipse Public License v1.0
|
||||
* which accompanies this distribution, and is available at
|
||||
* http://www.eclipse.org/legal/epl-v10.html
|
||||
*/
|
||||
package org.eclipse.hawkbit.amqp;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
import static org.junit.Assert.assertEquals;
|
||||
import static org.junit.Assert.assertNotNull;
|
||||
import static org.junit.Assert.assertTrue;
|
||||
import static org.mockito.Matchers.any;
|
||||
import static org.mockito.Matchers.anyObject;
|
||||
import static org.mockito.Matchers.eq;
|
||||
import static org.mockito.Mockito.when;
|
||||
|
||||
import java.net.URI;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.Optional;
|
||||
import java.util.UUID;
|
||||
|
||||
import org.eclipse.hawkbit.api.ArtifactUrl;
|
||||
import org.eclipse.hawkbit.api.ArtifactUrlHandler;
|
||||
import org.eclipse.hawkbit.artifact.repository.model.DbArtifact;
|
||||
import org.eclipse.hawkbit.dmf.amqp.api.EventTopic;
|
||||
import org.eclipse.hawkbit.dmf.amqp.api.MessageHeaderKey;
|
||||
import org.eclipse.hawkbit.dmf.amqp.api.MessageType;
|
||||
import org.eclipse.hawkbit.dmf.json.model.DownloadAndUpdateRequest;
|
||||
import org.eclipse.hawkbit.repository.SystemManagement;
|
||||
import org.eclipse.hawkbit.repository.event.remote.TargetAssignDistributionSetEvent;
|
||||
import org.eclipse.hawkbit.repository.event.remote.TargetDeletedEvent;
|
||||
import org.eclipse.hawkbit.repository.event.remote.entity.CancelTargetAssignmentEvent;
|
||||
import org.eclipse.hawkbit.repository.jpa.RepositoryApplicationConfiguration;
|
||||
import org.eclipse.hawkbit.repository.model.Action;
|
||||
import org.eclipse.hawkbit.repository.model.Artifact;
|
||||
import org.eclipse.hawkbit.repository.model.DistributionSet;
|
||||
import org.eclipse.hawkbit.repository.model.SoftwareModule;
|
||||
import org.eclipse.hawkbit.repository.model.Target;
|
||||
import org.eclipse.hawkbit.repository.model.TenantMetaData;
|
||||
import org.eclipse.hawkbit.repository.test.util.AbstractIntegrationTest;
|
||||
import org.eclipse.hawkbit.util.IpUtil;
|
||||
import org.junit.Test;
|
||||
import org.mockito.ArgumentCaptor;
|
||||
import org.mockito.Mockito;
|
||||
import org.springframework.amqp.core.Message;
|
||||
import org.springframework.amqp.core.MessageProperties;
|
||||
import org.springframework.amqp.rabbit.core.RabbitTemplate;
|
||||
import org.springframework.amqp.support.converter.AbstractJavaTypeMapper;
|
||||
import org.springframework.amqp.support.converter.Jackson2JsonMessageConverter;
|
||||
import org.springframework.boot.test.SpringApplicationConfiguration;
|
||||
import org.springframework.test.context.ActiveProfiles;
|
||||
|
||||
import com.google.common.collect.Lists;
|
||||
|
||||
import ru.yandex.qatools.allure.annotations.Description;
|
||||
import ru.yandex.qatools.allure.annotations.Features;
|
||||
import ru.yandex.qatools.allure.annotations.Stories;
|
||||
|
||||
@ActiveProfiles({ "test" })
|
||||
@Features("Component Tests - Device Management Federation API")
|
||||
@Stories("AmqpMessage Dispatcher Service Test")
|
||||
@SpringApplicationConfiguration(classes = { RepositoryApplicationConfiguration.class })
|
||||
public class AmqpMessageDispatcherServiceTest extends AbstractIntegrationTest {
|
||||
|
||||
private static final String TENANT = "default";
|
||||
private static final Long TENANT_ID = 4711L;
|
||||
|
||||
private static final URI AMQP_URI = IpUtil.createAmqpUri("vHost", "mytest");
|
||||
|
||||
private static final String TEST_TOKEN = "testToken";
|
||||
|
||||
private static final String CONTROLLER_ID = "1";
|
||||
|
||||
private AmqpMessageDispatcherService amqpMessageDispatcherService;
|
||||
|
||||
private RabbitTemplate rabbitTemplate;
|
||||
|
||||
private DefaultAmqpSenderService senderService;
|
||||
|
||||
private Target testTarget;
|
||||
|
||||
@Override
|
||||
public void before() throws Exception {
|
||||
super.before();
|
||||
testTarget = targetManagement.createTarget(entityFactory.target().create().controllerId(CONTROLLER_ID)
|
||||
.securityToken(TEST_TOKEN).address(AMQP_URI.toString()));
|
||||
|
||||
this.rabbitTemplate = Mockito.mock(RabbitTemplate.class);
|
||||
when(rabbitTemplate.getMessageConverter()).thenReturn(new Jackson2JsonMessageConverter());
|
||||
|
||||
senderService = Mockito.mock(DefaultAmqpSenderService.class);
|
||||
|
||||
final ArtifactUrlHandler artifactUrlHandlerMock = Mockito.mock(ArtifactUrlHandler.class);
|
||||
when(artifactUrlHandlerMock.getUrls(anyObject(), anyObject()))
|
||||
.thenReturn(Lists.newArrayList(new ArtifactUrl("http", "download", "http://mockurl")));
|
||||
|
||||
systemManagement = Mockito.mock(SystemManagement.class);
|
||||
final TenantMetaData tenantMetaData = Mockito.mock(TenantMetaData.class);
|
||||
when(tenantMetaData.getId()).thenReturn(TENANT_ID);
|
||||
when(tenantMetaData.getTenant()).thenReturn(TENANT);
|
||||
|
||||
when(systemManagement.getTenantMetadata()).thenReturn(tenantMetaData);
|
||||
|
||||
amqpMessageDispatcherService = new AmqpMessageDispatcherService(rabbitTemplate, senderService,
|
||||
artifactUrlHandlerMock, systemSecurityContext, systemManagement, targetManagement, serviceMatcher);
|
||||
|
||||
}
|
||||
|
||||
@Test
|
||||
@Description("Verifies that download and install event with no software modul works")
|
||||
public void testSendDownloadRequesWithEmptySoftwareModules() {
|
||||
|
||||
final TargetAssignDistributionSetEvent targetAssignDistributionSetEvent = new TargetAssignDistributionSetEvent(
|
||||
"DEFAULT", 1L, 1L, CONTROLLER_ID, serviceMatcher.getServiceId());
|
||||
amqpMessageDispatcherService.targetAssignDistributionSet(targetAssignDistributionSetEvent);
|
||||
|
||||
final Message sendMessage = getCaptureAdressEvent(targetAssignDistributionSetEvent);
|
||||
final DownloadAndUpdateRequest downloadAndUpdateRequest = assertDownloadAndInstallMessage(sendMessage, 1L);
|
||||
assertThat(downloadAndUpdateRequest.getTargetSecurityToken()).isEqualTo(TEST_TOKEN);
|
||||
assertTrue("No softwaremmodule should be contained in the request",
|
||||
downloadAndUpdateRequest.getSoftwareModules().isEmpty());
|
||||
}
|
||||
|
||||
private Message getCaptureAdressEvent(final TargetAssignDistributionSetEvent targetAssignDistributionSetEvent) {
|
||||
final Target target = targetManagement
|
||||
.findTargetByControllerID(targetAssignDistributionSetEvent.getControllerId()).get();
|
||||
final Message sendMessage = createArgumentCapture(target.getAddress());
|
||||
return sendMessage;
|
||||
}
|
||||
|
||||
private Action createAction(final DistributionSet testDs) {
|
||||
return deploymentManagement.findAction(assignDistributionSet(testDs, testTarget).getActions().get(0)).get();
|
||||
}
|
||||
|
||||
@Test
|
||||
@Description("Verifies that download and install event with 3 software moduls and no artifacts works")
|
||||
public void testSendDownloadRequesWithSoftwareModulesAndNoArtifacts() {
|
||||
final DistributionSet createDistributionSet = testdataFactory
|
||||
.createDistributionSet(UUID.randomUUID().toString());
|
||||
final Action action = createAction(createDistributionSet);
|
||||
|
||||
final TargetAssignDistributionSetEvent targetAssignDistributionSetEvent = new TargetAssignDistributionSetEvent(
|
||||
action, serviceMatcher.getServiceId());
|
||||
amqpMessageDispatcherService.targetAssignDistributionSet(targetAssignDistributionSetEvent);
|
||||
final Message sendMessage = getCaptureAdressEvent(targetAssignDistributionSetEvent);
|
||||
final DownloadAndUpdateRequest downloadAndUpdateRequest = assertDownloadAndInstallMessage(sendMessage,
|
||||
action.getId());
|
||||
assertThat(createDistributionSet.getModules()).hasSameSizeAs(downloadAndUpdateRequest.getSoftwareModules());
|
||||
assertThat(downloadAndUpdateRequest.getTargetSecurityToken()).isEqualTo(TEST_TOKEN);
|
||||
for (final org.eclipse.hawkbit.dmf.json.model.SoftwareModule softwareModule : downloadAndUpdateRequest
|
||||
.getSoftwareModules()) {
|
||||
assertTrue("Artifact list for softwaremodule should be empty", softwareModule.getArtifacts().isEmpty());
|
||||
for (final SoftwareModule softwareModule2 : action.getDistributionSet().getModules()) {
|
||||
assertNotNull("Sofware module ID should be set", softwareModule.getModuleId());
|
||||
if (!softwareModule.getModuleId().equals(softwareModule2.getId())) {
|
||||
continue;
|
||||
}
|
||||
assertEquals(
|
||||
"Software module type in event should be the same as the softwaremodule in the distribution set",
|
||||
softwareModule.getModuleType(), softwareModule2.getType().getKey());
|
||||
assertEquals(
|
||||
"Software module version in event should be the same as the softwaremodule in the distribution set",
|
||||
softwareModule.getModuleVersion(), softwareModule2.getVersion());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
@Description("Verifies that download and install event with software moduls and artifacts works")
|
||||
public void testSendDownloadRequest() {
|
||||
DistributionSet dsA = testdataFactory.createDistributionSet(UUID.randomUUID().toString());
|
||||
SoftwareModule module = dsA.getModules().iterator().next();
|
||||
final List<DbArtifact> receivedList = new ArrayList<>();
|
||||
for (final Artifact artifact : testdataFactory.createArtifacts(module.getId())) {
|
||||
receivedList.add(new DbArtifact());
|
||||
}
|
||||
module = softwareManagement.findSoftwareModuleById(module.getId()).get();
|
||||
dsA = distributionSetManagement.findDistributionSetById(dsA.getId()).get();
|
||||
|
||||
final Action action = createAction(dsA);
|
||||
|
||||
Mockito.when(rabbitTemplate.convertSendAndReceive(any())).thenReturn(receivedList);
|
||||
|
||||
final TargetAssignDistributionSetEvent targetAssignDistributionSetEvent = new TargetAssignDistributionSetEvent(
|
||||
action, serviceMatcher.getServiceId());
|
||||
amqpMessageDispatcherService.targetAssignDistributionSet(targetAssignDistributionSetEvent);
|
||||
final Message sendMessage = getCaptureAdressEvent(targetAssignDistributionSetEvent);
|
||||
final DownloadAndUpdateRequest downloadAndUpdateRequest = assertDownloadAndInstallMessage(sendMessage,
|
||||
action.getId());
|
||||
|
||||
assertEquals("DownloadAndUpdateRequest event should contains 3 software modules", 3,
|
||||
downloadAndUpdateRequest.getSoftwareModules().size());
|
||||
assertThat(downloadAndUpdateRequest.getTargetSecurityToken()).isEqualTo(TEST_TOKEN);
|
||||
|
||||
for (final org.eclipse.hawkbit.dmf.json.model.SoftwareModule softwareModule : downloadAndUpdateRequest
|
||||
.getSoftwareModules()) {
|
||||
if (!softwareModule.getModuleId().equals(module.getId())) {
|
||||
continue;
|
||||
}
|
||||
assertThat(softwareModule.getArtifacts().size()).isEqualTo(module.getArtifacts().size()).isGreaterThan(0);
|
||||
|
||||
module.getArtifacts().forEach(dbArtifact -> {
|
||||
final Optional<org.eclipse.hawkbit.dmf.json.model.Artifact> found = softwareModule.getArtifacts()
|
||||
.stream().filter(dmfartifact -> dmfartifact.getFilename().equals(dbArtifact.getFilename()))
|
||||
.findAny();
|
||||
|
||||
assertThat(found).as("The artifact should exist in message").isPresent();
|
||||
assertThat(found.get().getSize()).isEqualTo(dbArtifact.getSize());
|
||||
assertThat(found.get().getHashes().getMd5()).isEqualTo(dbArtifact.getMd5Hash());
|
||||
assertThat(found.get().getHashes().getSha1()).isEqualTo(dbArtifact.getSha1Hash());
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
@Description("Verifies that send cancel event works")
|
||||
public void testSendCancelRequest() {
|
||||
final CancelTargetAssignmentEvent cancelTargetAssignmentDistributionSetEvent = new CancelTargetAssignmentEvent(
|
||||
testTarget, 1L, serviceMatcher.getServiceId());
|
||||
amqpMessageDispatcherService
|
||||
.targetCancelAssignmentToDistributionSet(cancelTargetAssignmentDistributionSetEvent);
|
||||
final Message sendMessage = createArgumentCapture(
|
||||
cancelTargetAssignmentDistributionSetEvent.getEntity().getAddress());
|
||||
assertCancelMessage(sendMessage);
|
||||
|
||||
}
|
||||
|
||||
@Test
|
||||
@Description("Verifies that sending a delete message when receiving a delete event works.")
|
||||
public void sendDeleteRequest() {
|
||||
|
||||
// setup
|
||||
final String amqpUri = "amqp://anyhost";
|
||||
final TargetDeletedEvent targetDeletedEvent = new TargetDeletedEvent(TENANT, 1L, CONTROLLER_ID, amqpUri,
|
||||
Target.class.getName(), serviceMatcher.getServiceId());
|
||||
|
||||
// test
|
||||
amqpMessageDispatcherService.targetDelete(targetDeletedEvent);
|
||||
|
||||
// verify
|
||||
final Message sendMessage = createArgumentCapture(URI.create(amqpUri));
|
||||
assertDeleteMessage(sendMessage);
|
||||
}
|
||||
|
||||
@Test
|
||||
@Description("Verifies that a delete message is not send if the address is not an amqp address.")
|
||||
public void sendDeleteRequestWithNoAmqpAdress() {
|
||||
|
||||
// setup
|
||||
final String noAmqpUri = "http://anyhost";
|
||||
final TargetDeletedEvent targetDeletedEvent = new TargetDeletedEvent(TENANT, 1L, CONTROLLER_ID, noAmqpUri,
|
||||
Target.class.getName(), serviceMatcher.getServiceId());
|
||||
|
||||
// test
|
||||
amqpMessageDispatcherService.targetDelete(targetDeletedEvent);
|
||||
|
||||
// verify
|
||||
Mockito.verifyZeroInteractions(senderService);
|
||||
}
|
||||
|
||||
@Test
|
||||
@Description("Verfies that a delete message is not send if the address is null.")
|
||||
public void sendDeleteRequestWithNullAdress() {
|
||||
|
||||
// setup
|
||||
final String noAmqpUri = null;
|
||||
final TargetDeletedEvent targetDeletedEvent = new TargetDeletedEvent(TENANT, 1L, CONTROLLER_ID, noAmqpUri,
|
||||
Target.class.getName(), serviceMatcher.getServiceId());
|
||||
|
||||
// test
|
||||
amqpMessageDispatcherService.targetDelete(targetDeletedEvent);
|
||||
|
||||
// verify
|
||||
Mockito.verifyZeroInteractions(senderService);
|
||||
}
|
||||
|
||||
private void assertCancelMessage(final Message sendMessage) {
|
||||
assertEventMessage(sendMessage);
|
||||
final Long actionId = convertMessage(sendMessage, Long.class);
|
||||
assertEquals("Action ID should be 1", actionId, Long.valueOf(1));
|
||||
assertEquals("The topc in the message should be a CANCEL_DOWNLOAD value", EventTopic.CANCEL_DOWNLOAD,
|
||||
sendMessage.getMessageProperties().getHeaders().get(MessageHeaderKey.TOPIC));
|
||||
}
|
||||
|
||||
private void assertDeleteMessage(final Message sendMessage) {
|
||||
|
||||
assertNotNull(sendMessage);
|
||||
assertThat(sendMessage.getMessageProperties().getHeaders().get(MessageHeaderKey.THING_ID))
|
||||
.isEqualTo(CONTROLLER_ID);
|
||||
assertThat(sendMessage.getMessageProperties().getHeaders().get(MessageHeaderKey.TENANT)).isEqualTo(TENANT);
|
||||
assertThat(sendMessage.getMessageProperties().getHeaders().get(MessageHeaderKey.TYPE))
|
||||
.isEqualTo(MessageType.THING_DELETED);
|
||||
}
|
||||
|
||||
private DownloadAndUpdateRequest assertDownloadAndInstallMessage(final Message sendMessage, final Long action) {
|
||||
assertEventMessage(sendMessage);
|
||||
final DownloadAndUpdateRequest downloadAndUpdateRequest = convertMessage(sendMessage,
|
||||
DownloadAndUpdateRequest.class);
|
||||
assertEquals(downloadAndUpdateRequest.getActionId(), action);
|
||||
assertEquals("The topic of the event shuold contain DOWNLOAD_AND_INSTALL", EventTopic.DOWNLOAD_AND_INSTALL,
|
||||
sendMessage.getMessageProperties().getHeaders().get(MessageHeaderKey.TOPIC));
|
||||
assertEquals("Security token of target", downloadAndUpdateRequest.getTargetSecurityToken(), TEST_TOKEN);
|
||||
|
||||
return downloadAndUpdateRequest;
|
||||
|
||||
}
|
||||
|
||||
private void assertEventMessage(final Message sendMessage) {
|
||||
assertNotNull("The message should not be null", sendMessage);
|
||||
|
||||
assertEquals("The value of the message header THING_ID should be " + CONTROLLER_ID, CONTROLLER_ID,
|
||||
sendMessage.getMessageProperties().getHeaders().get(MessageHeaderKey.THING_ID));
|
||||
assertEquals("The value of the message header TYPE should be EVENT", MessageType.EVENT,
|
||||
sendMessage.getMessageProperties().getHeaders().get(MessageHeaderKey.TYPE));
|
||||
assertEquals("The content type message should be " + MessageProperties.CONTENT_TYPE_JSON,
|
||||
MessageProperties.CONTENT_TYPE_JSON, sendMessage.getMessageProperties().getContentType());
|
||||
}
|
||||
|
||||
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")
|
||||
private <T> T convertMessage(final Message message, final Class<T> clazz) {
|
||||
message.getMessageProperties().getHeaders().put(AbstractJavaTypeMapper.DEFAULT_CLASSID_FIELD_NAME,
|
||||
clazz.getTypeName());
|
||||
return (T) rabbitTemplate.getMessageConverter().fromMessage(message);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,492 @@
|
||||
/**
|
||||
* Copyright (c) 2015 Bosch Software Innovations GmbH and others.
|
||||
*
|
||||
* All rights reserved. This program and the accompanying materials
|
||||
* are made available under the terms of the Eclipse Public License v1.0
|
||||
* which accompanies this distribution, and is available at
|
||||
* http://www.eclipse.org/legal/epl-v10.html
|
||||
*/
|
||||
package org.eclipse.hawkbit.amqp;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
import static org.junit.Assert.fail;
|
||||
import static org.mockito.Matchers.any;
|
||||
import static org.mockito.Matchers.anyObject;
|
||||
import static org.mockito.Matchers.anyString;
|
||||
import static org.mockito.Mockito.mock;
|
||||
import static org.mockito.Mockito.times;
|
||||
import static org.mockito.Mockito.verify;
|
||||
import static org.mockito.Mockito.when;
|
||||
|
||||
import java.lang.reflect.Field;
|
||||
import java.net.MalformedURLException;
|
||||
import java.net.URI;
|
||||
import java.net.URL;
|
||||
import java.util.Collection;
|
||||
import java.util.Map;
|
||||
import java.util.Optional;
|
||||
|
||||
import org.eclipse.hawkbit.api.HostnameResolver;
|
||||
import org.eclipse.hawkbit.artifact.repository.ArtifactRepository;
|
||||
import org.eclipse.hawkbit.artifact.repository.model.DbArtifact;
|
||||
import org.eclipse.hawkbit.artifact.repository.model.DbArtifactHash;
|
||||
import org.eclipse.hawkbit.cache.DownloadIdCache;
|
||||
import org.eclipse.hawkbit.dmf.amqp.api.EventTopic;
|
||||
import org.eclipse.hawkbit.dmf.amqp.api.MessageHeaderKey;
|
||||
import org.eclipse.hawkbit.dmf.amqp.api.MessageType;
|
||||
import org.eclipse.hawkbit.dmf.json.model.ActionStatus;
|
||||
import org.eclipse.hawkbit.dmf.json.model.ActionUpdateStatus;
|
||||
import org.eclipse.hawkbit.dmf.json.model.AttributeUpdate;
|
||||
import org.eclipse.hawkbit.dmf.json.model.DownloadResponse;
|
||||
import org.eclipse.hawkbit.dmf.json.model.TenantSecurityToken;
|
||||
import org.eclipse.hawkbit.dmf.json.model.TenantSecurityToken.FileResource;
|
||||
import org.eclipse.hawkbit.repository.ArtifactManagement;
|
||||
import org.eclipse.hawkbit.repository.ControllerManagement;
|
||||
import org.eclipse.hawkbit.repository.EntityFactory;
|
||||
import org.eclipse.hawkbit.repository.builder.ActionStatusBuilder;
|
||||
import org.eclipse.hawkbit.repository.builder.ActionStatusCreate;
|
||||
import org.eclipse.hawkbit.repository.exception.EntityNotFoundException;
|
||||
import org.eclipse.hawkbit.repository.jpa.model.helper.SecurityTokenGeneratorHolder;
|
||||
import org.eclipse.hawkbit.repository.model.Action;
|
||||
import org.eclipse.hawkbit.repository.model.Action.Status;
|
||||
import org.eclipse.hawkbit.repository.model.Artifact;
|
||||
import org.eclipse.hawkbit.repository.model.DistributionSet;
|
||||
import org.eclipse.hawkbit.repository.model.Target;
|
||||
import org.eclipse.hawkbit.security.SecurityTokenGenerator;
|
||||
import org.junit.Before;
|
||||
import org.junit.Test;
|
||||
import org.junit.runner.RunWith;
|
||||
import org.mockito.ArgumentCaptor;
|
||||
import org.mockito.Captor;
|
||||
import org.mockito.Mock;
|
||||
import org.mockito.runners.MockitoJUnitRunner;
|
||||
import org.springframework.amqp.AmqpRejectAndDontRequeueException;
|
||||
import org.springframework.amqp.core.Message;
|
||||
import org.springframework.amqp.core.MessageProperties;
|
||||
import org.springframework.amqp.rabbit.core.RabbitTemplate;
|
||||
import org.springframework.amqp.support.converter.Jackson2JsonMessageConverter;
|
||||
import org.springframework.amqp.support.converter.MessageConverter;
|
||||
import org.springframework.http.HttpStatus;
|
||||
|
||||
import ru.yandex.qatools.allure.annotations.Description;
|
||||
import ru.yandex.qatools.allure.annotations.Features;
|
||||
import ru.yandex.qatools.allure.annotations.Stories;
|
||||
|
||||
@RunWith(MockitoJUnitRunner.class)
|
||||
@Features("Component Tests - Device Management Federation API")
|
||||
@Stories("AmqpMessage Handler Service Test")
|
||||
public class AmqpMessageHandlerServiceTest {
|
||||
|
||||
private static final String SHA1 = "12345";
|
||||
private static final String TENANT = "DEFAULT";
|
||||
private static final Long TENANT_ID = 123L;
|
||||
private static final String CONTROLLLER_ID = "123";
|
||||
private static final Long TARGET_ID = 123L;
|
||||
|
||||
private AmqpMessageHandlerService amqpMessageHandlerService;
|
||||
private AmqpAuthenticationMessageHandler amqpAuthenticationMessageHandlerService;
|
||||
|
||||
private MessageConverter messageConverter;
|
||||
|
||||
@Mock
|
||||
private AmqpMessageDispatcherService amqpMessageDispatcherServiceMock;
|
||||
|
||||
@Mock
|
||||
private ControllerManagement controllerManagementMock;
|
||||
|
||||
@Mock
|
||||
private EntityFactory entityFactoryMock;
|
||||
|
||||
@Mock
|
||||
private ArtifactManagement artifactManagementMock;
|
||||
|
||||
@Mock
|
||||
private AmqpControllerAuthentication authenticationManagerMock;
|
||||
|
||||
@Mock
|
||||
private ArtifactRepository artifactRepositoryMock;
|
||||
|
||||
@Mock
|
||||
private DownloadIdCache downloadIdCache;
|
||||
|
||||
@Mock
|
||||
private HostnameResolver hostnameResolverMock;
|
||||
|
||||
@Mock
|
||||
private RabbitTemplate rabbitTemplate;
|
||||
|
||||
@Captor
|
||||
private ArgumentCaptor<Map<String, String>> attributesCaptor;
|
||||
|
||||
@Captor
|
||||
private ArgumentCaptor<String> targetIdCaptor;
|
||||
|
||||
@Before
|
||||
public void before() throws Exception {
|
||||
messageConverter = new Jackson2JsonMessageConverter();
|
||||
when(rabbitTemplate.getMessageConverter()).thenReturn(messageConverter);
|
||||
when(artifactManagementMock.findFirstArtifactBySHA1(SHA1)).thenReturn(Optional.empty());
|
||||
|
||||
amqpMessageHandlerService = new AmqpMessageHandlerService(rabbitTemplate, amqpMessageDispatcherServiceMock,
|
||||
controllerManagementMock, entityFactoryMock);
|
||||
|
||||
amqpMessageHandlerService = new AmqpMessageHandlerService(rabbitTemplate, amqpMessageDispatcherServiceMock,
|
||||
controllerManagementMock, entityFactoryMock);
|
||||
amqpAuthenticationMessageHandlerService = new AmqpAuthenticationMessageHandler(rabbitTemplate,
|
||||
authenticationManagerMock, artifactManagementMock, downloadIdCache, hostnameResolverMock,
|
||||
controllerManagementMock);
|
||||
}
|
||||
|
||||
@Test
|
||||
@Description("Tests not allowed content-type in message")
|
||||
public void wrongContentType() {
|
||||
final MessageProperties messageProperties = new MessageProperties();
|
||||
messageProperties.setContentType("xml");
|
||||
final Message message = new Message(new byte[0], messageProperties);
|
||||
try {
|
||||
amqpMessageHandlerService.onMessage(message, MessageType.THING_CREATED.name(), TENANT, "vHost");
|
||||
fail("AmqpRejectAndDontRequeueException was excepeted due to worng content type");
|
||||
} catch (final AmqpRejectAndDontRequeueException e) {
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
@Description("Tests the creation of a target/thing by calling the same method that incoming RabbitMQ messages would access.")
|
||||
public void createThing() {
|
||||
final String knownThingId = "1";
|
||||
final MessageProperties messageProperties = createMessageProperties(MessageType.THING_CREATED);
|
||||
messageProperties.setHeader(MessageHeaderKey.THING_ID, "1");
|
||||
final Message message = messageConverter.toMessage(new byte[0], messageProperties);
|
||||
|
||||
final Target targetMock = mock(Target.class);
|
||||
|
||||
final ArgumentCaptor<String> targetIdCaptor = ArgumentCaptor.forClass(String.class);
|
||||
final ArgumentCaptor<URI> uriCaptor = ArgumentCaptor.forClass(URI.class);
|
||||
when(controllerManagementMock.findOrRegisterTargetIfItDoesNotexist(targetIdCaptor.capture(),
|
||||
uriCaptor.capture())).thenReturn(targetMock);
|
||||
when(controllerManagementMock.findOldestActiveActionByTarget(any())).thenReturn(Optional.empty());
|
||||
|
||||
amqpMessageHandlerService.onMessage(message, MessageType.THING_CREATED.name(), TENANT, "vHost");
|
||||
|
||||
// verify
|
||||
assertThat(targetIdCaptor.getValue()).as("Thing id is wrong").isEqualTo(knownThingId);
|
||||
assertThat(uriCaptor.getValue().toString()).as("Uri is not right").isEqualTo("amqp://vHost/MyTest");
|
||||
|
||||
}
|
||||
|
||||
@Test
|
||||
@Description("Tests the target attribute update by calling the same method that incoming RabbitMQ messages would access.")
|
||||
public void updateAttributes() {
|
||||
final String knownThingId = "1";
|
||||
final MessageProperties messageProperties = createMessageProperties(MessageType.EVENT);
|
||||
messageProperties.setHeader(MessageHeaderKey.THING_ID, "1");
|
||||
messageProperties.setHeader(MessageHeaderKey.TOPIC, "UPDATE_ATTRIBUTES");
|
||||
final AttributeUpdate attributeUpdate = new AttributeUpdate();
|
||||
attributeUpdate.getAttributes().put("testKey1", "testValue1");
|
||||
attributeUpdate.getAttributes().put("testKey2", "testValue2");
|
||||
|
||||
final Message message = amqpMessageHandlerService.getMessageConverter().toMessage(attributeUpdate,
|
||||
messageProperties);
|
||||
|
||||
when(controllerManagementMock.updateControllerAttributes(targetIdCaptor.capture(), attributesCaptor.capture()))
|
||||
.thenReturn(null);
|
||||
|
||||
amqpMessageHandlerService.onMessage(message, MessageType.EVENT.name(), TENANT, "vHost");
|
||||
|
||||
// verify
|
||||
assertThat(targetIdCaptor.getValue()).as("Thing id is wrong").isEqualTo(knownThingId);
|
||||
assertThat(attributesCaptor.getValue()).as("Attributes is not right")
|
||||
.isEqualTo(attributeUpdate.getAttributes());
|
||||
|
||||
}
|
||||
|
||||
@Test
|
||||
@Description("Tests the creation of a thing without a 'reply to' header in message.")
|
||||
public void createThingWitoutReplyTo() {
|
||||
final MessageProperties messageProperties = createMessageProperties(MessageType.THING_CREATED, null);
|
||||
messageProperties.setHeader(MessageHeaderKey.THING_ID, "1");
|
||||
final Message message = messageConverter.toMessage("", messageProperties);
|
||||
|
||||
try {
|
||||
amqpMessageHandlerService.onMessage(message, MessageType.THING_CREATED.name(), TENANT, "vHost");
|
||||
fail("AmqpRejectAndDontRequeueException was excepeted since no replyTo header was set");
|
||||
} catch (final AmqpRejectAndDontRequeueException exception) {
|
||||
// test ok - exception was excepted
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@Test
|
||||
@Description("Tests the creation of a target/thing without a thingID by calling the same method that incoming RabbitMQ messages would access.")
|
||||
public void createThingWithoutID() {
|
||||
final MessageProperties messageProperties = createMessageProperties(MessageType.THING_CREATED);
|
||||
final Message message = messageConverter.toMessage(new byte[0], messageProperties);
|
||||
try {
|
||||
amqpMessageHandlerService.onMessage(message, MessageType.THING_CREATED.name(), TENANT, "vHost");
|
||||
fail("AmqpRejectAndDontRequeueException was excepeted since no thingID was set");
|
||||
} catch (final AmqpRejectAndDontRequeueException exception) {
|
||||
// test ok - exception was excepted
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
@Description("Tests the call of the same method that incoming RabbitMQ messages would access with an unknown message type.")
|
||||
public void unknownMessageType() {
|
||||
final String type = "bumlux";
|
||||
final MessageProperties messageProperties = createMessageProperties(MessageType.THING_CREATED);
|
||||
messageProperties.setHeader(MessageHeaderKey.THING_ID, "");
|
||||
final Message message = messageConverter.toMessage(new byte[0], messageProperties);
|
||||
|
||||
try {
|
||||
amqpMessageHandlerService.onMessage(message, type, TENANT, "vHost");
|
||||
fail("AmqpRejectAndDontRequeueException was excepeted due to unknown message type");
|
||||
} catch (final AmqpRejectAndDontRequeueException exception) {
|
||||
// test ok - exception was excepted
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
@Description("Tests a invalid message without event topic")
|
||||
public void invalidEventTopic() {
|
||||
final MessageProperties messageProperties = createMessageProperties(MessageType.EVENT);
|
||||
final Message message = new Message(new byte[0], messageProperties);
|
||||
try {
|
||||
amqpMessageHandlerService.onMessage(message, MessageType.EVENT.name(), TENANT, "vHost");
|
||||
fail("AmqpRejectAndDontRequeueException was excepeted due to unknown message type");
|
||||
} catch (final AmqpRejectAndDontRequeueException e) {
|
||||
}
|
||||
|
||||
try {
|
||||
messageProperties.setHeader(MessageHeaderKey.TOPIC, "wrongTopic");
|
||||
amqpMessageHandlerService.onMessage(message, MessageType.EVENT.name(), TENANT, "vHost");
|
||||
fail("AmqpRejectAndDontRequeueException was excepeted due to unknown topic");
|
||||
} catch (final AmqpRejectAndDontRequeueException e) {
|
||||
}
|
||||
|
||||
messageProperties.setHeader(MessageHeaderKey.TOPIC, EventTopic.CANCEL_DOWNLOAD.name());
|
||||
try {
|
||||
amqpMessageHandlerService.onMessage(message, MessageType.EVENT.name(), TENANT, "vHost");
|
||||
fail("AmqpRejectAndDontRequeueException was excepeted because there was no event topic");
|
||||
} catch (final AmqpRejectAndDontRequeueException exception) {
|
||||
// test ok - exception was excepted
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@Test
|
||||
@Description("Tests the update of an action of a target without a exist action id")
|
||||
public void updateActionStatusWithoutActionId() {
|
||||
when(controllerManagementMock.findActionWithDetails(any())).thenReturn(Optional.empty());
|
||||
final MessageProperties messageProperties = createMessageProperties(MessageType.EVENT);
|
||||
messageProperties.setHeader(MessageHeaderKey.TOPIC, EventTopic.UPDATE_ACTION_STATUS.name());
|
||||
final ActionUpdateStatus actionUpdateStatus = new ActionUpdateStatus(1L, ActionStatus.DOWNLOAD);
|
||||
final Message message = amqpMessageHandlerService.getMessageConverter().toMessage(actionUpdateStatus,
|
||||
messageProperties);
|
||||
|
||||
try {
|
||||
amqpMessageHandlerService.onMessage(message, MessageType.EVENT.name(), TENANT, "vHost");
|
||||
fail("AmqpRejectAndDontRequeueException was excepeted since no action id was set");
|
||||
} catch (final AmqpRejectAndDontRequeueException exception) {
|
||||
// test ok - exception was excepted
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
@Description("Tests the update of an action of a target without a exist action id")
|
||||
public void updateActionStatusWithoutExistActionId() {
|
||||
final MessageProperties messageProperties = createMessageProperties(MessageType.EVENT);
|
||||
messageProperties.setHeader(MessageHeaderKey.TOPIC, EventTopic.UPDATE_ACTION_STATUS.name());
|
||||
when(controllerManagementMock.findActionWithDetails(any())).thenReturn(Optional.empty());
|
||||
|
||||
final ActionUpdateStatus actionUpdateStatus = createActionUpdateStatus(ActionStatus.DOWNLOAD);
|
||||
final Message message = amqpMessageHandlerService.getMessageConverter().toMessage(actionUpdateStatus,
|
||||
messageProperties);
|
||||
|
||||
try {
|
||||
amqpMessageHandlerService.onMessage(message, MessageType.EVENT.name(), TENANT, "vHost");
|
||||
fail("AmqpRejectAndDontRequeueException was excepeted since no action id was set");
|
||||
} catch (final AmqpRejectAndDontRequeueException exception) {
|
||||
// test ok - exception was excepted
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@Test
|
||||
@Description("Tests that an download request is denied for an artifact which does not exists")
|
||||
public void authenticationRequestDeniedForArtifactWhichDoesNotExists() {
|
||||
final MessageProperties messageProperties = createMessageProperties(null);
|
||||
final TenantSecurityToken securityToken = new TenantSecurityToken(TENANT, TENANT_ID, CONTROLLLER_ID, TARGET_ID,
|
||||
FileResource.createFileResourceBySha1("12345"));
|
||||
final Message message = amqpMessageHandlerService.getMessageConverter().toMessage(securityToken,
|
||||
messageProperties);
|
||||
|
||||
// test
|
||||
final Message onMessage = amqpAuthenticationMessageHandlerService.onAuthenticationRequest(message);
|
||||
|
||||
// verify
|
||||
final DownloadResponse downloadResponse = (DownloadResponse) messageConverter.fromMessage(onMessage);
|
||||
assertThat(downloadResponse).as("Message body should not null").isNotNull();
|
||||
assertThat(downloadResponse.getResponseCode()).as("Message body response code is wrong")
|
||||
.isEqualTo(HttpStatus.NOT_FOUND.value());
|
||||
}
|
||||
|
||||
@Test
|
||||
@Description("Tests that an download request is denied for an artifact which is not assigned to the requested target")
|
||||
public void authenticationRequestDeniedForArtifactWhichIsNotAssignedToTarget() {
|
||||
final MessageProperties messageProperties = createMessageProperties(null);
|
||||
final TenantSecurityToken securityToken = new TenantSecurityToken(TENANT, TENANT_ID, CONTROLLLER_ID, TARGET_ID,
|
||||
FileResource.createFileResourceBySha1("12345"));
|
||||
final Message message = amqpMessageHandlerService.getMessageConverter().toMessage(securityToken,
|
||||
messageProperties);
|
||||
|
||||
final Artifact localArtifactMock = mock(Artifact.class);
|
||||
when(artifactManagementMock.findFirstArtifactBySHA1(anyString())).thenReturn(Optional.of(localArtifactMock));
|
||||
when(controllerManagementMock.getActionForDownloadByTargetAndSoftwareModule(anyObject(), anyObject()))
|
||||
.thenThrow(EntityNotFoundException.class);
|
||||
|
||||
// test
|
||||
final Message onMessage = amqpAuthenticationMessageHandlerService.onAuthenticationRequest(message);
|
||||
|
||||
// verify
|
||||
final DownloadResponse downloadResponse = (DownloadResponse) messageConverter.fromMessage(onMessage);
|
||||
assertThat(downloadResponse).as("Message body should not null").isNotNull();
|
||||
assertThat(downloadResponse.getResponseCode()).as("Message body response code is wrong")
|
||||
.isEqualTo(HttpStatus.NOT_FOUND.value());
|
||||
}
|
||||
|
||||
@Test
|
||||
@Description("Tests that an download request is allowed for an artifact which exists and assigned to the requested target")
|
||||
public void authenticationRequestAllowedForArtifactWhichExistsAndAssignedToTarget() throws MalformedURLException {
|
||||
final MessageProperties messageProperties = createMessageProperties(null);
|
||||
final TenantSecurityToken securityToken = new TenantSecurityToken(TENANT, TENANT_ID, CONTROLLLER_ID, TARGET_ID,
|
||||
FileResource.createFileResourceBySha1("12345"));
|
||||
final Message message = amqpMessageHandlerService.getMessageConverter().toMessage(securityToken,
|
||||
messageProperties);
|
||||
|
||||
// mock
|
||||
final Artifact localArtifactMock = mock(Artifact.class);
|
||||
when(localArtifactMock.getSha1Hash()).thenReturn(SHA1);
|
||||
|
||||
final DbArtifact dbArtifactMock = mock(DbArtifact.class);
|
||||
when(artifactManagementMock.findFirstArtifactBySHA1(SHA1)).thenReturn(Optional.of(localArtifactMock));
|
||||
when(controllerManagementMock.hasTargetArtifactAssigned(securityToken.getControllerId(), SHA1))
|
||||
.thenReturn(true);
|
||||
when(artifactManagementMock.loadArtifactBinary(anyString())).thenReturn(Optional.of(dbArtifactMock));
|
||||
when(dbArtifactMock.getArtifactId()).thenReturn("artifactId");
|
||||
when(dbArtifactMock.getSize()).thenReturn(1L);
|
||||
when(dbArtifactMock.getHashes()).thenReturn(new DbArtifactHash(SHA1, "md5"));
|
||||
when(hostnameResolverMock.resolveHostname()).thenReturn(new URL("http://localhost"));
|
||||
|
||||
// test
|
||||
final Message onMessage = amqpAuthenticationMessageHandlerService.onAuthenticationRequest(message);
|
||||
|
||||
// verify
|
||||
final DownloadResponse downloadResponse = (DownloadResponse) messageConverter.fromMessage(onMessage);
|
||||
assertThat(downloadResponse).as("Message body should not null").isNotNull();
|
||||
assertThat(downloadResponse.getResponseCode()).as("Message body response code is wrong")
|
||||
.isEqualTo(HttpStatus.OK.value());
|
||||
assertThat(downloadResponse.getArtifact().getSize()).as("Wrong artifact size in message body").isEqualTo(1L);
|
||||
assertThat(downloadResponse.getArtifact().getHashes().getSha1()).as("Wrong sha1 hash").isEqualTo(SHA1);
|
||||
assertThat(downloadResponse.getArtifact().getHashes().getMd5()).as("Wrong md5 hash").isEqualTo("md5");
|
||||
assertThat(downloadResponse.getDownloadUrl()).as("download url is wrong")
|
||||
.startsWith("http://localhost/api/v1/downloadserver/downloadId/");
|
||||
}
|
||||
|
||||
@Test
|
||||
@Description("Tests TODO")
|
||||
public void lookupNextUpdateActionAfterFinished() throws IllegalAccessException {
|
||||
|
||||
// Mock
|
||||
final Action action = createActionWithTarget(22L, Status.FINISHED);
|
||||
when(controllerManagementMock.findActionWithDetails(any())).thenReturn(Optional.of(action));
|
||||
when(controllerManagementMock.addUpdateActionStatus(any())).thenReturn(action);
|
||||
final ActionStatusBuilder builder = mock(ActionStatusBuilder.class);
|
||||
final ActionStatusCreate create = mock(ActionStatusCreate.class);
|
||||
when(builder.create(22L)).thenReturn(create);
|
||||
when(create.status(any())).thenReturn(create);
|
||||
when(entityFactoryMock.actionStatus()).thenReturn(builder);
|
||||
// for the test the same action can be used
|
||||
when(controllerManagementMock.findOldestActiveActionByTarget(any())).thenReturn(Optional.of(action));
|
||||
|
||||
final MessageProperties messageProperties = createMessageProperties(MessageType.EVENT);
|
||||
messageProperties.setHeader(MessageHeaderKey.TOPIC, EventTopic.UPDATE_ACTION_STATUS.name());
|
||||
final ActionUpdateStatus actionUpdateStatus = createActionUpdateStatus(ActionStatus.FINISHED, 23L);
|
||||
final Message message = amqpMessageHandlerService.getMessageConverter().toMessage(actionUpdateStatus,
|
||||
messageProperties);
|
||||
|
||||
// test
|
||||
amqpMessageHandlerService.onMessage(message, MessageType.EVENT.name(), TENANT, "vHost");
|
||||
|
||||
final ArgumentCaptor<String> tenantCaptor = ArgumentCaptor.forClass(String.class);
|
||||
final ArgumentCaptor<Target> targetCaptor = ArgumentCaptor.forClass(Target.class);
|
||||
final ArgumentCaptor<Long> actionIdCaptor = ArgumentCaptor.forClass(Long.class);
|
||||
|
||||
verify(amqpMessageDispatcherServiceMock, times(1)).sendUpdateMessageToTarget(tenantCaptor.capture(),
|
||||
targetCaptor.capture(), actionIdCaptor.capture(), any(Collection.class));
|
||||
final String tenant = tenantCaptor.getValue();
|
||||
final String controllerId = targetCaptor.getValue().getControllerId();
|
||||
final Long actionId = actionIdCaptor.getValue();
|
||||
|
||||
assertThat(tenant).as("event has tenant").isEqualTo("DEFAULT");
|
||||
assertThat(controllerId).as("event has wrong controller id").isEqualTo("target1");
|
||||
assertThat(actionId).as("event has wrong action id").isEqualTo(22L);
|
||||
|
||||
}
|
||||
|
||||
private ActionUpdateStatus createActionUpdateStatus(final ActionStatus status) {
|
||||
return createActionUpdateStatus(status, 2L);
|
||||
}
|
||||
|
||||
private ActionUpdateStatus createActionUpdateStatus(final ActionStatus status, final Long id) {
|
||||
final ActionUpdateStatus actionUpdateStatus = new ActionUpdateStatus(id, status);
|
||||
actionUpdateStatus.setSoftwareModuleId(Long.valueOf(2));
|
||||
return actionUpdateStatus;
|
||||
}
|
||||
|
||||
private MessageProperties createMessageProperties(final MessageType type) {
|
||||
return createMessageProperties(type, "MyTest");
|
||||
}
|
||||
|
||||
private MessageProperties createMessageProperties(final MessageType type, final String replyTo) {
|
||||
final MessageProperties messageProperties = new MessageProperties();
|
||||
if (type != null) {
|
||||
messageProperties.setHeader(MessageHeaderKey.TYPE, type.name());
|
||||
}
|
||||
messageProperties.setHeader(MessageHeaderKey.TENANT, TENANT);
|
||||
messageProperties.setContentType(MessageProperties.CONTENT_TYPE_JSON);
|
||||
messageProperties.setReplyTo(replyTo);
|
||||
return messageProperties;
|
||||
}
|
||||
|
||||
private Action createActionWithTarget(final Long targetId, final Status status) throws IllegalAccessException {
|
||||
// is needed for the creation of targets
|
||||
initalizeSecurityTokenGenerator();
|
||||
|
||||
// Mock
|
||||
final Action actionMock = mock(Action.class);
|
||||
final Target targetMock = mock(Target.class);
|
||||
final DistributionSet distributionSetMock = mock(DistributionSet.class);
|
||||
|
||||
when(distributionSetMock.getId()).thenReturn(1L);
|
||||
when(actionMock.getDistributionSet()).thenReturn(distributionSetMock);
|
||||
when(actionMock.getId()).thenReturn(targetId);
|
||||
when(actionMock.getStatus()).thenReturn(status);
|
||||
when(actionMock.getTenant()).thenReturn("DEFAULT");
|
||||
when(actionMock.getTarget()).thenReturn(targetMock);
|
||||
when(targetMock.getControllerId()).thenReturn("target1");
|
||||
when(targetMock.getSecurityToken()).thenReturn("securityToken");
|
||||
when(targetMock.getAddress()).thenReturn(null);
|
||||
return actionMock;
|
||||
}
|
||||
|
||||
private void initalizeSecurityTokenGenerator() throws IllegalAccessException {
|
||||
final SecurityTokenGeneratorHolder instance = SecurityTokenGeneratorHolder.getInstance();
|
||||
final Field[] fields = instance.getClass().getDeclaredFields();
|
||||
for (final Field field : fields) {
|
||||
if (field.getType().isAssignableFrom(SecurityTokenGenerator.class)) {
|
||||
field.setAccessible(true);
|
||||
field.set(instance, new SecurityTokenGenerator());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,119 @@
|
||||
/**
|
||||
* Copyright (c) 2015 Bosch Software Innovations GmbH and others.
|
||||
*
|
||||
* All rights reserved. This program and the accompanying materials
|
||||
* are made available under the terms of the Eclipse Public License v1.0
|
||||
* which accompanies this distribution, and is available at
|
||||
* http://www.eclipse.org/legal/epl-v10.html
|
||||
*/
|
||||
package org.eclipse.hawkbit.amqp;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
import static org.junit.Assert.fail;
|
||||
import static org.mockito.Mockito.when;
|
||||
|
||||
import org.eclipse.hawkbit.dmf.json.model.ActionStatus;
|
||||
import org.eclipse.hawkbit.dmf.json.model.ActionUpdateStatus;
|
||||
import org.eclipse.hawkbit.repository.event.remote.entity.TargetCreatedEvent;
|
||||
import org.eclipse.hawkbit.repository.test.matcher.Expect;
|
||||
import org.eclipse.hawkbit.repository.test.matcher.ExpectEvents;
|
||||
import org.junit.Before;
|
||||
import org.junit.Test;
|
||||
import org.junit.runner.RunWith;
|
||||
import org.mockito.Mock;
|
||||
import org.mockito.runners.MockitoJUnitRunner;
|
||||
import org.springframework.amqp.core.Message;
|
||||
import org.springframework.amqp.core.MessageProperties;
|
||||
import org.springframework.amqp.rabbit.core.RabbitTemplate;
|
||||
import org.springframework.amqp.support.converter.Jackson2JsonMessageConverter;
|
||||
import org.springframework.amqp.support.converter.MessageConversionException;
|
||||
|
||||
import ru.yandex.qatools.allure.annotations.Description;
|
||||
import ru.yandex.qatools.allure.annotations.Features;
|
||||
import ru.yandex.qatools.allure.annotations.Stories;
|
||||
|
||||
@RunWith(MockitoJUnitRunner.class)
|
||||
@Features("Component Tests - Device Management Federation API")
|
||||
@Stories("Base Amqp Service Test")
|
||||
public class BaseAmqpServiceTest {
|
||||
|
||||
@Mock
|
||||
private RabbitTemplate rabbitTemplate;
|
||||
|
||||
private BaseAmqpService baseAmqpService;
|
||||
|
||||
@Before
|
||||
public void setup() {
|
||||
when(rabbitTemplate.getMessageConverter()).thenReturn(new Jackson2JsonMessageConverter());
|
||||
baseAmqpService = new BaseAmqpService(rabbitTemplate);
|
||||
|
||||
}
|
||||
|
||||
@Test
|
||||
@Description("Verify that the message conversion works")
|
||||
public void convertMessageTest() {
|
||||
final ActionUpdateStatus actionUpdateStatus = createActionStatus();
|
||||
|
||||
final Message message = rabbitTemplate.getMessageConverter().toMessage(actionUpdateStatus,
|
||||
createJsonProperties());
|
||||
final ActionUpdateStatus convertedActionUpdateStatus = baseAmqpService.convertMessage(message,
|
||||
ActionUpdateStatus.class);
|
||||
|
||||
assertThat(convertedActionUpdateStatus).isEqualToComparingFieldByField(actionUpdateStatus);
|
||||
|
||||
}
|
||||
|
||||
@Test
|
||||
@Description("Tests invalid null message content")
|
||||
@ExpectEvents({ @Expect(type = TargetCreatedEvent.class, count = 0) })
|
||||
public void convertMessageWithNullContent() {
|
||||
try {
|
||||
baseAmqpService.convertMessage(new Message(null, createJsonProperties()), ActionUpdateStatus.class);
|
||||
fail("Expected MessageConversionException for inavlid JSON");
|
||||
} catch (final MessageConversionException e) {
|
||||
// expected
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@Test
|
||||
@Description("Tests invalid empty message content")
|
||||
@ExpectEvents({ @Expect(type = TargetCreatedEvent.class, count = 0) })
|
||||
public void updateActionStatusWithEmptyContent() {
|
||||
try {
|
||||
baseAmqpService.convertMessage(new Message("".getBytes(), createJsonProperties()),
|
||||
ActionUpdateStatus.class);
|
||||
fail("Expected MessageConversionException for inavlid JSON");
|
||||
} catch (final MessageConversionException e) {
|
||||
// expected
|
||||
}
|
||||
}
|
||||
|
||||
private MessageProperties createJsonProperties() {
|
||||
final MessageProperties messageProperties = new MessageProperties();
|
||||
messageProperties.setContentType(MessageProperties.CONTENT_TYPE_JSON);
|
||||
return messageProperties;
|
||||
}
|
||||
|
||||
@Test
|
||||
@Description("Tests invalid json message content")
|
||||
@ExpectEvents({ @Expect(type = TargetCreatedEvent.class, count = 0) })
|
||||
public void updateActionStatusWithInvalidJsonContent() {
|
||||
try {
|
||||
baseAmqpService.convertMessage(new Message("Invalid Json".getBytes(), createJsonProperties()),
|
||||
ActionUpdateStatus.class);
|
||||
fail("Expected MessageConversionException for inavlid JSON");
|
||||
} catch (final MessageConversionException e) {
|
||||
// expected
|
||||
}
|
||||
}
|
||||
|
||||
private ActionUpdateStatus createActionStatus() {
|
||||
final ActionUpdateStatus actionUpdateStatus = new ActionUpdateStatus(1L, ActionStatus.RUNNING);
|
||||
actionUpdateStatus.setSoftwareModuleId(2L);
|
||||
actionUpdateStatus.addMessage("Message 1");
|
||||
actionUpdateStatus.addMessage("Message 2");
|
||||
return actionUpdateStatus;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,468 @@
|
||||
/**
|
||||
* Copyright (c) 2015 Bosch Software Innovations GmbH and others.
|
||||
*
|
||||
* All rights reserved. This program and the accompanying materials
|
||||
* are made available under the terms of the Eclipse Public License v1.0
|
||||
* which accompanies this distribution, and is available at
|
||||
* http://www.eclipse.org/legal/epl-v10.html
|
||||
*/
|
||||
package org.eclipse.hawkbit.integration;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
|
||||
import org.eclipse.hawkbit.amqp.AmqpProperties;
|
||||
import org.eclipse.hawkbit.amqp.DmfApiConfiguration;
|
||||
import org.eclipse.hawkbit.dmf.amqp.api.AmqpSettings;
|
||||
import org.eclipse.hawkbit.dmf.json.model.DownloadResponse;
|
||||
import org.eclipse.hawkbit.dmf.json.model.TenantSecurityToken;
|
||||
import org.eclipse.hawkbit.dmf.json.model.TenantSecurityToken.FileResource;
|
||||
import org.eclipse.hawkbit.rabbitmq.test.AbstractAmqpIntegrationTest;
|
||||
import org.eclipse.hawkbit.rabbitmq.test.AmqpTestConfiguration;
|
||||
import org.eclipse.hawkbit.repository.jpa.RepositoryApplicationConfiguration;
|
||||
import org.eclipse.hawkbit.repository.model.Artifact;
|
||||
import org.eclipse.hawkbit.repository.model.DistributionSet;
|
||||
import org.eclipse.hawkbit.repository.model.SoftwareModule;
|
||||
import org.eclipse.hawkbit.repository.model.Target;
|
||||
import org.eclipse.hawkbit.repository.model.TargetWithActionType;
|
||||
import org.eclipse.hawkbit.tenancy.configuration.TenantConfigurationProperties.TenantConfigurationKey;
|
||||
import org.junit.Before;
|
||||
import org.junit.Test;
|
||||
import org.springframework.amqp.core.Message;
|
||||
import org.springframework.amqp.core.MessageProperties;
|
||||
import org.springframework.amqp.rabbit.core.RabbitAdmin;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.boot.test.SpringApplicationConfiguration;
|
||||
import org.springframework.http.HttpStatus;
|
||||
import org.springframework.util.StringUtils;
|
||||
|
||||
import ru.yandex.qatools.allure.annotations.Description;
|
||||
import ru.yandex.qatools.allure.annotations.Features;
|
||||
import ru.yandex.qatools.allure.annotations.Stories;
|
||||
|
||||
@Features("Component Tests - Device Management Federation API")
|
||||
@Stories("Amqp Authentication Message Handler")
|
||||
@SpringApplicationConfiguration(classes = { RepositoryApplicationConfiguration.class, AmqpTestConfiguration.class,
|
||||
DmfApiConfiguration.class })
|
||||
public class AmqpAuthenticationMessageHandlerIntegrationTest extends AbstractAmqpIntegrationTest {
|
||||
|
||||
private static final String TARGET_SECRUITY_TOKEN = "12345";
|
||||
private static final String TARGET_TOKEN_HEADER = "TargetToken " + TARGET_SECRUITY_TOKEN;
|
||||
private static final String TENANT_EXIST = "DEFAULT";
|
||||
private static final String TARGET = "NewDmfTarget";
|
||||
|
||||
@Autowired
|
||||
private AmqpProperties amqpProperties;
|
||||
|
||||
@Before
|
||||
public void testSetup() {
|
||||
enableTargetTokenAuthentification();
|
||||
}
|
||||
|
||||
@Test
|
||||
@Description("Tests wrong content type. This message is invalid and should not be requeued. Additional the receive message is null")
|
||||
public void wrongContentType() {
|
||||
final Message createAndSendMessage = getDmfClient()
|
||||
.sendAndReceive(new Message("".getBytes(), new MessageProperties()));
|
||||
assertThat(createAndSendMessage).isNull();
|
||||
assertEmptyAuthenticationMessageCount();
|
||||
}
|
||||
|
||||
@Test
|
||||
@Description("Tests a null message. This message is invalid and should not be requeued. Additional the receive message is null")
|
||||
public void securityTokenIsNull() {
|
||||
final Message createAndSendMessage = sendAndReceiveAuthenticationMessage(null);
|
||||
assertThat(createAndSendMessage).isNull();
|
||||
assertEmptyAuthenticationMessageCount();
|
||||
}
|
||||
|
||||
@Test
|
||||
@Description("Tenant in the message is null. This message is invalid and should not be requeued. Additional the receive message is null")
|
||||
public void securityTokenTenantIsNull() {
|
||||
final TenantSecurityToken securityToken = createTenantSecurityToken(null, TARGET,
|
||||
FileResource.createFileResourceBySha1(TARGET_SECRUITY_TOKEN));
|
||||
final Message createAndSendMessage = sendAndReceiveAuthenticationMessage(securityToken);
|
||||
assertThat(createAndSendMessage).isNull();
|
||||
assertEmptyAuthenticationMessageCount();
|
||||
}
|
||||
|
||||
@Test
|
||||
@Description("Target in the message is null.This message is invalid and should not requeued. Additional the receive message is null")
|
||||
public void securityTokenFileResourceIsNull() {
|
||||
enableAnonymousAuthentification();
|
||||
final TenantSecurityToken securityToken = createTenantSecurityToken(TENANT_EXIST, TARGET, null);
|
||||
final Message returnMessage = sendAndReceiveAuthenticationMessage(securityToken);
|
||||
verifyResult(returnMessage, HttpStatus.NOT_FOUND, null);
|
||||
}
|
||||
|
||||
@Test
|
||||
@Description("Verify that login fails if the given credential not match.")
|
||||
public void loginFailedBadCredentials() {
|
||||
tenantConfigurationManagement.addOrUpdateConfiguration(TenantConfigurationKey.ANONYMOUS_DOWNLOAD_MODE_ENABLED,
|
||||
false);
|
||||
final TenantSecurityToken securityToken = createTenantSecurityToken(TENANT_EXIST, TARGET,
|
||||
FileResource.createFileResourceBySha1(TARGET_SECRUITY_TOKEN));
|
||||
final Message createAndSendMessage = sendAndReceiveAuthenticationMessage(securityToken);
|
||||
|
||||
verifyResult(createAndSendMessage, HttpStatus.FORBIDDEN, "Login failed");
|
||||
|
||||
}
|
||||
|
||||
@Test
|
||||
@Description("Verify that the receive message contains a 404 code,if the artifact could not found")
|
||||
public void fileResourceGetSha1InSecurityTokenIsNull() {
|
||||
enableAnonymousAuthentification();
|
||||
final TenantSecurityToken securityToken = createTenantSecurityToken(TENANT_EXIST, TARGET,
|
||||
FileResource.createFileResourceBySha1(null));
|
||||
|
||||
final Message returnMessage = sendAndReceiveAuthenticationMessage(securityToken);
|
||||
verifyResult(returnMessage, HttpStatus.NOT_FOUND,
|
||||
"Artifact for resource FileResource [sha1=null, artifactId=null, filename=null]not found ");
|
||||
|
||||
}
|
||||
|
||||
@Test
|
||||
@Description("Verify that the receive message contains a 404 code , if the distributionSet is not assigned to the target")
|
||||
public void artifactForFileResourceSHA1FoundByTargetIdTargetExistsButIsNotAssigned() {
|
||||
final Target target = createTarget(TARGET);
|
||||
|
||||
final DistributionSet distributionSet = createDistributionSet();
|
||||
final List<Artifact> artifacts = createArtifacts(distributionSet);
|
||||
final String sha1Hash = artifacts.get(0).getSha1Hash();
|
||||
final TenantSecurityToken securityToken = createTenantSecurityToken(target.getTenant(), target.getId(), null,
|
||||
FileResource.createFileResourceBySha1(sha1Hash));
|
||||
|
||||
final Message returnMessage = sendAndReceiveAuthenticationMessage(securityToken);
|
||||
verifyResult(returnMessage, HttpStatus.NOT_FOUND,
|
||||
"Artifact for resource FileResource [sha1=" + sha1Hash + ", artifactId=null, filename=null]not found ");
|
||||
}
|
||||
|
||||
@Test
|
||||
@Description("Verify that the receive message contains a 404 code, if there is no artifact for the given sha1")
|
||||
public void artifactForFileResourceSHA1NotFound() {
|
||||
enableAnonymousAuthentification();
|
||||
final TenantSecurityToken securityToken = createTenantSecurityToken(TENANT_EXIST, TARGET,
|
||||
FileResource.createFileResourceBySha1(TARGET_SECRUITY_TOKEN));
|
||||
|
||||
final Message returnMessage = sendAndReceiveAuthenticationMessage(securityToken);
|
||||
verifyResult(returnMessage, HttpStatus.NOT_FOUND,
|
||||
"Artifact for resource FileResource [sha1=12345, artifactId=null, filename=null]not found ");
|
||||
|
||||
}
|
||||
|
||||
@Test
|
||||
@Description("Verify that the receive message contains a 404 code, if there is no existing target for the given controller id")
|
||||
public void artifactForFileResourceSHA1FoundTargetNotExists() {
|
||||
enableAnonymousAuthentification();
|
||||
final DistributionSet distributionSet = createDistributionSet();
|
||||
final List<Artifact> artifacts = createArtifacts(distributionSet);
|
||||
final String sha1Hash = artifacts.get(0).getSha1Hash();
|
||||
final TenantSecurityToken securityToken = createTenantSecurityToken(TENANT_EXIST, TARGET,
|
||||
FileResource.createFileResourceBySha1(sha1Hash));
|
||||
|
||||
final Message returnMessage = sendAndReceiveAuthenticationMessage(securityToken);
|
||||
verifyResult(returnMessage, HttpStatus.NOT_FOUND,
|
||||
"Artifact for resource FileResource [sha1=" + sha1Hash + ", artifactId=null, filename=null]not found ");
|
||||
|
||||
}
|
||||
|
||||
@Test
|
||||
@Description("Verify that the receive message contains a 404 code, if there is no existing artifact for the target")
|
||||
public void artifactForFileResourceSHA1FoundTargetExistsButNotAssigned() {
|
||||
createTarget(TARGET);
|
||||
|
||||
final DistributionSet distributionSet = createDistributionSet();
|
||||
final List<Artifact> artifacts = createArtifacts(distributionSet);
|
||||
final TenantSecurityToken securityToken = createTenantSecurityToken(TENANT_EXIST, TARGET,
|
||||
FileResource.createFileResourceBySha1(artifacts.get(0).getSha1Hash()));
|
||||
|
||||
final Message returnMessage = sendAndReceiveAuthenticationMessage(securityToken);
|
||||
verifyResult(returnMessage, HttpStatus.NOT_FOUND,
|
||||
"Artifact for resource FileResource [sha1=2f532b93ed23b4341a81dc9b1ee8a1c44b5526ab, artifactId=null, filename=null]not found ");
|
||||
|
||||
}
|
||||
|
||||
@Test
|
||||
@Description("Verify that the receive message contains a 200 code and a artifact for the existing controller id ")
|
||||
public void artifactForFileResourceSHA1FoundTargetExistsIsAssigned() {
|
||||
createTarget(TARGET);
|
||||
|
||||
final DistributionSet distributionSet = createDistributionSet();
|
||||
final List<Artifact> artifacts = createArtifacts(distributionSet);
|
||||
final Artifact artifact = artifacts.get(0);
|
||||
final TenantSecurityToken securityToken = createTenantSecurityToken(TENANT_EXIST, TARGET,
|
||||
FileResource.createFileResourceBySha1(artifact.getSha1Hash()));
|
||||
|
||||
deploymentManagement.assignDistributionSet(distributionSet.getId(),
|
||||
Arrays.asList(new TargetWithActionType(TARGET)));
|
||||
|
||||
final Message returnMessage = sendAndReceiveAuthenticationMessage(securityToken);
|
||||
verifyOkResult(returnMessage, artifact);
|
||||
|
||||
}
|
||||
|
||||
@Test
|
||||
@Description("Verify that the receive message contains a 200 code and a artifact for the existing target id ")
|
||||
public void artifactForFileResourceSHA1FoundByTargetIdTargetExistsIsAssigned() {
|
||||
final Target target = createTarget(TARGET);
|
||||
|
||||
final DistributionSet distributionSet = createDistributionSet();
|
||||
final List<Artifact> artifacts = createArtifacts(distributionSet);
|
||||
final Artifact artifact = artifacts.get(0);
|
||||
final TenantSecurityToken securityToken = createTenantSecurityToken(TENANT_EXIST, target.getId(), null,
|
||||
FileResource.createFileResourceBySha1(artifact.getSha1Hash()));
|
||||
|
||||
deploymentManagement.assignDistributionSet(distributionSet.getId(),
|
||||
Arrays.asList(new TargetWithActionType(TARGET)));
|
||||
|
||||
final Message returnMessage = sendAndReceiveAuthenticationMessage(securityToken);
|
||||
verifyOkResult(returnMessage, artifact);
|
||||
|
||||
}
|
||||
|
||||
@Test
|
||||
@Description("Verify that the receive message contains a 200 code and a artifact without a controller id (anonymous enabled)")
|
||||
public void anonymousAuthentification() {
|
||||
enableAnonymousAuthentification();
|
||||
final DistributionSet distributionSet = createDistributionSet();
|
||||
final List<Artifact> artifacts = createArtifacts(distributionSet);
|
||||
final Artifact artifact = artifacts.get(0);
|
||||
final TenantSecurityToken securityToken = createTenantSecurityToken(TENANT_EXIST, null, null,
|
||||
FileResource.createFileResourceBySha1(artifact.getSha1Hash()));
|
||||
|
||||
final Message returnMessage = sendAndReceiveAuthenticationMessage(securityToken);
|
||||
verifyOkResult(returnMessage, artifact);
|
||||
|
||||
}
|
||||
|
||||
@Test
|
||||
@Description("Artifact is downloaded anonymous as there is not target id or controller id assigned to the target.")
|
||||
public void targetTokenAuthentification() {
|
||||
createTarget(TARGET);
|
||||
|
||||
final DistributionSet distributionSet = createDistributionSet();
|
||||
final List<Artifact> artifacts = createArtifacts(distributionSet);
|
||||
final Artifact artifact = artifacts.get(0);
|
||||
final TenantSecurityToken securityToken = createTenantSecurityToken(TENANT_EXIST, TARGET,
|
||||
FileResource.createFileResourceBySha1(artifact.getSha1Hash()));
|
||||
|
||||
deploymentManagement.assignDistributionSet(distributionSet.getId(),
|
||||
Arrays.asList(new TargetWithActionType(TARGET)));
|
||||
|
||||
final Message returnMessage = sendAndReceiveAuthenticationMessage(securityToken);
|
||||
verifyOkResult(returnMessage, artifact);
|
||||
|
||||
}
|
||||
|
||||
@Test
|
||||
@Description("Verify that the receive message contains a 404, if there is no artifact to the given filename")
|
||||
public void artifactForFileResourceFileNameNotFound() {
|
||||
enableAnonymousAuthentification();
|
||||
final TenantSecurityToken securityToken = createTenantSecurityToken(TENANT_EXIST, TARGET,
|
||||
FileResource.createFileResourceByFilename("Test.txt"));
|
||||
|
||||
final Message returnMessage = sendAndReceiveAuthenticationMessage(securityToken);
|
||||
verifyResult(returnMessage, HttpStatus.NOT_FOUND,
|
||||
"Artifact for resource FileResource [sha1=null, artifactId=null, filename=Test.txt]not found ");
|
||||
|
||||
}
|
||||
|
||||
@Test
|
||||
@Description("Verify that the receive message contains a 404, if there is no distribution set assigned to the target")
|
||||
public void artifactForFileResourceFileNameFoundTargetExistsButNotAssigned() {
|
||||
createTarget(TARGET);
|
||||
|
||||
final DistributionSet distributionSet = createDistributionSet();
|
||||
final List<Artifact> artifacts = createArtifacts(distributionSet);
|
||||
final TenantSecurityToken securityToken = createTenantSecurityToken(TENANT_EXIST, TARGET,
|
||||
FileResource.createFileResourceByFilename(artifacts.get(0).getFilename()));
|
||||
|
||||
final Message returnMessage = sendAndReceiveAuthenticationMessage(securityToken);
|
||||
verifyResult(returnMessage, HttpStatus.NOT_FOUND,
|
||||
"Artifact for resource FileResource [sha1=null, artifactId=null, filename=filename0]not found ");
|
||||
|
||||
}
|
||||
|
||||
@Test
|
||||
@Description("Verify that the receive message contains a 404, if there is no exisiting target")
|
||||
public void artifactForFileResourceArtifactIdFoundTargetNotExists() {
|
||||
enableAnonymousAuthentification();
|
||||
final DistributionSet distributionSet = createDistributionSet();
|
||||
final List<Artifact> artifacts = createArtifacts(distributionSet);
|
||||
final FileResource fileResource = FileResource.createFileResourceByArtifactId(artifacts.get(0).getId());
|
||||
final TenantSecurityToken securityToken = createTenantSecurityToken(TENANT_EXIST, TARGET, fileResource);
|
||||
|
||||
final Message returnMessage = sendAndReceiveAuthenticationMessage(securityToken);
|
||||
verifyResult(returnMessage, HttpStatus.NOT_FOUND, "Artifact for resource FileResource [sha1=null, artifactId="
|
||||
+ artifacts.get(0).getId() + ", filename=null]not found ");
|
||||
|
||||
}
|
||||
|
||||
@Test
|
||||
@Description("Verify that the receive message contains a 200 and a artifact for the given artifact id")
|
||||
public void artifactForFileResourceArtifactIdFoundTargetExistsIsAssigned() {
|
||||
createTarget(TARGET);
|
||||
|
||||
final DistributionSet distributionSet = createDistributionSet();
|
||||
final List<Artifact> artifacts = createArtifacts(distributionSet);
|
||||
final Artifact artifact = artifacts.get(0);
|
||||
final FileResource fileResource = FileResource.createFileResourceByArtifactId(artifact.getId());
|
||||
final TenantSecurityToken securityToken = createTenantSecurityToken(TENANT_EXIST, TARGET, fileResource);
|
||||
|
||||
deploymentManagement.assignDistributionSet(distributionSet.getId(),
|
||||
Arrays.asList(new TargetWithActionType(TARGET)));
|
||||
|
||||
final Message returnMessage = sendAndReceiveAuthenticationMessage(securityToken);
|
||||
verifyOkResult(returnMessage, artifact);
|
||||
|
||||
}
|
||||
|
||||
@Test
|
||||
@Description("Verify that the receive message contains a 404, if there is no artifact to the given softwareModuleFilename")
|
||||
public void artifactForFileResourceSoftwareModuleFilenameNotFound() {
|
||||
enableAnonymousAuthentification();
|
||||
final TenantSecurityToken securityToken = createTenantSecurityToken(TENANT_EXIST, TARGET,
|
||||
FileResource.softwareModuleFilename(1L, "Test.txt"));
|
||||
|
||||
final Message returnMessage = sendAndReceiveAuthenticationMessage(securityToken);
|
||||
verifyResult(returnMessage, HttpStatus.NOT_FOUND,
|
||||
"Artifact for resource FileResource [sha1=null, artifactId=null, filename=null]not found ");
|
||||
|
||||
}
|
||||
|
||||
@Test
|
||||
@Description("Verify that the receive message contains a 404, if there is no existing target for the file resource")
|
||||
public void artifactForFileResourceSoftwareModuleFilenameFoundTargetNotExists() {
|
||||
enableAnonymousAuthentification();
|
||||
final DistributionSet distributionSet = createDistributionSet();
|
||||
final List<Artifact> artifacts = createArtifacts(distributionSet);
|
||||
|
||||
final SoftwareModule softwareModule = distributionSet.getModules().stream().findFirst().get();
|
||||
final Artifact artifact = findArtifactOfSoftwareModule(artifacts, softwareModule);
|
||||
|
||||
final FileResource fileResource = FileResource.softwareModuleFilename(softwareModule.getId(),
|
||||
softwareModule.getArtifact(artifact.getId()).get().getFilename());
|
||||
final TenantSecurityToken securityToken = createTenantSecurityToken(TENANT_EXIST, TARGET, fileResource);
|
||||
|
||||
final Message returnMessage = sendAndReceiveAuthenticationMessage(securityToken);
|
||||
verifyResult(returnMessage, HttpStatus.NOT_FOUND,
|
||||
"Artifact for resource FileResource [sha1=null, artifactId=null, filename=null]not found ");
|
||||
|
||||
}
|
||||
|
||||
@Test
|
||||
@Description("Verify that the receive message contains a 200 and a artifact, if there is a existing artifct fpt the for the given softwareModuleFilename")
|
||||
public void artifactForFileResourceSoftwareModuleFilenameFoundTargetExistsIsAssigned() {
|
||||
createTarget(TARGET);
|
||||
|
||||
final DistributionSet distributionSet = createDistributionSet();
|
||||
final List<Artifact> artifacts = createArtifacts(distributionSet);
|
||||
|
||||
final SoftwareModule softwareModule = distributionSet.getModules().stream().findFirst().get();
|
||||
final Artifact artifact = findArtifactOfSoftwareModule(artifacts, softwareModule);
|
||||
|
||||
final FileResource fileResource = FileResource.softwareModuleFilename(softwareModule.getId(),
|
||||
softwareModule.getArtifact(artifact.getId()).get().getFilename());
|
||||
final TenantSecurityToken securityToken = createTenantSecurityToken(TENANT_EXIST, TARGET, fileResource);
|
||||
|
||||
deploymentManagement.assignDistributionSet(distributionSet.getId(),
|
||||
Arrays.asList(new TargetWithActionType(TARGET)));
|
||||
|
||||
final Message returnMessage = sendAndReceiveAuthenticationMessage(securityToken);
|
||||
verifyOkResult(returnMessage, artifact);
|
||||
|
||||
}
|
||||
|
||||
private void verifyOkResult(Message returnMessage, Artifact artifact) {
|
||||
final DownloadResponse convertedMessage = verifyResult(returnMessage, HttpStatus.OK, null);
|
||||
assertThat(convertedMessage.getDownloadUrl()).isNotNull();
|
||||
assertThat(convertedMessage.getArtifact()).isNotNull();
|
||||
assertThat(convertedMessage.getArtifact().getHashes().getSha1()).isEqualTo(artifact.getSha1Hash());
|
||||
|
||||
}
|
||||
|
||||
private void enableAnonymousAuthentification() {
|
||||
tenantConfigurationManagement.addOrUpdateConfiguration(TenantConfigurationKey.ANONYMOUS_DOWNLOAD_MODE_ENABLED,
|
||||
true);
|
||||
tenantConfigurationManagement.addOrUpdateConfiguration(
|
||||
TenantConfigurationKey.AUTHENTICATION_MODE_TARGET_SECURITY_TOKEN_ENABLED, false);
|
||||
}
|
||||
|
||||
private void enableTargetTokenAuthentification() {
|
||||
tenantConfigurationManagement.addOrUpdateConfiguration(TenantConfigurationKey.ANONYMOUS_DOWNLOAD_MODE_ENABLED,
|
||||
false);
|
||||
tenantConfigurationManagement.addOrUpdateConfiguration(
|
||||
TenantConfigurationKey.AUTHENTICATION_MODE_TARGET_SECURITY_TOKEN_ENABLED, true);
|
||||
}
|
||||
|
||||
private Target createTarget(final String controllerId) {
|
||||
return targetManagement.createTarget(
|
||||
entityFactory.target().create().controllerId(controllerId).securityToken(TARGET_SECRUITY_TOKEN));
|
||||
}
|
||||
|
||||
private TenantSecurityToken createTenantSecurityToken(final String tenant, final String controllerId,
|
||||
final FileResource fileResource) {
|
||||
final TenantSecurityToken tenantSecurityToken = new TenantSecurityToken(tenant, controllerId, fileResource);
|
||||
tenantSecurityToken.putHeader(TenantSecurityToken.AUTHORIZATION_HEADER, TARGET_TOKEN_HEADER);
|
||||
return tenantSecurityToken;
|
||||
}
|
||||
|
||||
private TenantSecurityToken createTenantSecurityToken(final String tenant, final Long targetId,
|
||||
final String controllerId, final FileResource fileResource) {
|
||||
final TenantSecurityToken tenantSecurityToken = new TenantSecurityToken(tenant, null, controllerId, targetId,
|
||||
fileResource);
|
||||
tenantSecurityToken.putHeader(TenantSecurityToken.AUTHORIZATION_HEADER, TARGET_TOKEN_HEADER);
|
||||
return tenantSecurityToken;
|
||||
}
|
||||
|
||||
private DistributionSet createDistributionSet() {
|
||||
return testdataFactory.createDistributionSet("one");
|
||||
}
|
||||
|
||||
private List<Artifact> createArtifacts(final DistributionSet distributionSet) {
|
||||
final List<Artifact> artifacts = new ArrayList<>();
|
||||
for (final SoftwareModule module : distributionSet.getModules()) {
|
||||
artifacts.addAll(testdataFactory.createArtifacts(module.getId()));
|
||||
}
|
||||
return artifacts;
|
||||
}
|
||||
|
||||
private DownloadResponse verifyResult(final Message returnMessage, final HttpStatus expectedStatus,
|
||||
final String expectedMessage) {
|
||||
final DownloadResponse convertedMessage = (DownloadResponse) getDmfClient().getMessageConverter()
|
||||
.fromMessage(returnMessage);
|
||||
assertThat(convertedMessage.getResponseCode()).isEqualTo(expectedStatus.value());
|
||||
|
||||
if (!StringUtils.isEmpty(expectedMessage)) {
|
||||
assertThat(convertedMessage.getMessage()).isEqualTo(expectedMessage);
|
||||
}
|
||||
return convertedMessage;
|
||||
}
|
||||
|
||||
private Message sendAndReceiveAuthenticationMessage(final TenantSecurityToken securityToken) {
|
||||
return getDmfClient().sendAndReceive(createMessage(securityToken, new MessageProperties()));
|
||||
}
|
||||
|
||||
private Artifact findArtifactOfSoftwareModule(final List<Artifact> artifacts, final SoftwareModule softwareModule) {
|
||||
return artifacts.stream().filter(space -> space.getSoftwareModule().getId().equals(softwareModule.getId()))
|
||||
.findFirst().get();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected String getExchange() {
|
||||
return AmqpSettings.AUTHENTICATION_EXCHANGE;
|
||||
}
|
||||
|
||||
private int getAuthenticationMessageCount() {
|
||||
return Integer.parseInt(getRabbitAdmin().getQueueProperties(amqpProperties.getAuthenticationReceiverQueue())
|
||||
.get(RabbitAdmin.QUEUE_MESSAGE_COUNT).toString());
|
||||
}
|
||||
|
||||
private void assertEmptyAuthenticationMessageCount() {
|
||||
assertThat(getAuthenticationMessageCount()).isEqualTo(0);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,127 @@
|
||||
/**
|
||||
* Copyright (c) 2015 Bosch Software Innovations GmbH and others.
|
||||
*
|
||||
* All rights reserved. This program and the accompanying materials
|
||||
* are made available under the terms of the Eclipse Public License v1.0
|
||||
* which accompanies this distribution, and is available at
|
||||
* http://www.eclipse.org/legal/epl-v10.html
|
||||
*/
|
||||
package org.eclipse.hawkbit.integration;
|
||||
|
||||
import java.util.Optional;
|
||||
import java.util.UUID;
|
||||
import java.util.concurrent.Callable;
|
||||
|
||||
import org.eclipse.hawkbit.repository.event.remote.TargetAssignDistributionSetEvent;
|
||||
import org.eclipse.hawkbit.repository.event.remote.TargetDeletedEvent;
|
||||
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.ActionUpdatedEvent;
|
||||
import org.eclipse.hawkbit.repository.event.remote.entity.CancelTargetAssignmentEvent;
|
||||
import org.eclipse.hawkbit.repository.event.remote.entity.DistributionSetCreatedEvent;
|
||||
import org.eclipse.hawkbit.repository.event.remote.entity.SoftwareModuleCreatedEvent;
|
||||
import org.eclipse.hawkbit.repository.event.remote.entity.TargetCreatedEvent;
|
||||
import org.eclipse.hawkbit.repository.event.remote.entity.TargetUpdatedEvent;
|
||||
import org.eclipse.hawkbit.repository.model.DistributionSet;
|
||||
import org.eclipse.hawkbit.repository.model.DistributionSetAssignmentResult;
|
||||
import org.eclipse.hawkbit.repository.model.Target;
|
||||
import org.eclipse.hawkbit.repository.model.TargetUpdateStatus;
|
||||
import org.eclipse.hawkbit.repository.test.matcher.Expect;
|
||||
import org.eclipse.hawkbit.repository.test.matcher.ExpectEvents;
|
||||
import org.junit.Test;
|
||||
|
||||
import ru.yandex.qatools.allure.annotations.Description;
|
||||
import ru.yandex.qatools.allure.annotations.Features;
|
||||
import ru.yandex.qatools.allure.annotations.Stories;
|
||||
|
||||
@Features("Component Tests - Device Management Federation API")
|
||||
@Stories("Amqp Message Dispatcher Service")
|
||||
public class AmqpMessageDispatcherServiceIntegrationTest extends AmqpServiceIntegrationTest {
|
||||
|
||||
@Test
|
||||
@Description("Verify that a distribution assignment send a download and install message.")
|
||||
@ExpectEvents({ @Expect(type = TargetCreatedEvent.class, count = 1),
|
||||
@Expect(type = TargetAssignDistributionSetEvent.class, count = 1),
|
||||
@Expect(type = ActionCreatedEvent.class, count = 1),
|
||||
@Expect(type = SoftwareModuleCreatedEvent.class, count = 3),
|
||||
@Expect(type = DistributionSetCreatedEvent.class, count = 1),
|
||||
@Expect(type = TargetUpdatedEvent.class, count = 1), @Expect(type = TargetPollEvent.class, count = 2) })
|
||||
public void sendDownloadAndInstallStatus() {
|
||||
registerTargetAndAssignDistributionSet();
|
||||
|
||||
createAndSendTarget(TENANT_EXIST);
|
||||
waitUntilTargetStatusIsPending();
|
||||
assertDownloadAndInstallMessage(getDistributionSet().getModules());
|
||||
}
|
||||
|
||||
@Test
|
||||
@Description("Verify that a distribution assignment multiple times send cancel and assign events with right softwaremodules")
|
||||
@ExpectEvents({ @Expect(type = TargetCreatedEvent.class, count = 1),
|
||||
@Expect(type = TargetAssignDistributionSetEvent.class, count = 1),
|
||||
@Expect(type = CancelTargetAssignmentEvent.class, count = 1),
|
||||
@Expect(type = ActionCreatedEvent.class, count = 2), @Expect(type = ActionUpdatedEvent.class, count = 1),
|
||||
@Expect(type = SoftwareModuleCreatedEvent.class, count = 6),
|
||||
@Expect(type = DistributionSetCreatedEvent.class, count = 2),
|
||||
@Expect(type = TargetUpdatedEvent.class, count = 2), @Expect(type = TargetPollEvent.class, count = 3) })
|
||||
public void assignDistributionSetMultipleTimes() {
|
||||
final DistributionSetAssignmentResult assignmentResult = registerTargetAndAssignDistributionSet();
|
||||
|
||||
final DistributionSet distributionSet2 = testdataFactory.createDistributionSet(UUID.randomUUID().toString());
|
||||
registerTargetAndAssignDistributionSet(distributionSet2.getId(), TargetUpdateStatus.PENDING,
|
||||
getDistributionSet().getModules());
|
||||
assertCancelActionMessage(assignmentResult.getActions().get(0));
|
||||
|
||||
createAndSendTarget(TENANT_EXIST);
|
||||
waitUntilTargetStatusIsPending();
|
||||
assertCancelActionMessage(assignmentResult.getActions().get(0));
|
||||
|
||||
}
|
||||
|
||||
@Test
|
||||
@Description("Verify that a cancel assignment send a cancel message.")
|
||||
@ExpectEvents({ @Expect(type = TargetCreatedEvent.class, count = 1),
|
||||
@Expect(type = TargetAssignDistributionSetEvent.class, count = 1),
|
||||
@Expect(type = CancelTargetAssignmentEvent.class, count = 1),
|
||||
@Expect(type = ActionUpdatedEvent.class, count = 1), @Expect(type = ActionCreatedEvent.class, count = 1),
|
||||
@Expect(type = SoftwareModuleCreatedEvent.class, count = 3),
|
||||
@Expect(type = DistributionSetCreatedEvent.class, count = 1),
|
||||
@Expect(type = TargetUpdatedEvent.class, count = 1), @Expect(type = TargetPollEvent.class, count = 2) })
|
||||
public void sendCancelStatus() {
|
||||
final Long actionId = registerTargetAndCancelActionId();
|
||||
|
||||
createAndSendTarget(TENANT_EXIST);
|
||||
waitUntilTargetStatusIsPending();
|
||||
assertCancelActionMessage(actionId);
|
||||
}
|
||||
|
||||
@Test
|
||||
@Description("Verify that when a target is deleted a target delete message is send.")
|
||||
@ExpectEvents({ @Expect(type = TargetCreatedEvent.class, count = 1),
|
||||
@Expect(type = TargetPollEvent.class, count = 1), @Expect(type = TargetDeletedEvent.class, count = 1) })
|
||||
public void sendDeleteMessage() {
|
||||
|
||||
registerAndAssertTargetWithExistingTenant(REGISTER_TARGET, 1);
|
||||
targetManagement.deleteTarget(REGISTER_TARGET);
|
||||
assertDeleteMessage(REGISTER_TARGET);
|
||||
}
|
||||
|
||||
private void waitUntilTargetStatusIsPending() {
|
||||
waitUntil(() -> {
|
||||
final Optional<Target> findTargetByControllerID = targetManagement
|
||||
.findTargetByControllerID(REGISTER_TARGET);
|
||||
return findTargetByControllerID.isPresent()
|
||||
&& TargetUpdateStatus.PENDING.equals(findTargetByControllerID.get().getUpdateStatus());
|
||||
});
|
||||
}
|
||||
|
||||
private void waitUntil(final Callable<Boolean> callable) {
|
||||
createConditionFactory().until(() -> {
|
||||
return securityRule.runAsPrivileged(callable);
|
||||
});
|
||||
}
|
||||
|
||||
private void createAndSendTarget(final String tenant) {
|
||||
createAndSendTarget(REGISTER_TARGET, tenant);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,654 @@
|
||||
/**
|
||||
* Copyright (c) 2015 Bosch Software Innovations GmbH and others.
|
||||
*
|
||||
* All rights reserved. This program and the accompanying materials
|
||||
* are made available under the terms of the Eclipse Public License v1.0
|
||||
* which accompanies this distribution, and is available at
|
||||
* http://www.eclipse.org/legal/epl-v10.html
|
||||
*/
|
||||
package org.eclipse.hawkbit.integration;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
import static org.assertj.core.api.Assertions.fail;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Optional;
|
||||
import java.util.UUID;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
import org.eclipse.hawkbit.amqp.AmqpProperties;
|
||||
import org.eclipse.hawkbit.dmf.amqp.api.EventTopic;
|
||||
import org.eclipse.hawkbit.dmf.amqp.api.MessageHeaderKey;
|
||||
import org.eclipse.hawkbit.dmf.amqp.api.MessageType;
|
||||
import org.eclipse.hawkbit.dmf.json.model.ActionStatus;
|
||||
import org.eclipse.hawkbit.dmf.json.model.ActionUpdateStatus;
|
||||
import org.eclipse.hawkbit.dmf.json.model.AttributeUpdate;
|
||||
import org.eclipse.hawkbit.repository.event.remote.TargetAssignDistributionSetEvent;
|
||||
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.ActionUpdatedEvent;
|
||||
import org.eclipse.hawkbit.repository.event.remote.entity.CancelTargetAssignmentEvent;
|
||||
import org.eclipse.hawkbit.repository.event.remote.entity.DistributionSetCreatedEvent;
|
||||
import org.eclipse.hawkbit.repository.event.remote.entity.SoftwareModuleCreatedEvent;
|
||||
import org.eclipse.hawkbit.repository.event.remote.entity.TargetCreatedEvent;
|
||||
import org.eclipse.hawkbit.repository.event.remote.entity.TargetUpdatedEvent;
|
||||
import org.eclipse.hawkbit.repository.model.Action;
|
||||
import org.eclipse.hawkbit.repository.model.Action.Status;
|
||||
import org.eclipse.hawkbit.repository.model.DistributionSet;
|
||||
import org.eclipse.hawkbit.repository.model.DistributionSetAssignmentResult;
|
||||
import org.eclipse.hawkbit.repository.model.TargetUpdateStatus;
|
||||
import org.eclipse.hawkbit.repository.test.matcher.Expect;
|
||||
import org.eclipse.hawkbit.repository.test.matcher.ExpectEvents;
|
||||
import org.junit.Test;
|
||||
import org.mockito.Mockito;
|
||||
import org.springframework.amqp.core.Message;
|
||||
import org.springframework.amqp.core.MessageProperties;
|
||||
import org.springframework.amqp.rabbit.core.RabbitAdmin;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
|
||||
import ru.yandex.qatools.allure.annotations.Description;
|
||||
import ru.yandex.qatools.allure.annotations.Features;
|
||||
import ru.yandex.qatools.allure.annotations.Stories;
|
||||
|
||||
@Features("Component Tests - Device Management Federation API")
|
||||
@Stories("Amqp Message Handler Service")
|
||||
public class AmqpMessageHandlerServiceIntegrationTest extends AmqpServiceIntegrationTest {
|
||||
|
||||
@Autowired
|
||||
private AmqpProperties amqpProperties;
|
||||
|
||||
@Test
|
||||
@Description("Tests register target")
|
||||
@ExpectEvents({ @Expect(type = TargetCreatedEvent.class, count = 2),
|
||||
@Expect(type = TargetPollEvent.class, count = 3) })
|
||||
public void registerTargets() {
|
||||
registerAndAssertTargetWithExistingTenant(REGISTER_TARGET, 1);
|
||||
|
||||
final String target2 = "Target2";
|
||||
registerAndAssertTargetWithExistingTenant(target2, 2);
|
||||
final Long pollingTimeTarget2 = controllerManagement.findByControllerId(target2).get().getLastTargetQuery();
|
||||
registerSameTargetAndAssertBasedOnLastPolling(target2, 2, TargetUpdateStatus.REGISTERED, pollingTimeTarget2);
|
||||
Mockito.verifyZeroInteractions(getDeadletterListener());
|
||||
}
|
||||
|
||||
@Test
|
||||
@Description("Tests register invalid target withy empty controller id. Tests register invalid target with null controller id")
|
||||
@ExpectEvents({ @Expect(type = TargetCreatedEvent.class, count = 0) })
|
||||
public void registerEmptyTarget() {
|
||||
createAndSendTarget("", TENANT_EXIST);
|
||||
assertAllTargetsCount(0);
|
||||
verifyOneDeadLetterMessage();
|
||||
|
||||
}
|
||||
|
||||
@Test
|
||||
@Description("Tests register invalid target with whitspace controller id. Tests register invalid target with null controller id")
|
||||
@ExpectEvents({ @Expect(type = TargetCreatedEvent.class, count = 0) })
|
||||
public void registerWhitespaceTarget() {
|
||||
createAndSendTarget("Invalid Invalid", TENANT_EXIST);
|
||||
assertAllTargetsCount(0);
|
||||
verifyOneDeadLetterMessage();
|
||||
|
||||
}
|
||||
|
||||
@Test
|
||||
@Description("Tests register invalid target with null controller id. Tests register invalid target with null controller id")
|
||||
@ExpectEvents({ @Expect(type = TargetCreatedEvent.class, count = 0) })
|
||||
public void registerInvalidNullTargets() {
|
||||
createAndSendTarget(null, TENANT_EXIST);
|
||||
assertAllTargetsCount(0);
|
||||
verifyOneDeadLetterMessage();
|
||||
|
||||
}
|
||||
|
||||
@Test
|
||||
@Description("Tests not allowed content-type in message. This message should forwarded to the deadletter queue")
|
||||
@ExpectEvents({ @Expect(type = TargetCreatedEvent.class, count = 0) })
|
||||
public void wrongContentType() {
|
||||
final Message createTargetMessage = createTargetMessage(REGISTER_TARGET, TENANT_EXIST);
|
||||
createTargetMessage.getMessageProperties().setContentType("WrongContentType");
|
||||
getDmfClient().send(createTargetMessage);
|
||||
|
||||
verifyOneDeadLetterMessage();
|
||||
assertAllTargetsCount(0);
|
||||
}
|
||||
|
||||
@Test
|
||||
@Description("Tests null reply to property in message header. This message should forwarded to the deadletter queue")
|
||||
@ExpectEvents({ @Expect(type = TargetCreatedEvent.class, count = 0) })
|
||||
public void missingReplyToProperty() {
|
||||
final Message createTargetMessage = createTargetMessage(REGISTER_TARGET, TENANT_EXIST);
|
||||
createTargetMessage.getMessageProperties().setReplyTo(null);
|
||||
getDmfClient().send(createTargetMessage);
|
||||
|
||||
verifyOneDeadLetterMessage();
|
||||
assertAllTargetsCount(0);
|
||||
}
|
||||
|
||||
@Test
|
||||
@Description("Tests missing reply to property in message header. This message should forwarded to the deadletter queue")
|
||||
@ExpectEvents({ @Expect(type = TargetCreatedEvent.class, count = 0) })
|
||||
public void emptyReplyToProperty() {
|
||||
final Message createTargetMessage = createTargetMessage(REGISTER_TARGET, TENANT_EXIST);
|
||||
createTargetMessage.getMessageProperties().setReplyTo("");
|
||||
getDmfClient().send(createTargetMessage);
|
||||
|
||||
verifyOneDeadLetterMessage();
|
||||
assertAllTargetsCount(0);
|
||||
}
|
||||
|
||||
@Test
|
||||
@Description("Tests missing thing id property in message. This message should forwarded to the deadletter queue")
|
||||
@ExpectEvents({ @Expect(type = TargetCreatedEvent.class, count = 0) })
|
||||
public void missingThingIdProperty() {
|
||||
final Message createTargetMessage = createTargetMessage(null, TENANT_EXIST);
|
||||
createTargetMessage.getMessageProperties().getHeaders().remove(MessageHeaderKey.THING_ID);
|
||||
getDmfClient().send(createTargetMessage);
|
||||
|
||||
verifyOneDeadLetterMessage();
|
||||
assertAllTargetsCount(0);
|
||||
}
|
||||
|
||||
@Test
|
||||
@Description("Tests null thing id property in message. This message should forwarded to the deadletter queue")
|
||||
@ExpectEvents({ @Expect(type = TargetCreatedEvent.class, count = 0) })
|
||||
public void nullThingIdProperty() {
|
||||
final Message createTargetMessage = createTargetMessage(null, TENANT_EXIST);
|
||||
getDmfClient().send(createTargetMessage);
|
||||
|
||||
verifyOneDeadLetterMessage();
|
||||
assertAllTargetsCount(0);
|
||||
}
|
||||
|
||||
@Test
|
||||
@Description("Tests missing tenant message header. This message should forwarded to the deadletter queue")
|
||||
@ExpectEvents({ @Expect(type = TargetCreatedEvent.class, count = 0) })
|
||||
public void missingTenantHeader() {
|
||||
final Message createTargetMessage = createTargetMessage(REGISTER_TARGET, TENANT_EXIST);
|
||||
createTargetMessage.getMessageProperties().getHeaders().remove(MessageHeaderKey.TENANT);
|
||||
getDmfClient().send(createTargetMessage);
|
||||
|
||||
verifyOneDeadLetterMessage();
|
||||
assertAllTargetsCount(0);
|
||||
}
|
||||
|
||||
@Test
|
||||
@Description("Tests null tenant message header. This message should forwarded to the deadletter queue")
|
||||
@ExpectEvents({ @Expect(type = TargetCreatedEvent.class, count = 0) })
|
||||
public void nullTenantHeader() {
|
||||
final Message createTargetMessage = createTargetMessage(REGISTER_TARGET, null);
|
||||
getDmfClient().send(createTargetMessage);
|
||||
|
||||
verifyOneDeadLetterMessage();
|
||||
assertAllTargetsCount(0);
|
||||
}
|
||||
|
||||
@Test
|
||||
@Description("Tests empty tenant message header. This message should forwarded to the deadletter queue")
|
||||
@ExpectEvents({ @Expect(type = TargetCreatedEvent.class, count = 0) })
|
||||
public void emptyTenantHeader() {
|
||||
final Message createTargetMessage = createTargetMessage(REGISTER_TARGET, "");
|
||||
getDmfClient().send(createTargetMessage);
|
||||
|
||||
verifyOneDeadLetterMessage();
|
||||
assertAllTargetsCount(0);
|
||||
}
|
||||
|
||||
@Test
|
||||
@Description("Tests tenant not exist. This message should forwarded to the deadletter queue")
|
||||
@ExpectEvents({ @Expect(type = TargetCreatedEvent.class, count = 0) })
|
||||
public void tenantNotExist() {
|
||||
final Message createTargetMessage = createTargetMessage(REGISTER_TARGET, "TenantNotExist");
|
||||
getDmfClient().send(createTargetMessage);
|
||||
|
||||
verifyOneDeadLetterMessage();
|
||||
assertThat(systemManagement.findTenants()).hasSize(1);
|
||||
}
|
||||
|
||||
@Test
|
||||
@Description("Tests missing type message header. This message should forwarded to the deadletter queue")
|
||||
@ExpectEvents({ @Expect(type = TargetCreatedEvent.class, count = 0) })
|
||||
public void missingTypeHeader() {
|
||||
final Message createTargetMessage = createTargetMessage(null, TENANT_EXIST);
|
||||
createTargetMessage.getMessageProperties().getHeaders().remove(MessageHeaderKey.TYPE);
|
||||
getDmfClient().send(createTargetMessage);
|
||||
|
||||
verifyOneDeadLetterMessage();
|
||||
assertAllTargetsCount(0);
|
||||
}
|
||||
|
||||
@Test
|
||||
@Description("Tests null type message header. This message should forwarded to the deadletter queue")
|
||||
@ExpectEvents({ @Expect(type = TargetCreatedEvent.class, count = 0) })
|
||||
public void nullTypeHeader() {
|
||||
final Message createTargetMessage = createTargetMessage(null, TENANT_EXIST);
|
||||
createTargetMessage.getMessageProperties().getHeaders().put(MessageHeaderKey.TYPE, null);
|
||||
getDmfClient().send(createTargetMessage);
|
||||
|
||||
verifyOneDeadLetterMessage();
|
||||
assertAllTargetsCount(0);
|
||||
}
|
||||
|
||||
@Test
|
||||
@Description("Tests empty type message header. This message should forwarded to the deadletter queue")
|
||||
@ExpectEvents({ @Expect(type = TargetCreatedEvent.class, count = 0) })
|
||||
public void emptyTypeHeader() {
|
||||
final Message createTargetMessage = createTargetMessage(null, TENANT_EXIST);
|
||||
createTargetMessage.getMessageProperties().getHeaders().put(MessageHeaderKey.TYPE, "");
|
||||
getDmfClient().send(createTargetMessage);
|
||||
|
||||
verifyOneDeadLetterMessage();
|
||||
assertAllTargetsCount(0);
|
||||
}
|
||||
|
||||
@Test
|
||||
@Description("Tests invalid type message header. This message should forwarded to the deadletter queue")
|
||||
@ExpectEvents({ @Expect(type = TargetCreatedEvent.class, count = 0) })
|
||||
public void invalidTypeHeader() {
|
||||
final Message createTargetMessage = createTargetMessage(null, TENANT_EXIST);
|
||||
createTargetMessage.getMessageProperties().getHeaders().put(MessageHeaderKey.TYPE, "NotExist");
|
||||
getDmfClient().send(createTargetMessage);
|
||||
|
||||
verifyOneDeadLetterMessage();
|
||||
assertAllTargetsCount(0);
|
||||
}
|
||||
|
||||
@Test
|
||||
@Description("Tests null topic message header. This message should forwarded to the deadletter queue")
|
||||
@ExpectEvents({ @Expect(type = TargetCreatedEvent.class, count = 0) })
|
||||
public void nullTopicHeader() {
|
||||
final Message eventMessage = createEventMessage(TENANT_EXIST, EventTopic.UPDATE_ACTION_STATUS, "");
|
||||
eventMessage.getMessageProperties().getHeaders().put(MessageHeaderKey.TOPIC, null);
|
||||
getDmfClient().send(eventMessage);
|
||||
|
||||
verifyOneDeadLetterMessage();
|
||||
}
|
||||
|
||||
@Test
|
||||
@Description("Tests null topic message header. This message should forwarded to the deadletter queue")
|
||||
@ExpectEvents({ @Expect(type = TargetCreatedEvent.class, count = 0) })
|
||||
public void emptyTopicHeader() {
|
||||
final Message eventMessage = createEventMessage(TENANT_EXIST, EventTopic.UPDATE_ACTION_STATUS, "");
|
||||
eventMessage.getMessageProperties().getHeaders().put(MessageHeaderKey.TOPIC, "");
|
||||
getDmfClient().send(eventMessage);
|
||||
|
||||
verifyOneDeadLetterMessage();
|
||||
}
|
||||
|
||||
@Test
|
||||
@Description("Tests null topic message header. This message should forwarded to the deadletter queue")
|
||||
@ExpectEvents({ @Expect(type = TargetCreatedEvent.class, count = 0) })
|
||||
public void invalidTopicHeader() {
|
||||
final Message eventMessage = createEventMessage(TENANT_EXIST, EventTopic.UPDATE_ACTION_STATUS, "");
|
||||
eventMessage.getMessageProperties().getHeaders().put(MessageHeaderKey.TOPIC, "NotExist");
|
||||
getDmfClient().send(eventMessage);
|
||||
|
||||
verifyOneDeadLetterMessage();
|
||||
}
|
||||
|
||||
@Test
|
||||
@Description("Tests missing topic message header. This message should forwarded to the deadletter queue")
|
||||
@ExpectEvents({ @Expect(type = TargetCreatedEvent.class, count = 0) })
|
||||
public void missingTopicHeader() {
|
||||
final Message eventMessage = createEventMessage(TENANT_EXIST, EventTopic.UPDATE_ACTION_STATUS, "");
|
||||
eventMessage.getMessageProperties().getHeaders().remove(MessageHeaderKey.TOPIC);
|
||||
getDmfClient().send(eventMessage);
|
||||
|
||||
verifyOneDeadLetterMessage();
|
||||
}
|
||||
|
||||
@Test
|
||||
@Description("Tests invalid null message content. This message should forwarded to the deadletter queue")
|
||||
@ExpectEvents({ @Expect(type = TargetCreatedEvent.class, count = 0) })
|
||||
public void updateActionStatusWithNullContent() {
|
||||
final Message eventMessage = createEventMessage(TENANT_EXIST, EventTopic.UPDATE_ACTION_STATUS, null);
|
||||
getDmfClient().send(eventMessage);
|
||||
verifyOneDeadLetterMessage();
|
||||
}
|
||||
|
||||
@Test
|
||||
@Description("Tests invalid empty message content. This message should forwarded to the deadletter queue")
|
||||
@ExpectEvents({ @Expect(type = TargetCreatedEvent.class, count = 0) })
|
||||
public void updateActionStatusWithEmptyContent() {
|
||||
final Message eventMessage = createEventMessage(TENANT_EXIST, EventTopic.UPDATE_ACTION_STATUS, "");
|
||||
getDmfClient().send(eventMessage);
|
||||
verifyOneDeadLetterMessage();
|
||||
}
|
||||
|
||||
@Test
|
||||
@Description("Tests invalid json message content. This message should forwarded to the deadletter queue")
|
||||
@ExpectEvents({ @Expect(type = TargetCreatedEvent.class, count = 0) })
|
||||
public void updateActionStatusWithInvalidJsonContent() {
|
||||
final Message eventMessage = createEventMessage(TENANT_EXIST, EventTopic.UPDATE_ACTION_STATUS,
|
||||
"Invalid Content");
|
||||
getDmfClient().send(eventMessage);
|
||||
verifyOneDeadLetterMessage();
|
||||
}
|
||||
|
||||
@Test
|
||||
@Description("Tests invalid topic message header. This message should forwarded to the deadletter queue")
|
||||
@ExpectEvents({ @Expect(type = TargetCreatedEvent.class, count = 0) })
|
||||
public void updateActionStatusWithInvalidActionId() {
|
||||
final ActionUpdateStatus actionUpdateStatus = new ActionUpdateStatus(1L, ActionStatus.RUNNING);
|
||||
final Message eventMessage = createEventMessage(TENANT_EXIST, EventTopic.UPDATE_ACTION_STATUS,
|
||||
actionUpdateStatus);
|
||||
getDmfClient().send(eventMessage);
|
||||
verifyOneDeadLetterMessage();
|
||||
}
|
||||
|
||||
@Test
|
||||
@Description("Tests register target and cancel a assignment")
|
||||
@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 = SoftwareModuleCreatedEvent.class, count = 3),
|
||||
@Expect(type = DistributionSetCreatedEvent.class, count = 1),
|
||||
@Expect(type = TargetUpdatedEvent.class, count = 2), @Expect(type = TargetPollEvent.class, count = 1) })
|
||||
public void finishActionStatus() {
|
||||
registerTargetAndSendAndAssertUpdateActionStatus(ActionStatus.FINISHED, Status.FINISHED);
|
||||
}
|
||||
|
||||
@Test
|
||||
@Description("Register a target and send a update action status (running). Verfiy 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 = 0), @Expect(type = ActionCreatedEvent.class, count = 1),
|
||||
@Expect(type = SoftwareModuleCreatedEvent.class, count = 3),
|
||||
@Expect(type = DistributionSetCreatedEvent.class, count = 1),
|
||||
@Expect(type = TargetUpdatedEvent.class, count = 1), @Expect(type = TargetPollEvent.class, count = 1) })
|
||||
public void runningActionStatus() {
|
||||
registerTargetAndSendAndAssertUpdateActionStatus(ActionStatus.RUNNING, Status.RUNNING);
|
||||
}
|
||||
|
||||
@Test
|
||||
@Description("Register a target and send a update action status (download). Verfiy if the updated action status is correct.")
|
||||
@ExpectEvents({ @Expect(type = TargetCreatedEvent.class, count = 1),
|
||||
@Expect(type = TargetAssignDistributionSetEvent.class, count = 1),
|
||||
@Expect(type = ActionCreatedEvent.class, count = 1),
|
||||
@Expect(type = SoftwareModuleCreatedEvent.class, count = 3),
|
||||
@Expect(type = DistributionSetCreatedEvent.class, count = 1),
|
||||
@Expect(type = TargetUpdatedEvent.class, count = 1), @Expect(type = TargetPollEvent.class, count = 1) })
|
||||
public void downloadActionStatus() {
|
||||
registerTargetAndSendAndAssertUpdateActionStatus(ActionStatus.DOWNLOAD, Status.DOWNLOAD);
|
||||
}
|
||||
|
||||
@Test
|
||||
@Description("Register a target and send a update action status (error). Verfiy 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 = SoftwareModuleCreatedEvent.class, count = 3),
|
||||
@Expect(type = DistributionSetCreatedEvent.class, count = 1),
|
||||
@Expect(type = TargetUpdatedEvent.class, count = 2), @Expect(type = TargetPollEvent.class, count = 1) })
|
||||
public void errorActionStatus() {
|
||||
registerTargetAndSendAndAssertUpdateActionStatus(ActionStatus.ERROR, Status.ERROR);
|
||||
}
|
||||
|
||||
@Test
|
||||
@Description("Register a target and send a update action status (warning). Verfiy if the updated action status is correct.")
|
||||
@ExpectEvents({ @Expect(type = TargetCreatedEvent.class, count = 1),
|
||||
@Expect(type = TargetAssignDistributionSetEvent.class, count = 1),
|
||||
@Expect(type = ActionCreatedEvent.class, count = 1),
|
||||
@Expect(type = SoftwareModuleCreatedEvent.class, count = 3),
|
||||
@Expect(type = DistributionSetCreatedEvent.class, count = 1),
|
||||
@Expect(type = TargetUpdatedEvent.class, count = 1), @Expect(type = TargetPollEvent.class, count = 1) })
|
||||
public void warningActionStatus() {
|
||||
registerTargetAndSendAndAssertUpdateActionStatus(ActionStatus.WARNING, Status.WARNING);
|
||||
}
|
||||
|
||||
@Test
|
||||
@Description("Register a target and send a update action status (retrieved). Verfiy if the updated action status is correct.")
|
||||
@ExpectEvents({ @Expect(type = TargetCreatedEvent.class, count = 1),
|
||||
@Expect(type = TargetAssignDistributionSetEvent.class, count = 1),
|
||||
@Expect(type = ActionCreatedEvent.class, count = 1),
|
||||
@Expect(type = SoftwareModuleCreatedEvent.class, count = 3),
|
||||
@Expect(type = DistributionSetCreatedEvent.class, count = 1),
|
||||
@Expect(type = TargetUpdatedEvent.class, count = 1), @Expect(type = TargetPollEvent.class, count = 1) })
|
||||
public void retrievedActionStatus() {
|
||||
registerTargetAndSendAndAssertUpdateActionStatus(ActionStatus.RETRIEVED, Status.RETRIEVED);
|
||||
}
|
||||
|
||||
@Test
|
||||
@Description("Register a target and send a invalid update action status (cancel). This message should forwarded to the deadletter queue")
|
||||
@ExpectEvents({ @Expect(type = TargetCreatedEvent.class, count = 1),
|
||||
@Expect(type = TargetAssignDistributionSetEvent.class, count = 1),
|
||||
@Expect(type = ActionCreatedEvent.class, count = 1),
|
||||
@Expect(type = SoftwareModuleCreatedEvent.class, count = 3),
|
||||
@Expect(type = DistributionSetCreatedEvent.class, count = 1),
|
||||
@Expect(type = TargetUpdatedEvent.class, count = 1), @Expect(type = TargetPollEvent.class, count = 1) })
|
||||
public void cancelNotAllowActionStatus() {
|
||||
registerTargetAndSendActionStatus(ActionStatus.CANCELED);
|
||||
verifyOneDeadLetterMessage();
|
||||
}
|
||||
|
||||
@Test
|
||||
@Description("Verfiy receiving a download and install message if a deployment is done before the target has polled the first time.")
|
||||
@ExpectEvents({ @Expect(type = TargetCreatedEvent.class, count = 1),
|
||||
@Expect(type = TargetAssignDistributionSetEvent.class, count = 1),
|
||||
@Expect(type = ActionCreatedEvent.class, count = 1),
|
||||
@Expect(type = SoftwareModuleCreatedEvent.class, count = 3),
|
||||
@Expect(type = DistributionSetCreatedEvent.class, count = 1),
|
||||
@Expect(type = TargetUpdatedEvent.class, count = 1), @Expect(type = TargetPollEvent.class, count = 1) })
|
||||
public void receiveDownLoadAndInstallMessageAfterAssignment() {
|
||||
|
||||
// setup
|
||||
controllerManagement.findOrRegisterTargetIfItDoesNotexist(REGISTER_TARGET, null);
|
||||
final DistributionSet distributionSet = testdataFactory.createDistributionSet(UUID.randomUUID().toString());
|
||||
assignDistributionSet(distributionSet.getId(), REGISTER_TARGET);
|
||||
|
||||
// test
|
||||
registerAndAssertTargetWithExistingTenant(REGISTER_TARGET, 1, TargetUpdateStatus.PENDING, "bumlux");
|
||||
|
||||
// verify
|
||||
assertDownloadAndInstallMessage(distributionSet.getModules());
|
||||
Mockito.verifyZeroInteractions(getDeadletterListener());
|
||||
}
|
||||
|
||||
@Test
|
||||
@Description("Verfiy receiving a cancel update message if a deployment is canceled before the target has polled the first time.")
|
||||
@ExpectEvents({ @Expect(type = TargetCreatedEvent.class, count = 1),
|
||||
@Expect(type = TargetAssignDistributionSetEvent.class, count = 1),
|
||||
@Expect(type = ActionCreatedEvent.class, count = 1),
|
||||
@Expect(type = SoftwareModuleCreatedEvent.class, count = 3),
|
||||
@Expect(type = DistributionSetCreatedEvent.class, count = 1),
|
||||
@Expect(type = CancelTargetAssignmentEvent.class, count = 1),
|
||||
@Expect(type = ActionUpdatedEvent.class, count = 1), @Expect(type = TargetUpdatedEvent.class, count = 1),
|
||||
@Expect(type = TargetPollEvent.class, count = 1) })
|
||||
public void receiveCancelUpdateMessageAfterAssignmentWasCanceled() {
|
||||
|
||||
// Setup
|
||||
controllerManagement.findOrRegisterTargetIfItDoesNotexist(REGISTER_TARGET, null);
|
||||
final DistributionSet distributionSet = testdataFactory.createDistributionSet(UUID.randomUUID().toString());
|
||||
final DistributionSetAssignmentResult distributionSetAssignmentResult = assignDistributionSet(
|
||||
distributionSet.getId(), REGISTER_TARGET);
|
||||
deploymentManagement.cancelAction(distributionSetAssignmentResult.getActions().get(0));
|
||||
|
||||
// test
|
||||
registerAndAssertTargetWithExistingTenant(REGISTER_TARGET, 1, TargetUpdateStatus.PENDING, "bumlux");
|
||||
|
||||
// verify
|
||||
assertCancelActionMessage(distributionSetAssignmentResult.getActions().get(0));
|
||||
Mockito.verifyZeroInteractions(getDeadletterListener());
|
||||
}
|
||||
|
||||
@Test
|
||||
@Description("Register a target and send a invalid update action status (canceled). The current status (pending) is not a canceling state. This message should forwarded to the deadletter queue")
|
||||
@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 = SoftwareModuleCreatedEvent.class, count = 3),
|
||||
@Expect(type = CancelTargetAssignmentEvent.class, count = 1),
|
||||
@Expect(type = DistributionSetCreatedEvent.class, count = 1),
|
||||
@Expect(type = TargetUpdatedEvent.class, count = 1), @Expect(type = TargetPollEvent.class, count = 1) })
|
||||
public void actionNotExists() {
|
||||
final Long actionId = registerTargetAndCancelActionId();
|
||||
final Long actionNotExist = actionId + 1;
|
||||
|
||||
sendActionUpdateStatus(new ActionUpdateStatus(actionNotExist, ActionStatus.CANCELED));
|
||||
verifyOneDeadLetterMessage();
|
||||
}
|
||||
|
||||
@Test
|
||||
@Description("Register a target and send a invalid update action status (cancel_rejected). This message should forwarded to the deadletter queue")
|
||||
@ExpectEvents({ @Expect(type = TargetCreatedEvent.class, count = 1),
|
||||
@Expect(type = TargetAssignDistributionSetEvent.class, count = 1),
|
||||
@Expect(type = ActionCreatedEvent.class, count = 1),
|
||||
@Expect(type = SoftwareModuleCreatedEvent.class, count = 3),
|
||||
@Expect(type = DistributionSetCreatedEvent.class, count = 1),
|
||||
@Expect(type = TargetUpdatedEvent.class, count = 1), @Expect(type = TargetPollEvent.class, count = 1) })
|
||||
public void canceledRejectedNotAllowActionStatus() {
|
||||
registerTargetAndSendActionStatus(ActionStatus.CANCEL_REJECTED);
|
||||
verifyOneDeadLetterMessage();
|
||||
}
|
||||
|
||||
@Test
|
||||
@Description("Register a target and send a valid update action status (cancel_rejected). Verfiy if the updated action status is correct.")
|
||||
@ExpectEvents({ @Expect(type = TargetCreatedEvent.class, count = 1),
|
||||
@Expect(type = TargetAssignDistributionSetEvent.class, count = 1),
|
||||
@Expect(type = CancelTargetAssignmentEvent.class, count = 1),
|
||||
@Expect(type = ActionUpdatedEvent.class, count = 1), @Expect(type = ActionCreatedEvent.class, count = 1),
|
||||
@Expect(type = SoftwareModuleCreatedEvent.class, count = 3),
|
||||
@Expect(type = DistributionSetCreatedEvent.class, count = 1),
|
||||
@Expect(type = TargetUpdatedEvent.class, count = 1), @Expect(type = TargetPollEvent.class, count = 1) })
|
||||
public void canceledRejectedActionStatus() {
|
||||
final Long actionId = registerTargetAndCancelActionId();
|
||||
|
||||
sendActionUpdateStatus(new ActionUpdateStatus(actionId, ActionStatus.CANCEL_REJECTED));
|
||||
assertAction(actionId, Status.RUNNING, Status.CANCELING, Status.CANCEL_REJECTED);
|
||||
}
|
||||
|
||||
@Test
|
||||
@Description("Verify that sending an update controller attribute message to an existing target works.")
|
||||
@ExpectEvents({ @Expect(type = TargetCreatedEvent.class, count = 1),
|
||||
@Expect(type = TargetUpdatedEvent.class, count = 1), @Expect(type = TargetPollEvent.class, count = 1) })
|
||||
public void updateAttributes() {
|
||||
|
||||
// setup
|
||||
final String target = "ControllerAttributeTestTarget";
|
||||
registerAndAssertTargetWithExistingTenant(target, 1);
|
||||
final AttributeUpdate controllerAttribute = new AttributeUpdate();
|
||||
controllerAttribute.getAttributes().put("test1", "testA");
|
||||
controllerAttribute.getAttributes().put("test2", "testB");
|
||||
|
||||
// test
|
||||
sendUpdateAttributeMessage(target, TENANT_EXIST, controllerAttribute);
|
||||
|
||||
// validate
|
||||
assertUpdateAttributes(target, controllerAttribute.getAttributes());
|
||||
}
|
||||
|
||||
@Test
|
||||
@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),
|
||||
@Expect(type = TargetUpdatedEvent.class, count = 0), @Expect(type = TargetPollEvent.class, count = 1) })
|
||||
public void updateAttributesWithNoThingId() {
|
||||
|
||||
// setup
|
||||
final String target = "ControllerAttributeTestTarget";
|
||||
registerAndAssertTargetWithExistingTenant(target, 1);
|
||||
final AttributeUpdate controllerAttribute = new AttributeUpdate();
|
||||
controllerAttribute.getAttributes().put("test1", "testA");
|
||||
controllerAttribute.getAttributes().put("test2", "testB");
|
||||
final Message createUpdateAttributesMessage = createUpdateAttributesMessage(null, TENANT_EXIST,
|
||||
controllerAttribute);
|
||||
createUpdateAttributesMessage.getMessageProperties().getHeaders().remove(MessageHeaderKey.THING_ID);
|
||||
|
||||
// test
|
||||
getDmfClient().send(createUpdateAttributesMessage);
|
||||
|
||||
// verify
|
||||
verifyOneDeadLetterMessage();
|
||||
final AttributeUpdate controllerAttributeEmpty = new AttributeUpdate();
|
||||
assertUpdateAttributes(target, controllerAttributeEmpty.getAttributes());
|
||||
}
|
||||
|
||||
@Test
|
||||
@Description("Verify that sending an update controller attribute message with invalid body to an existing target does not work.")
|
||||
@ExpectEvents({ @Expect(type = TargetCreatedEvent.class, count = 1),
|
||||
@Expect(type = TargetUpdatedEvent.class, count = 0), @Expect(type = TargetPollEvent.class, count = 1) })
|
||||
public void updateAttributesWithWrongBody() {
|
||||
|
||||
// setup
|
||||
final String target = "ControllerAttributeTestTarget";
|
||||
registerAndAssertTargetWithExistingTenant(target, 1);
|
||||
final AttributeUpdate controllerAttribute = new AttributeUpdate();
|
||||
controllerAttribute.getAttributes().put("test1", "testA");
|
||||
controllerAttribute.getAttributes().put("test2", "testB");
|
||||
final Message createUpdateAttributesMessageWrongBody = createUpdateAttributesMessageWrongBody(target,
|
||||
TENANT_EXIST);
|
||||
|
||||
// test
|
||||
getDmfClient().send(createUpdateAttributesMessageWrongBody);
|
||||
|
||||
// verify
|
||||
verifyOneDeadLetterMessage();
|
||||
}
|
||||
|
||||
private Long registerTargetAndSendActionStatus(final ActionStatus sendActionStatus) {
|
||||
final DistributionSetAssignmentResult assignmentResult = registerTargetAndAssignDistributionSet();
|
||||
final Long actionId = assignmentResult.getActions().get(0);
|
||||
sendActionUpdateStatus(new ActionUpdateStatus(actionId, sendActionStatus));
|
||||
return actionId;
|
||||
}
|
||||
|
||||
private void sendActionUpdateStatus(final ActionUpdateStatus actionStatus) {
|
||||
final Message eventMessage = createEventMessage(TENANT_EXIST, EventTopic.UPDATE_ACTION_STATUS, actionStatus);
|
||||
getDmfClient().send(eventMessage);
|
||||
}
|
||||
|
||||
private void registerTargetAndSendAndAssertUpdateActionStatus(final ActionStatus sendActionStatus,
|
||||
final Status expectedActionStatus) {
|
||||
final Long actionId = registerTargetAndSendActionStatus(sendActionStatus);
|
||||
assertAction(actionId, Status.RUNNING, expectedActionStatus);
|
||||
}
|
||||
|
||||
private void assertAction(final Long actionId, final Status... expectedActionStates) {
|
||||
createConditionFactory().await().until(() -> {
|
||||
try {
|
||||
securityRule.runAsPrivileged(() -> {
|
||||
final Optional<Action> findActionWithDetails = controllerManagement.findActionWithDetails(actionId);
|
||||
assertThat(findActionWithDetails).isPresent();
|
||||
assertThat(convertStatusList(findActionWithDetails.get())).containsOnly(expectedActionStates);
|
||||
return null;
|
||||
});
|
||||
} catch (final Exception e) {
|
||||
fail(e.getMessage());
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private List<Status> convertStatusList(Action action) {
|
||||
return action.getActionStatus().stream().map(actionStatus -> actionStatus.getStatus())
|
||||
.collect(Collectors.toList());
|
||||
}
|
||||
|
||||
private Message createEventMessage(final String tenant, final EventTopic eventTopic, final Object payload) {
|
||||
final MessageProperties messageProperties = createMessagePropertiesWithTenant(tenant);
|
||||
messageProperties.getHeaders().put(MessageHeaderKey.TYPE, MessageType.EVENT.toString());
|
||||
messageProperties.getHeaders().put(MessageHeaderKey.TOPIC, eventTopic.toString());
|
||||
|
||||
return createMessage(payload, messageProperties);
|
||||
}
|
||||
|
||||
private void sendUpdateAttributeMessage(final String target, final String tenant,
|
||||
final AttributeUpdate attributeUpdate) {
|
||||
final Message updateMessage = createUpdateAttributesMessage(target, tenant, attributeUpdate);
|
||||
getDmfClient().send(updateMessage);
|
||||
}
|
||||
|
||||
private int getAuthenticationMessageCount() {
|
||||
return Integer.parseInt(getRabbitAdmin().getQueueProperties(amqpProperties.getReceiverQueue())
|
||||
.get(RabbitAdmin.QUEUE_MESSAGE_COUNT).toString());
|
||||
}
|
||||
|
||||
private void assertEmptyReceiverQueueCount() {
|
||||
assertThat(getAuthenticationMessageCount()).isEqualTo(0);
|
||||
}
|
||||
|
||||
private void verifyOneDeadLetterMessage() {
|
||||
assertEmptyReceiverQueueCount();
|
||||
createConditionFactory().until(() -> {
|
||||
Mockito.verify(getDeadletterListener(), Mockito.times(1)).handleMessage(Mockito.any());
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,298 @@
|
||||
/**
|
||||
* Copyright (c) 2015 Bosch Software Innovations GmbH and others.
|
||||
*
|
||||
* All rights reserved. This program and the accompanying materials
|
||||
* are made available under the terms of the Eclipse Public License v1.0
|
||||
* which accompanies this distribution, and is available at
|
||||
* http://www.eclipse.org/legal/epl-v10.html
|
||||
*/
|
||||
package org.eclipse.hawkbit.integration;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
|
||||
import java.util.Map;
|
||||
import java.util.Optional;
|
||||
import java.util.Set;
|
||||
import java.util.UUID;
|
||||
import java.util.concurrent.Callable;
|
||||
|
||||
import org.eclipse.hawkbit.amqp.DmfApiConfiguration;
|
||||
import org.eclipse.hawkbit.dmf.amqp.api.AmqpSettings;
|
||||
import org.eclipse.hawkbit.dmf.amqp.api.EventTopic;
|
||||
import org.eclipse.hawkbit.dmf.amqp.api.MessageHeaderKey;
|
||||
import org.eclipse.hawkbit.dmf.amqp.api.MessageType;
|
||||
import org.eclipse.hawkbit.dmf.json.model.AttributeUpdate;
|
||||
import org.eclipse.hawkbit.dmf.json.model.DownloadAndUpdateRequest;
|
||||
import org.eclipse.hawkbit.integration.listener.DeadletterListener;
|
||||
import org.eclipse.hawkbit.integration.listener.ReplyToListener;
|
||||
import org.eclipse.hawkbit.matcher.SoftwareModuleJsonMatcher;
|
||||
import org.eclipse.hawkbit.rabbitmq.test.AbstractAmqpIntegrationTest;
|
||||
import org.eclipse.hawkbit.rabbitmq.test.AmqpTestConfiguration;
|
||||
import org.eclipse.hawkbit.repository.jpa.RepositoryApplicationConfiguration;
|
||||
import org.eclipse.hawkbit.repository.model.DistributionSet;
|
||||
import org.eclipse.hawkbit.repository.model.DistributionSetAssignmentResult;
|
||||
import org.eclipse.hawkbit.repository.model.Target;
|
||||
import org.eclipse.hawkbit.repository.model.TargetUpdateStatus;
|
||||
import org.eclipse.hawkbit.util.IpUtil;
|
||||
import org.junit.Assert;
|
||||
import org.junit.Before;
|
||||
import org.mockito.Mockito;
|
||||
import org.springframework.amqp.core.Message;
|
||||
import org.springframework.amqp.core.MessageProperties;
|
||||
import org.springframework.amqp.rabbit.connection.ConnectionFactory;
|
||||
import org.springframework.amqp.rabbit.test.RabbitListenerTestHarness;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.boot.test.SpringApplicationConfiguration;
|
||||
|
||||
/**
|
||||
*
|
||||
* Common class for {@link AmqpMessageHandlerServiceIntegrationTest} and
|
||||
* {@link AmqpMessageDispatcherServiceIntegrationTest}.
|
||||
*/
|
||||
@SpringApplicationConfiguration(classes = { RepositoryApplicationConfiguration.class, AmqpTestConfiguration.class,
|
||||
DmfApiConfiguration.class, DmfTestConfiguration.class })
|
||||
public abstract class AmqpServiceIntegrationTest extends AbstractAmqpIntegrationTest {
|
||||
|
||||
protected static final String TENANT_EXIST = "DEFAULT";
|
||||
protected static final String REGISTER_TARGET = "NewDmfTarget";
|
||||
protected static final String CREATED_BY = "CONTROLLER_PLUG_AND_PLAY";
|
||||
|
||||
private DeadletterListener deadletterListener;
|
||||
private ReplyToListener replyToListener;
|
||||
private DistributionSet distributionSet;
|
||||
|
||||
@Autowired
|
||||
private RabbitListenerTestHarness harness;
|
||||
|
||||
@Autowired
|
||||
private ConnectionFactory connectionFactory;
|
||||
|
||||
@Before
|
||||
public void initListener() {
|
||||
deadletterListener = harness.getSpy(DeadletterListener.LISTENER_ID);
|
||||
assertThat(deadletterListener).isNotNull();
|
||||
Mockito.reset(deadletterListener);
|
||||
replyToListener = harness.getSpy(ReplyToListener.LISTENER_ID);
|
||||
assertThat(replyToListener).isNotNull();
|
||||
Mockito.reset(replyToListener);
|
||||
getDmfClient().setExchange(AmqpSettings.DMF_EXCHANGE);
|
||||
}
|
||||
|
||||
protected <T> T waitUntilIsPresent(final Callable<Optional<T>> callable) {
|
||||
createConditionFactory().until(() -> {
|
||||
return securityRule.runAsPrivileged(() -> callable.call().isPresent());
|
||||
});
|
||||
|
||||
try {
|
||||
return securityRule.runAsPrivileged(() -> callable.call().get());
|
||||
} catch (final Exception e) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
protected void verifyDeadLetterMessages(final int expectedMessages) {
|
||||
createConditionFactory().until(() -> {
|
||||
Mockito.verify(getDeadletterListener(), Mockito.times(expectedMessages)).handleMessage(Mockito.any());
|
||||
});
|
||||
}
|
||||
|
||||
protected DeadletterListener getDeadletterListener() {
|
||||
return deadletterListener;
|
||||
}
|
||||
|
||||
protected DistributionSet getDistributionSet() {
|
||||
return distributionSet;
|
||||
}
|
||||
|
||||
protected DistributionSetAssignmentResult registerTargetAndAssignDistributionSet() {
|
||||
distributionSet = testdataFactory.createDistributionSet(UUID.randomUUID().toString());
|
||||
return registerTargetAndAssignDistributionSet(distributionSet.getId(), TargetUpdateStatus.REGISTERED,
|
||||
distributionSet.getModules());
|
||||
}
|
||||
|
||||
protected DistributionSetAssignmentResult registerTargetAndAssignDistributionSet(final Long assignDs,
|
||||
final TargetUpdateStatus expectedStatus,
|
||||
final Set<org.eclipse.hawkbit.repository.model.SoftwareModule> expectedSoftwareModulesInMessage) {
|
||||
registerAndAssertTargetWithExistingTenant(REGISTER_TARGET, 1, expectedStatus, CREATED_BY);
|
||||
|
||||
final DistributionSetAssignmentResult assignmentResult = assignDistributionSet(assignDs, REGISTER_TARGET);
|
||||
assertDownloadAndInstallMessage(expectedSoftwareModulesInMessage);
|
||||
return assignmentResult;
|
||||
}
|
||||
|
||||
protected void assertCancelActionMessage(final Long actionId) {
|
||||
final Message replyMessage = assertReplyMessageHeader(EventTopic.CANCEL_DOWNLOAD);
|
||||
|
||||
final Long actionUpdateStatus = (Long) getDmfClient().getMessageConverter().fromMessage(replyMessage);
|
||||
assertThat(actionUpdateStatus).isEqualTo(actionId);
|
||||
}
|
||||
|
||||
protected void assertDeleteMessage(final String target) {
|
||||
|
||||
verifyReplyToListener();
|
||||
final Message replyMessage = replyToListener.getDeleteMessages().get(target);
|
||||
assertAllTargetsCount(0);
|
||||
final Map<String, Object> headers = replyMessage.getMessageProperties().getHeaders();
|
||||
assertThat(headers.get(MessageHeaderKey.THING_ID)).isEqualTo(target);
|
||||
assertThat(headers.get(MessageHeaderKey.TENANT)).isEqualTo(TENANT_EXIST);
|
||||
assertThat(headers.get(MessageHeaderKey.TYPE)).isEqualTo(MessageType.THING_DELETED.toString());
|
||||
}
|
||||
|
||||
protected void assertDownloadAndInstallMessage(
|
||||
final Set<org.eclipse.hawkbit.repository.model.SoftwareModule> dsModules) {
|
||||
final Message replyMessage = assertReplyMessageHeader(EventTopic.DOWNLOAD_AND_INSTALL);
|
||||
assertAllTargetsCount(1);
|
||||
|
||||
final DownloadAndUpdateRequest downloadAndUpdateRequest = (DownloadAndUpdateRequest) getDmfClient()
|
||||
.getMessageConverter().fromMessage(replyMessage);
|
||||
|
||||
Assert.assertThat(dsModules,
|
||||
SoftwareModuleJsonMatcher.containsExactly(downloadAndUpdateRequest.getSoftwareModules()));
|
||||
|
||||
final Target updatedTarget = waitUntilIsPresent(
|
||||
() -> targetManagement.findTargetByControllerID(REGISTER_TARGET));
|
||||
|
||||
assertThat(updatedTarget.getSecurityToken()).isEqualTo(downloadAndUpdateRequest.getTargetSecurityToken());
|
||||
}
|
||||
|
||||
protected void createAndSendTarget(final String target, final String tenant) {
|
||||
final Message message = createTargetMessage(target, tenant);
|
||||
getDmfClient().send(message);
|
||||
}
|
||||
|
||||
protected void verifyReplyToListener() {
|
||||
createConditionFactory().until(() -> {
|
||||
Mockito.verify(replyToListener, Mockito.atLeast(1)).handleMessage(Mockito.any());
|
||||
});
|
||||
}
|
||||
|
||||
protected Long cancelAction(final Long actionId) {
|
||||
deploymentManagement.cancelAction(actionId);
|
||||
assertCancelActionMessage(actionId);
|
||||
return actionId;
|
||||
}
|
||||
|
||||
protected Long registerTargetAndCancelActionId() {
|
||||
final DistributionSetAssignmentResult assignmentResult = registerTargetAndAssignDistributionSet();
|
||||
return cancelAction(assignmentResult.getActions().get(0));
|
||||
}
|
||||
|
||||
protected void assertAllTargetsCount(final long expectedTargetsCount) {
|
||||
assertThat(targetManagement.countTargetsAll()).isEqualTo(expectedTargetsCount);
|
||||
}
|
||||
|
||||
private Message assertReplyMessageHeader(final EventTopic eventTopic) {
|
||||
verifyReplyToListener();
|
||||
final Message replyMessage = replyToListener.getEventTopicMessages().get(eventTopic);
|
||||
assertAllTargetsCount(1);
|
||||
final Map<String, Object> headers = replyMessage.getMessageProperties().getHeaders();
|
||||
assertThat(headers.get(MessageHeaderKey.TOPIC)).isEqualTo(eventTopic.toString());
|
||||
assertThat(headers.get(MessageHeaderKey.THING_ID)).isEqualTo(REGISTER_TARGET);
|
||||
assertThat(headers.get(MessageHeaderKey.TENANT)).isEqualTo(TENANT_EXIST);
|
||||
assertThat(headers.get(MessageHeaderKey.TYPE)).isEqualTo(MessageType.EVENT.toString());
|
||||
return replyMessage;
|
||||
}
|
||||
|
||||
protected void registerAndAssertTargetWithExistingTenant(final String target,
|
||||
final int existingTargetsAfterCreation) {
|
||||
|
||||
registerAndAssertTargetWithExistingTenant(target, existingTargetsAfterCreation, TargetUpdateStatus.REGISTERED,
|
||||
CREATED_BY);
|
||||
|
||||
}
|
||||
|
||||
protected void registerAndAssertTargetWithExistingTenant(final String target,
|
||||
final int existingTargetsAfterCreation, final TargetUpdateStatus expectedTargetStatus,
|
||||
final String createdBy) {
|
||||
createAndSendTarget(target, TENANT_EXIST);
|
||||
final Target registerdTarget = waitUntilIsPresent(() -> targetManagement.findTargetByControllerID(target));
|
||||
assertAllTargetsCount(existingTargetsAfterCreation);
|
||||
assertTarget(registerdTarget, expectedTargetStatus, createdBy);
|
||||
}
|
||||
|
||||
protected void registerSameTargetAndAssertBasedOnLastPolling(final String target,
|
||||
final int existingTargetsAfterCreation, final TargetUpdateStatus expectedTargetStatus,
|
||||
final Long pollingTimeTargetOld) {
|
||||
createAndSendTarget(target, TENANT_EXIST);
|
||||
final Target registerdTarget = waitUntilIsPresent(
|
||||
() -> findTargetBasedOnNewPolltime(target, pollingTimeTargetOld));
|
||||
assertAllTargetsCount(existingTargetsAfterCreation);
|
||||
assertThat(registerdTarget.getUpdateStatus()).isEqualTo(expectedTargetStatus);
|
||||
}
|
||||
|
||||
private Optional<Target> findTargetBasedOnNewPolltime(final String controllerId, final Long pollingTimeTargetOld) {
|
||||
final Optional<Target> target2 = controllerManagement.findByControllerId(controllerId);
|
||||
final Long pollingTimeTargetNew = target2.get().getLastTargetQuery();
|
||||
if (pollingTimeTargetOld < pollingTimeTargetNew) {
|
||||
return target2;
|
||||
}
|
||||
return Optional.empty();
|
||||
}
|
||||
|
||||
private void assertTarget(final Target target, final TargetUpdateStatus updateStatus, final String createdBy) {
|
||||
assertThat(target.getTenant()).isEqualTo(TENANT_EXIST);
|
||||
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(connectionFactory.getVirtualHost(), DmfTestConfiguration.REPLY_TO_EXCHANGE));
|
||||
}
|
||||
|
||||
protected Message createTargetMessage(final String target, final String tenant) {
|
||||
final MessageProperties messageProperties = createMessagePropertiesWithTenant(tenant);
|
||||
messageProperties.getHeaders().put(MessageHeaderKey.THING_ID, target);
|
||||
messageProperties.getHeaders().put(MessageHeaderKey.TYPE, MessageType.THING_CREATED.toString());
|
||||
messageProperties.setReplyTo(DmfTestConfiguration.REPLY_TO_EXCHANGE);
|
||||
|
||||
return createMessage("", messageProperties);
|
||||
}
|
||||
|
||||
protected MessageProperties createMessagePropertiesWithTenant(final String tenant) {
|
||||
final MessageProperties messageProperties = new MessageProperties();
|
||||
messageProperties.getHeaders().put(MessageHeaderKey.TENANT, tenant);
|
||||
return messageProperties;
|
||||
}
|
||||
|
||||
protected Message createUpdateAttributesMessage(final String target, final String tenant,
|
||||
final AttributeUpdate attributeUpdate) {
|
||||
final MessageProperties messageProperties = createMessagePropertiesWithTenant(tenant);
|
||||
messageProperties.getHeaders().put(MessageHeaderKey.THING_ID, target);
|
||||
messageProperties.getHeaders().put(MessageHeaderKey.TYPE, MessageType.EVENT.toString());
|
||||
messageProperties.getHeaders().put(MessageHeaderKey.TOPIC, EventTopic.UPDATE_ATTRIBUTES.toString());
|
||||
|
||||
return createMessage(attributeUpdate, messageProperties);
|
||||
|
||||
}
|
||||
|
||||
protected Message createUpdateAttributesMessageWrongBody(final String target, final String tenant) {
|
||||
final MessageProperties messageProperties = createMessagePropertiesWithTenant(tenant);
|
||||
messageProperties.getHeaders().put(MessageHeaderKey.THING_ID, target);
|
||||
messageProperties.getHeaders().put(MessageHeaderKey.TYPE, MessageType.EVENT.toString());
|
||||
messageProperties.getHeaders().put(MessageHeaderKey.TOPIC, EventTopic.UPDATE_ATTRIBUTES.toString());
|
||||
|
||||
return createMessage("wrongbody", messageProperties);
|
||||
|
||||
}
|
||||
|
||||
protected void assertUpdateAttributes(final String controllerId, final Map<String, String> attributes) {
|
||||
final Target findByControllerId = waitUntilIsPresent(
|
||||
() -> controllerManagement.findByControllerId(controllerId));
|
||||
final Map<String, String> controllerAttributes = targetManagement
|
||||
.getControllerAttributes(findByControllerId.getControllerId());
|
||||
assertThat(controllerAttributes.size()).isEqualTo(attributes.size());
|
||||
attributes.forEach((k, v) -> assertKeyValueInMap(k, v, controllerAttributes));
|
||||
}
|
||||
|
||||
private void assertKeyValueInMap(final String key, final String value,
|
||||
final Map<String, String> controllerAttributes) {
|
||||
assertThat(controllerAttributes.containsKey(key)).isTrue();
|
||||
assertThat(controllerAttributes.get(key)).isEqualTo(value);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected String getExchange() {
|
||||
return AmqpSettings.DMF_EXCHANGE;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,55 @@
|
||||
/**
|
||||
* Copyright (c) 2015 Bosch Software Innovations GmbH and others.
|
||||
*
|
||||
* All rights reserved. This program and the accompanying materials
|
||||
* are made available under the terms of the Eclipse Public License v1.0
|
||||
* which accompanies this distribution, and is available at
|
||||
* http://www.eclipse.org/legal/epl-v10.html
|
||||
*/
|
||||
package org.eclipse.hawkbit.integration;
|
||||
|
||||
import org.eclipse.hawkbit.integration.listener.DeadletterListener;
|
||||
import org.eclipse.hawkbit.integration.listener.ReplyToListener;
|
||||
import org.springframework.amqp.core.Binding;
|
||||
import org.springframework.amqp.core.BindingBuilder;
|
||||
import org.springframework.amqp.core.FanoutExchange;
|
||||
import org.springframework.amqp.core.Queue;
|
||||
import org.springframework.amqp.rabbit.test.RabbitListenerTest;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
|
||||
/**
|
||||
* DMF Test configuration.
|
||||
*/
|
||||
@Configuration
|
||||
@RabbitListenerTest
|
||||
public class DmfTestConfiguration {
|
||||
|
||||
public static final String REPLY_TO_EXCHANGE = "reply.queue";
|
||||
|
||||
@Bean
|
||||
DeadletterListener deadletterListener() {
|
||||
return new DeadletterListener();
|
||||
}
|
||||
|
||||
@Bean
|
||||
ReplyToListener replyToListener() {
|
||||
return new ReplyToListener();
|
||||
}
|
||||
|
||||
@Bean
|
||||
Queue replyToQueue() {
|
||||
return new Queue(ReplyToListener.REPLY_TO_QUEUE, false, false, true);
|
||||
}
|
||||
|
||||
@Bean
|
||||
FanoutExchange replyToExchange() {
|
||||
return new FanoutExchange(REPLY_TO_EXCHANGE, false, true);
|
||||
}
|
||||
|
||||
@Bean
|
||||
Binding bindQueueToReplyToExchange() {
|
||||
return BindingBuilder.bind(replyToQueue()).to(replyToExchange());
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,25 @@
|
||||
/**
|
||||
* Copyright (c) 2015 Bosch Software Innovations GmbH and others.
|
||||
*
|
||||
* All rights reserved. This program and the accompanying materials
|
||||
* are made available under the terms of the Eclipse Public License v1.0
|
||||
* which accompanies this distribution, and is available at
|
||||
* http://www.eclipse.org/legal/epl-v10.html
|
||||
*/
|
||||
package org.eclipse.hawkbit.integration.listener;
|
||||
|
||||
import org.eclipse.hawkbit.rabbitmq.test.listener.TestRabbitListener;
|
||||
import org.springframework.amqp.core.Message;
|
||||
import org.springframework.amqp.rabbit.annotation.RabbitListener;
|
||||
|
||||
public class DeadletterListener implements TestRabbitListener {
|
||||
|
||||
public static final String LISTENER_ID = "deadletter";
|
||||
|
||||
@Override
|
||||
@RabbitListener(id = "deadletter", queues = "dmf_connector_deadletter_ttl")
|
||||
public void handleMessage(Message message) {
|
||||
// currently the message is not needed
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,66 @@
|
||||
/**
|
||||
* Copyright (c) 2015 Bosch Software Innovations GmbH and others.
|
||||
*
|
||||
* All rights reserved. This program and the accompanying materials
|
||||
* are made available under the terms of the Eclipse Public License v1.0
|
||||
* which accompanies this distribution, and is available at
|
||||
* http://www.eclipse.org/legal/epl-v10.html
|
||||
*/
|
||||
package org.eclipse.hawkbit.integration.listener;
|
||||
|
||||
import static org.junit.Assert.fail;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
|
||||
import org.eclipse.hawkbit.dmf.amqp.api.EventTopic;
|
||||
import org.eclipse.hawkbit.dmf.amqp.api.MessageHeaderKey;
|
||||
import org.eclipse.hawkbit.dmf.amqp.api.MessageType;
|
||||
import org.eclipse.hawkbit.rabbitmq.test.listener.TestRabbitListener;
|
||||
import org.springframework.amqp.core.Message;
|
||||
import org.springframework.amqp.rabbit.annotation.RabbitListener;
|
||||
|
||||
public class ReplyToListener implements TestRabbitListener {
|
||||
|
||||
public static final String LISTENER_ID = "replyto";
|
||||
public static final String REPLY_TO_QUEUE = "reply_queue";
|
||||
|
||||
private final Map<EventTopic, Message> eventTopicMessages = new HashMap<>();
|
||||
private final Map<String, Message> deleteMessages = new HashMap<>();
|
||||
|
||||
@Override
|
||||
@RabbitListener(id = LISTENER_ID, queues = REPLY_TO_QUEUE)
|
||||
public void handleMessage(final Message message) {
|
||||
|
||||
final MessageType messageType = MessageType
|
||||
.valueOf(message.getMessageProperties().getHeaders().get(MessageHeaderKey.TYPE).toString());
|
||||
|
||||
if (messageType == MessageType.EVENT) {
|
||||
final EventTopic eventTopic = EventTopic
|
||||
.valueOf(message.getMessageProperties().getHeaders().get(MessageHeaderKey.TOPIC).toString());
|
||||
eventTopicMessages.put(eventTopic, message);
|
||||
return;
|
||||
}
|
||||
|
||||
if (messageType == MessageType.THING_DELETED) {
|
||||
final String targetName = message.getMessageProperties().getHeaders().get(MessageHeaderKey.THING_ID)
|
||||
.toString();
|
||||
deleteMessages.put(targetName, message);
|
||||
return;
|
||||
}
|
||||
|
||||
// if message type is not EVENT or THING_DELETED something unexpected
|
||||
// happened
|
||||
fail("Unexpected message type");
|
||||
|
||||
}
|
||||
|
||||
public Map<EventTopic, Message> getEventTopicMessages() {
|
||||
return eventTopicMessages;
|
||||
}
|
||||
|
||||
public Map<String, Message> getDeleteMessages() {
|
||||
return deleteMessages;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,113 @@
|
||||
/**
|
||||
* Copyright (c) 2015 Bosch Software Innovations GmbH and others.
|
||||
*
|
||||
* All rights reserved. This program and the accompanying materials
|
||||
* are made available under the terms of the Eclipse Public License v1.0
|
||||
* which accompanies this distribution, and is available at
|
||||
* http://www.eclipse.org/legal/epl-v10.html
|
||||
*/
|
||||
package org.eclipse.hawkbit.matcher;
|
||||
|
||||
import java.util.Collection;
|
||||
import java.util.List;
|
||||
import java.util.Set;
|
||||
|
||||
import org.eclipse.hawkbit.repository.model.SoftwareModule;
|
||||
import org.hamcrest.BaseMatcher;
|
||||
import org.hamcrest.Description;
|
||||
import org.hamcrest.Factory;
|
||||
|
||||
/**
|
||||
* Set matcher for {@link SoftwareModule} and a list of
|
||||
* {@link org.eclipse.hawkbit.dmf.json.model.SoftwareModule}.
|
||||
*/
|
||||
public final class SoftwareModuleJsonMatcher {
|
||||
|
||||
/**
|
||||
* Creates a matcher that matches when the list of repository software
|
||||
* modules arelogically equal to the specified JSON software modules.
|
||||
* <p>
|
||||
* If the specified repository software modules are <code>null</code> then
|
||||
* the created matcher will only match if the JSON software modules are
|
||||
* <code>null</code>
|
||||
* <p>
|
||||
* For example:
|
||||
*
|
||||
* <pre>
|
||||
* List<SoftwareModule> modules;
|
||||
* List<org.eclipse.hawkbit.dmf.json.model.SoftwareModule> expectedModules;
|
||||
*
|
||||
* assertThat(modules, containsExactly(expectedModules));
|
||||
* </pre>
|
||||
*
|
||||
* @param expectedModules
|
||||
* the json sofware modules.
|
||||
*/
|
||||
@Factory
|
||||
public static SoftwareModulesMatcher containsExactly(
|
||||
final List<org.eclipse.hawkbit.dmf.json.model.SoftwareModule> expectedModules) {
|
||||
return new SoftwareModulesMatcher(expectedModules);
|
||||
}
|
||||
|
||||
private static class SoftwareModulesMatcher extends BaseMatcher<Set<SoftwareModule>> {
|
||||
|
||||
private final List<org.eclipse.hawkbit.dmf.json.model.SoftwareModule> expectedModules;
|
||||
|
||||
public SoftwareModulesMatcher(List<org.eclipse.hawkbit.dmf.json.model.SoftwareModule> expectedModules) {
|
||||
this.expectedModules = expectedModules;
|
||||
}
|
||||
|
||||
static boolean containsExactly(Object actual,
|
||||
List<org.eclipse.hawkbit.dmf.json.model.SoftwareModule> expected) {
|
||||
if (actual == null) {
|
||||
return expected == null;
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
final Collection<SoftwareModule> modules = (Collection<SoftwareModule>) actual;
|
||||
|
||||
if (modules.size() != expected.size()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
for (final SoftwareModule repoSoftwareModule : modules) {
|
||||
boolean containsElement = false;
|
||||
|
||||
for (final org.eclipse.hawkbit.dmf.json.model.SoftwareModule jsonSoftwareModule : expected) {
|
||||
if (!jsonSoftwareModule.getModuleId().equals(repoSoftwareModule.getId())) {
|
||||
continue;
|
||||
}
|
||||
containsElement = true;
|
||||
|
||||
if (!jsonSoftwareModule.getModuleType().equals(repoSoftwareModule.getType().getKey())) {
|
||||
return false;
|
||||
}
|
||||
if (!jsonSoftwareModule.getModuleVersion().equals(repoSoftwareModule.getVersion())) {
|
||||
return false;
|
||||
}
|
||||
if (jsonSoftwareModule.getArtifacts().size() != repoSoftwareModule.getArtifacts().size()) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
if (!containsElement) {
|
||||
return false;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean matches(Object actualValue) {
|
||||
return containsExactly(actualValue, expectedModules);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void describeTo(Description description) {
|
||||
description.appendValue(expectedModules);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,42 @@
|
||||
#
|
||||
# Copyright (c) 2015 Bosch Software Innovations GmbH and others.
|
||||
#
|
||||
# All rights reserved. This program and the accompanying materials
|
||||
# are made available under the terms of the Eclipse Public License v1.0
|
||||
# which accompanies this distribution, and is available at
|
||||
# http://www.eclipse.org/legal/epl-v10.html
|
||||
#
|
||||
|
||||
# RabbitMQ
|
||||
spring.rabbitmq.host=localhost
|
||||
|
||||
spring.jpa.database=H2
|
||||
flyway.sqlMigrationSuffix=${spring.jpa.database}.sql
|
||||
|
||||
# Default tenant configuration properties
|
||||
hawkbit.server.tenant.configuration.authentication-header-enabled.keyName=authentication.header.enabled
|
||||
hawkbit.server.tenant.configuration.authentication-header-enabled.defaultValue=false
|
||||
hawkbit.server.tenant.configuration.authentication-header-enabled.dataType=java.lang.Boolean
|
||||
hawkbit.server.tenant.configuration.authentication-header-enabled.validator=org.eclipse.hawkbit.tenancy.configuration.validator.TenantConfigurationBooleanValidator
|
||||
|
||||
hawkbit.server.tenant.configuration.authentication-header-authority.keyName=authentication.header.authority
|
||||
hawkbit.server.tenant.configuration.authentication-header-authority.defaultValue=false
|
||||
|
||||
hawkbit.server.tenant.configuration.authentication-targettoken-enabled.keyName=authentication.targettoken.enabled
|
||||
hawkbit.server.tenant.configuration.authentication-targettoken-enabled.defaultValue=false
|
||||
hawkbit.server.tenant.configuration.authentication-targettoken-enabled.dataType=java.lang.Boolean
|
||||
hawkbit.server.tenant.configuration.authentication-targettoken-enabled.validator=org.eclipse.hawkbit.tenancy.configuration.validator.TenantConfigurationBooleanValidator
|
||||
|
||||
hawkbit.server.tenant.configuration.authentication-gatewaytoken-enabled.keyName=authentication.gatewaytoken.enabled
|
||||
hawkbit.server.tenant.configuration.authentication-gatewaytoken-enabled.defaultValue=false
|
||||
hawkbit.server.tenant.configuration.authentication-gatewaytoken-enabled.dataType=java.lang.Boolean
|
||||
hawkbit.server.tenant.configuration.authentication-gatewaytoken-enabled.validator=org.eclipse.hawkbit.tenancy.configuration.validator.TenantConfigurationBooleanValidator
|
||||
|
||||
hawkbit.server.tenant.configuration.authentication-gatewaytoken-key.keyName=authentication.gatewaytoken.key
|
||||
hawkbit.server.tenant.configuration.authentication-gatewaytoken-key.defaultValue=false
|
||||
|
||||
hawkbit.server.tenant.configuration.anonymous-download-enabled.keyName=anonymous.download.enabled
|
||||
hawkbit.server.tenant.configuration.anonymous-download-enabled.defaultValue=false
|
||||
hawkbit.server.tenant.configuration.anonymous-download-enabled.dataType=java.lang.Boolean
|
||||
hawkbit.server.tenant.configuration.anonymous-download-enabled.validator=org.eclipse.hawkbit.tenancy.configuration.validator.TenantConfigurationBooleanValidator
|
||||
|
||||
@@ -0,0 +1,35 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<!--
|
||||
|
||||
Copyright (c) 2015 Bosch Software Innovations GmbH and others.
|
||||
|
||||
All rights reserved. This program and the accompanying materials
|
||||
are made available under the terms of the Eclipse Public License v1.0
|
||||
which accompanies this distribution, and is available at
|
||||
http://www.eclipse.org/legal/epl-v10.html
|
||||
|
||||
-->
|
||||
<configuration>
|
||||
<include resource="org/springframework/boot/logging/logback/base.xml" />
|
||||
|
||||
<logger name="org.eclipse.hawkbit.eventbus.DeadEventListener" level="WARN" />
|
||||
<Logger name="org.springframework.boot.actuate.audit.listener.AuditListener" level="WARN" />
|
||||
|
||||
<Logger name="org.hibernate.validator.internal.util.Version" level="WARN" />
|
||||
|
||||
<Logger name="org.apache.coyote.http11.Http11NioProtocol" level="WARN" />
|
||||
<Logger name="org.apache.tomcat.util.net.NioSelectorPool" level="WARN" />
|
||||
<Logger name="org.apache.tomcat.jdbc.pool.ConnectionPool" level="DEBUG" />
|
||||
<Logger name="org.apache.catalina.startup.DigesterFactory" level="ERROR" />
|
||||
|
||||
|
||||
<!-- <Logger name="org.eclipse.hawkbit.rest.util.MockMvcResultPrinter" level="DEBUG" /> -->
|
||||
|
||||
<!-- Security Log with hints on potential attacks -->
|
||||
<logger name="server-security" level="INFO" />
|
||||
|
||||
<Root level="INFO">
|
||||
<appender-ref ref="CONSOLE" />
|
||||
</Root>
|
||||
|
||||
</configuration>
|
||||
Reference in New Issue
Block a user