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:
Kai Zimmermann
2017-04-27 17:10:59 +02:00
committed by GitHub
parent 12ec039199
commit 525669724f
52 changed files with 6 additions and 6 deletions

View File

@@ -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());
}
}

View File

@@ -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;
}
}

View File

@@ -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);
}
}

View File

@@ -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;
}
}

View File

@@ -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;
}
}

View File

@@ -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();
}
}

View File

@@ -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;
}
}

View File

@@ -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);
}
}

View File

@@ -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);
}
}

View File

@@ -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());
}
}

View File

@@ -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));
}
}

View File

@@ -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;
}
}

View File

@@ -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 {
}

View File

@@ -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;
}
}

View File

@@ -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);
}
}

View File

@@ -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());
}
}
}
}

View File

@@ -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;
}
}

View File

@@ -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);
}
}

View File

@@ -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);
}
}

View File

@@ -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());
});
}
}

View File

@@ -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;
}
}

View File

@@ -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());
}
}

View File

@@ -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
}
}

View File

@@ -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;
}
}

View File

@@ -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);
}
}
}

View File

@@ -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

View File

@@ -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>