Initial check in accordance with Parallel IP

This commit is contained in:
Kai Zimmermann
2016-01-21 13:18:55 +01:00
parent c5e4f1139f
commit 7497ab61ed
763 changed files with 114508 additions and 0 deletions

139
hawkbit-dmf-amqp/pom.xml Executable file
View File

@@ -0,0 +1,139 @@
<!--
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
-->
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.eclipse.hawkbit</groupId>
<artifactId>hawkbit-parent</artifactId>
<version>0.1.0-SNAPSHOT</version>
</parent>
<artifactId>hawkbit-dmf-amqp</artifactId>
<name>hawkBit :: DMF Amqp</name>
<dependencies>
<dependency>
<groupId>org.eclipse.hawkbit</groupId>
<artifactId>hawkbit-repository</artifactId>
<version>${project.version}</version>
</dependency>
<dependency>
<groupId>org.eclipse.hawkbit</groupId>
<artifactId>hawkbit-security-integration</artifactId>
<version>${project.version}</version>
</dependency>
<dependency>
<groupId>org.eclipse.hawkbit</groupId>
<artifactId>hawkbit-dmf-api</artifactId>
<version>${project.version}</version>
</dependency>
<dependency>
<groupId>org.springframework.amqp</groupId>
<artifactId>spring-rabbit</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.security</groupId>
<artifactId>spring-security-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot</artifactId>
</dependency>
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-annotations</artifactId>
</dependency>
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-databind</artifactId>
</dependency>
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-api</artifactId>
</dependency>
<!-- Test -->
<dependency>
<groupId>com.h2database</groupId>
<artifactId>h2</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.mariadb.jdbc</groupId>
<artifactId>mariadb-java-client</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>javax.el</groupId>
<artifactId>javax.el-api</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.eclipse.hawkbit</groupId>
<artifactId>hawkbit-repository</artifactId>
<version>${project.version}</version>
<classifier>tests</classifier>
</dependency>
<dependency>
<groupId>ru.yandex.qatools.allure</groupId>
<artifactId>allure-junit-adaptor</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.springframework.data</groupId>
<artifactId>spring-data-rest-webmvc</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.springframework.security</groupId>
<artifactId>spring-security-aspects</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-test</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.easytesting</groupId>
<artifactId>fest-assert-core</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.easytesting</groupId>
<artifactId>fest-assert</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>de.flapdoodle.embed</groupId>
<artifactId>de.flapdoodle.embed.mongo</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.springframework.security</groupId>
<artifactId>spring-security-config</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context-support</artifactId>
<scope>test</scope>
</dependency>
</dependencies>
</project>

View File

@@ -0,0 +1,151 @@
/**
* 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.HashMap;
import java.util.Map;
import org.eclipse.hawkbit.dmf.amqp.api.AmqpSettings;
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.config.SimpleRabbitListenerContainerFactory;
import org.springframework.amqp.rabbit.connection.ConnectionFactory;
import org.springframework.amqp.rabbit.core.RabbitTemplate;
import org.springframework.amqp.rabbit.listener.SimpleMessageListenerContainer;
import org.springframework.amqp.support.converter.Jackson2JsonMessageConverter;
import org.springframework.amqp.support.converter.MessageConverter;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.context.annotation.Bean;
/**
* The spring AMQP configuration which is enabled by using the profile
* {@code amqp} to use a AMQP for communication with SP enabled devices.
*
*
*
*/
@EnableConfigurationProperties(AmqpProperties.class)
public class AmqpConfiguration {
@Autowired
protected AmqpProperties amqpProperties;
@Autowired
private ConnectionFactory connectionFactory;
@Autowired
private RabbitTemplate rabbitTemplate;
/**
* Method to set the Jackson2JsonMessageConverter.
*
* @return the Jackson2JsonMessageConverter
*/
@Bean
public MessageConverter jsonMessageConverter() {
final Jackson2JsonMessageConverter jackson2JsonMessageConverter = new Jackson2JsonMessageConverter();
rabbitTemplate.setMessageConverter(jackson2JsonMessageConverter);
return jackson2JsonMessageConverter;
}
/**
* Create the sp receiver queue.
*
* @return the receiver queue
*/
@Bean
public Queue receiverQueue() {
return new Queue(amqpProperties.getReceiverQueue(), true, false, false, getDeadLetterExchangeArgs());
}
/**
* Create the dead letter fanout exchange.
*
* @return the fanout exchange
*/
@Bean
public FanoutExchange senderExchange() {
return new FanoutExchange(AmqpSettings.DMF_EXCHANGE);
}
/**
* Create dead letter queue.
*
* @return the queue
*/
@Bean
public Queue deadLetterQueue() {
return new Queue(amqpProperties.getDeadLetterQueue());
}
/**
* Create the dead letter fanout exchange.
*
* @return the fanout exchange
*/
@Bean
public FanoutExchange exchangeDeadLetter() {
return new FanoutExchange(amqpProperties.getDeadLetterExchange());
}
/**
* Create the Binding deadLetterQueue to exchangeDeadLetter.
*
* @return the binding
*/
@Bean
public Binding bindDeadLetterQueueToLwm2mExchange() {
return BindingBuilder.bind(deadLetterQueue()).to(exchangeDeadLetter());
}
/**
* Create the Binding {@link AmqpConfiguration#receiverQueueFromSp()} to
* {@link AmqpConfiguration#senderConnectorToSpExchange()}.
*
* @return the binding and create the queue and exchange
*/
@Bean
public Binding bindSenderExchangeToSpQueue() {
return BindingBuilder.bind(receiverQueue()).to(senderExchange());
}
/**
* Create amqp handler service bean.
*
* @return
*/
@Bean
public AmqpMessageHandlerService amqpMessageHandlerService() {
return new AmqpMessageHandlerService();
}
/**
* Returns the Listener factory.
*
* @return the {@link SimpleMessageListenerContainer} that gets used receive
* AMQP messages
*/
@Bean(name = { "listenerContainerFactory" })
public SimpleRabbitListenerContainerFactory listenerContainerFactory() {
final SimpleRabbitListenerContainerFactory containerFactory = new SimpleRabbitListenerContainerFactory();
containerFactory.setDefaultRequeueRejected(false);
containerFactory.setConnectionFactory(connectionFactory);
return containerFactory;
}
private Map<String, Object> getDeadLetterExchangeArgs() {
final Map<String, Object> args = new HashMap<String, Object>();
args.put("x-dead-letter-exchange", amqpProperties.getDeadLetterExchange());
return args;
}
}

View File

@@ -0,0 +1,152 @@
/**
* 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.ArrayList;
import java.util.List;
import javax.annotation.PostConstruct;
import org.eclipse.hawkbit.dmf.json.model.TenantSecruityToken;
import org.eclipse.hawkbit.im.authentication.TenantAwareAuthenticationDetails;
import org.eclipse.hawkbit.repository.ControllerManagement;
import org.eclipse.hawkbit.repository.SystemManagement;
import org.eclipse.hawkbit.security.CoapAnonymousPreAuthenticatedFilter;
import org.eclipse.hawkbit.security.ControllerPreAuthenticateSecurityTokenFilter;
import org.eclipse.hawkbit.security.ControllerPreAuthenticatedGatewaySecurityTokenFilter;
import org.eclipse.hawkbit.security.ControllerPreAuthenticatedSecurityHeaderFilter;
import org.eclipse.hawkbit.security.PreAuthTokenSourceTrustAuthenticationProvider;
import org.eclipse.hawkbit.security.PreAuthenficationFilter;
import org.eclipse.hawkbit.security.SecurityProperties;
import org.eclipse.hawkbit.tenancy.TenantAware;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.core.Authentication;
import org.springframework.security.web.authentication.preauth.PreAuthenticatedAuthenticationToken;
import org.springframework.stereotype.Component;
/**
*
*
*/
@Component
public class AmqpControllerAuthentfication {
private static final Logger LOGGER = LoggerFactory.getLogger(AmqpControllerAuthentfication.class);
private final PreAuthTokenSourceTrustAuthenticationProvider preAuthenticatedAuthenticationProvider;
private final List<PreAuthenficationFilter> filterChain = new ArrayList<>();
@Autowired
private ControllerManagement controllerManagement;
@Autowired
private SystemManagement systemManagement;
@Autowired
private TenantAware tenantAware;
@Autowired
private SecurityProperties secruityProperties;
/**
* Constructor.
*/
public AmqpControllerAuthentfication() {
preAuthenticatedAuthenticationProvider = new PreAuthTokenSourceTrustAuthenticationProvider();
}
/**
* Called by spring when bean instantiated and autowired.
*/
@PostConstruct
public void postConstruct() {
addFilter();
}
private void addFilter() {
final ControllerPreAuthenticatedGatewaySecurityTokenFilter gatewaySecurityTokenFilter = new ControllerPreAuthenticatedGatewaySecurityTokenFilter(
systemManagement, tenantAware);
filterChain.add(gatewaySecurityTokenFilter);
final ControllerPreAuthenticatedSecurityHeaderFilter securityHeaderFilter = new ControllerPreAuthenticatedSecurityHeaderFilter(
secruityProperties.getRpCnHeader(), secruityProperties.getRpSslIssuerHashHeader(), systemManagement,
tenantAware);
filterChain.add(securityHeaderFilter);
final ControllerPreAuthenticateSecurityTokenFilter securityTokenFilter = new ControllerPreAuthenticateSecurityTokenFilter(
systemManagement, controllerManagement, tenantAware);
filterChain.add(securityTokenFilter);
filterChain.add(new CoapAnonymousPreAuthenticatedFilter());
}
/**
* Performs authentication with the secruity token.
*
* @param secruityToken
* the authentication request object
* @return the authentfication object
*/
public Authentication doAuthenticate(final TenantSecruityToken secruityToken) {
PreAuthenticatedAuthenticationToken authentication = new PreAuthenticatedAuthenticationToken(null, null);
for (final PreAuthenficationFilter filter : filterChain) {
final PreAuthenticatedAuthenticationToken authenticationRest = createAuthentication(filter, secruityToken);
if (authenticationRest != null) {
authentication = authenticationRest;
authentication.setDetails(new TenantAwareAuthenticationDetails(secruityToken.getTenant(), true));
break;
}
}
return preAuthenticatedAuthenticationProvider.authenticate(authentication);
}
private PreAuthenticatedAuthenticationToken createAuthentication(final PreAuthenficationFilter filter,
final TenantSecruityToken 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);
final PreAuthenticatedAuthenticationToken authRequest = new PreAuthenticatedAuthenticationToken(principal,
credentials);
return authRequest;
}
public void setControllerManagement(final ControllerManagement controllerManagement) {
this.controllerManagement = controllerManagement;
}
public void setSecruityProperties(final SecurityProperties secruityProperties) {
this.secruityProperties = secruityProperties;
}
public void setSystemManagement(final SystemManagement systemManagement) {
this.systemManagement = systemManagement;
}
public void setTenantAware(final TenantAware tenantAware) {
this.tenantAware = tenantAware;
}
}

View File

@@ -0,0 +1,182 @@
/**
* 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.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.eventbus.EventSubscriber;
import org.eclipse.hawkbit.eventbus.event.CancelTargetAssignmentEvent;
import org.eclipse.hawkbit.eventbus.event.TargetAssignDistributionSetEvent;
import org.eclipse.hawkbit.repository.model.LocalArtifact;
import org.eclipse.hawkbit.tenancy.TenantAware;
import org.eclipse.hawkbit.util.ArtifactUrlHandler;
import org.eclipse.hawkbit.util.IpUtil;
import org.springframework.amqp.core.Message;
import org.springframework.amqp.core.MessageProperties;
import org.springframework.amqp.rabbit.core.RabbitTemplate;
import org.springframework.beans.factory.annotation.Autowired;
import com.google.common.eventbus.Subscribe;
/**
* {@link AmqpMessageDispatcherService} handles all outgoing AMQP messages.
*
*
*
*/
@EventSubscriber
public class AmqpMessageDispatcherService {
@Autowired
private RabbitTemplate rabbitTemplate;
@Autowired
private TenantAware tenantAware;
@Autowired
private ArtifactUrlHandler artifactUrlHandler;
/**
* Method to send a message to a RabbitMQ Exchange after the Distribution
* set has been assign to a Target.
*
* @param targetAssignDistributionSetEvent
* the object to be send.
*/
@Subscribe
public void targetAssignDistributionSet(final TargetAssignDistributionSetEvent targetAssignDistributionSetEvent) {
final URI targetAdress = targetAssignDistributionSetEvent.getTargetAdress();
if (!IpUtil.isAmqpUri(targetAdress)) {
return;
}
final String controllerId = targetAssignDistributionSetEvent.getControllerId();
final Collection<org.eclipse.hawkbit.repository.model.SoftwareModule> modules = targetAssignDistributionSetEvent
.getSoftwareModules();
final DownloadAndUpdateRequest downloadAndUpdateRequest = new DownloadAndUpdateRequest();
downloadAndUpdateRequest.setActionId(targetAssignDistributionSetEvent.getActionId());
for (final org.eclipse.hawkbit.repository.model.SoftwareModule softwareModule : modules) {
final SoftwareModule amqpSoftwareModule = convertToAmqpSoftwareModule(controllerId, softwareModule);
downloadAndUpdateRequest.addSoftwareModule(amqpSoftwareModule);
}
final Message message = rabbitTemplate.getMessageConverter().toMessage(downloadAndUpdateRequest,
createConnectorMessageProperties(controllerId, EventTopic.DOWNLOAD_AND_INSTALL));
sendMessage(targetAdress.getHost(), message);
}
/**
* Method to send a message to a RabbitMQ Exchange after the assignment of
* the Distribution set to a Target has been canceled.
*
* @param cancelTargetAssignmentDistributionSetEvent
* the object to be send.
*/
@Subscribe
public void targetCancelAssignmentToDistributionSet(
final CancelTargetAssignmentEvent cancelTargetAssignmentDistributionSetEvent) {
final String controllerId = cancelTargetAssignmentDistributionSetEvent.getControllerId();
final Long actionId = cancelTargetAssignmentDistributionSetEvent.getActionId();
final Message message = rabbitTemplate.getMessageConverter().toMessage(actionId,
createConnectorMessageProperties(controllerId, EventTopic.CANCEL_DOWNLOAD));
sendMessage(cancelTargetAssignmentDistributionSetEvent.getTargetAdress().getHost(), message);
}
/**
* Send message to exchange.
*
* @param exchange
* the exchange
* @param message
* the message
*/
public void sendMessage(final String exchange, final Message message) {
rabbitTemplate.setExchange(exchange);
rabbitTemplate.send(message);
}
private MessageProperties createConnectorMessageProperties(final String controllerId, final EventTopic topic) {
final MessageProperties messageProperties = createMessageProperties();
messageProperties.setHeader(MessageHeaderKey.TOPIC, topic);
messageProperties.setHeader(MessageHeaderKey.THING_ID, controllerId);
messageProperties.setHeader(MessageHeaderKey.TENANT, tenantAware.getCurrentTenant());
messageProperties.setHeader(MessageHeaderKey.TYPE, MessageType.EVENT);
return messageProperties;
}
private MessageProperties createMessageProperties() {
final MessageProperties messageProperties = new MessageProperties();
messageProperties.setContentType(MessageProperties.CONTENT_TYPE_JSON);
messageProperties.setHeader(MessageHeaderKey.CONTENT_TYPE, MessageProperties.CONTENT_TYPE_JSON);
return messageProperties;
}
private SoftwareModule convertToAmqpSoftwareModule(final String targetId,
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(targetId, softwareModule.getLocalArtifacts());
amqpSoftwareModule.setArtifacts(artifacts);
return amqpSoftwareModule;
}
private List<Artifact> convertArtifacts(final String targetId, final List<LocalArtifact> localArtifacts) {
if (localArtifacts.isEmpty()) {
return Collections.emptyList();
}
final List<Artifact> convertedArtifacts = localArtifacts.stream()
.map(localArtifact -> convertArtifact(targetId, localArtifact)).collect(Collectors.toList());
return convertedArtifacts;
}
private Artifact convertArtifact(final String targetId, final LocalArtifact localArtifact) {
final Artifact artifact = new Artifact();
artifact.getUrls().put(Artifact.UrlProtocol.COAP,
artifactUrlHandler.getUrl(targetId, localArtifact, Artifact.UrlProtocol.COAP));
artifact.getUrls().put(Artifact.UrlProtocol.HTTP,
artifactUrlHandler.getUrl(targetId, localArtifact, Artifact.UrlProtocol.HTTP));
artifact.getUrls().put(Artifact.UrlProtocol.HTTPS,
artifactUrlHandler.getUrl(targetId, localArtifact, Artifact.UrlProtocol.HTTPS));
artifact.setFilename(localArtifact.getFilename());
artifact.setHashes(new ArtifactHash(localArtifact.getSha1Hash(), null));
artifact.setSize(localArtifact.getSize());
return artifact;
}
public void setTenantAware(final TenantAware tenantAware) {
this.tenantAware = tenantAware;
}
public void setRabbitTemplate(final RabbitTemplate rabbitTemplate) {
this.rabbitTemplate = rabbitTemplate;
}
public void setArtifactUrlHandler(final ArtifactUrlHandler artifactUrlHandler) {
this.artifactUrlHandler = artifactUrlHandler;
}
}

View File

@@ -0,0 +1,417 @@
/**
* 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.net.URISyntaxException;
import java.util.Collections;
import java.util.List;
import java.util.Map;
import java.util.UUID;
import org.apache.commons.lang3.StringUtils;
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.CacheConstants;
import org.eclipse.hawkbit.cache.DownloadArtifactCache;
import org.eclipse.hawkbit.cache.DownloadType;
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.Artifact;
import org.eclipse.hawkbit.dmf.json.model.ArtifactHash;
import org.eclipse.hawkbit.dmf.json.model.DownloadResponse;
import org.eclipse.hawkbit.dmf.json.model.TenantSecruityToken;
import org.eclipse.hawkbit.eventbus.event.TargetAssignDistributionSetEvent;
import org.eclipse.hawkbit.im.authentication.SpPermission.SpringEvalExpressions;
import org.eclipse.hawkbit.im.authentication.TenantAwareAuthenticationDetails;
import org.eclipse.hawkbit.repository.ArtifactManagement;
import org.eclipse.hawkbit.repository.ControllerManagement;
import org.eclipse.hawkbit.repository.exception.EntityNotFoundException;
import org.eclipse.hawkbit.repository.model.Action;
import org.eclipse.hawkbit.repository.model.Action.Status;
import org.eclipse.hawkbit.repository.model.ActionStatus;
import org.eclipse.hawkbit.repository.model.DistributionSet;
import org.eclipse.hawkbit.repository.model.LocalArtifact;
import org.eclipse.hawkbit.repository.model.SoftwareModule;
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.core.Message;
import org.springframework.amqp.core.MessageProperties;
import org.springframework.amqp.rabbit.annotation.RabbitListener;
import org.springframework.amqp.rabbit.core.RabbitTemplate;
import org.springframework.amqp.support.converter.AbstractJavaTypeMapper;
import org.springframework.amqp.support.converter.MessageConverter;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.cache.Cache;
import org.springframework.http.HttpStatus;
import org.springframework.messaging.handler.annotation.Header;
import org.springframework.security.authentication.AnonymousAuthenticationToken;
import org.springframework.security.authentication.AuthenticationServiceException;
import org.springframework.security.authentication.BadCredentialsException;
import org.springframework.security.authentication.CredentialsExpiredException;
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;
import org.springframework.web.util.UriComponentsBuilder;
import com.google.common.eventbus.EventBus;
/**
*
* {@link AmqpMessageHandlerService} handles all incoming AMQP messages.
*
*
*
*
*/
public class AmqpMessageHandlerService {
private static final Logger LOG = LoggerFactory.getLogger(AmqpMessageHandlerService.class);
@Autowired
private RabbitTemplate rabbitTemplate;
@Autowired
private ControllerManagement controllerManagement;
@Autowired
private AmqpControllerAuthentfication authenticationManager;
@Autowired
private ArtifactManagement artifactManagement;
@Autowired
private EventBus eventBus;
@Autowired
@Qualifier(CacheConstants.DOWNLOAD_ID_CACHE)
private Cache cache;
@Autowired
private HostnameResolver hostnameResolver;
/**
* /** Method to handle all incoming amqp messages.
*
* @param message
* incoming message
* @param type
* the message type
* @param contentType
* the contentType of the message
* @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}", containerFactory = "listenerContainerFactory")
public Message onMessage(final Message message, @Header(MessageHeaderKey.TYPE) final String type,
@Header(MessageHeaderKey.TENANT) final String tenant) {
checkContentTypeJson(message);
final SecurityContext oldContext = SecurityContextHolder.getContext();
try {
final MessageType messageType = MessageType.valueOf(type);
switch (messageType) {
case THING_CREATED:
setTenantSecurityContext(tenant);
registerTarget(message);
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;
case AUTHENTIFICATION:
return handleAuthentifiactionMessage(message);
default:
logAndThrowMessageError(message, "No handle method was found for the given message type.");
}
} finally {
SecurityContextHolder.setContext(oldContext);
}
return null;
}
private Message handleAuthentifiactionMessage(final Message message) {
final DownloadResponse authentificationResponse = new DownloadResponse();
final MessageProperties messageProperties = message.getMessageProperties();
final TenantSecruityToken secruityToken = convertMessage(message, TenantSecruityToken.class);
final String sha1 = secruityToken.getSha1();
try {
SecurityContextHolder.getContext().setAuthentication(authenticationManager.doAuthenticate(secruityToken));
final LocalArtifact localArtifact = artifactManagement
.findFirstLocalArtifactsBySHA1(secruityToken.getSha1());
if (localArtifact == null) {
throw new EntityNotFoundException();
}
// 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.
final Action action = controllerManagement.getActionForDownloadByTargetAndSoftwareModule(
secruityToken.getControllerId(), localArtifact.getSoftwareModule());
LOG.info("Found action for download authentication request action: {}, sha1: {}", action,
secruityToken.getSha1());
final Artifact artifact = convertDbArtifact(artifactManagement.loadLocalArtifactBinary(localArtifact));
if (artifact == null) {
throw new EntityNotFoundException();
}
authentificationResponse.setArtifact(artifact);
final String downloadId = UUID.randomUUID().toString();
final DownloadArtifactCache downloadCache = new DownloadArtifactCache(DownloadType.BY_SHA1, sha1);
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 with sha1 " + sha1 + "not found ";
LOG.warn(errorMessage, e);
authentificationResponse.setResponseCode(HttpStatus.NOT_FOUND.value());
authentificationResponse.setMessage(errorMessage);
}
return rabbitTemplate.getMessageConverter().toMessage(authentificationResponse, messageProperties);
}
private 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;
}
protected void logAndThrowMessageError(final Message message, final String error) {
LOG.error("Error \"{}\" reported by message {}", error, message.getMessageProperties().getMessageId());
throw new IllegalArgumentException(error);
}
private void setSecurityContext(final Authentication authentication) {
final SecurityContextImpl securityContextImpl = new SecurityContextImpl();
securityContextImpl.setAuthentication(authentication);
SecurityContextHolder.setContext(securityContextImpl);
}
private 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);
}
private 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 value.toString();
}
/**
* 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 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 Event.");
}
final URI amqpUri = IpUtil.createAmqpUri(replyTo);
final Target target = controllerManagement.findOrRegisterTargetIfItDoesNotexist(thingId, amqpUri);
LOG.debug("Target {} reported online state.", thingId);
lookIfUpdateAvailable(target);
}
private void lookIfUpdateAvailable(final Target target) {
final List<Action> actions = controllerManagement.findActionByTargetAndActive(target);
if (actions.isEmpty()) {
return;
}
// action are ordered by ASC
final Action action = actions.get(0);
final DistributionSet distributionSet = action.getDistributionSet();
final List<SoftwareModule> softwareModuleList = controllerManagement
.findSoftwareModulesByDistributionSet(distributionSet);
eventBus.post(new TargetAssignDistributionSetEvent(target.getControllerId(), action.getId(), softwareModuleList,
target.getTargetInfo().getAddress()));
}
/**
* 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);
return;
default:
logAndThrowMessageError(message, "Got event without appropriate topic.");
}
}
/**
* 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 Long actionId = actionUpdateStatus.getActionId();
LOG.debug("Target notifies intermediate about action {} with status {}.", actionId,
actionUpdateStatus.getActionStatus().name());
if (actionId == null) {
logAndThrowMessageError(message, "Invalid message no action id");
}
final Action action = controllerManagement.findActionWithDetails(actionId);
if (action == null) {
logAndThrowMessageError(message,
"Got intermediate notification about action " + actionId + " but action does not exist");
}
final ActionStatus actionStatus = new ActionStatus();
final List<String> messageText = actionUpdateStatus.getMessage();
final String messageString = String.join(", ", messageText);
actionStatus.addMessage(messageString);
actionStatus.setAction(action);
actionStatus.setOccurredAt(System.currentTimeMillis());
switch (actionUpdateStatus.getActionStatus()) {
case DOWNLOAD:
actionStatus.setStatus(Status.DOWNLOAD);
break;
case RETRIEVED:
actionStatus.setStatus(Status.RETRIEVED);
break;
case RUNNING:
actionStatus.setStatus(Status.RUNNING);
break;
case FINISHED:
actionStatus.setStatus(Status.FINISHED);
break;
case ERROR:
actionStatus.setStatus(Status.ERROR);
break;
case WARNING:
actionStatus.setStatus(Status.WARNING);
break;
default:
logAndThrowMessageError(message, "Status for action does not exisit.");
}
final Action savedAction = controllerManagement.addUpdateActionStatus(actionStatus, action);
if (Status.FINISHED == savedAction.getStatus()) {
lookIfUpdateAvailable(action.getTarget());
}
}
/**
* 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
*/
@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);
}
/**
* Is needed to verify if an incoming message has the content type json.
*
* @param message
* the to verify
* @param contentType
* the content type
* @return true if the content type has json, false it not.
*/
private void checkContentTypeJson(final Message message) {
final MessageProperties messageProperties = message.getMessageProperties();
if (messageProperties.getContentType() != null && messageProperties.getContentType().contains("json")) {
return;
}
throw new IllegalArgumentException("Content-Type is not JSON compatible");
}
void setControllerManagement(final ControllerManagement controllerManagement) {
this.controllerManagement = controllerManagement;
}
void setHostnameResolver(final HostnameResolver hostnameResolver) {
this.hostnameResolver = hostnameResolver;
}
void setRabbitTemplate(final RabbitTemplate rabbitTemplate) {
this.rabbitTemplate = rabbitTemplate;
}
MessageConverter getMessageConverter() {
return rabbitTemplate.getMessageConverter();
}
void setAuthenticationManager(final AmqpControllerAuthentfication authenticationManager) {
this.authenticationManager = authenticationManager;
}
void setArtifactManagement(final ArtifactManagement artifactManagement) {
this.artifactManagement = artifactManagement;
}
void setCache(final Cache cache) {
this.cache = cache;
}
void setEventBus(final EventBus eventBus) {
this.eventBus = eventBus;
}
}

View File

@@ -0,0 +1,73 @@
/**
* 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.boot.context.properties.ConfigurationProperties;
/**
* Bean which holds the necessary properties for configuring the AMQP
* connection.
*
*
*
*
*/
@ConfigurationProperties("hawkbit.dmf.rabbitmq")
public class AmqpProperties {
private String deadLetterQueue = "dmf_connector_deadletter";
private String deadLetterExchange = "dmf.connector.deadletter";
private String receiverQueue = "dmf_receiver";
/**
* Returns the dead letter exchange.
*
* @return dead letter exchange
*/
public String getDeadLetterExchange() {
return deadLetterExchange;
}
/**
* Sets the dead letter exchange.
*
* @param deadLetterExchange
* the deadLetterExchange to be set
*/
public void setDeadLetterExchange(final String deadLetterExchange) {
this.deadLetterExchange = deadLetterExchange;
}
/**
* Returns the dead letter queue.
*
* @return the dead letter queue
*/
public String getDeadLetterQueue() {
return deadLetterQueue;
}
/**
* Sets the dead letter queue.
*
* @param deadLetterQueue
* the deadLetterQueue ro be set
*/
public void setDeadLetterQueue(final String deadLetterQueue) {
this.deadLetterQueue = deadLetterQueue;
}
public String getReceiverQueue() {
return receiverQueue;
}
public void setReceiverQueue(final String receiverQueue) {
this.receiverQueue = receiverQueue;
}
}

View File

@@ -0,0 +1,29 @@
/**
* 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.annotation;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
import org.eclipse.hawkbit.amqp.AmqpConfiguration;
import org.springframework.context.annotation.Import;
/**
* Annotation to enable amqp.
*
*
*/
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Import(AmqpConfiguration.class)
public @interface EnableAmqp {
}

View File

@@ -0,0 +1,36 @@
/**
* 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.util;
import org.eclipse.hawkbit.dmf.json.model.Artifact;
import org.eclipse.hawkbit.repository.model.LocalArtifact;
/**
* Interface declaration of the {@link ArtifactUrlHandler} which generates the
* URLs to specific artifacts.
*
*
*
*/
public interface ArtifactUrlHandler {
/**
* Returns a generated URL for a given artifact for a specific protocol.
*
* @param controllerId
* the authentifacted controller id
* @param localArtifact
* the artifact to retrieve a URL to
* @param protocol
* the protocol the URL should be generated
* @return an URL for the given artifact in a given protocol
*/
String getUrl(String controllerId, LocalArtifact localArtifact, final Artifact.UrlProtocol protocol);
}

View File

@@ -0,0 +1,337 @@
/**
* 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.util;
import org.springframework.boot.context.properties.ConfigurationProperties;
/**
*
*
*/
@ConfigurationProperties("hawkbit.artifact.url")
public class ArtifactUrlHandlerProperties {
private static final String DEFAULT_IP_LOCALHOST = "127.0.0.1";
private static final String LOCALHOST = "localhost";
private final Http http = new Http();
private final Https https = new Https();
private final Coap coap = new Coap();
/**
* @return the http
*/
public Http getHttp() {
return http;
}
/**
* @return the https
*/
public Https getHttps() {
return https;
}
/**
* @return the coap
*/
public Coap getCoap() {
return coap;
}
/**
* @param protocol
* the protocol schema to retrieve the properties.
* @return the properties to a protocol or {@code null} if protocol does not
* have properties or protocol not supported
*/
public ProtocolProperties getProperties(final String protocol) {
switch (protocol) {
case "http":
return getHttp();
case "https":
return getHttps();
case "coap":
return getCoap();
default:
return null;
}
}
/**
* Interface for declaring common properties through all supported protocols
* pattern.
*
*
*
*/
public interface ProtocolProperties {
/**
* @return the hostname value to resolve in the pattern.
*/
String getHostname();
/**
* @return the IP address value to resolve in the pattern.
*/
String getIp();
/**
* @return the port value to resolve in the pattern.
*/
String getPort();
/**
* @return the pattern to build the URL.
*/
String getPattern();
}
/**
* Object to hold the properties for the HTTP protocol.
*
*
*
*/
public static class Http implements ProtocolProperties {
private String hostname = LOCALHOST;
private String ip = DEFAULT_IP_LOCALHOST;
private String port = "";
/**
* An ant-URL pattern with placeholder to build the URL on. The URL can
* have specific artifact placeholder.
*/
private String pattern = "{protocol}://{hostname}:{port}/{tenant}/controller/v1/{targetId}/softwaremodules/{softwareModuleId}/artifacts/{artifactFileName}";
/**
* @return the hostname
*/
@Override
public String getHostname() {
return hostname;
}
/**
* @param hostname
* the hostname to set
*/
public void setHostname(final String hostname) {
this.hostname = hostname;
}
/**
* @return the ip
*/
@Override
public String getIp() {
return ip;
}
/**
* @param ip
* the ip to set
*/
public void setIp(final String ip) {
this.ip = ip;
}
/**
* @return the urlPattern
*/
@Override
public String getPattern() {
return pattern;
}
/**
* @param urlPattern
* the urlPattern to set
*/
public void setPattern(final String urlPattern) {
this.pattern = urlPattern;
}
/**
* @return the port
*/
@Override
public String getPort() {
return port;
}
/**
* @param port
* the port to set
*/
public void setPort(final String port) {
this.port = port;
}
}
/**
* Object to hold the properties for the HTTP protocol.
*
*
*
*/
public static class Https implements ProtocolProperties {
private String hostname = LOCALHOST;
private String ip = DEFAULT_IP_LOCALHOST;
private String port = "";
/**
* An ant-URL pattern with placeholder to build the URL on. The URL can
* have specific artifact placeholder.
*/
private String pattern = "{protocol}://{hostname}:{port}/{tenant}/controller/v1/{targetId}/softwaremodules/{softwareModuleId}/artifacts/{artifactFileName}";
/**
* @return the hostname
*/
@Override
public String getHostname() {
return hostname;
}
/**
* @param hostname
* the hostname to set
*/
public void setHostname(final String hostname) {
this.hostname = hostname;
}
/**
* @return the ip
*/
@Override
public String getIp() {
return ip;
}
/**
* @param ip
* the ip to set
*/
public void setIp(final String ip) {
this.ip = ip;
}
/**
* @return the urlPattern
*/
@Override
public String getPattern() {
return pattern;
}
/**
* @param urlPattern
* the urlPattern to set
*/
public void setPattern(final String urlPattern) {
this.pattern = urlPattern;
}
/**
* @return the port
*/
@Override
public String getPort() {
return port;
}
/**
* @param port
* the port to set
*/
public void setPort(final String port) {
this.port = port;
}
}
/**
* Object to hold the properties for the HTTP protocol.
*
*
*
*/
public static class Coap implements ProtocolProperties {
private String hostname = LOCALHOST;
private String ip = DEFAULT_IP_LOCALHOST;
private String port = "5683";
/**
* An ant-URL pattern with placeholder to build the URL on. The URL can
* have specific artifact placeholder.
*/
private String pattern = "{protocol}://{ip}:{port}/fw/{tenant}/{targetId}/sha1/{artifactSHA1}";
/**
* @return the hostname
*/
@Override
public String getHostname() {
return hostname;
}
/**
* @param hostname
* the hostname to set
*/
public void setHostname(final String hostname) {
this.hostname = hostname;
}
/**
* @return the ip
*/
@Override
public String getIp() {
return ip;
}
/**
* @param ip
* the ip to set
*/
public void setIp(final String ip) {
this.ip = ip;
}
/**
* @return the urlPattern
*/
@Override
public String getPattern() {
return pattern;
}
/**
* @param urlPattern
* the urlPattern to set
*/
public void setPattern(final String urlPattern) {
this.pattern = urlPattern;
}
/**
* @return the port
*/
@Override
public String getPort() {
return port;
}
/**
* @param port
* the port to set
*/
public void setPort(final String port) {
this.port = port;
}
}
}

View File

@@ -0,0 +1,87 @@
/**
* 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.util;
import java.util.HashMap;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Set;
import org.eclipse.hawkbit.dmf.json.model.Artifact;
import org.eclipse.hawkbit.repository.model.LocalArtifact;
import org.eclipse.hawkbit.tenancy.TenantAware;
import org.eclipse.hawkbit.util.ArtifactUrlHandlerProperties.ProtocolProperties;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.stereotype.Component;
import com.google.common.base.Strings;
/**
*
*
*/
@Component
@EnableConfigurationProperties(ArtifactUrlHandlerProperties.class)
public class PropertyBasedArtifactUrlHandler implements ArtifactUrlHandler {
private static final String PROTOCOL_PLACEHOLDER = "protocol";
private static final String TARGET_ID_PLACEHOLDER = "targetId";
private static final String IP_PLACEHOLDER = "ip";
private static final String PORT_PLACEHOLDER = "port";
private static final String HOSTNAME_PLACEHOLDER = "hostname";
private static final String ARTIFACT_FILENAME_PLACEHOLDER = "artifactFileName";
private static final String ARTIFACT_SHA1_PLACEHOLDER = "artifactSHA1";
private static final String TENANT_PLACEHOLDER = "tenant";
private static final String SOFTWARE_MODULE_ID_PLACDEHOLDER = "softwareModuleId";
@Autowired
private ArtifactUrlHandlerProperties urlHandlerProperties;
@Autowired
private TenantAware tenantAware;
@Override
public String getUrl(final String targetId, final LocalArtifact artifact, final Artifact.UrlProtocol protocol) {
final String protocolString = protocol.name().toLowerCase();
final ProtocolProperties properties = urlHandlerProperties.getProperties(protocolString);
if (properties == null || properties.getPattern() == null) {
return null;
}
String urlPattern = properties.getPattern();
final Set<Entry<String, String>> entrySet = getReplaceMap(targetId, artifact, protocolString, properties)
.entrySet();
for (final Entry<String, String> entry : entrySet) {
if (entry.getKey().equals(PORT_PLACEHOLDER)) {
urlPattern = urlPattern.replace(":{" + entry.getKey() + "}",
Strings.isNullOrEmpty(entry.getValue()) ? "" : ":" + entry.getValue());
} else {
urlPattern = urlPattern.replace("{" + entry.getKey() + "}", entry.getValue());
}
}
return urlPattern;
}
private Map<String, String> getReplaceMap(final String targetId, final LocalArtifact artifact,
final String protocol, final ProtocolProperties properties) {
final Map<String, String> replaceMap = new HashMap<>();
replaceMap.put(IP_PLACEHOLDER, properties.getIp());
replaceMap.put(HOSTNAME_PLACEHOLDER, properties.getHostname());
replaceMap.put(ARTIFACT_FILENAME_PLACEHOLDER, artifact.getFilename());
replaceMap.put(ARTIFACT_SHA1_PLACEHOLDER, artifact.getSha1Hash());
replaceMap.put(PROTOCOL_PLACEHOLDER, protocol);
replaceMap.put(PORT_PLACEHOLDER, properties.getPort());
replaceMap.put(TENANT_PLACEHOLDER, tenantAware.getCurrentTenant());
replaceMap.put(TARGET_ID_PLACEHOLDER, targetId);
replaceMap.put(SOFTWARE_MODULE_ID_PLACDEHOLDER, String.valueOf(artifact.getSoftwareModule().getId()));
return replaceMap;
}
}

View File

@@ -0,0 +1,202 @@
/**
* 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.fest.assertions.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 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.TenantSecruityToken;
import org.eclipse.hawkbit.repository.ArtifactManagement;
import org.eclipse.hawkbit.repository.ControllerManagement;
import org.eclipse.hawkbit.repository.SystemManagement;
import org.eclipse.hawkbit.security.SecurityContextTenantAware;
import org.eclipse.hawkbit.security.SecurityProperties;
import org.eclipse.hawkbit.tenancy.configuration.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.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;
/**
*
*
*/
@Features("AMQP Authenfication Test")
@Stories("Tests the authenfication")
public class AmqpControllerAuthentficationTest {
private static final String TENANT = "DEFAULT";
private static String CONTROLLLER_ID = "123";
private AmqpMessageHandlerService amqpMessageHandlerService;
private MessageConverter messageConverter;
private SystemManagement systemManagement;
private AmqpControllerAuthentfication authenticationManager;
@Before
public void before() throws Exception {
amqpMessageHandlerService = new AmqpMessageHandlerService();
messageConverter = new Jackson2JsonMessageConverter();
final RabbitTemplate rabbitTemplate = new RabbitTemplate();
rabbitTemplate.setMessageConverter(messageConverter);
amqpMessageHandlerService.setRabbitTemplate(rabbitTemplate);
authenticationManager = new AmqpControllerAuthentfication();
authenticationManager.setControllerManagement(mock(ControllerManagement.class));
final SecurityProperties secruityProperties = mock(SecurityProperties.class);
when(secruityProperties.getRpSslIssuerHashHeader()).thenReturn("X-Ssl-Issuer-Hash-%d");
authenticationManager.setSecruityProperties(secruityProperties);
systemManagement = mock(SystemManagement.class);
authenticationManager.setSystemManagement(systemManagement);
when(systemManagement.getConfigurationValue(any(), any())).thenReturn(Boolean.FALSE);
final ControllerManagement controllerManagement = mock(ControllerManagement.class);
when(controllerManagement.getSecurityTokenByControllerId(anyString())).thenReturn(CONTROLLLER_ID);
authenticationManager.setControllerManagement(controllerManagement);
amqpMessageHandlerService.setArtifactManagement(mock(ArtifactManagement.class));
authenticationManager.setTenantAware(new SecurityContextTenantAware());
authenticationManager.postConstruct();
amqpMessageHandlerService.setAuthenticationManager(authenticationManager);
}
@Test(expected = BadCredentialsException.class)
@Description("Tests authentication manager without principal")
public void testAuthenticationeBadCredantialsWithoutPricipal() {
final TenantSecruityToken securityToken = new TenantSecruityToken(TENANT, CONTROLLLER_ID, "12345");
authenticationManager.doAuthenticate(securityToken);
fail();
}
@Test(expected = BadCredentialsException.class)
@Description("Tests authentication manager without wrong credential")
public void testAuthenticationBadCredantialsWithWrongCredential() {
final TenantSecruityToken securityToken = new TenantSecruityToken(TENANT, CONTROLLLER_ID, "12345");
when(systemManagement.getConfigurationValue(
eq(TenantConfigurationKey.AUTHENTICATION_MODE_TARGET_SECURITY_TOKEN_ENABLED), any()))
.thenReturn(Boolean.TRUE);
securityToken.getHeaders().put(TenantSecruityToken.AUTHORIZATION_HEADER, "TargetToken 12" + CONTROLLLER_ID);
authenticationManager.doAuthenticate(securityToken);
fail();
}
@Test
@Description("Tests authentication successfull")
public void testSuccessfullAuthentication() {
final TenantSecruityToken securityToken = new TenantSecruityToken(TENANT, CONTROLLLER_ID, "12345");
when(systemManagement.getConfigurationValue(
eq(TenantConfigurationKey.AUTHENTICATION_MODE_TARGET_SECURITY_TOKEN_ENABLED), any()))
.thenReturn(Boolean.TRUE);
securityToken.getHeaders().put(TenantSecruityToken.AUTHORIZATION_HEADER, "TargetToken " + CONTROLLLER_ID);
final Authentication authentication = authenticationManager.doAuthenticate(securityToken);
assertThat(authentication).isNotNull();
}
@Test
@Description("Tests authentication message without principal")
public void testAuthenticationMessageBadCredantialsWithoutPricipal() {
final MessageProperties messageProperties = createMessageProperties(MessageType.AUTHENTIFICATION);
final TenantSecruityToken securityToken = new TenantSecruityToken(TENANT, CONTROLLLER_ID, "12345");
final Message message = amqpMessageHandlerService.getMessageConverter().toMessage(securityToken,
messageProperties);
// test
final Message onMessage = amqpMessageHandlerService.onMessage(message, MessageType.AUTHENTIFICATION.name(),
TENANT);
// 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(MessageType.AUTHENTIFICATION);
final TenantSecruityToken securityToken = new TenantSecruityToken(TENANT, CONTROLLLER_ID, "12345");
when(systemManagement.getConfigurationValue(
eq(TenantConfigurationKey.AUTHENTICATION_MODE_TARGET_SECURITY_TOKEN_ENABLED), any()))
.thenReturn(Boolean.TRUE);
securityToken.getHeaders().put(TenantSecruityToken.AUTHORIZATION_HEADER, "TargetToken 12" + CONTROLLLER_ID);
final Message message = amqpMessageHandlerService.getMessageConverter().toMessage(securityToken,
messageProperties);
// test
final Message onMessage = amqpMessageHandlerService.onMessage(message, MessageType.AUTHENTIFICATION.name(),
TENANT);
// 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 testSuccessfullMessageAuthentication() {
final MessageProperties messageProperties = createMessageProperties(MessageType.AUTHENTIFICATION);
final TenantSecruityToken securityToken = new TenantSecruityToken(TENANT, CONTROLLLER_ID, "12345");
when(systemManagement.getConfigurationValue(
eq(TenantConfigurationKey.AUTHENTICATION_MODE_TARGET_SECURITY_TOKEN_ENABLED), any()))
.thenReturn(Boolean.TRUE);
securityToken.getHeaders().put(TenantSecruityToken.AUTHORIZATION_HEADER, "TargetToken " + CONTROLLLER_ID);
final Message message = amqpMessageHandlerService.getMessageConverter().toMessage(securityToken,
messageProperties);
// test
final Message onMessage = amqpMessageHandlerService.onMessage(message, MessageType.AUTHENTIFICATION.name(),
TENANT);
// verify
final DownloadResponse downloadResponse = (DownloadResponse) messageConverter.fromMessage(onMessage);
assertThat(downloadResponse).isNotNull();
assertThat(downloadResponse.getResponseCode()).isEqualTo(HttpStatus.NOT_FOUND.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();
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,200 @@
/**
* 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.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
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.anyString;
import static org.mockito.Matchers.eq;
import static org.mockito.Mockito.spy;
import static org.mockito.Mockito.when;
import java.util.ArrayList;
import java.util.List;
import org.eclipse.hawkbit.AbstractIntegrationTestWithMongoDB;
import org.eclipse.hawkbit.TestDataUtil;
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.eventbus.event.CancelTargetAssignmentEvent;
import org.eclipse.hawkbit.eventbus.event.TargetAssignDistributionSetEvent;
import org.eclipse.hawkbit.repository.model.Artifact;
import org.eclipse.hawkbit.repository.model.DistributionSet;
import org.eclipse.hawkbit.repository.model.LocalArtifact;
import org.eclipse.hawkbit.repository.model.SoftwareModule;
import org.eclipse.hawkbit.util.ArtifactUrlHandler;
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.Jackson2JsonMessageConverter;
import org.springframework.amqp.support.converter.MessageConverter;
import org.springframework.test.context.ActiveProfiles;
import ru.yandex.qatools.allure.annotations.Description;
import ru.yandex.qatools.allure.annotations.Features;
import ru.yandex.qatools.allure.annotations.Stories;
@ActiveProfiles({ "test" })
@Features("AMQP Dispatcher Test")
@Stories("Tests send messages")
public class AmqpMessageDispatcherServiceTest extends AbstractIntegrationTestWithMongoDB {
private AmqpMessageDispatcherService amqpMessageDispatcherService;
private MessageConverter messageConverter;
private RabbitTemplate rabbitTemplate;
private static final String CONTROLLER_ID = "1";
@Override
public void before() throws Exception {
super.before();
amqpMessageDispatcherService = new AmqpMessageDispatcherService();
amqpMessageDispatcherService = spy(amqpMessageDispatcherService);
messageConverter = new Jackson2JsonMessageConverter();
final ArtifactUrlHandler artifactUrlHandlerMock = Mockito.mock(ArtifactUrlHandler.class);
when(artifactUrlHandlerMock.getUrl(anyString(), any(), anyObject())).thenReturn("http://mockurl");
this.rabbitTemplate = Mockito.mock(RabbitTemplate.class);
when(rabbitTemplate.getMessageConverter()).thenReturn(messageConverter);
amqpMessageDispatcherService.setRabbitTemplate(rabbitTemplate);
amqpMessageDispatcherService.setTenantAware(tenantAware);
amqpMessageDispatcherService.setArtifactUrlHandler(artifactUrlHandlerMock);
}
@Test
@Description("Verfies that download and install event with no software modul works")
public void testSendDownloadRequesWithEmptySoftwareModules() {
final TargetAssignDistributionSetEvent targetAssignDistributionSetEvent = new TargetAssignDistributionSetEvent(
CONTROLLER_ID, 1l, new ArrayList<SoftwareModule>(), IpUtil.createAmqpUri("mytest"));
amqpMessageDispatcherService.targetAssignDistributionSet(targetAssignDistributionSetEvent);
final Message sendMessage = createArgumentCapture(targetAssignDistributionSetEvent.getTargetAdress().getHost());
final DownloadAndUpdateRequest downloadAndUpdateRequest = assertDownloadAndInstallMessage(sendMessage);
assertTrue(downloadAndUpdateRequest.getSoftwareModules().isEmpty());
}
@Test
@Description("Verfies that download and install event with 3 software moduls and no artifacts works")
public void testSendDownloadRequesWithSoftwareModulesAndNoArtifacts() {
final DistributionSet dsA = TestDataUtil.generateDistributionSet("", softwareManagement,
distributionSetManagement);
final TargetAssignDistributionSetEvent targetAssignDistributionSetEvent = new TargetAssignDistributionSetEvent(
CONTROLLER_ID, 1l, dsA.getModules(), IpUtil.createAmqpUri("mytest"));
amqpMessageDispatcherService.targetAssignDistributionSet(targetAssignDistributionSetEvent);
final Message sendMessage = createArgumentCapture(targetAssignDistributionSetEvent.getTargetAdress().getHost());
final DownloadAndUpdateRequest downloadAndUpdateRequest = assertDownloadAndInstallMessage(sendMessage);
assertEquals(3, downloadAndUpdateRequest.getSoftwareModules().size());
for (final org.eclipse.hawkbit.dmf.json.model.SoftwareModule softwareModule : downloadAndUpdateRequest
.getSoftwareModules()) {
assertTrue(softwareModule.getArtifacts().isEmpty());
for (final SoftwareModule softwareModule2 : dsA.getModules()) {
assertNotNull(softwareModule.getModuleId());
if (!softwareModule.getModuleId().equals(softwareModule2.getId())) {
continue;
}
assertEquals(softwareModule.getModuleType(), softwareModule2.getType().getKey());
assertEquals(softwareModule.getModuleVersion(), softwareModule2.getVersion());
}
}
}
@Test
@Description("Verfies that download and install event with software moduls and artifacts works")
public void testSendDownloadRequest() {
final DistributionSet dsA = TestDataUtil.generateDistributionSet("", softwareManagement,
distributionSetManagement);
final SoftwareModule module = dsA.getModules().iterator().next();
final List<DbArtifact> receivedList = new ArrayList<>();
for (final Artifact artifact : TestDataUtil.generateArtifacts(artifactManagement, module.getId())) {
module.addArtifact((LocalArtifact) artifact);
receivedList.add(new DbArtifact());
}
Mockito.when(rabbitTemplate.convertSendAndReceive(any())).thenReturn(receivedList);
final TargetAssignDistributionSetEvent targetAssignDistributionSetEvent = new TargetAssignDistributionSetEvent(
CONTROLLER_ID, 1l, dsA.getModules(), IpUtil.createAmqpUri("mytest"));
amqpMessageDispatcherService.targetAssignDistributionSet(targetAssignDistributionSetEvent);
final Message sendMessage = createArgumentCapture(targetAssignDistributionSetEvent.getTargetAdress().getHost());
final DownloadAndUpdateRequest downloadAndUpdateRequest = assertDownloadAndInstallMessage(sendMessage);
assertEquals(3, downloadAndUpdateRequest.getSoftwareModules().size());
for (final org.eclipse.hawkbit.dmf.json.model.SoftwareModule softwareModule : downloadAndUpdateRequest
.getSoftwareModules()) {
if (!softwareModule.getModuleId().equals(module.getId())) {
continue;
}
assertFalse(softwareModule.getArtifacts().isEmpty());
}
}
@Test
@Description("Verfies that send cancel event works")
public void testSendCancelRequest() {
final CancelTargetAssignmentEvent cancelTargetAssignmentDistributionSetEvent = new CancelTargetAssignmentEvent(
CONTROLLER_ID, 1l, IpUtil.createAmqpUri("mytest"));
amqpMessageDispatcherService
.targetCancelAssignmentToDistributionSet(cancelTargetAssignmentDistributionSetEvent);
final Message sendMessage = createArgumentCapture(
cancelTargetAssignmentDistributionSetEvent.getTargetAdress().getHost());
assertCancelMessage(sendMessage);
}
private void assertCancelMessage(final Message sendMessage) {
assertEventMessage(sendMessage);
final Long actionId = (Long) messageConverter.fromMessage(sendMessage);
assertEquals(actionId, Long.valueOf(1));
assertEquals(EventTopic.CANCEL_DOWNLOAD,
sendMessage.getMessageProperties().getHeaders().get(MessageHeaderKey.TOPIC));
}
private DownloadAndUpdateRequest assertDownloadAndInstallMessage(final Message sendMessage) {
assertEventMessage(sendMessage);
final DownloadAndUpdateRequest downloadAndUpdateRequest = (DownloadAndUpdateRequest) messageConverter
.fromMessage(sendMessage);
assertEquals(downloadAndUpdateRequest.getActionId(), Long.valueOf(1));
assertEquals(EventTopic.DOWNLOAD_AND_INSTALL,
sendMessage.getMessageProperties().getHeaders().get(MessageHeaderKey.TOPIC));
return downloadAndUpdateRequest;
}
/**
* @param sendMessage
*/
private void assertEventMessage(final Message sendMessage) {
assertNotNull(sendMessage);
assertEquals(CONTROLLER_ID, sendMessage.getMessageProperties().getHeaders().get(MessageHeaderKey.THING_ID));
assertEquals(MessageType.EVENT, sendMessage.getMessageProperties().getHeaders().get(MessageHeaderKey.TYPE));
assertEquals(MessageProperties.CONTENT_TYPE_JSON, sendMessage.getMessageProperties().getContentType());
}
protected Message createArgumentCapture(final String exchange) {
final ArgumentCaptor<Message> argumentCaptor = ArgumentCaptor.forClass(Message.class);
Mockito.verify(amqpMessageDispatcherService).sendMessage(eq(exchange), argumentCaptor.capture());
return argumentCaptor.getValue();
}
}

View File

@@ -0,0 +1,432 @@
/**
* 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.fest.assertions.api.Assertions.assertThat;
import static org.junit.Assert.fail;
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.ArrayList;
import java.util.List;
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.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.DownloadResponse;
import org.eclipse.hawkbit.dmf.json.model.TenantSecruityToken;
import org.eclipse.hawkbit.eventbus.event.TargetAssignDistributionSetEvent;
import org.eclipse.hawkbit.repository.ArtifactManagement;
import org.eclipse.hawkbit.repository.ControllerManagement;
import org.eclipse.hawkbit.repository.exception.EntityNotFoundException;
import org.eclipse.hawkbit.repository.model.Action;
import org.eclipse.hawkbit.repository.model.Action.Status;
import org.eclipse.hawkbit.repository.model.LocalArtifact;
import org.eclipse.hawkbit.repository.model.SoftwareModule;
import org.eclipse.hawkbit.repository.model.Target;
import org.eclipse.hawkbit.repository.model.helper.SecurityTokenGeneratorHolder;
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.Matchers;
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.cache.Cache;
import org.springframework.http.HttpStatus;
import com.google.common.eventbus.EventBus;
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("AMQP Controller Test")
@Stories("Tests the servcies for message handler and dispatcher")
public class AmqpMessageHandlerServiceTest {
private static final String TENANT = "DEFAULT";
private AmqpMessageHandlerService amqpMessageHandlerService;
private MessageConverter messageConverter;
@Mock
private ControllerManagement controllerManagementMock;
@Mock
private ArtifactManagement artifactManagementMock;
@Mock
private AmqpControllerAuthentfication authenticationManagerMock;
@Mock
private ArtifactRepository artifactRepositoryMock;
@Mock
private Cache cacheMock;
@Mock
private HostnameResolver hostnameResolverMock;
@Mock
private EventBus eventBus;
@Before
public void before() throws Exception {
amqpMessageHandlerService = new AmqpMessageHandlerService();
amqpMessageHandlerService.setControllerManagement(controllerManagementMock);
messageConverter = new Jackson2JsonMessageConverter();
final RabbitTemplate rabbitTemplate = new RabbitTemplate();
rabbitTemplate.setMessageConverter(messageConverter);
amqpMessageHandlerService.setRabbitTemplate(rabbitTemplate);
amqpMessageHandlerService.setAuthenticationManager(authenticationManagerMock);
amqpMessageHandlerService.setArtifactManagement(artifactManagementMock);
amqpMessageHandlerService.setCache(cacheMock);
amqpMessageHandlerService.setHostnameResolver(hostnameResolverMock);
amqpMessageHandlerService.setEventBus(eventBus);
}
@Test(expected = IllegalArgumentException.class)
@Description("Tests not allowed content-type in message")
public void testWrongContentType() {
final MessageProperties messageProperties = new MessageProperties();
messageProperties.setContentType("xml");
final Message message = new Message(new byte[0], messageProperties);
amqpMessageHandlerService.onMessage(message, MessageType.THING_CREATED.name(), TENANT);
fail();
}
@Test
@Description("Tests the creation of a target/thing by calling the same method that incoming RabbitMQ messages would access.")
public void testCreateThing() {
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);
// mock
final ArgumentCaptor<String> targetIdCaptor = ArgumentCaptor.forClass(String.class);
final ArgumentCaptor<URI> uriCaptor = ArgumentCaptor.forClass(URI.class);
when(controllerManagementMock.findOrRegisterTargetIfItDoesNotexist(targetIdCaptor.capture(),
uriCaptor.capture())).thenReturn(null);
// test
amqpMessageHandlerService.onMessage(message, MessageType.THING_CREATED.name(), TENANT);
// verify
assertThat(targetIdCaptor.getValue()).isEqualTo(knownThingId);
assertThat(uriCaptor.getValue().toString()).isEqualTo("amqp://MyTest");
}
@Test
@Description("Tests the creation of a thing without a 'reply to' header in message.")
public void testCreateThingWitoutReplyTo() {
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);
fail("IllegalArgumentException was excepeted since no replyTo header was set");
} catch (final IllegalArgumentException 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 testCreateThingWithoutID() {
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);
fail("IllegalArgumentException was excepeted since no thingID was set");
} catch (final IllegalArgumentException 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 testUnknownMessageType() {
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);
fail("IllegalArgumentException was excepeted due to unknown message type");
} catch (final IllegalArgumentException exception) {
// test ok - exception was excepted
}
}
@Test
@Description("Tests a invalid message without event topic")
public void testInvalidEventTopic() {
final MessageProperties messageProperties = createMessageProperties(MessageType.EVENT);
final Message message = new Message(new byte[0], messageProperties);
try {
amqpMessageHandlerService.onMessage(message, MessageType.EVENT.name(), TENANT);
fail();
} catch (final IllegalArgumentException e) {
}
try {
messageProperties.setHeader(MessageHeaderKey.TOPIC, "wrongTopic");
amqpMessageHandlerService.onMessage(message, MessageType.EVENT.name(), TENANT);
fail();
} catch (final IllegalArgumentException e) {
}
messageProperties.setHeader(MessageHeaderKey.TOPIC, EventTopic.CANCEL_DOWNLOAD.name());
try {
amqpMessageHandlerService.onMessage(message, MessageType.EVENT.name(), TENANT);
fail("IllegalArgumentException was excepeted because there was no event topic");
} catch (final IllegalArgumentException exception) {
// test ok - exception was excepted
}
}
@Test
@Description("Tests the update of an action of a target without a exist action id")
public void testUpdateActionStatusWithoutActionId() {
final MessageProperties messageProperties = createMessageProperties(MessageType.EVENT);
messageProperties.setHeader(MessageHeaderKey.TOPIC, EventTopic.UPDATE_ACTION_STATUS.name());
final ActionUpdateStatus actionUpdateStatus = new ActionUpdateStatus();
actionUpdateStatus.setActionStatus(ActionStatus.DOWNLOAD);
final Message message = amqpMessageHandlerService.getMessageConverter().toMessage(actionUpdateStatus,
messageProperties);
try {
amqpMessageHandlerService.onMessage(message, MessageType.EVENT.name(), TENANT);
fail("IllegalArgumentException was excepeted since no action id was set");
} catch (final IllegalArgumentException exception) {
// test ok - exception was excepted
}
}
@Test
@Description("Tests the update of an action of a target without a exist action id")
public void testUpdateActionStatusWithoutExistActionId() {
final MessageProperties messageProperties = createMessageProperties(MessageType.EVENT);
messageProperties.setHeader(MessageHeaderKey.TOPIC, EventTopic.UPDATE_ACTION_STATUS.name());
final ActionUpdateStatus actionUpdateStatus = createActionUpdateStatus(ActionStatus.DOWNLOAD);
final Message message = amqpMessageHandlerService.getMessageConverter().toMessage(actionUpdateStatus,
messageProperties);
try {
amqpMessageHandlerService.onMessage(message, MessageType.EVENT.name(), TENANT);
fail("IllegalArgumentException was excepeted since no action id was set");
} catch (final IllegalArgumentException 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(MessageType.AUTHENTIFICATION);
final TenantSecruityToken securityToken = new TenantSecruityToken(TENANT, "123", "12345");
final Message message = amqpMessageHandlerService.getMessageConverter().toMessage(securityToken,
messageProperties);
// test
final Message onMessage = amqpMessageHandlerService.onMessage(message, MessageType.AUTHENTIFICATION.name(),
TENANT);
// verify
final DownloadResponse downloadResponse = (DownloadResponse) messageConverter.fromMessage(onMessage);
assertThat(downloadResponse).isNotNull();
assertThat(downloadResponse.getResponseCode()).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(MessageType.AUTHENTIFICATION);
final TenantSecruityToken securityToken = new TenantSecruityToken(TENANT, "123", "12345");
final Message message = amqpMessageHandlerService.getMessageConverter().toMessage(securityToken,
messageProperties);
final LocalArtifact localArtifactMock = mock(LocalArtifact.class);
when(artifactManagementMock.findFirstLocalArtifactsBySHA1(anyString())).thenReturn(localArtifactMock);
when(controllerManagementMock.getActionForDownloadByTargetAndSoftwareModule(anyObject(), anyObject()))
.thenThrow(EntityNotFoundException.class);
// test
final Message onMessage = amqpMessageHandlerService.onMessage(message, MessageType.AUTHENTIFICATION.name(),
TENANT);
// verify
final DownloadResponse downloadResponse = (DownloadResponse) messageConverter.fromMessage(onMessage);
assertThat(downloadResponse).isNotNull();
assertThat(downloadResponse.getResponseCode()).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(MessageType.AUTHENTIFICATION);
final TenantSecruityToken securityToken = new TenantSecruityToken(TENANT, "123", "12345");
final Message message = amqpMessageHandlerService.getMessageConverter().toMessage(securityToken,
messageProperties);
// mock
final LocalArtifact localArtifactMock = mock(LocalArtifact.class);
final Action actionMock = mock(Action.class);
final DbArtifact dbArtifactMock = mock(DbArtifact.class);
when(artifactManagementMock.findFirstLocalArtifactsBySHA1(anyString())).thenReturn(localArtifactMock);
when(controllerManagementMock.getActionForDownloadByTargetAndSoftwareModule(anyObject(), anyObject()))
.thenReturn(actionMock);
when(artifactManagementMock.loadLocalArtifactBinary(localArtifactMock)).thenReturn(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 = amqpMessageHandlerService.onMessage(message, MessageType.AUTHENTIFICATION.name(),
TENANT);
// verify
final DownloadResponse downloadResponse = (DownloadResponse) messageConverter.fromMessage(onMessage);
assertThat(downloadResponse).isNotNull();
assertThat(downloadResponse.getResponseCode()).isEqualTo(HttpStatus.OK.value());
assertThat(downloadResponse.getArtifact().getSize()).isEqualTo(1L);
assertThat(downloadResponse.getDownloadUrl()).startsWith("http://localhost/api/v1/downloadserver/downloadId/");
}
@Test
@Description("Tests TODO")
public void lookupNextUpdateActionAfterFinished() throws IllegalArgumentException, IllegalAccessException {
// Mock
final Action action = createActionWithTarget(22L, Status.FINISHED);
when(controllerManagementMock.findActionWithDetails(Matchers.any())).thenReturn(action);
when(controllerManagementMock.addUpdateActionStatus(Matchers.any(), Matchers.any())).thenReturn(action);
// for the test the same action can be used
final List<Action> actionList = new ArrayList<Action>();
actionList.add(action);
when(controllerManagementMock.findActionByTargetAndActive(Matchers.any())).thenReturn(actionList);
final List<SoftwareModule> softwareModuleList = createSoftwareModuleList();
when(controllerManagementMock.findSoftwareModulesByDistributionSet(Matchers.any()))
.thenReturn(softwareModuleList);
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);
// verify
final ArgumentCaptor<TargetAssignDistributionSetEvent> captorTargetAssignDistributionSetEvent = ArgumentCaptor
.forClass(TargetAssignDistributionSetEvent.class);
verify(eventBus, times(1)).post(captorTargetAssignDistributionSetEvent.capture());
final TargetAssignDistributionSetEvent targetAssignDistributionSetEvent = captorTargetAssignDistributionSetEvent
.getValue();
assertThat(targetAssignDistributionSetEvent.getControllerId()).isEqualTo("target1");
assertThat(targetAssignDistributionSetEvent.getActionId()).isEqualTo(22L);
assertThat(targetAssignDistributionSetEvent.getSoftwareModules()).isEqualTo(softwareModuleList);
}
private ActionUpdateStatus createActionUpdateStatus(final ActionStatus status) {
return createActionUpdateStatus(status, 2l);
}
private ActionUpdateStatus createActionUpdateStatus(final ActionStatus status, final Long id) {
final ActionUpdateStatus actionUpdateStatus = new ActionUpdateStatus();
actionUpdateStatus.setActionId(id);
actionUpdateStatus.setSoftwareModuleId(Long.valueOf(2));
actionUpdateStatus.setActionStatus(status);
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();
messageProperties.setHeader(MessageHeaderKey.TYPE, type.name());
messageProperties.setHeader(MessageHeaderKey.TENANT, TENANT);
messageProperties.setContentType(MessageProperties.CONTENT_TYPE_JSON);
messageProperties.setReplyTo(replyTo);
return messageProperties;
}
private List<SoftwareModule> createSoftwareModuleList() {
final List<SoftwareModule> softwareModuleList = new ArrayList<SoftwareModule>();
final SoftwareModule softwareModule = new SoftwareModule();
softwareModule.setId(777L);
softwareModuleList.add(softwareModule);
return softwareModuleList;
}
private Action createActionWithTarget(final Long targetId, final Status status)
throws IllegalArgumentException, IllegalAccessException {
// is needed for the creation of targets
initalizeSecurityTokenGenerator();
// Mock
final Action action = new Action();
action.setId(targetId);
action.setStatus(status);
action.setTenant("DEFAULT");
final Target target = new Target("target1");
action.setTarget(target);
return action;
}
private void initalizeSecurityTokenGenerator() throws IllegalArgumentException, 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,78 @@
/**
* 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.util;
import static org.junit.Assert.assertEquals;
import org.eclipse.hawkbit.AbstractIntegrationTestWithMongoDB;
import org.eclipse.hawkbit.TestDataUtil;
import org.eclipse.hawkbit.dmf.json.model.Artifact;
import org.eclipse.hawkbit.repository.model.DistributionSet;
import org.eclipse.hawkbit.repository.model.LocalArtifact;
import org.eclipse.hawkbit.repository.model.SoftwareModule;
import org.eclipse.hawkbit.tenancy.TenantAware;
import org.junit.Before;
import org.junit.Test;
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 - Artifact URL Handler")
@Stories("Test to generate the artifact download URL")
public class PropertyBasedArtifactUrlHandlerTest extends AbstractIntegrationTestWithMongoDB {
@Autowired
private ArtifactUrlHandler urlHandlerProperties;
@Autowired
private TenantAware tenantAware;
private LocalArtifact localArtifact;
private final String controllerId = "Test";
@Before
public void setup() {
final DistributionSet dsA = TestDataUtil.generateDistributionSet("", softwareManagement,
distributionSetManagement);
final SoftwareModule module = dsA.getModules().iterator().next();
localArtifact = (LocalArtifact) TestDataUtil.generateArtifacts(artifactManagement, module.getId()).stream()
.findAny().get();
}
@Test
@Description("Tests generate the http download url")
public void testHttpUrl() {
final String url = urlHandlerProperties.getUrl(controllerId, localArtifact, Artifact.UrlProtocol.HTTP);
assertEquals("http://localhost/" + tenantAware.getCurrentTenant() + "/controller/v1/" + controllerId
+ "/softwaremodules/" + localArtifact.getSoftwareModule().getId() + "/artifacts/"
+ localArtifact.getFilename(), url);
}
@Test
@Description("Tests generate the https download url")
public void testHttpsUrl() {
final String url = urlHandlerProperties.getUrl(controllerId, localArtifact, Artifact.UrlProtocol.HTTPS);
assertEquals("https://localhost/" + tenantAware.getCurrentTenant() + "/controller/v1/" + controllerId
+ "/softwaremodules/" + localArtifact.getSoftwareModule().getId() + "/artifacts/"
+ localArtifact.getFilename(), url);
}
@Test
@Description("Tests generate the coap download url")
public void testCoapUrl() {
final String url = urlHandlerProperties.getUrl(controllerId, localArtifact, Artifact.UrlProtocol.COAP);
assertEquals("coap://127.0.0.1:5683/fw/" + tenantAware.getCurrentTenant() + "/" + controllerId + "/sha1/"
+ localArtifact.getSha1Hash(), url);
}
}