Merge pull request #217 from bsinno/feature_configurable_mgmt_sim_scenario

Feature configurable mgmt sim scenario
This commit is contained in:
Kai Zimmermann
2016-06-23 10:08:21 +02:00
committed by GitHub
72 changed files with 1453 additions and 531 deletions

View File

@@ -71,6 +71,10 @@
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-logging</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.security</groupId>
<artifactId>spring-security-web</artifactId>
@@ -83,26 +87,6 @@
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter</artifactId>
</dependency>
<!-- Log4j API and Core implementation required for binding -->
<dependency>
<groupId>org.apache.logging.log4j</groupId>
<artifactId>log4j-api</artifactId>
</dependency>
<!-- Logging binding for java-util-logging e.g. Tomcat -->
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>jul-to-slf4j</artifactId>
</dependency>
<!-- Logging binding for Jakarta Commons Logging -->
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>jcl-over-slf4j</artifactId>
</dependency>
<!-- Logging binding for Log4J Logging -->
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>log4j-over-slf4j</artifactId>
</dependency>
<dependency>
<groupId>com.vaadin</groupId>
<artifactId>vaadin-spring-boot-starter</artifactId>

View File

@@ -27,7 +27,7 @@ public abstract class AbstractSimulatedDevice {
private UpdateStatus updateStatus = new UpdateStatus(ResponseStatus.SUCCESSFUL, "Simulation complete!");
private Protocol protocol = Protocol.DMF_AMQP;
private String targetSecurityToken;
private int pollDelaySec;
private int nextPollCounterSec;
/**
@@ -84,13 +84,30 @@ public abstract class AbstractSimulatedDevice {
* the ID of the simulated device
* @param tenant
* the tenant of the simulated device
* @param int
* pollDelaySec
*/
AbstractSimulatedDevice(final String id, final String tenant, final Protocol protocol) {
AbstractSimulatedDevice(final String id, final String tenant, final Protocol protocol, final int pollDelaySec) {
this.id = id;
this.tenant = tenant;
this.status = Status.UNKNWON;
this.progress = 0.0;
this.protocol = protocol;
this.pollDelaySec = pollDelaySec;
}
/**
* Can be called by a scheduler to trigger a device polling, like in real
* scenarios devices are frequently asking for updates etc.
*/
public abstract void poll();
public int getPollDelaySec() {
return pollDelaySec;
}
public void setPollDelaySec(final int pollDelaySec) {
this.pollDelaySec = pollDelaySec;
}
/**

View File

@@ -23,7 +23,6 @@ public class DDISimulatedDevice extends AbstractSimulatedDevice {
private static final Logger LOGGER = LoggerFactory.getLogger(DDISimulatedDevice.class);
private final int pollDelaySec;
private final ControllerResource controllerResource;
private final DeviceSimulatorUpdater deviceUpdater;
@@ -45,11 +44,9 @@ public class DDISimulatedDevice extends AbstractSimulatedDevice {
*/
public DDISimulatedDevice(final String id, final String tenant, final int pollDelaySec,
final ControllerResource controllerResource, final DeviceSimulatorUpdater deviceUpdater) {
super(id, tenant, Protocol.DDI_HTTP);
this.pollDelaySec = pollDelaySec;
super(id, tenant, Protocol.DDI_HTTP, pollDelaySec);
this.controllerResource = controllerResource;
this.deviceUpdater = deviceUpdater;
setNextPollCounterSec(pollDelaySec);
}
@Override
@@ -58,13 +55,10 @@ public class DDISimulatedDevice extends AbstractSimulatedDevice {
removed = true;
}
public int getPollDelaySec() {
return pollDelaySec;
}
/**
* Polls the base URL for the DDI API interface.
*/
@Override
public void poll() {
if (!removed) {
final String basePollJson = controllerResource.get(getTenant(), getId());

View File

@@ -8,10 +8,13 @@
*/
package org.eclipse.hawkbit.simulator;
import org.eclipse.hawkbit.simulator.amqp.SpSenderService;
/**
* A simulated device using the DMF API of the hawkBit update server.
*/
public class DMFSimulatedDevice extends AbstractSimulatedDevice {
private final SpSenderService spSenderService;
/**
* @param id
@@ -19,8 +22,15 @@ public class DMFSimulatedDevice extends AbstractSimulatedDevice {
* @param tenant
* the tenant of the simulated device
*/
public DMFSimulatedDevice(final String id, final String tenant) {
super(id, tenant, Protocol.DMF_AMQP);
public DMFSimulatedDevice(final String id, final String tenant, final SpSenderService spSenderService,
final int pollDelaySec) {
super(id, tenant, Protocol.DMF_AMQP, pollDelaySec);
this.spSenderService = spSenderService;
}
@Override
public void poll() {
spSenderService.createOrUpdateThing(super.getTenant(), super.getId());
}
}

View File

@@ -9,8 +9,8 @@
package org.eclipse.hawkbit.simulator;
import java.util.Collection;
import java.util.LinkedHashMap;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
@@ -25,7 +25,7 @@ import org.springframework.stereotype.Service;
@Service
public class DeviceSimulatorRepository {
private final Map<DeviceKey, AbstractSimulatedDevice> devices = new LinkedHashMap<>();
private final Map<DeviceKey, AbstractSimulatedDevice> devices = new ConcurrentHashMap<>();
@Autowired
private SimulatedDeviceFactory deviceFactory;

View File

@@ -8,12 +8,11 @@
*/
package org.eclipse.hawkbit.simulator;
import java.util.List;
import java.util.Collection;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.TimeUnit;
import java.util.stream.Collectors;
import org.eclipse.hawkbit.simulator.event.NextPollCounterUpdate;
import org.slf4j.Logger;
@@ -51,18 +50,17 @@ public class NextPollTimeController {
private class NextPollUpdaterRunnable implements Runnable {
@Override
public void run() {
final List<AbstractSimulatedDevice> devices = repository.getAll().stream()
.filter(device -> device instanceof DDISimulatedDevice).collect(Collectors.toList());
final Collection<AbstractSimulatedDevice> devices = repository.getAll();
devices.forEach(device -> {
int nextCounter = device.getNextPollCounterSec() - 1;
if (nextCounter < 0 && device instanceof DDISimulatedDevice) {
if (nextCounter < 0) {
try {
pollService.submit(() -> ((DDISimulatedDevice) device).poll());
pollService.submit(() -> device.poll());
} catch (final IllegalStateException e) {
LOGGER.trace("Device could not be polled", e);
}
nextCounter = ((DDISimulatedDevice) device).getPollDelaySec();
nextCounter = device.getPollDelaySec();
}
device.setNextPollCounterSec(nextCounter);

View File

@@ -11,6 +11,7 @@ package org.eclipse.hawkbit.simulator;
import java.net.URL;
import org.eclipse.hawkbit.simulator.AbstractSimulatedDevice.Protocol;
import org.eclipse.hawkbit.simulator.amqp.SpSenderService;
import org.eclipse.hawkbit.simulator.http.ControllerResource;
import org.eclipse.hawkbit.simulator.http.GatewayTokenInterceptor;
import org.springframework.beans.factory.annotation.Autowired;
@@ -28,6 +29,9 @@ public class SimulatedDeviceFactory {
@Autowired
private DeviceSimulatorUpdater deviceUpdater;
@Autowired
private SpSenderService spSenderService;
/**
* Creating a simulated devices.
*
@@ -55,7 +59,7 @@ public class SimulatedDeviceFactory {
* the protocol which should be used be the simulated device
* @param pollDelaySec
* the poll delay time in seconds which should be used for
* {@link DDISimulatedDevice}s
* {@link DDISimulatedDevice}s and {@link DMFSimulatedDevice}
* @param baseEndpoint
* the http base endpoint which should be used for
* {@link DDISimulatedDevice}s
@@ -68,7 +72,7 @@ public class SimulatedDeviceFactory {
final int pollDelaySec, final URL baseEndpoint, final String gatewayToken) {
switch (protocol) {
case DMF_AMQP:
return new DMFSimulatedDevice(id, tenant);
return new DMFSimulatedDevice(id, tenant, spSenderService, pollDelaySec);
case DDI_HTTP:
final ControllerResource controllerResource = Feign.builder().logger(new Logger.ErrorLogger())
.requestInterceptor(new GatewayTokenInterceptor(gatewayToken)).logLevel(Logger.Level.BASIC)

View File

@@ -66,7 +66,7 @@ public class SimulationController {
@RequestParam(value = "tenant", defaultValue = "DEFAULT") final String tenant,
@RequestParam(value = "api", defaultValue = "dmf") final String api,
@RequestParam(value = "endpoint", defaultValue = "http://localhost:8080") final String endpoint,
@RequestParam(value = "polldelay", defaultValue = "30") final int pollDelay,
@RequestParam(value = "polldelay", defaultValue = "1800") final int pollDelay,
@RequestParam(value = "gatewaytoken", defaultValue = "") final String gatewayToken)
throws MalformedURLException {

View File

@@ -10,6 +10,7 @@ package org.eclipse.hawkbit.simulator;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.TimeUnit;
import org.eclipse.hawkbit.simulator.AbstractSimulatedDevice.Protocol;
import org.hibernate.validator.constraints.NotEmpty;
@@ -68,9 +69,9 @@ public class SimulationProperties {
private String endpoint = "http://localhost:8080";
/**
* Poll time in case of DDI API based simulation.
* Poll time in {@link TimeUnit#SECONDS} for simulated devices.
*/
private int pollDelay = 30;
private int pollDelay = (int) TimeUnit.MINUTES.toSeconds(30);
/**
* Optional gateway token for DDI API based simulation.

View File

@@ -12,52 +12,103 @@ import java.time.Duration;
import java.util.HashMap;
import java.util.Map;
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.config.SimpleRabbitListenerContainerFactory;
import org.springframework.amqp.rabbit.connection.CachingConnectionFactory;
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.autoconfigure.amqp.RabbitProperties;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.retry.backoff.ExponentialBackOffPolicy;
import org.springframework.retry.support.RetryTemplate;
/**
* The spring AMQP configuration to use a AMQP for communication with SP update
* server.
*
*
*
*/
@Configuration
@EnableConfigurationProperties(AmqpProperties.class)
public class AmqpConfiguration {
private static final Logger LOGGER = LoggerFactory.getLogger(AmqpConfiguration.class);
@Autowired
protected AmqpProperties amqpProperties;
@Autowired
private ConnectionFactory connectionFactory;
@Autowired
private RabbitTemplate rabbitTemplate;
/**
* Create jackson message converter bean.
*
* @return the jackson message converter
* @return {@link RabbitTemplate} with automatic retry, published confirms
* and {@link Jackson2JsonMessageConverter}.
*/
@Bean
public MessageConverter jsonMessageConverter() {
final Jackson2JsonMessageConverter jackson2JsonMessageConverter = new Jackson2JsonMessageConverter();
rabbitTemplate.setMessageConverter(jackson2JsonMessageConverter);
return jackson2JsonMessageConverter;
public RabbitTemplate rabbitTemplate() {
final RabbitTemplate rabbitTemplate = new RabbitTemplate(connectionFactory);
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 correlation ID {} confirmed by broker.", correlationData.getId());
} else {
LOGGER.error("Broker is unable to handle message with correlation ID {} : {}", correlationData.getId(),
cause);
}
});
return rabbitTemplate;
}
@Configuration
protected static class RabbitConnectionFactoryCreator {
/**
* {@link ConnectionFactory} with enabled publisher confirms and
* heartbeat.
*
* @param config
* with standard {@link RabbitProperties}
* @return {@link ConnectionFactory}
*/
@Bean
public ConnectionFactory rabbitConnectionFactory(final RabbitProperties config) {
final CachingConnectionFactory factory = new CachingConnectionFactory();
factory.setRequestedHeartBeat(60);
factory.setPublisherConfirms(true);
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;
}
}
/**
@@ -136,10 +187,10 @@ public class AmqpConfiguration {
@Bean(name = { "listenerContainerFactory" })
public SimpleRabbitListenerContainerFactory listenerContainerFactory() {
final SimpleRabbitListenerContainerFactory containerFactory = new SimpleRabbitListenerContainerFactory();
containerFactory.setDefaultRequeueRejected(false);
containerFactory.setDefaultRequeueRejected(true);
containerFactory.setConnectionFactory(connectionFactory);
containerFactory.setConcurrentConsumers(20);
containerFactory.setMaxConcurrentConsumers(20);
containerFactory.setConcurrentConsumers(3);
containerFactory.setMaxConcurrentConsumers(10);
containerFactory.setPrefetchCount(20);
return containerFactory;
}

View File

@@ -8,6 +8,7 @@
*/
package org.eclipse.hawkbit.simulator.amqp;
import org.springframework.amqp.AmqpRejectAndDontRequeueException;
import org.springframework.amqp.core.Message;
import org.springframework.amqp.core.MessageProperties;
import org.springframework.amqp.rabbit.core.RabbitTemplate;
@@ -55,7 +56,7 @@ public abstract class ReceiverService extends MessageService {
if (contentType != null && contentType.contains("json")) {
return;
}
throw new IllegalArgumentException("Content-Type is not JSON compatible");
throw new AmqpRejectAndDontRequeueException("Content-Type is not JSON compatible");
}
}

View File

@@ -8,9 +8,14 @@
*/
package org.eclipse.hawkbit.simulator.amqp;
import java.util.UUID;
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.amqp.rabbit.support.CorrelationData;
import org.springframework.amqp.support.converter.AbstractJavaTypeMapper;
import org.springframework.beans.factory.annotation.Autowired;
@@ -22,6 +27,8 @@ import org.springframework.beans.factory.annotation.Autowired;
*/
public abstract class SenderService extends MessageService {
private static final Logger LOGGER = LoggerFactory.getLogger(SenderService.class);
/**
* Constructor for sender service.
*
@@ -40,18 +47,26 @@ public abstract class SenderService extends MessageService {
/**
* Send a message if the message is not null.
*
* @param adress
* @param address
* the exchange name
* @param message
* the amqp message which will be send if its not null
*/
public void sendMessage(final String adress, final Message message) {
public void sendMessage(final String address, final Message message) {
if (message == null) {
return;
}
message.getMessageProperties().getHeaders().remove(AbstractJavaTypeMapper.DEFAULT_CLASSID_FIELD_NAME);
rabbitTemplate.setExchange(adress);
rabbitTemplate.send(message);
final String correlationId = UUID.randomUUID().toString();
message.getMessageProperties().setCorrelationId(correlationId.getBytes());
if (LOGGER.isTraceEnabled()) {
LOGGER.trace("Sending message {} to exchange {} with correlationId {}", message, address, correlationId);
} else {
LOGGER.debug("Sending message to exchange {} with correlationId {}", address, correlationId);
}
rabbitTemplate.send(address, null, message, new CorrelationData(correlationId));
}
/**

View File

@@ -35,14 +35,21 @@ import com.google.common.collect.Lists;
public class SpReceiverService extends ReceiverService {
private static final Logger LOGGER = LoggerFactory.getLogger(ReceiverService.class);
public static final String SOFTWARE_MODULE_FIRMWARE = "firmware";
private final SpSenderService spSenderService;
private final DeviceSimulatorUpdater deviceUpdater;
/**
* Constructor.
*
* @param rabbitTemplate
* for sending messages
* @param amqpProperties
* for amqp configuration
* @param spSenderService
* to send messages
* @param deviceUpdater
* simulator service for updates
*/
@Autowired
public SpReceiverService(final RabbitTemplate rabbitTemplate, final AmqpProperties amqpProperties,

View File

@@ -8,7 +8,7 @@
*/
package org.eclipse.hawkbit.simulator.event;
import java.util.List;
import java.util.Collection;
import org.eclipse.hawkbit.simulator.AbstractSimulatedDevice;
@@ -20,7 +20,7 @@ import org.eclipse.hawkbit.simulator.AbstractSimulatedDevice;
*/
public class NextPollCounterUpdate {
private final List<AbstractSimulatedDevice> devices;
private final Collection<AbstractSimulatedDevice> devices;
/**
* Creates poll timer update event.
@@ -28,14 +28,14 @@ public class NextPollCounterUpdate {
* @param devices
* the devices which progress has been updated
*/
public NextPollCounterUpdate(final List<AbstractSimulatedDevice> devices) {
public NextPollCounterUpdate(final Collection<AbstractSimulatedDevice> devices) {
this.devices = devices;
}
/**
* @return the devices of the event
*/
public List<AbstractSimulatedDevice> getDevices() {
public Collection<AbstractSimulatedDevice> getDevices() {
return devices;
}

View File

@@ -76,7 +76,6 @@ public class GenerateDialog extends Window {
pollDelayTextField = createRequiredTextfield("poll delay (sec)", new ObjectProperty<Integer>(10),
FontAwesome.CLOCK_O, new RangeValidator<Integer>("Must be between 1 and 60", Integer.class, 1, 60));
pollDelayTextField.setVisible(false);
pollUrlTextField = createRequiredTextfield("base poll URL endpoint", "http://localhost:8080",
FontAwesome.FLAG_O, new RegexpValidator(
@@ -193,7 +192,6 @@ public class GenerateDialog extends Window {
protocolGroup.select(Protocol.DMF_AMQP);
protocolGroup.addValueChangeListener(event -> {
final boolean directDeviceOptionSelected = event.getProperty().getValue().equals(Protocol.DDI_HTTP);
pollDelayTextField.setVisible(directDeviceOptionSelected);
pollUrlTextField.setVisible(directDeviceOptionSelected);
gatewayTokenTextField.setVisible(directDeviceOptionSelected);
});

View File

@@ -8,7 +8,7 @@
*/
package org.eclipse.hawkbit.simulator.ui;
import java.util.List;
import java.util.Collection;
import java.util.Locale;
import org.eclipse.hawkbit.simulator.AbstractSimulatedDevice;
@@ -167,7 +167,7 @@ public class SimulatorView extends VerticalLayout implements View {
@SuppressWarnings("unchecked")
@Subscribe
public void pollCounterUpdate(final NextPollCounterUpdate update) {
final List<AbstractSimulatedDevice> devices = update.getDevices();
final Collection<AbstractSimulatedDevice> devices = update.getDevices();
this.getUI().access(() -> devices.forEach(device -> {
final BeanItem<AbstractSimulatedDevice> item = beanContainer.getItem(device.getId());
if (item != null) {

View File

@@ -24,7 +24,6 @@ spring.rabbitmq.virtualHost=/
spring.rabbitmq.host=localhost
spring.rabbitmq.port=5672
spring.rabbitmq.dynamic=true
spring.rabbitmq.listener.prefetch=100
security.basic.enabled=false
server.port=8083

View File

@@ -19,10 +19,7 @@
<Logger name="org.apache.coyote.http11.Http11NioProtocol" level="WARN" />
<Logger name="org.apache.tomcat.util.net.NioSelectorPool" level="WARN" />
<Logger name="org.apache.catalina.startup.DigesterFactory" level="ERROR" />
<!-- Security Log with hints on potential attacks -->
<logger name="server-security" level="INFO" />
<Logger name="org.apache.catalina.startup.DigesterFactory" level="ERROR" />
<Root level="INFO">
<AppenderRef ref="Console" />

View File

@@ -15,7 +15,7 @@ import org.springframework.cloud.netflix.feign.FeignClient;
/**
* Client binding for the DistributionSet resource of the management API.
*/
@FeignClient(url = "${hawkbit.url:localhost:8080}/" + MgmtRestConstants.DISTRIBUTIONSET_V1_REQUEST_MAPPING)
@FeignClient(url = "${hawkbit.url:localhost:8080}" + MgmtRestConstants.DISTRIBUTIONSET_V1_REQUEST_MAPPING)
public interface MgmtDistributionSetClientResource extends MgmtDistributionSetRestApi {
}

View File

@@ -15,6 +15,6 @@ import org.springframework.cloud.netflix.feign.FeignClient;
/**
* Client binding for the DistributionSetTag resource of the management API.
*/
@FeignClient(url = "${hawkbit.url:localhost:8080}/" + MgmtRestConstants.DISTRIBUTIONSET_TAG_V1_REQUEST_MAPPING)
@FeignClient(url = "${hawkbit.url:localhost:8080}" + MgmtRestConstants.DISTRIBUTIONSET_TAG_V1_REQUEST_MAPPING)
public interface MgmtDistributionSetTagClientResource extends MgmtDistributionSetTagRestApi {
}

View File

@@ -16,7 +16,7 @@ import org.springframework.cloud.netflix.feign.FeignClient;
* Client binding for the DistributionSetType resource of the management API.
*
*/
@FeignClient(url = "${hawkbit.url:localhost:8080}/" + MgmtRestConstants.DISTRIBUTIONSETTYPE_V1_REQUEST_MAPPING)
@FeignClient(url = "${hawkbit.url:localhost:8080}" + MgmtRestConstants.DISTRIBUTIONSETTYPE_V1_REQUEST_MAPPING)
public interface MgmtDistributionSetTypeClientResource extends MgmtDistributionSetTypeRestApi {
}

View File

@@ -16,7 +16,7 @@ import org.springframework.cloud.netflix.feign.FeignClient;
* A feign-client interface declaration which allows to build a feign-client
* stub.
*/
@FeignClient(url = "${hawkbit.url:localhost:8080}/" + MgmtRestConstants.SOFTWAREMODULE_V1_REQUEST_MAPPING)
@FeignClient(url = "${hawkbit.url:localhost:8080}" + MgmtRestConstants.SOFTWAREMODULE_V1_REQUEST_MAPPING)
public interface MgmtDownloadArtifactClientResource extends MgmtDownloadArtifactRestApi {
}

View File

@@ -15,6 +15,6 @@ import org.springframework.cloud.netflix.feign.FeignClient;
/**
*
*/
@FeignClient(url = "${hawkbit.url:localhost:8080}/" + MgmtRestConstants.DOWNLOAD_ID_V1_REQUEST_MAPPING_BASE)
@FeignClient(url = "${hawkbit.url:localhost:8080}" + MgmtRestConstants.DOWNLOAD_ID_V1_REQUEST_MAPPING_BASE)
public interface MgmtDownloadClientResource extends MgmtDownloadRestApi {
}

View File

@@ -15,6 +15,6 @@ import org.springframework.cloud.netflix.feign.FeignClient;
/**
* Client binding for the Rollout resource of the management API.
*/
@FeignClient(url = "${hawkbit.url:localhost:8080}/" + MgmtRestConstants.ROLLOUT_V1_REQUEST_MAPPING)
@FeignClient(url = "${hawkbit.url:localhost:8080}" + MgmtRestConstants.ROLLOUT_V1_REQUEST_MAPPING)
public interface MgmtRolloutClientResource extends MgmtRolloutRestApi {
}

View File

@@ -24,9 +24,10 @@ import feign.Param;
/**
* Client binding for the SoftwareModule resource of the management API.
*/
@FeignClient(url = "${hawkbit.url:localhost:8080}/" + MgmtRestConstants.SOFTWAREMODULE_V1_REQUEST_MAPPING)
@FeignClient(url = "${hawkbit.url:localhost:8080}" + MgmtRestConstants.SOFTWAREMODULE_V1_REQUEST_MAPPING)
public interface MgmtSoftwareModuleClientResource extends MgmtSoftwareModuleRestApi {
@Override
@RequestMapping(method = RequestMethod.POST, value = "/{softwareModuleId}/artifacts")
ResponseEntity<MgmtArtifact> uploadArtifact(@PathVariable("softwareModuleId") final Long softwareModuleId,
@Param("file") final MultipartFile file,

View File

@@ -15,6 +15,6 @@ import org.springframework.cloud.netflix.feign.FeignClient;
/**
* Client binding for the SoftwareModuleType resource of the management API.
*/
@FeignClient(url = "${hawkbit.url:localhost:8080}/" + MgmtRestConstants.SOFTWAREMODULETYPE_V1_REQUEST_MAPPING)
@FeignClient(url = "${hawkbit.url:localhost:8080}" + MgmtRestConstants.SOFTWAREMODULETYPE_V1_REQUEST_MAPPING)
public interface MgmtSoftwareModuleTypeClientResource extends MgmtSoftwareModuleTypeRestApi {
}

View File

@@ -16,6 +16,6 @@ import org.springframework.cloud.netflix.feign.FeignClient;
* Client binding for the {@link MgmtSystemRestApi}.
*
*/
@FeignClient(url = "${hawkbit.url:localhost:8080}/" + MgmtRestConstants.SYSTEM_V1_REQUEST_MAPPING)
@FeignClient(url = "${hawkbit.url:localhost:8080}" + MgmtRestConstants.SYSTEM_V1_REQUEST_MAPPING)
public interface MgmtSystemClientResource extends MgmtSystemRestApi {
}

View File

@@ -16,7 +16,7 @@ import org.springframework.cloud.netflix.feign.FeignClient;
* Client binding for the {@link MgmtSystemManagementRestApi}.
*
*/
@FeignClient(url = "${hawkbit.url:localhost:8080}/" + MgmtRestConstants.SYSTEM_ADMIN_MAPPING)
@FeignClient(url = "${hawkbit.url:localhost:8080}" + MgmtRestConstants.SYSTEM_ADMIN_MAPPING)
public interface MgmtSystemManagementClientResource extends MgmtSystemManagementRestApi {
}

View File

@@ -15,6 +15,6 @@ import org.springframework.cloud.netflix.feign.FeignClient;
/**
* Client binding for the Target resource of the management API.
*/
@FeignClient(url = "${hawkbit.url:localhost:8080}/" + MgmtRestConstants.TARGET_V1_REQUEST_MAPPING)
@FeignClient(url = "${hawkbit.url:localhost:8080}" + MgmtRestConstants.TARGET_V1_REQUEST_MAPPING)
public interface MgmtTargetClientResource extends MgmtTargetRestApi {
}

View File

@@ -15,6 +15,6 @@ import org.springframework.cloud.netflix.feign.FeignClient;
/**
* Client binding for the TargetTag resource of the management API.
*/
@FeignClient(url = "${hawkbit.url:localhost:8080}/" + MgmtRestConstants.TARGET_TAG_V1_REQUEST_MAPPING)
@FeignClient(url = "${hawkbit.url:localhost:8080}" + MgmtRestConstants.TARGET_TAG_V1_REQUEST_MAPPING)
public interface MgmtTargetTagClientResource extends MgmtTargetTagRestApi {
}

View File

@@ -84,32 +84,48 @@ public class DistributionSetBuilder {
* @return a single entry list of {@link MgmtDistributionSetRequestBodyPost}
*/
public List<MgmtDistributionSetRequestBodyPost> build() {
return Lists.newArrayList(doBuild(name));
return Lists.newArrayList(doBuild(""));
}
/**
* Builds a list of multiple {@link MgmtDistributionSetRequestBodyPost} to
* create multiple distribution sets at once. An increasing number will be
* added to the name of the distribution set. The version and type will
* remain the same.
* used for version of the distribution set. The name and type will remain
* the same.
*
* @param count
* the amount of distribution sets body which should be created
* @return a list of {@link MgmtDistributionSetRequestBodyPost}
*/
public List<MgmtDistributionSetRequestBodyPost> buildAsList(final int count) {
return buildAsList(0, count);
}
/**
* Builds a list of multiple {@link MgmtDistributionSetRequestBodyPost} to
* create multiple distribution sets at once. An increasing number will be
* used for version of the distribution set starting from given offset. The
* name and type will remain the same.
*
* @param count
* the amount of distribution sets body which should be created
* @param offset
* for for index start
* @return a list of {@link MgmtDistributionSetRequestBodyPost}
*/
public List<MgmtDistributionSetRequestBodyPost> buildAsList(final int offset, final int count) {
final ArrayList<MgmtDistributionSetRequestBodyPost> bodyList = Lists.newArrayList();
for (int index = 0; index < count; index++) {
bodyList.add(doBuild(name + index));
for (int index = offset; index < count + offset; index++) {
bodyList.add(doBuild(String.valueOf(index)));
}
return bodyList;
}
private MgmtDistributionSetRequestBodyPost doBuild(final String prefixName) {
private MgmtDistributionSetRequestBodyPost doBuild(final String suffix) {
final MgmtDistributionSetRequestBodyPost body = new MgmtDistributionSetRequestBodyPost();
body.setName(prefixName);
body.setVersion(version);
body.setName(name);
body.setVersion(version + suffix);
body.setType(type);
body.setDescription(description);
body.setModules(modules);

View File

@@ -101,7 +101,7 @@ public class DistributionSetTypeBuilder {
* {@link MgmtDistributionSetTypeRequestBodyPost}
*/
public List<MgmtDistributionSetTypeRequestBodyPost> build() {
return Lists.newArrayList(doBuild(name, key));
return Lists.newArrayList(doBuild(""));
}
/**
@@ -118,16 +118,16 @@ public class DistributionSetTypeBuilder {
public List<MgmtDistributionSetTypeRequestBodyPost> buildAsList(final int count) {
final ArrayList<MgmtDistributionSetTypeRequestBodyPost> bodyList = Lists.newArrayList();
for (int index = 0; index < count; index++) {
bodyList.add(doBuild(name + index, key + index));
bodyList.add(doBuild(String.valueOf(index)));
}
return bodyList;
}
private MgmtDistributionSetTypeRequestBodyPost doBuild(final String prefixName, final String prefixKey) {
private MgmtDistributionSetTypeRequestBodyPost doBuild(final String suffix) {
final MgmtDistributionSetTypeRequestBodyPost body = new MgmtDistributionSetTypeRequestBodyPost();
body.setKey(prefixKey);
body.setName(prefixName);
body.setKey(key + suffix);
body.setName(name + suffix);
body.setDescription(description);
body.setMandatorymodules(mandatorymodules);
body.setOptionalmodules(optionalmodules);

View File

@@ -90,13 +90,13 @@ public class SoftwareModuleBuilder {
* @return a single entry list of {@link MgmtSoftwareModuleRequestBodyPost}
*/
public List<MgmtSoftwareModuleRequestBodyPost> build() {
return Lists.newArrayList(doBuild(name));
return Lists.newArrayList(doBuild(""));
}
/**
* Builds a list of multiple {@link MgmtSoftwareModuleRequestBodyPost} to
* create multiple software module at once. An increasing number will be
* added to the name of the software module. The version and type will
* added to the version of the software module. The name and type will
* remain the same.
*
* @param count
@@ -106,16 +106,16 @@ public class SoftwareModuleBuilder {
public List<MgmtSoftwareModuleRequestBodyPost> buildAsList(final int count) {
final ArrayList<MgmtSoftwareModuleRequestBodyPost> bodyList = Lists.newArrayList();
for (int index = 0; index < count; index++) {
bodyList.add(doBuild(name + index));
bodyList.add(doBuild(String.valueOf(index)));
}
return bodyList;
}
private MgmtSoftwareModuleRequestBodyPost doBuild(final String prefixName) {
private MgmtSoftwareModuleRequestBodyPost doBuild(final String suffix) {
final MgmtSoftwareModuleRequestBodyPost body = new MgmtSoftwareModuleRequestBodyPost();
body.setName(prefixName);
body.setVersion(version);
body.setName(name);
body.setVersion(version + suffix);
body.setType(type);
body.setVendor(vendor);
body.setDescription(description);

View File

@@ -69,7 +69,7 @@ public class SoftwareModuleTypeBuilder {
* {@link MgmtSoftwareModuleTypeRequestBodyPost}
*/
public List<MgmtSoftwareModuleTypeRequestBodyPost> build() {
return Lists.newArrayList(doBuild(key, name));
return Lists.newArrayList(doBuild(""));
}
/**
@@ -85,15 +85,15 @@ public class SoftwareModuleTypeBuilder {
public List<MgmtSoftwareModuleTypeRequestBodyPost> buildAsList(final int count) {
final ArrayList<MgmtSoftwareModuleTypeRequestBodyPost> bodyList = Lists.newArrayList();
for (int index = 0; index < count; index++) {
bodyList.add(doBuild(key + index, name + index));
bodyList.add(doBuild(String.valueOf(index)));
}
return bodyList;
}
private MgmtSoftwareModuleTypeRequestBodyPost doBuild(final String prefixKey, final String prefixName) {
private MgmtSoftwareModuleTypeRequestBodyPost doBuild(final String suffix) {
final MgmtSoftwareModuleTypeRequestBodyPost body = new MgmtSoftwareModuleTypeRequestBodyPost();
body.setKey(prefixKey);
body.setName(prefixName);
body.setKey(key + suffix);
body.setName(name + suffix);
body.setDescription(description);
body.setMaxAssignments(maxAssignments);
return body;

View File

@@ -8,7 +8,6 @@
*/
package org.eclipse.hawkbit.mgmt.client.resource.builder;
import java.util.ArrayList;
import java.util.List;
import org.eclipse.hawkbit.mgmt.json.model.softwaremoduletype.MgmtSoftwareModuleTypeRequestBodyPost;
@@ -28,6 +27,7 @@ public class TargetBuilder {
private String controllerId;
private String name;
private String description;
private String address;
/**
* @param controllerId
@@ -49,6 +49,16 @@ public class TargetBuilder {
return this;
}
/**
* @param address
* the address of the target
* @return the builder itself
*/
public TargetBuilder address(final String address) {
this.address = address;
return this;
}
/**
* @param description
* the description of the target
@@ -66,32 +76,54 @@ public class TargetBuilder {
* @return a single entry list of {@link MgmtTargetRequestBody}
*/
public List<MgmtTargetRequestBody> build() {
return Lists.newArrayList(doBuild(controllerId));
return Lists.newArrayList(doBuild(""));
}
/**
* Builds a list of multiple {@link MgmtTargetRequestBody} to create
* multiple targets at once. An increasing number will be added to the
* controllerId of the target. The name and description will remain.
* controllerId and name of the target. The description will remain.
*
* @param count
* the amount of software module type bodies which should be
* created
* the amount of target bodies which should be created
* @param offset
* for
* @return a list of {@link MgmtSoftwareModuleTypeRequestBodyPost}
*/
public List<MgmtTargetRequestBody> buildAsList(final int count) {
final ArrayList<MgmtTargetRequestBody> bodyList = Lists.newArrayList();
for (int index = 0; index < count; index++) {
bodyList.add(doBuild(controllerId + index));
return buildAsList(0, count);
}
/**
* Builds a list of multiple {@link MgmtTargetRequestBody} to create
* multiple targets at once. An increasing number will be added to the
* controllerId and name of the target starting from the provided offset.
* The description will remain.
*
* @param count
* the amount of target bodies which should be created
* @param offset
* for for index start
* @return a list of {@link MgmtSoftwareModuleTypeRequestBodyPost}
*/
public List<MgmtTargetRequestBody> buildAsList(final int offset, final int count) {
final List<MgmtTargetRequestBody> bodyList = Lists.newArrayList();
for (int index = offset; index < count + offset; index++) {
bodyList.add(doBuild(String.valueOf(index)));
}
return bodyList;
}
private MgmtTargetRequestBody doBuild(final String prefixControllerId) {
private MgmtTargetRequestBody doBuild(final String suffix) {
final MgmtTargetRequestBody body = new MgmtTargetRequestBody();
body.setControllerId(prefixControllerId);
body.setName(name);
body.setControllerId(controllerId + suffix);
if (name == null) {
name = controllerId;
}
body.setName(name + suffix);
body.setDescription(description);
body.setAddress(address);
return body;
}

View File

@@ -9,23 +9,40 @@
package org.eclipse.hawkbit.mgmt.client;
import org.eclipse.hawkbit.feign.core.client.FeignClientConfiguration;
import org.eclipse.hawkbit.feign.core.client.IgnoreMultipleConsumersProducersSpringMvcContract;
import org.eclipse.hawkbit.mgmt.client.resource.MgmtDistributionSetClientResource;
import org.eclipse.hawkbit.mgmt.client.resource.MgmtRolloutClientResource;
import org.eclipse.hawkbit.mgmt.client.resource.MgmtSoftwareModuleClientResource;
import org.eclipse.hawkbit.mgmt.client.resource.MgmtTargetClientResource;
import org.eclipse.hawkbit.mgmt.client.scenarios.ConfigurableScenario;
import org.eclipse.hawkbit.mgmt.client.scenarios.CreateStartedRolloutExample;
import org.eclipse.hawkbit.mgmt.client.scenarios.GettingStartedDefaultScenario;
import org.eclipse.hawkbit.mgmt.client.scenarios.upload.FeignMultipartEncoder;
import org.eclipse.hawkbit.mgmt.rest.api.MgmtRestConstants;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.boot.CommandLineRunner;
import org.springframework.boot.autoconfigure.AutoConfigureAfter;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.boot.builder.SpringApplicationBuilder;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.cloud.netflix.feign.EnableFeignClients;
import org.springframework.cloud.netflix.feign.support.ResponseEntityDecoder;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Import;
import org.springframework.hateoas.hal.Jackson2HalModule;
import com.fasterxml.jackson.databind.DeserializationFeature;
import com.fasterxml.jackson.databind.ObjectMapper;
import feign.Feign;
import feign.Logger;
import feign.auth.BasicAuthRequestInterceptor;
import feign.jackson.JacksonDecoder;
import feign.slf4j.Slf4jLogger;
@SpringBootApplication
@EnableFeignClients
@EnableFeignClients("org.eclipse.hawkbit.mgmt.client.resource")
@EnableConfigurationProperties(ClientConfigurationProperties.class)
@Configuration
@AutoConfigureAfter(FeignClientConfiguration.class)
@@ -36,7 +53,7 @@ public class Application implements CommandLineRunner {
private ClientConfigurationProperties configuration;
@Autowired
private GettingStartedDefaultScenario gettingStarted;
private ConfigurableScenario configuredScenario;
@Autowired
private CreateStartedRolloutExample gettingStartedRolloutScenario;
@@ -51,9 +68,8 @@ public class Application implements CommandLineRunner {
// run the create and start rollout example
gettingStartedRolloutScenario.run();
} else {
// run the getting started scenario which creates a setup of
// distribution set and software modules to be used
gettingStarted.run();
// run the configured scenario from properties
configuredScenario.run();
}
}
@@ -63,8 +79,13 @@ public class Application implements CommandLineRunner {
}
@Bean
public GettingStartedDefaultScenario gettingStartedDefaultScenario() {
return new GettingStartedDefaultScenario();
public ConfigurableScenario configurableScenario(final MgmtDistributionSetClientResource distributionSetResource,
@Qualifier("mgmtSoftwareModuleClientResource") final MgmtSoftwareModuleClientResource softwareModuleResource,
@Qualifier("uploadSoftwareModule") final MgmtSoftwareModuleClientResource uploadSoftwareModule,
final MgmtTargetClientResource targetResource, final MgmtRolloutClientResource rolloutResource,
final ClientConfigurationProperties clientConfigurationProperties) {
return new ConfigurableScenario(distributionSetResource, softwareModuleResource, uploadSoftwareModule,
targetResource, rolloutResource, clientConfigurationProperties);
}
@Bean
@@ -72,7 +93,27 @@ public class Application implements CommandLineRunner {
return new CreateStartedRolloutExample();
}
private boolean containsArg(final String containsArg, final String... args) {
@Bean
public Logger.Level feignLoggerLevel() {
return Logger.Level.FULL;
}
@Bean
public MgmtSoftwareModuleClientResource uploadSoftwareModule() {
final ObjectMapper mapper = new ObjectMapper()
.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false)
.registerModule(new Jackson2HalModule());
return Feign.builder().contract(new IgnoreMultipleConsumersProducersSpringMvcContract())
.requestInterceptor(
new BasicAuthRequestInterceptor(configuration.getUsername(), configuration.getPassword()))
.logger(new Slf4jLogger()).encoder(new FeignMultipartEncoder())
.decoder(new ResponseEntityDecoder(new JacksonDecoder(mapper)))
.target(MgmtSoftwareModuleClientResource.class,
configuration.getUrl() + MgmtRestConstants.SOFTWAREMODULE_V1_REQUEST_MAPPING);
}
private static boolean containsArg(final String containsArg, final String... args) {
for (final String arg : args) {
if (arg.equalsIgnoreCase(containsArg)) {
return true;
@@ -80,4 +121,4 @@ public class Application implements CommandLineRunner {
}
return false;
}
}
}

View File

@@ -8,6 +8,9 @@
*/
package org.eclipse.hawkbit.mgmt.client;
import java.util.ArrayList;
import java.util.List;
import org.springframework.boot.context.properties.ConfigurationProperties;
/**
@@ -34,6 +37,142 @@ public class ClientConfigurationProperties {
private String password = "admin"; // NOSONAR this password is only used for
// examples
private final List<Scenario> scenarios = new ArrayList<>();
/**
* Simulation {@link Scenario}.
*
*/
public static class Scenario {
private boolean cleanRepository;
private int targets = 100;
private int distributionSets = 10;
private int appModulesPerDistributionSet = 2;
private String dsName = "Package";
private String smSwName = "Application";
private String smFwName = "Firmware";
private String targetName = "Device";
private int artifactsPerSM = 1;
private String targetAddress = "amqp:/simulator.replyTo";
private boolean runRollouts = true;
private int rolloutDeploymentGroups = 4;
/**
* Artifact size. Values can use the suffixed "MB" or "KB" to indicate a
* Megabyte or Kilobyte size.
*/
private String artifactSize = "1MB";
public boolean isCleanRepository() {
return cleanRepository;
}
public void setCleanRepository(final boolean cleanRepository) {
this.cleanRepository = cleanRepository;
}
public int getRolloutDeploymentGroups() {
return rolloutDeploymentGroups;
}
public void setRolloutDeploymentGroups(final int rolloutDeploymentGroups) {
this.rolloutDeploymentGroups = rolloutDeploymentGroups;
}
public boolean isRunRollouts() {
return runRollouts;
}
public void setRunRollouts(final boolean runRollouts) {
this.runRollouts = runRollouts;
}
public String getTargetAddress() {
return targetAddress;
}
public void setTargetAddress(final String targetAddress) {
this.targetAddress = targetAddress;
}
public int getArtifactsPerSM() {
return artifactsPerSM;
}
public void setArtifactsPerSM(final int artifactsPerSM) {
this.artifactsPerSM = artifactsPerSM;
}
public String getArtifactSize() {
return artifactSize;
}
public void setArtifactSize(final String artifactSize) {
this.artifactSize = artifactSize;
}
public String getTargetName() {
return targetName;
}
public void setTargetName(final String targetName) {
this.targetName = targetName;
}
public String getDsName() {
return dsName;
}
public void setDsName(final String dsName) {
this.dsName = dsName;
}
public String getSmSwName() {
return smSwName;
}
public void setSmSwName(final String smSwName) {
this.smSwName = smSwName;
}
public String getSmFwName() {
return smFwName;
}
public void setSmFwName(final String smFwName) {
this.smFwName = smFwName;
}
public int getTargets() {
return targets;
}
public int getDistributionSets() {
return distributionSets;
}
public int getAppModulesPerDistributionSet() {
return appModulesPerDistributionSet;
}
public void setTargets(final int targets) {
this.targets = targets;
}
public void setDistributionSets(final int distributionSets) {
this.distributionSets = distributionSets;
}
public void setAppModulesPerDistributionSet(final int appModulesPerDistributionSet) {
this.appModulesPerDistributionSet = appModulesPerDistributionSet;
}
}
public List<Scenario> getScenarios() {
return scenarios;
}
public String getUrl() {
return url;
}

View File

@@ -0,0 +1,242 @@
/**
* 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.mgmt.client.scenarios;
import java.util.List;
import java.util.Random;
import java.util.concurrent.TimeUnit;
import org.eclipse.hawkbit.mgmt.client.ClientConfigurationProperties;
import org.eclipse.hawkbit.mgmt.client.ClientConfigurationProperties.Scenario;
import org.eclipse.hawkbit.mgmt.client.resource.MgmtDistributionSetClientResource;
import org.eclipse.hawkbit.mgmt.client.resource.MgmtRolloutClientResource;
import org.eclipse.hawkbit.mgmt.client.resource.MgmtSoftwareModuleClientResource;
import org.eclipse.hawkbit.mgmt.client.resource.MgmtTargetClientResource;
import org.eclipse.hawkbit.mgmt.client.resource.builder.DistributionSetBuilder;
import org.eclipse.hawkbit.mgmt.client.resource.builder.RolloutBuilder;
import org.eclipse.hawkbit.mgmt.client.resource.builder.SoftwareModuleAssigmentBuilder;
import org.eclipse.hawkbit.mgmt.client.resource.builder.SoftwareModuleBuilder;
import org.eclipse.hawkbit.mgmt.client.resource.builder.TargetBuilder;
import org.eclipse.hawkbit.mgmt.client.scenarios.upload.ArtifactFile;
import org.eclipse.hawkbit.mgmt.json.model.PagedList;
import org.eclipse.hawkbit.mgmt.json.model.distributionset.MgmtDistributionSet;
import org.eclipse.hawkbit.mgmt.json.model.rollout.MgmtRolloutResponseBody;
import org.eclipse.hawkbit.mgmt.json.model.softwaremodule.MgmtSoftwareModule;
import org.eclipse.hawkbit.mgmt.json.model.target.MgmtTarget;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Qualifier;
/**
*
* A configurable scenario which runs the configured scenarios.
*
* @see {@link ClientConfigurationProperties#getScenarios()}
*
*/
public class ConfigurableScenario {
private static final int PAGE_SIZE = 100;
private static final Logger LOGGER = LoggerFactory.getLogger(ConfigurableScenario.class);
private final MgmtDistributionSetClientResource distributionSetResource;
private final MgmtSoftwareModuleClientResource softwareModuleResource;
private final MgmtSoftwareModuleClientResource uploadSoftwareModule;
private final MgmtTargetClientResource targetResource;
private final MgmtRolloutClientResource rolloutResource;
private final ClientConfigurationProperties clientConfigurationProperties;
public ConfigurableScenario(final MgmtDistributionSetClientResource distributionSetResource,
@Qualifier("mgmtSoftwareModuleClientResource") final MgmtSoftwareModuleClientResource softwareModuleResource,
@Qualifier("uploadSoftwareModule") final MgmtSoftwareModuleClientResource uploadSoftwareModule,
final MgmtTargetClientResource targetResource, final MgmtRolloutClientResource rolloutResource,
final ClientConfigurationProperties clientConfigurationProperties) {
this.distributionSetResource = distributionSetResource;
this.softwareModuleResource = softwareModuleResource;
this.uploadSoftwareModule = uploadSoftwareModule;
this.targetResource = targetResource;
this.rolloutResource = rolloutResource;
this.clientConfigurationProperties = clientConfigurationProperties;
}
/**
* Run the default getting started scenario.
*/
public void run() {
LOGGER.info("Running Configurable Scenario...");
clientConfigurationProperties.getScenarios().forEach(this::createScenario);
}
private void createScenario(final Scenario scenario) {
if (scenario.isCleanRepository()) {
cleanRepository();
}
createTargets(scenario);
createDistributionSets(scenario);
if (scenario.isRunRollouts()) {
runRollouts(scenario);
}
}
private void cleanRepository() {
LOGGER.info("Cleaning repository");
deleteTargets();
deleteRollouts();
deleteDistributionSets();
deleteSoftwareModules();
LOGGER.info("Cleaning repository -> Done");
}
private void deleteRollouts() {
// TODO: complete this as soon as rollouts can be deleted
}
private void deleteSoftwareModules() {
PagedList<MgmtSoftwareModule> modules;
do {
modules = softwareModuleResource.getSoftwareModules(0, PAGE_SIZE, null, null).getBody();
modules.getContent().forEach(module -> softwareModuleResource.deleteSoftwareModule(module.getModuleId()));
} while (modules.getTotal() > PAGE_SIZE);
}
private void deleteDistributionSets() {
PagedList<MgmtDistributionSet> distributionSets;
do {
distributionSets = distributionSetResource.getDistributionSets(0, PAGE_SIZE, null, null).getBody();
distributionSets.getContent().forEach(set -> distributionSetResource.deleteDistributionSet(set.getDsId()));
} while (distributionSets.getTotal() > PAGE_SIZE);
}
private void deleteTargets() {
PagedList<MgmtTarget> targets;
do {
targets = targetResource.getTargets(0, PAGE_SIZE, null, null).getBody();
targets.getContent().forEach(target -> targetResource.deleteTarget(target.getControllerId()));
} while (targets.getTotal() > PAGE_SIZE);
}
private void runRollouts(final Scenario scenario) {
distributionSetResource.getDistributionSets(0, scenario.getDistributionSets(), null, null).getBody()
.getContent().forEach(set -> runRollout(set, scenario));
}
private void runRollout(final MgmtDistributionSet set, final Scenario scenario) {
LOGGER.info("Run rollout for set {}", set.getDsId());
// create a Rollout
final MgmtRolloutResponseBody rolloutResponseBody = rolloutResource
.create(new RolloutBuilder().name("Rollout" + set.getName() + set.getVersion())
.groupSize(scenario.getRolloutDeploymentGroups()).targetFilterQuery("name==*")
.distributionSetId(set.getDsId()).successThreshold("80").errorThreshold("5").build())
.getBody();
// start the created Rollout
rolloutResource.start(rolloutResponseBody.getRolloutId(), true);
// wait until rollout is complete
do {
try {
TimeUnit.SECONDS.sleep(35);
} catch (final InterruptedException e) {
LOGGER.warn("Interrupted!");
Thread.currentThread().interrupt();
}
} while (targetResource.getTargets(0, 1, null, "updateStatus==IN_SYNC").getBody().getTotal() < scenario
.getTargets());
LOGGER.info("Run rollout for set {} -> Done", set.getDsId());
}
private void createDistributionSets(final Scenario scenario) {
LOGGER.info("Creating {} distribution sets", scenario.getDistributionSets());
final byte[] artifact = generateArtifact(scenario);
distributionSetResource.createDistributionSets(new DistributionSetBuilder().name(scenario.getDsName())
.type("os_app").version("1.0.").buildAsList(scenario.getDistributionSets())).getBody()
.forEach(dsSet -> {
final List<MgmtSoftwareModule> modules = addModules(scenario, dsSet, artifact);
final SoftwareModuleAssigmentBuilder assign = new SoftwareModuleAssigmentBuilder();
modules.forEach(module -> assign.id(module.getModuleId()));
distributionSetResource.assignSoftwareModules(dsSet.getDsId(), assign.build());
});
LOGGER.info("Creating {} distribution sets -> Done", scenario.getDistributionSets());
}
private List<MgmtSoftwareModule> addModules(final Scenario scenario, final MgmtDistributionSet dsSet,
final byte[] artifact) {
final List<MgmtSoftwareModule> modules = softwareModuleResource
.createSoftwareModules(new SoftwareModuleBuilder().name(scenario.getSmFwName() + "-os")
.version(dsSet.getVersion()).type("os").build())
.getBody();
modules.addAll(softwareModuleResource.createSoftwareModules(
new SoftwareModuleBuilder().name(scenario.getSmSwName() + "-app").version(dsSet.getVersion() + ".")
.type("application").buildAsList(scenario.getAppModulesPerDistributionSet()))
.getBody());
for (int x = 0; x < scenario.getArtifactsPerSM(); x++) {
modules.forEach(module -> {
final ArtifactFile file = new ArtifactFile("dummyfile.dummy", null, "multipart/form-data", artifact);
uploadSoftwareModule.uploadArtifact(module.getModuleId(), file, null, null, null);
});
}
return modules;
}
private static byte[] generateArtifact(final Scenario scenario) {
// Exception squid:S2245 - not used for cryptographic function
@SuppressWarnings("squid:S2245")
final Random random = new Random();
// create byte array
final byte[] nbyte = new byte[parseSize(scenario.getArtifactSize())];
// put the next byte in the array
random.nextBytes(nbyte);
return nbyte;
}
private void createTargets(final Scenario scenario) {
LOGGER.info("Creating {} targets", scenario.getTargets());
for (int i = 0; i < (scenario.getTargets() / PAGE_SIZE); i++) {
targetResource.createTargets(
new TargetBuilder().controllerId(scenario.getTargetName()).address(scenario.getTargetAddress())
.buildAsList(i * PAGE_SIZE, (i + 1) * PAGE_SIZE > scenario.getTargets()
? (scenario.getTargets() - (i * PAGE_SIZE)) : PAGE_SIZE));
}
LOGGER.info("Creating {} targets -> Done", scenario.getTargets());
}
private static int parseSize(final String s) {
final String size = s.toUpperCase();
if (size.endsWith("KB")) {
return Integer.valueOf(size.substring(0, size.length() - 2)) * 1024;
}
if (size.endsWith("MB")) {
return Integer.valueOf(size.substring(0, size.length() - 2)) * 1024 * 1024;
}
return Integer.valueOf(size);
}
}

View File

@@ -28,6 +28,7 @@ import org.eclipse.hawkbit.mgmt.json.model.rollout.MgmtRolloutResponseBody;
import org.eclipse.hawkbit.mgmt.json.model.softwaremodule.MgmtSoftwareModule;
import org.eclipse.hawkbit.mgmt.json.model.softwaremoduletype.MgmtSoftwareModuleType;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
/**
* Example for creating and starting a Rollout.
@@ -36,7 +37,7 @@ import org.springframework.beans.factory.annotation.Autowired;
public class CreateStartedRolloutExample {
/* known software module type name and key */
private static final String SM_MODULE_TYPE = "firmware";
private static final String SM_MODULE_TYPE = "gettingstarted-rollout-example";
/* known distribution set type name and key */
private static final String DS_MODULE_TYPE = SM_MODULE_TYPE;
@@ -45,6 +46,7 @@ public class CreateStartedRolloutExample {
private MgmtDistributionSetClientResource distributionSetResource;
@Autowired
@Qualifier("mgmtSoftwareModuleClientResource")
private MgmtSoftwareModuleClientResource softwareModuleResource;
@Autowired

View File

@@ -1,135 +0,0 @@
/**
* 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.mgmt.client.scenarios;
import java.util.List;
import org.eclipse.hawkbit.mgmt.client.resource.MgmtDistributionSetClientResource;
import org.eclipse.hawkbit.mgmt.client.resource.MgmtDistributionSetTypeClientResource;
import org.eclipse.hawkbit.mgmt.client.resource.MgmtSoftwareModuleClientResource;
import org.eclipse.hawkbit.mgmt.client.resource.MgmtSoftwareModuleTypeClientResource;
import org.eclipse.hawkbit.mgmt.client.resource.builder.DistributionSetBuilder;
import org.eclipse.hawkbit.mgmt.client.resource.builder.DistributionSetTypeBuilder;
import org.eclipse.hawkbit.mgmt.client.resource.builder.SoftwareModuleAssigmentBuilder;
import org.eclipse.hawkbit.mgmt.client.resource.builder.SoftwareModuleBuilder;
import org.eclipse.hawkbit.mgmt.client.resource.builder.SoftwareModuleTypeBuilder;
import org.eclipse.hawkbit.mgmt.json.model.distributionset.MgmtDistributionSet;
import org.eclipse.hawkbit.mgmt.json.model.softwaremodule.MgmtSoftwareModule;
import org.eclipse.hawkbit.mgmt.json.model.softwaremoduletype.MgmtSoftwareModuleType;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
/**
*
* Default getting started scenario.
*
*/
public class GettingStartedDefaultScenario {
private static final Logger LOGGER = LoggerFactory.getLogger(GettingStartedDefaultScenario.class);
/* known software module type name and key */
private static final String SM_MODULE_TYPE = "gettingstarted";
/* known distribution set type name and key */
private static final String DS_MODULE_TYPE = SM_MODULE_TYPE;
/* known distribution name of this getting started example */
private static final String SM_EXAMPLE_NAME = "gettingstarted-example";
/* known distribution name of this getting started example */
private static final String DS_EXAMPLE_NAME = SM_EXAMPLE_NAME;
@Autowired
private MgmtDistributionSetClientResource distributionSetResource;
@Autowired
private MgmtDistributionSetTypeClientResource distributionSetTypeResource;
@Autowired
private MgmtSoftwareModuleClientResource softwareModuleResource;
@Autowired
private MgmtSoftwareModuleTypeClientResource softwareModuleTypeResource;
/**
* Run the default getting started scenario.
*/
public void run() {
LOGGER.info("Running Getting-Started-Scenario...");
// create one SoftwareModuleTypes
LOGGER.info("Creating software module type {}", SM_MODULE_TYPE);
final List<MgmtSoftwareModuleType> createdSoftwareModuleTypes = softwareModuleTypeResource
.createSoftwareModuleTypes(new SoftwareModuleTypeBuilder().key(SM_MODULE_TYPE).name(SM_MODULE_TYPE)
.maxAssignments(1).build())
.getBody();
// create one DistributionSetType
LOGGER.info("Creating distribution set type {}", DS_MODULE_TYPE);
distributionSetTypeResource.createDistributionSetTypes(new DistributionSetTypeBuilder().key(DS_MODULE_TYPE)
.name(DS_MODULE_TYPE).mandatorymodules(createdSoftwareModuleTypes.get(0).getModuleId()).build());
// create three DistributionSet
final String dsVersion1 = "1.0.0";
final String dsVersion2 = "2.0.0";
final String dsVersion3 = "2.1.0";
LOGGER.info("Creating distribution set {}:{}", DS_EXAMPLE_NAME, dsVersion1);
final List<MgmtDistributionSet> distributionSetsRest1 = distributionSetResource.createDistributionSets(
new DistributionSetBuilder().name(DS_EXAMPLE_NAME).version(dsVersion1).type(DS_MODULE_TYPE).build())
.getBody();
LOGGER.info("Creating distribution set {}:{}", DS_EXAMPLE_NAME, dsVersion2);
final List<MgmtDistributionSet> distributionSetsRest2 = distributionSetResource.createDistributionSets(
new DistributionSetBuilder().name(DS_EXAMPLE_NAME).version(dsVersion2).type(DS_MODULE_TYPE).build())
.getBody();
LOGGER.info("Creating distribution set {}:{}", DS_EXAMPLE_NAME, dsVersion3);
final List<MgmtDistributionSet> distributionSetsRest3 = distributionSetResource.createDistributionSets(
new DistributionSetBuilder().name(DS_EXAMPLE_NAME).version(dsVersion3).type(DS_MODULE_TYPE).build())
.getBody();
// create three SoftwareModules
final String swVersion1 = "1";
final String swVersion2 = "2";
final String swVersion3 = "3";
LOGGER.info("Creating distribution set {}:{}", SM_EXAMPLE_NAME, swVersion1);
final List<MgmtSoftwareModule> softwareModulesRest1 = softwareModuleResource.createSoftwareModules(
new SoftwareModuleBuilder().name(SM_EXAMPLE_NAME).version(swVersion1).type(SM_MODULE_TYPE).build())
.getBody();
LOGGER.info("Creating distribution set {}:{}", SM_EXAMPLE_NAME, swVersion2);
final List<MgmtSoftwareModule> softwareModulesRest2 = softwareModuleResource.createSoftwareModules(
new SoftwareModuleBuilder().name(SM_EXAMPLE_NAME).version(swVersion2).type(SM_MODULE_TYPE).build())
.getBody();
LOGGER.info("Creating distribution set {}:{}", SM_EXAMPLE_NAME, swVersion3);
final List<MgmtSoftwareModule> softwareModulesRest3 = softwareModuleResource.createSoftwareModules(
new SoftwareModuleBuilder().name(SM_EXAMPLE_NAME).version(swVersion3).type(SM_MODULE_TYPE).build())
.getBody();
// Assign SoftwareModules to DistributionSet
LOGGER.info("Assign software module {}:{} to distribution set {}:{}", SM_EXAMPLE_NAME, swVersion1,
DS_EXAMPLE_NAME, dsVersion1);
distributionSetResource.assignSoftwareModules(distributionSetsRest1.get(0).getDsId(),
new SoftwareModuleAssigmentBuilder().id(softwareModulesRest1.get(0).getModuleId()).build());
LOGGER.info("Assign software module {}:{} to distribution set {}:{}", SM_EXAMPLE_NAME, swVersion2,
DS_EXAMPLE_NAME, dsVersion2);
distributionSetResource.assignSoftwareModules(distributionSetsRest2.get(0).getDsId(),
new SoftwareModuleAssigmentBuilder().id(softwareModulesRest2.get(0).getModuleId()).build());
LOGGER.info("Assign software module {}:{} to distribution set {}:{}", SM_EXAMPLE_NAME, swVersion3,
DS_EXAMPLE_NAME, dsVersion3);
distributionSetResource.assignSoftwareModules(distributionSetsRest3.get(0).getDsId(),
new SoftwareModuleAssigmentBuilder().id(softwareModulesRest3.get(0).getModuleId()).build());
}
}

View File

@@ -0,0 +1,108 @@
/**
* 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.mgmt.client.scenarios.upload;
import java.io.ByteArrayInputStream;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.util.Optional;
import org.springframework.util.Assert;
import org.springframework.util.FileCopyUtils;
import org.springframework.web.multipart.MultipartFile;
/**
* Implementation for {@link MultipartFile} for hawkBit artifact upload.
*
*/
public class ArtifactFile implements MultipartFile {
private final String name;
private final String originalFilename;
private final String contentType;
private final byte[] content;
/**
* Create a new ArtifactFile with the given content.
*
* @param name
* the name of the file
* @param content
* the content of the file
*/
public ArtifactFile(final String name, final byte[] content) {
this(name, "", null, content);
}
/**
* Create a new ArtifactFile with the given content.
*
* @param name
* of the file
* @param originalFilename
* the original filename (as on the client's machine)
* @param contentType
* the content type
* @param content
* of the file
*/
public ArtifactFile(final String name, final String originalFilename, final String contentType,
final byte[] content) {
Assert.hasLength(name, "Name must not be null");
this.name = name;
this.originalFilename = Optional.ofNullable(originalFilename).orElse("");
this.contentType = contentType;
this.content = Optional.ofNullable(content).orElse(new byte[0]);
}
@Override
public String getName() {
return this.name;
}
@Override
public String getOriginalFilename() {
return this.originalFilename;
}
@Override
public String getContentType() {
return this.contentType;
}
@Override
public boolean isEmpty() {
return this.content.length == 0;
}
@Override
public long getSize() {
return this.content.length;
}
@Override
public byte[] getBytes() throws IOException {
return this.content;
}
@Override
public InputStream getInputStream() throws IOException {
return new ByteArrayInputStream(this.content);
}
@Override
public void transferTo(final File dest) throws IOException {
FileCopyUtils.copy(this.content, dest);
}
}

View File

@@ -0,0 +1,155 @@
/**
* 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.mgmt.client.scenarios.upload;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.lang.reflect.Type;
import java.nio.charset.Charset;
import java.util.List;
import java.util.Map.Entry;
import org.springframework.core.io.InputStreamResource;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpOutputMessage;
import org.springframework.http.MediaType;
import org.springframework.http.converter.HttpMessageConverter;
import org.springframework.util.LinkedMultiValueMap;
import org.springframework.util.MultiValueMap;
import org.springframework.web.client.RestTemplate;
import org.springframework.web.multipart.MultipartFile;
import feign.RequestTemplate;
import feign.codec.EncodeException;
import feign.codec.Encoder;
/**
* A feign encoder implementation which handles {@link MultipartFile} body.
*/
public class FeignMultipartEncoder implements Encoder {
private final List<HttpMessageConverter<?>> converters = new RestTemplate().getMessageConverters();
private final HttpHeaders multipartHeaders = new HttpHeaders();
private final HttpHeaders jsonHeaders = new HttpHeaders();
private static final Charset UTF_8 = Charset.forName("UTF-8");
public FeignMultipartEncoder() {
multipartHeaders.setContentType(MediaType.MULTIPART_FORM_DATA);
jsonHeaders.setContentType(MediaType.APPLICATION_JSON);
}
@Override
public void encode(final Object object, final Type bodyType, final RequestTemplate template) {
encodeMultipartFormRequest(object, template);
}
private void encodeMultipartFormRequest(final Object value, final RequestTemplate template) {
if (value == null) {
throw new EncodeException("Cannot encode request with null value.");
}
if (!isMultipartFile(value)) {
throw new EncodeException("Only multipart can be handled by this encoder");
}
encodeRequest(encodeMultipartFile((MultipartFile) value), multipartHeaders, template);
}
@SuppressWarnings("unchecked")
private void encodeRequest(final Object value, final HttpHeaders requestHeaders, final RequestTemplate template) {
final ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
final HttpOutputMessage dummyRequest = new HttpOutputMessageImpl(outputStream, requestHeaders);
try {
final Class<?> requestType = value.getClass();
final MediaType requestContentType = requestHeaders.getContentType();
for (final HttpMessageConverter<?> messageConverter : converters) {
if (messageConverter.canWrite(requestType, requestContentType)) {
((HttpMessageConverter<Object>) messageConverter).write(value, requestContentType, dummyRequest);
break;
}
}
} catch (final IOException ex) {
throw new EncodeException("Cannot encode request.", ex);
}
final HttpHeaders headers = dummyRequest.getHeaders();
if (headers != null) {
for (final Entry<String, List<String>> entry : headers.entrySet()) {
template.header(entry.getKey(), entry.getValue());
}
}
/*
* we should use a template output stream... this will cause issues if
* files are too big, since the whole request will be in memory.
*/
template.body(outputStream.toByteArray(), UTF_8);
}
private MultiValueMap<String, Object> encodeMultipartFile(final MultipartFile file) {
try {
final MultiValueMap<String, Object> multiValueMap = new LinkedMultiValueMap<>();
multiValueMap.add("file", new MultipartFileResource(file.getName(), file.getSize(), file.getInputStream()));
return multiValueMap;
} catch (final IOException ex) {
throw new EncodeException("Cannot encode request.", ex);
}
}
private static boolean isMultipartFile(final Object object) {
return object instanceof MultipartFile;
}
private static final class HttpOutputMessageImpl implements HttpOutputMessage {
private final OutputStream body;
private final HttpHeaders headers;
private HttpOutputMessageImpl(final OutputStream body, final HttpHeaders headers) {
this.body = body;
this.headers = headers;
}
@Override
public OutputStream getBody() throws IOException {
return body;
}
@Override
public HttpHeaders getHeaders() {
return headers;
}
}
/**
* Dummy resource class. Wraps file content and its original name.
*/
static class MultipartFileResource extends InputStreamResource {
private final String filename;
private final long size;
public MultipartFileResource(final String filename, final long size, final InputStream inputStream) {
super(inputStream);
this.size = size;
this.filename = filename;
}
@Override
public String getFilename() {
return this.filename;
}
@Override
public long contentLength() throws IOException {
return size;
}
}
}

View File

@@ -7,8 +7,16 @@
# http://www.eclipse.org/legal/epl-v10.html
#
hawkbit.url=localhost:8080
hawkbit.url=http://localhost:8080
hawkbit.username=admin
hawkbit.password=admin
spring.main.show-banner=false
spring.main.show-banner=false
hawkbit.scenarios.[0].cleanRepository=false
hawkbit.scenarios.[0].targets=0
hawkbit.scenarios.[0].ds-name=gettingstarted-example
hawkbit.scenarios.[0].distribution-sets=3
hawkbit.scenarios.[0].sm-fw-name=gettingstarted-example
hawkbit.scenarios.[0].sm-sw-name=gettingstarted-example
hawkbit.scenarios.[0].runRollouts=false

View File

@@ -10,17 +10,11 @@
-->
<configuration>
<appender name="STDOUT" class="ch.qos.logback.core.ConsoleAppender">
<!-- Log message format -->
<encoder>
<pattern>%d{HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%n
</pattern>
</encoder>
</appender>
<include resource="org/springframework/boot/logging/logback/base.xml" />
<logger name="org.eclipse.hawkbit" level="info" />
<logger name="feign.Logger" level="debug" />
<root level="error">
<root level="INFO">
<appender-ref ref="STDOUT" />
</root>

View File

@@ -87,9 +87,7 @@ public class ArtifactStore implements ArtifactRepository {
/**
* Retrieves a {@link GridFSDBFile} from the store by it's MD5 hash.
*
* @param tenant
* the tenant to retrieve the artifacts from, ignore case.
*
* @param md5Hash
* the md5-hash of the file to lookup.
* @return The gridfs file object or {@code null} if no file exists.
@@ -100,9 +98,7 @@ public class ArtifactStore implements ArtifactRepository {
/**
* Retrieves a {@link GridFSDBFile} from the store by it's object id.
*
* @param tenant
* the tenant to retrieve the artifacts from, ignore case.
*
* @param id
* the id of the file to lookup.
* @return The gridfs file object or {@code null} if no file exists.
@@ -231,15 +227,13 @@ public class ArtifactStore implements ArtifactRepository {
* @return a paged list of artifacts mapped from the given dbFiles
*/
private List<DbArtifact> map(final List<GridFSDBFile> dbFiles) {
return dbFiles.stream().map(dbFile -> map(dbFile)).collect(Collectors.toList());
return dbFiles.stream().map(this::map).collect(Collectors.toList());
}
/**
* Retrieves a list of {@link GridFSDBFile} from the store by all SHA1
* hashes.
*
* @param tenant
* the tenant to retrieve the artifacts from, ignore case.
*
* @param sha1Hashes
* the sha1-hashes of the files to lookup.
* @return list of artifacts
@@ -252,8 +246,6 @@ public class ArtifactStore implements ArtifactRepository {
/**
* Retrieves a list of {@link GridFSDBFile} from the store by all ids.
*
* @param tenant
* the tenant to retrieve the artifacts from, ignore case.
* @param ids
* the ids of the files to lookup.
* @return list of artfiacts

View File

@@ -10,18 +10,13 @@ package org.eclipse.hawkbit.artifact.repository;
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Import;
/**
* Auto configuration for the {@link ArtifactStore}.
*
*
*
*/
@Configuration
@ComponentScan
@ConditionalOnMissingBean(value = ArtifactRepository.class)
@Import(value = MongoConfiguration.class)
public class ArtifactStoreAutoConfiguration {

View File

@@ -24,82 +24,73 @@ public enum SpServerError {
/**
*
*/
SP_REPO_ENTITY_ALRREADY_EXISTS("hawkbit.server.error.repo.entitiyAlreayExists",
"The given entity already exists in database"),
SP_REPO_ENTITY_ALRREADY_EXISTS("hawkbit.server.error.repo.entitiyAlreayExists", "The given entity already exists in database"),
/**
*
*/
SP_REPO_INVALID_TARGET_ADDRESS("hawkbit.server.error.repo.invalidTargetAddress", "The target address is not well formed"),
/**
*
*/
SP_REPO_ENTITY_NOT_EXISTS("hawkbit.server.error.repo.entitiyNotFound",
"The given entity does not exist in database"),
SP_REPO_ENTITY_NOT_EXISTS("hawkbit.server.error.repo.entitiyNotFound", "The given entity does not exist in database"),
/**
*
*/
SP_REPO_CONCURRENT_MODIFICATION("hawkbit.server.error.repo.concurrentModification",
"The given entity has been changed by another user/session"),
SP_REPO_CONCURRENT_MODIFICATION("hawkbit.server.error.repo.concurrentModification", "The given entity has been changed by another user/session"),
/**
*
*/
SP_REST_SORT_PARAM_SYNTAX("hawkbit.server.error.rest.param.sortParamSyntax",
"The given sort paramter is not well formed"),
SP_REST_SORT_PARAM_SYNTAX("hawkbit.server.error.rest.param.sortParamSyntax", "The given sort paramter is not well formed"),
/**
*
*/
SP_REST_RSQL_SEARCH_PARAM_SYNTAX("hawkbit.server.error.rest.param.rsqlParamSyntax",
"The given search paramter is not well formed"),
SP_REST_RSQL_SEARCH_PARAM_SYNTAX("hawkbit.server.error.rest.param.rsqlParamSyntax", "The given search paramter is not well formed"),
/**
*
*/
SP_REST_RSQL_PARAM_INVALID_FIELD("hawkbit.server.error.rest.param.rsqlInvalidField",
"The given search parameter field does not exist"),
SP_REST_RSQL_PARAM_INVALID_FIELD("hawkbit.server.error.rest.param.rsqlInvalidField", "The given search parameter field does not exist"),
/**
*
*/
SP_REST_SORT_PARAM_INVALID_FIELD("hawkbit.server.error.rest.param.invalidField",
"The given sort parameter field does not exist"),
SP_REST_SORT_PARAM_INVALID_FIELD("hawkbit.server.error.rest.param.invalidField", "The given sort parameter field does not exist"),
/**
*
*/
SP_REST_SORT_PARAM_INVALID_DIRECTION("hawkbit.server.error.rest.param.invalidDirection",
"The given sort parameter direction does not exist"),
SP_REST_SORT_PARAM_INVALID_DIRECTION("hawkbit.server.error.rest.param.invalidDirection", "The given sort parameter direction does not exist"),
/**
*
*/
SP_REST_BODY_NOT_READABLE("hawkbit.server.error.rest.body.notReadable",
"The given request body is not well formed"),
SP_REST_BODY_NOT_READABLE("hawkbit.server.error.rest.body.notReadable", "The given request body is not well formed"),
/**
*
*/
SP_ARTIFACT_UPLOAD_FAILED("hawkbit.server.error.artifact.uploadFailed",
"Upload of artifact failed with internal server error."),
SP_ARTIFACT_UPLOAD_FAILED("hawkbit.server.error.artifact.uploadFailed", "Upload of artifact failed with internal server error."),
/**
*
*/
SP_ARTIFACT_UPLOAD_FAILED_MD5_MATCH("hawkbit.server.error.artifact.uploadFailed.checksum.md5.match",
"Upload of artifact failed as the provided MD5 checksum did not match with the provided artifact."),
SP_ARTIFACT_UPLOAD_FAILED_MD5_MATCH("hawkbit.server.error.artifact.uploadFailed.checksum.md5.match", "Upload of artifact failed as the provided MD5 checksum did not match with the provided artifact."),
/**
*
*/
SP_ARTIFACT_UPLOAD_FAILED_SHA1_MATCH("hawkbit.server.error.artifact.uploadFailed.checksum.sha1.match",
"Upload of artifact failed as the provided SHA1 checksum did not match with the provided artifact."),
SP_ARTIFACT_UPLOAD_FAILED_SHA1_MATCH("hawkbit.server.error.artifact.uploadFailed.checksum.sha1.match", "Upload of artifact failed as the provided SHA1 checksum did not match with the provided artifact."),
/**
*
*/
SP_DS_CREATION_FAILED_MISSING_MODULE("hawkbit.server.error.distributionset.creationFailed.missingModule",
"Creation if Distribution Set failed as module is missing that is configured as mandatory."),
SP_DS_CREATION_FAILED_MISSING_MODULE("hawkbit.server.error.distributionset.creationFailed.missingModule", "Creation if Distribution Set failed as module is missing that is configured as mandatory."),
/**
*
*/
SP_ARTIFACT_UPLOAD_FILE_LIMIT_EXCEEDED("hawkbit.server.error.artifact.uploadFailed.sizelimitexceeded",
"Upload of artifact failed as the file exceeds its maximum permitted size"),
SP_ARTIFACT_UPLOAD_FILE_LIMIT_EXCEEDED("hawkbit.server.error.artifact.uploadFailed.sizelimitexceeded", "Upload of artifact failed as the file exceeds its maximum permitted size"),
/**
*
*/
@@ -108,63 +99,53 @@ public enum SpServerError {
/**
*
*/
SP_ARTIFACT_DELETE_FAILED("hawkbit.server.error.artifact.deleteFailed",
"Deletion of artifact failed with internal server error."),
SP_ARTIFACT_DELETE_FAILED("hawkbit.server.error.artifact.deleteFailed", "Deletion of artifact failed with internal server error."),
/**
*
*/
SP_ARTIFACT_LOAD_FAILED("hawkbit.server.error.artifact.loadFailed",
"Load of artifact failed with internal server error."),
SP_ARTIFACT_LOAD_FAILED("hawkbit.server.error.artifact.loadFailed", "Load of artifact failed with internal server error."),
/**
*
*/
SP_ACTION_STATUS_TO_MANY_ENTRIES("hawkbit.server.error.action.status.tooManyEntries",
"Too many status entries have been inserted."),
SP_ACTION_STATUS_TO_MANY_ENTRIES("hawkbit.server.error.action.status.tooManyEntries", "Too many status entries have been inserted."),
/**
*
*/
SP_ATTRIBUTES_TO_MANY_ENTRIES("hawkbit.server.error.target.attributes.tooManyEntries",
"Too many attribute entries have been inserted."),
SP_ATTRIBUTES_TO_MANY_ENTRIES("hawkbit.server.error.target.attributes.tooManyEntries", "Too many attribute entries have been inserted."),
/**
* error message, which describes that the action can not be canceled cause
* the action is inactive.
*/
SP_ACTION_NOT_CANCELABLE("hawkbit.server.error.action.notcancelable",
"Only active actions which are in status pending are canceable."),
SP_ACTION_NOT_CANCELABLE("hawkbit.server.error.action.notcancelable", "Only active actions which are in status pending are canceable."),
/**
* error message, which describes that the action can not be force quit
* cause the action is inactive.
*/
SP_ACTION_NOT_FORCE_QUITABLE("hawkbit.server.error.action.notforcequitable",
"Only active actions which are in status pending can be force quit."),
SP_ACTION_NOT_FORCE_QUITABLE("hawkbit.server.error.action.notforcequitable", "Only active actions which are in status pending can be force quit."),
/**
*
*/
SP_DS_INCOMPLETE("hawkbit.server.error.distributionset.incomplete",
"Distribution set is assigned to a a target that is incomplete (i.e. mandatory modules are missing)"),
SP_DS_INCOMPLETE("hawkbit.server.error.distributionset.incomplete", "Distribution set is assigned to a a target that is incomplete (i.e. mandatory modules are missing)"),
/**
*
*/
SP_DS_TYPE_UNDEFINED("hawkbit.server.error.distributionset.type.undefined",
"Distribution set type is not yet defined. Modules cannot be added until definition."),
SP_DS_TYPE_UNDEFINED("hawkbit.server.error.distributionset.type.undefined", "Distribution set type is not yet defined. Modules cannot be added until definition."),
/**
*
*/
SP_DS_MODULE_UNSUPPORTED("hawkbit.server.error.distributionset.modules.unsupported",
"Distribution set type does not contain the given module, i.e. is incompatible."),
SP_DS_MODULE_UNSUPPORTED("hawkbit.server.error.distributionset.modules.unsupported", "Distribution set type does not contain the given module, i.e. is incompatible."),
/**
*
*/
SP_REPO_TENANT_NOT_EXISTS("hawkbit.server.error.repo.tenantNotExists",
"The entity cannot be inserted due the tenant does not exists"),
SP_REPO_TENANT_NOT_EXISTS("hawkbit.server.error.repo.tenantNotExists", "The entity cannot be inserted due the tenant does not exists"),
/**
*
@@ -174,14 +155,12 @@ public enum SpServerError {
/**
*
*/
SP_REPO_ENTITY_READ_ONLY("hawkbit.server.error.entityreadonly",
"The given entity is read only and the change cannot be completed."),
SP_REPO_ENTITY_READ_ONLY("hawkbit.server.error.entityreadonly", "The given entity is read only and the change cannot be completed."),
/**
*
*/
SP_CONFIGURATION_VALUE_INVALID("hawkbit.server.error.configValueInvalid",
"The given configuration value is invalid."),
SP_CONFIGURATION_VALUE_INVALID("hawkbit.server.error.configValueInvalid", "The given configuration value is invalid."),
/**
*
*/
@@ -190,8 +169,7 @@ public enum SpServerError {
/**
*
*/
SP_ROLLOUT_ILLEGAL_STATE("hawkbit.server.error.rollout.illegalstate",
"The rollout is currently in the wrong state for the current operation");
SP_ROLLOUT_ILLEGAL_STATE("hawkbit.server.error.rollout.illegalstate", "The rollout is currently in the wrong state for the current operation");
private final String key;
private final String message;

View File

@@ -196,8 +196,8 @@ public class AmqpConfiguration {
}
/**
* Create the Binding {@link AmqpConfiguration#receiverQueueFromSp()} to
* {@link AmqpConfiguration#senderConnectorToSpExchange()}.
* Create the Binding {@link AmqpConfiguration#receiverQueue()} to
* {@link AmqpConfiguration#senderExchange()}.
*
* @return the binding and create the queue and exchange
*/
@@ -236,9 +236,12 @@ public class AmqpConfiguration {
@Bean(name = { "listenerContainerFactory" })
public SimpleRabbitListenerContainerFactory listenerContainerFactory() {
final SimpleRabbitListenerContainerFactory containerFactory = new SimpleRabbitListenerContainerFactory();
containerFactory.setDefaultRequeueRejected(false);
containerFactory.setDefaultRequeueRejected(true);
containerFactory.setConnectionFactory(rabbitConnectionFactory);
containerFactory.setMissingQueuesFatal(amqpProperties.isMissingQueuesFatal());
containerFactory.setConcurrentConsumers(amqpProperties.getInitialConcurrentConsumers());
containerFactory.setMaxConcurrentConsumers(amqpProperties.getMaxConcurrentConsumers());
containerFactory.setPrefetchCount(amqpProperties.getPrefetchCount());
return containerFactory;
}

View File

@@ -14,6 +14,7 @@ import java.util.Collections;
import java.util.List;
import java.util.UUID;
import org.apache.commons.lang3.ArrayUtils;
import org.apache.commons.lang3.StringUtils;
import org.eclipse.hawkbit.api.HostnameResolver;
import org.eclipse.hawkbit.artifact.repository.model.DbArtifact;
@@ -35,8 +36,10 @@ import org.eclipse.hawkbit.im.authentication.TenantAwareAuthenticationDetails;
import org.eclipse.hawkbit.repository.ArtifactManagement;
import org.eclipse.hawkbit.repository.ControllerManagement;
import org.eclipse.hawkbit.repository.EntityFactory;
import org.eclipse.hawkbit.repository.RepositoryConstants;
import org.eclipse.hawkbit.repository.eventbus.event.TargetAssignDistributionSetEvent;
import org.eclipse.hawkbit.repository.exception.EntityNotFoundException;
import org.eclipse.hawkbit.repository.exception.TenantNotExistException;
import org.eclipse.hawkbit.repository.model.Action;
import org.eclipse.hawkbit.repository.model.Action.Status;
import org.eclipse.hawkbit.repository.model.ActionStatus;
@@ -47,6 +50,7 @@ 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.core.MessageProperties;
import org.springframework.amqp.rabbit.annotation.RabbitListener;
@@ -111,12 +115,6 @@ public class AmqpMessageHandlerService extends BaseAmqpService {
super(defaultTemplate);
}
@RabbitListener(queues = "${hawkbit.dmf.rabbitmq.receiverQueue}", containerFactory = "listenerContainerFactory")
private 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());
}
/**
* Method to handle all incoming amqp messages.
*
@@ -124,14 +122,17 @@ public class AmqpMessageHandlerService extends BaseAmqpService {
* incoming message
* @param type
* the message type
* @param contentType
* the contentType of the message
* @param tenant
* the contentType of the message
* @param virtualHost
* the virtual host
*
* @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) {
return onMessage(message, type, tenant, getRabbitTemplate().getConnectionFactory().getVirtualHost());
}
public Message onMessage(final Message message, final String type, final String tenant, final String virtualHost) {
checkContentTypeJson(message);
final SecurityContext oldContext = SecurityContextHolder.getContext();
@@ -153,6 +154,10 @@ public class AmqpMessageHandlerService extends BaseAmqpService {
default:
logAndThrowMessageError(message, "No handle method was found for the given message type.");
}
} catch (final IllegalArgumentException ex) {
throw new AmqpRejectAndDontRequeueException("Invalid message!", ex);
} catch (final TenantNotExistException teex) {
throw new AmqpRejectAndDontRequeueException(teex);
} finally {
SecurityContextHolder.setContext(oldContext);
}
@@ -340,11 +345,7 @@ public class AmqpMessageHandlerService extends BaseAmqpService {
final ActionUpdateStatus actionUpdateStatus = convertMessage(message, ActionUpdateStatus.class);
final Action action = checkActionExist(message, actionUpdateStatus);
final ActionStatus actionStatus = entityFactory.generateActionStatus();
actionUpdateStatus.getMessage().forEach(actionStatus::addMessage);
actionStatus.setAction(action);
actionStatus.setOccurredAt(System.currentTimeMillis());
final ActionStatus actionStatus = createActionStatus(message, actionUpdateStatus, action);
switch (actionUpdateStatus.getActionStatus()) {
case DOWNLOAD:
@@ -382,6 +383,21 @@ public class AmqpMessageHandlerService extends BaseAmqpService {
}
}
private ActionStatus createActionStatus(final Message message, final ActionUpdateStatus actionUpdateStatus,
final Action action) {
final ActionStatus actionStatus = entityFactory.generateActionStatus();
actionUpdateStatus.getMessage().forEach(actionStatus::addMessage);
if (ArrayUtils.isNotEmpty(message.getMessageProperties().getCorrelationId())) {
actionStatus.addMessage(RepositoryConstants.SERVER_MESSAGE_PREFIX + "DMF message correlation-id "
+ message.getMessageProperties().getCorrelationId());
}
actionStatus.setAction(action);
actionStatus.setOccurredAt(System.currentTimeMillis());
return actionStatus;
}
private Action getUpdateActionStatus(final ActionStatus actionStatus) {
if (actionStatus.getStatus().equals(Status.CANCELED)) {
return controllerManagement.addCancelActionStatus(actionStatus);
@@ -421,7 +437,7 @@ public class AmqpMessageHandlerService extends BaseAmqpService {
}
}
private void checkContentTypeJson(final Message message) {
private static void checkContentTypeJson(final Message message) {
final MessageProperties messageProperties = message.getMessageProperties();
if (messageProperties.getContentType() != null && messageProperties.getContentType().contains("json")) {
return;

View File

@@ -38,13 +38,54 @@ public class AmqpProperties {
/**
* Missing queue fatal.
*/
private boolean missingQueuesFatal = false;
private boolean missingQueuesFatal;
/**
* Requested heartbeat interval from broker in {@link TimeUnit#SECONDS}.
*/
private int requestedHeartBeat = (int) TimeUnit.SECONDS.toSeconds(60);
/**
* Sets an upper limit to the number of consumers.
*/
private int maxConcurrentConsumers = 10;
/**
* 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 = 10;
/**
* Initial number of consumers. Is scaled up if necessary up to
* {@link #maxConcurrentConsumers}.
*/
private int initialConcurrentConsumers = 3;
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;
}
/**
* Is missingQueuesFatal enabled
*

View File

@@ -15,6 +15,7 @@ import java.util.Map;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.amqp.AmqpRejectAndDontRequeueException;
import org.springframework.amqp.core.Message;
import org.springframework.amqp.rabbit.core.RabbitTemplate;
import org.springframework.amqp.support.converter.AbstractJavaTypeMapper;
@@ -110,7 +111,7 @@ public class BaseAmqpService {
protected final void logAndThrowMessageError(final Message message, final String error) {
LOGGER.warn("Warning! \"{}\" reported by message: {}", error, message);
throw new IllegalArgumentException(error);
throw new AmqpRejectAndDontRequeueException(error);
}
protected RabbitTemplate getRabbitTemplate() {

View File

@@ -46,6 +46,7 @@ public class DefaultAmqpSenderService implements AmqpSenderService {
final String correlationId = UUID.randomUUID().toString();
final String exchange = extractExchange(replyTo);
message.getMessageProperties().setCorrelationId(correlationId.getBytes());
if (LOGGER.isTraceEnabled()) {
LOGGER.trace("Sending message {} to exchange {} with correlationId {}", message, exchange, correlationId);

View File

@@ -59,6 +59,7 @@ import org.mockito.ArgumentCaptor;
import org.mockito.Matchers;
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;
@@ -170,7 +171,7 @@ public class AmqpMessageHandlerServiceTest {
try {
amqpMessageHandlerService.onMessage(message, MessageType.THING_CREATED.name(), TENANT, "vHost");
fail("IllegalArgumentException was excepeted since no replyTo header was set");
} catch (final IllegalArgumentException exception) {
} catch (final AmqpRejectAndDontRequeueException exception) {
// test ok - exception was excepted
}
@@ -184,7 +185,7 @@ public class AmqpMessageHandlerServiceTest {
try {
amqpMessageHandlerService.onMessage(message, MessageType.THING_CREATED.name(), TENANT, "vHost");
fail("IllegalArgumentException was excepeted since no thingID was set");
} catch (final IllegalArgumentException exception) {
} catch (final AmqpRejectAndDontRequeueException exception) {
// test ok - exception was excepted
}
}
@@ -200,7 +201,7 @@ public class AmqpMessageHandlerServiceTest {
try {
amqpMessageHandlerService.onMessage(message, type, TENANT, "vHost");
fail("IllegalArgumentException was excepeted due to unknown message type");
} catch (final IllegalArgumentException exception) {
} catch (final AmqpRejectAndDontRequeueException exception) {
// test ok - exception was excepted
}
}
@@ -213,21 +214,21 @@ public class AmqpMessageHandlerServiceTest {
try {
amqpMessageHandlerService.onMessage(message, MessageType.EVENT.name(), TENANT, "vHost");
fail("IllegalArgumentException was excepeted due to unknown message type");
} catch (final IllegalArgumentException e) {
} catch (final AmqpRejectAndDontRequeueException e) {
}
try {
messageProperties.setHeader(MessageHeaderKey.TOPIC, "wrongTopic");
amqpMessageHandlerService.onMessage(message, MessageType.EVENT.name(), TENANT, "vHost");
fail("IllegalArgumentException was excepeted due to unknown topic");
} catch (final IllegalArgumentException e) {
} catch (final AmqpRejectAndDontRequeueException e) {
}
messageProperties.setHeader(MessageHeaderKey.TOPIC, EventTopic.CANCEL_DOWNLOAD.name());
try {
amqpMessageHandlerService.onMessage(message, MessageType.EVENT.name(), TENANT, "vHost");
fail("IllegalArgumentException was excepeted because there was no event topic");
} catch (final IllegalArgumentException exception) {
} catch (final AmqpRejectAndDontRequeueException exception) {
// test ok - exception was excepted
}
@@ -246,7 +247,7 @@ public class AmqpMessageHandlerServiceTest {
try {
amqpMessageHandlerService.onMessage(message, MessageType.EVENT.name(), TENANT, "vHost");
fail("IllegalArgumentException was excepeted since no action id was set");
} catch (final IllegalArgumentException exception) {
} catch (final AmqpRejectAndDontRequeueException exception) {
// test ok - exception was excepted
}
}
@@ -263,7 +264,7 @@ public class AmqpMessageHandlerServiceTest {
try {
amqpMessageHandlerService.onMessage(message, MessageType.EVENT.name(), TENANT, "vHost");
fail("IllegalArgumentException was excepeted since no action id was set");
} catch (final IllegalArgumentException exception) {
} catch (final AmqpRejectAndDontRequeueException exception) {
// test ok - exception was excepted
}

View File

@@ -33,7 +33,6 @@ import ru.yandex.qatools.allure.annotations.Stories;
@Stories("Test to generate the artifact download URL")
@SpringApplicationConfiguration(classes = { AmqpTestConfiguration.class,
org.eclipse.hawkbit.RepositoryApplicationConfiguration.class })
public class PropertyBasedArtifactUrlHandlerTest extends AbstractIntegrationTestWithMongoDB {
private static final String HTTPS_LOCALHOST = "https://localhost:8080/";

View File

@@ -18,6 +18,9 @@ public class MgmtTargetRequestBody {
@JsonProperty(required = true)
private String controllerId;
@JsonProperty
private String address;
/**
* @return the name
*/
@@ -66,4 +69,12 @@ public class MgmtTargetRequestBody {
return this;
}
public String getAddress() {
return address;
}
public void setAddress(final String address) {
this.address = address;
}
}

View File

@@ -35,14 +35,14 @@ public interface MgmtTargetRestApi {
/**
* Handles the GET request of retrieving a single target.
*
* @param targetId
* @param controllerId
* the ID of the target to retrieve
* @return a single target with status OK.
*/
@RequestMapping(method = RequestMethod.GET, value = "/{targetId}", produces = { "application/hal+json",
@RequestMapping(method = RequestMethod.GET, value = "/{controllerId}", produces = { "application/hal+json",
MediaType.APPLICATION_JSON_VALUE })
ResponseEntity<MgmtTarget> getTarget(@PathVariable("targetId") final String targetId);
ResponseEntity<MgmtTarget> getTarget(@PathVariable("controllerId") final String controllerId);
/**
* Handles the GET request of retrieving all targets.
@@ -91,7 +91,7 @@ public interface MgmtTargetRestApi {
* path of the request. A given ID in the request body is ignored. It's not
* possible to set fields to {@code null} values.
*
* @param targetId
* @param controllerId
* the path parameter which contains the ID of the target
* @param targetRest
* the request body which contains the fields which should be
@@ -100,40 +100,40 @@ public interface MgmtTargetRestApi {
* @return the updated target response which contains all fields also fields
* which have not updated
*/
@RequestMapping(method = RequestMethod.PUT, value = "/{targetId}", consumes = { "application/hal+json",
@RequestMapping(method = RequestMethod.PUT, value = "/{controllerId}", consumes = { "application/hal+json",
MediaType.APPLICATION_JSON_VALUE }, produces = { "application/hal+json", MediaType.APPLICATION_JSON_VALUE })
ResponseEntity<MgmtTarget> updateTarget(@PathVariable("targetId") final String targetId,
ResponseEntity<MgmtTarget> updateTarget(@PathVariable("controllerId") final String controllerId,
@RequestBody final MgmtTargetRequestBody targetRest);
/**
* Handles the DELETE request of deleting a target.
*
* @param targetId
* @param controllerId
* the ID of the target to be deleted
* @return If the given targetId could exists and could be deleted Http OK.
* In any failure the JsonResponseExceptionHandler is handling the
* response.
* @return If the given controllerId could exists and could be deleted Http
* OK. In any failure the JsonResponseExceptionHandler is handling
* the response.
*/
@RequestMapping(method = RequestMethod.DELETE, value = "/{targetId}", produces = { "application/hal+json",
@RequestMapping(method = RequestMethod.DELETE, value = "/{controllerId}", produces = { "application/hal+json",
MediaType.APPLICATION_JSON_VALUE })
ResponseEntity<Void> deleteTarget(@PathVariable("targetId") final String targetId);
ResponseEntity<Void> deleteTarget(@PathVariable("controllerId") final String controllerId);
/**
* Handles the GET request of retrieving the attributes of a specific
* target.
*
* @param targetId
* @param controllerId
* the ID of the target to retrieve the attributes.
* @return the target attributes as map response with status OK
*/
@RequestMapping(method = RequestMethod.GET, value = "/{targetId}/attributes", produces = { "application/hal+json",
MediaType.APPLICATION_JSON_VALUE })
ResponseEntity<MgmtTargetAttributes> getAttributes(@PathVariable("targetId") final String targetId);
@RequestMapping(method = RequestMethod.GET, value = "/{controllerId}/attributes", produces = {
"application/hal+json", MediaType.APPLICATION_JSON_VALUE })
ResponseEntity<MgmtTargetAttributes> getAttributes(@PathVariable("controllerId") final String controllerId);
/**
* Handles the GET request of retrieving the Actions of a specific target.
*
* @param targetId
* @param controllerId
* to load actions for
* @param pagingOffsetParam
* the offset of list of targets for pagination, might not be
@@ -151,9 +151,9 @@ public interface MgmtTargetRestApi {
* status OK. The response is always paged. In any failure the
* JsonResponseExceptionHandler is handling the response.
*/
@RequestMapping(method = RequestMethod.GET, value = "/{targetId}/actions", produces = { "application/hal+json",
@RequestMapping(method = RequestMethod.GET, value = "/{controllerId}/actions", produces = { "application/hal+json",
MediaType.APPLICATION_JSON_VALUE })
ResponseEntity<PagedList<MgmtAction>> getActionHistory(@PathVariable("targetId") final String targetId,
ResponseEntity<PagedList<MgmtAction>> getActionHistory(@PathVariable("controllerId") final String controllerId,
@RequestParam(value = MgmtRestConstants.REQUEST_PARAMETER_PAGING_OFFSET, defaultValue = MgmtRestConstants.REQUEST_PARAMETER_PAGING_DEFAULT_OFFSET) final int pagingOffsetParam,
@RequestParam(value = MgmtRestConstants.REQUEST_PARAMETER_PAGING_LIMIT, defaultValue = MgmtRestConstants.REQUEST_PARAMETER_PAGING_DEFAULT_LIMIT) final int pagingLimitParam,
@RequestParam(value = MgmtRestConstants.REQUEST_PARAMETER_SORTING, required = false) final String sortParam,
@@ -163,22 +163,22 @@ public interface MgmtTargetRestApi {
* Handles the GET request of retrieving a specific Actions of a specific
* Target.
*
* @param targetId
* @param controllerId
* to load the action for
* @param actionId
* to load
* @return the action
*/
@RequestMapping(method = RequestMethod.GET, value = "/{targetId}/actions/{actionId}", produces = {
@RequestMapping(method = RequestMethod.GET, value = "/{controllerId}/actions/{actionId}", produces = {
"application/hal+json", MediaType.APPLICATION_JSON_VALUE })
ResponseEntity<MgmtAction> getAction(@PathVariable("targetId") final String targetId,
ResponseEntity<MgmtAction> getAction(@PathVariable("controllerId") final String controllerId,
@PathVariable("actionId") final Long actionId);
/**
* Handles the DELETE request of canceling an specific Actions of a specific
* Target.
*
* @param targetId
* @param controllerId
* the ID of the target in the URL path parameter
* @param actionId
* the ID of the action in the URL path parameter
@@ -186,8 +186,8 @@ public interface MgmtTargetRestApi {
* optional parameter, which indicates a force cancel
* @return status no content in case cancellation was successful
*/
@RequestMapping(method = RequestMethod.DELETE, value = "/{targetId}/actions/{actionId}")
ResponseEntity<Void> cancelAction(@PathVariable("targetId") final String targetId,
@RequestMapping(method = RequestMethod.DELETE, value = "/{controllerId}/actions/{actionId}")
ResponseEntity<Void> cancelAction(@PathVariable("controllerId") final String controllerId,
@PathVariable("actionId") final Long actionId,
@RequestParam(value = "force", required = false, defaultValue = "false") final boolean force);
@@ -195,7 +195,7 @@ public interface MgmtTargetRestApi {
* Handles the GET request of retrieving the ActionStatus of a specific
* target and action.
*
* @param targetId
* @param controllerId
* of the the action
* @param actionId
* of the status we are intend to load
@@ -212,10 +212,10 @@ public interface MgmtTargetRestApi {
* with status OK. The response is always paged. In any failure the
* JsonResponseExceptionHandler is handling the response.
*/
@RequestMapping(method = RequestMethod.GET, value = "/{targetId}/actions/{actionId}/status", produces = {
@RequestMapping(method = RequestMethod.GET, value = "/{controllerId}/actions/{actionId}/status", produces = {
"application/hal+json", MediaType.APPLICATION_JSON_VALUE })
ResponseEntity<PagedList<MgmtActionStatus>> getActionStatusList(@PathVariable("targetId") final String targetId,
@PathVariable("actionId") final Long actionId,
ResponseEntity<PagedList<MgmtActionStatus>> getActionStatusList(
@PathVariable("controllerId") final String controllerId, @PathVariable("actionId") final Long actionId,
@RequestParam(value = MgmtRestConstants.REQUEST_PARAMETER_PAGING_OFFSET, defaultValue = MgmtRestConstants.REQUEST_PARAMETER_PAGING_DEFAULT_OFFSET) final int pagingOffsetParam,
@RequestParam(value = MgmtRestConstants.REQUEST_PARAMETER_PAGING_LIMIT, defaultValue = MgmtRestConstants.REQUEST_PARAMETER_PAGING_DEFAULT_LIMIT) final int pagingLimitParam,
@RequestParam(value = MgmtRestConstants.REQUEST_PARAMETER_SORTING, required = false) final String sortParam);
@@ -224,40 +224,43 @@ public interface MgmtTargetRestApi {
* Handles the GET request of retrieving the assigned distribution set of an
* specific target.
*
* @param targetId
* @param controllerId
* the ID of the target to retrieve the assigned distribution
* @return the assigned distribution set with status OK, if none is assigned
* than {@code null} content (e.g. "{}")
*/
@RequestMapping(method = RequestMethod.GET, value = "/{targetId}/assignedDS", produces = { "application/hal+json",
MediaType.APPLICATION_JSON_VALUE })
ResponseEntity<MgmtDistributionSet> getAssignedDistributionSet(@PathVariable("targetId") final String targetId);
@RequestMapping(method = RequestMethod.GET, value = "/{controllerId}/assignedDS", produces = {
"application/hal+json", MediaType.APPLICATION_JSON_VALUE })
ResponseEntity<MgmtDistributionSet> getAssignedDistributionSet(
@PathVariable("controllerId") final String controllerId);
/**
* Changes the assigned distribution set of a target.
*
* @param targetId
* @param controllerId
* of the target to change
* @param dsId
* of the distributionset that is to be assigned
* @return http status
*/
@RequestMapping(method = RequestMethod.POST, value = "/{targetId}/assignedDS", consumes = { "application/hal+json",
@RequestMapping(method = RequestMethod.POST, value = "/{controllerId}/assignedDS", consumes = {
"application/hal+json",
MediaType.APPLICATION_JSON_VALUE }, produces = { "application/hal+json", MediaType.APPLICATION_JSON_VALUE })
ResponseEntity<Void> postAssignedDistributionSet(@PathVariable("targetId") final String targetId,
ResponseEntity<Void> postAssignedDistributionSet(@PathVariable("controllerId") final String controllerId,
@RequestBody final MgmtDistributionSetAssigment dsId);
/**
* Handles the GET request of retrieving the installed distribution set of
* an specific target.
*
* @param targetId
* @param controllerId
* the ID of the target to retrieve
* @return the assigned installed set with status OK, if none is installed
* than {@code null} content (e.g. "{}")
*/
@RequestMapping(method = RequestMethod.GET, value = "/{targetId}/installedDS", produces = { "application/hal+json",
MediaType.APPLICATION_JSON_VALUE })
ResponseEntity<MgmtDistributionSet> getInstalledDistributionSet(@PathVariable("targetId") final String targetId);
@RequestMapping(method = RequestMethod.GET, value = "/{controllerId}/installedDS", produces = {
"application/hal+json", MediaType.APPLICATION_JSON_VALUE })
ResponseEntity<MgmtDistributionSet> getInstalledDistributionSet(
@PathVariable("controllerId") final String controllerId);
}

View File

@@ -32,6 +32,7 @@ import org.eclipse.hawkbit.repository.model.PollStatus;
import org.eclipse.hawkbit.repository.model.Target;
import org.eclipse.hawkbit.repository.model.TargetUpdateStatus;
import org.eclipse.hawkbit.rest.data.SortDirection;
import org.eclipse.hawkbit.util.IpUtil;
/**
* A mapper which maps repository model to RESTful model representation and
@@ -141,7 +142,9 @@ public final class MgmtTargetMapper {
final URI address = target.getTargetInfo().getAddress();
if (address != null) {
targetRest.setIpAddress(address.getHost());
if (IpUtil.isIpAddresKnown(address)) {
targetRest.setIpAddress(address.getHost());
}
targetRest.setAddress(address.toString());
}
@@ -182,6 +185,7 @@ public final class MgmtTargetMapper {
final Target target = entityFactory.generateTarget(targetRest.getControllerId());
target.setDescription(targetRest.getDescription());
target.setName(targetRest.getName());
target.getTargetInfo().setAddress(targetRest.getAddress());
return target;
}

View File

@@ -68,8 +68,8 @@ public class MgmtTargetResource implements MgmtTargetRestApi {
private EntityFactory entityFactory;
@Override
public ResponseEntity<MgmtTarget> getTarget(@PathVariable("targetId") final String targetId) {
final Target findTarget = findTargetWithExceptionIfNotFound(targetId);
public ResponseEntity<MgmtTarget> getTarget(@PathVariable("controllerId") final String controllerId) {
final Target findTarget = findTargetWithExceptionIfNotFound(controllerId);
// to single response include poll status
final MgmtTarget response = MgmtTargetMapper.toResponse(findTarget);
MgmtTargetMapper.addPollStatus(findTarget, response);
@@ -115,9 +115,9 @@ public class MgmtTargetResource implements MgmtTargetRestApi {
}
@Override
public ResponseEntity<MgmtTarget> updateTarget(@PathVariable("targetId") final String targetId,
public ResponseEntity<MgmtTarget> updateTarget(@PathVariable("controllerId") final String controllerId,
@RequestBody final MgmtTargetRequestBody targetRest) {
final Target existingTarget = findTargetWithExceptionIfNotFound(targetId);
final Target existingTarget = findTargetWithExceptionIfNotFound(controllerId);
LOG.debug("updating target {}", existingTarget.getId());
if (targetRest.getDescription() != null) {
existingTarget.setDescription(targetRest.getDescription());
@@ -131,16 +131,16 @@ public class MgmtTargetResource implements MgmtTargetRestApi {
}
@Override
public ResponseEntity<Void> deleteTarget(@PathVariable("targetId") final String targetId) {
final Target target = findTargetWithExceptionIfNotFound(targetId);
public ResponseEntity<Void> deleteTarget(@PathVariable("controllerId") final String controllerId) {
final Target target = findTargetWithExceptionIfNotFound(controllerId);
this.targetManagement.deleteTargets(target.getId());
LOG.debug("{} target deleted, return status {}", targetId, HttpStatus.OK);
LOG.debug("{} target deleted, return status {}", controllerId, HttpStatus.OK);
return new ResponseEntity<>(HttpStatus.OK);
}
@Override
public ResponseEntity<MgmtTargetAttributes> getAttributes(@PathVariable("targetId") final String targetId) {
final Target foundTarget = findTargetWithExceptionIfNotFound(targetId);
public ResponseEntity<MgmtTargetAttributes> getAttributes(@PathVariable("controllerId") final String controllerId) {
final Target foundTarget = findTargetWithExceptionIfNotFound(controllerId);
final Map<String, String> controllerAttributes = foundTarget.getTargetInfo().getControllerAttributes();
if (controllerAttributes.isEmpty()) {
return new ResponseEntity<>(HttpStatus.NO_CONTENT);
@@ -153,13 +153,14 @@ public class MgmtTargetResource implements MgmtTargetRestApi {
}
@Override
public ResponseEntity<PagedList<MgmtAction>> getActionHistory(@PathVariable("targetId") final String targetId,
public ResponseEntity<PagedList<MgmtAction>> getActionHistory(
@PathVariable("controllerId") final String controllerId,
@RequestParam(value = MgmtRestConstants.REQUEST_PARAMETER_PAGING_OFFSET, defaultValue = MgmtRestConstants.REQUEST_PARAMETER_PAGING_DEFAULT_OFFSET) final int pagingOffsetParam,
@RequestParam(value = MgmtRestConstants.REQUEST_PARAMETER_PAGING_LIMIT, defaultValue = MgmtRestConstants.REQUEST_PARAMETER_PAGING_DEFAULT_LIMIT) final int pagingLimitParam,
@RequestParam(value = MgmtRestConstants.REQUEST_PARAMETER_SORTING, required = false) final String sortParam,
@RequestParam(value = MgmtRestConstants.REQUEST_PARAMETER_SEARCH, required = false) final String rsqlParam) {
final Target foundTarget = findTargetWithExceptionIfNotFound(targetId);
final Target foundTarget = findTargetWithExceptionIfNotFound(controllerId);
final int sanitizedOffsetParam = PagingUtility.sanitizeOffsetParam(pagingOffsetParam);
final int sanitizedLimitParam = PagingUtility.sanitizePageLimitParam(pagingLimitParam);
@@ -177,14 +178,15 @@ public class MgmtTargetResource implements MgmtTargetRestApi {
}
return new ResponseEntity<>(
new PagedList<>(MgmtTargetMapper.toResponse(targetId, activeActions.getContent()), totalActionCount),
new PagedList<>(MgmtTargetMapper.toResponse(controllerId, activeActions.getContent()),
totalActionCount),
HttpStatus.OK);
}
@Override
public ResponseEntity<MgmtAction> getAction(@PathVariable("targetId") final String targetId,
public ResponseEntity<MgmtAction> getAction(@PathVariable("controllerId") final String controllerId,
@PathVariable("actionId") final Long actionId) {
final Target target = findTargetWithExceptionIfNotFound(targetId);
final Target target = findTargetWithExceptionIfNotFound(controllerId);
final Action action = findActionWithExceptionIfNotFound(actionId);
if (!action.getTarget().getId().equals(target.getId())) {
@@ -192,18 +194,18 @@ public class MgmtTargetResource implements MgmtTargetRestApi {
return new ResponseEntity<>(HttpStatus.NOT_FOUND);
}
final MgmtAction result = MgmtTargetMapper.toResponse(targetId, action, action.isActive());
final MgmtAction result = MgmtTargetMapper.toResponse(controllerId, action, action.isActive());
if (!action.isCancelingOrCanceled()) {
result.add(linkTo(
methodOn(MgmtDistributionSetRestApi.class).getDistributionSet(action.getDistributionSet().getId()))
.withRel("distributionset"));
} else if (action.isCancelingOrCanceled()) {
result.add(linkTo(methodOn(MgmtTargetRestApi.class).getAction(targetId, action.getId()))
result.add(linkTo(methodOn(MgmtTargetRestApi.class).getAction(controllerId, action.getId()))
.withRel(MgmtRestConstants.TARGET_V1_CANCELED_ACTION));
}
result.add(linkTo(methodOn(MgmtTargetRestApi.class).getActionStatusList(targetId, action.getId(), 0,
result.add(linkTo(methodOn(MgmtTargetRestApi.class).getActionStatusList(controllerId, action.getId(), 0,
MgmtRestConstants.REQUEST_PARAMETER_PAGING_DEFAULT_LIMIT_VALUE,
ActionStatusFields.ID.getFieldName() + ":" + SortDirection.DESC))
.withRel(MgmtRestConstants.TARGET_V1_ACTION_STATUS));
@@ -212,10 +214,10 @@ public class MgmtTargetResource implements MgmtTargetRestApi {
}
@Override
public ResponseEntity<Void> cancelAction(@PathVariable("targetId") final String targetId,
public ResponseEntity<Void> cancelAction(@PathVariable("controllerId") final String controllerId,
@PathVariable("actionId") final Long actionId,
@RequestParam(value = "force", required = false, defaultValue = "false") final boolean force) {
final Target target = findTargetWithExceptionIfNotFound(targetId);
final Target target = findTargetWithExceptionIfNotFound(controllerId);
final Action action = findActionWithExceptionIfNotFound(actionId);
if (force) {
@@ -231,12 +233,12 @@ public class MgmtTargetResource implements MgmtTargetRestApi {
@Override
public ResponseEntity<PagedList<MgmtActionStatus>> getActionStatusList(
@PathVariable("targetId") final String targetId, @PathVariable("actionId") final Long actionId,
@PathVariable("controllerId") final String controllerId, @PathVariable("actionId") final Long actionId,
@RequestParam(value = MgmtRestConstants.REQUEST_PARAMETER_PAGING_OFFSET, defaultValue = MgmtRestConstants.REQUEST_PARAMETER_PAGING_DEFAULT_OFFSET) final int pagingOffsetParam,
@RequestParam(value = MgmtRestConstants.REQUEST_PARAMETER_PAGING_LIMIT, defaultValue = MgmtRestConstants.REQUEST_PARAMETER_PAGING_DEFAULT_LIMIT) final int pagingLimitParam,
@RequestParam(value = MgmtRestConstants.REQUEST_PARAMETER_SORTING, required = false) final String sortParam) {
final Target target = findTargetWithExceptionIfNotFound(targetId);
final Target target = findTargetWithExceptionIfNotFound(controllerId);
final Action action = findActionWithExceptionIfNotFound(actionId);
if (!action.getTarget().getId().equals(target.getId())) {
@@ -260,8 +262,8 @@ public class MgmtTargetResource implements MgmtTargetRestApi {
@Override
public ResponseEntity<MgmtDistributionSet> getAssignedDistributionSet(
@PathVariable("targetId") final String targetId) {
final Target findTarget = findTargetWithExceptionIfNotFound(targetId);
@PathVariable("controllerId") final String controllerId) {
final Target findTarget = findTargetWithExceptionIfNotFound(controllerId);
final MgmtDistributionSet distributionSetRest = MgmtDistributionSetMapper
.toResponse(findTarget.getAssignedDistributionSet());
final HttpStatus retStatus;
@@ -274,29 +276,29 @@ public class MgmtTargetResource implements MgmtTargetRestApi {
}
@Override
public ResponseEntity<Void> postAssignedDistributionSet(@PathVariable("targetId") final String targetId,
public ResponseEntity<Void> postAssignedDistributionSet(@PathVariable("controllerId") final String controllerId,
@RequestBody final MgmtDistributionSetAssigment dsId) {
findTargetWithExceptionIfNotFound(targetId);
findTargetWithExceptionIfNotFound(controllerId);
final ActionType type = (dsId.getType() != null) ? MgmtRestModelMapper.convertActionType(dsId.getType())
: ActionType.FORCED;
final Iterator<Target> changed = this.deploymentManagement
.assignDistributionSet(dsId.getId(), type, dsId.getForcetime(), targetId).getAssignedEntity()
.assignDistributionSet(dsId.getId(), type, dsId.getForcetime(), controllerId).getAssignedEntity()
.iterator();
if (changed.hasNext()) {
return new ResponseEntity<>(HttpStatus.OK);
}
LOG.error("Target update (ds {} assigment to target {}) failed! Returnd target list is empty.", dsId.getId(),
targetId);
controllerId);
return new ResponseEntity<>(HttpStatus.INTERNAL_SERVER_ERROR);
}
@Override
public ResponseEntity<MgmtDistributionSet> getInstalledDistributionSet(
@PathVariable("targetId") final String targetId) {
final Target findTarget = findTargetWithExceptionIfNotFound(targetId);
@PathVariable("controllerId") final String controllerId) {
final Target findTarget = findTargetWithExceptionIfNotFound(controllerId);
final MgmtDistributionSet distributionSetRest = MgmtDistributionSetMapper
.toResponse(findTarget.getTargetInfo().getInstalledDistributionSet());
final HttpStatus retStatus;
@@ -308,10 +310,10 @@ public class MgmtTargetResource implements MgmtTargetRestApi {
return new ResponseEntity<>(distributionSetRest, retStatus);
}
private Target findTargetWithExceptionIfNotFound(final String targetId) {
final Target findTarget = this.targetManagement.findTargetByControllerID(targetId);
private Target findTargetWithExceptionIfNotFound(final String controllerId) {
final Target findTarget = this.targetManagement.findTargetByControllerID(controllerId);
if (findTarget == null) {
throw new EntityNotFoundException("Target with Id {" + targetId + "} does not exist");
throw new EntityNotFoundException("Target with Id {" + controllerId + "} does not exist");
}
return findTarget;
}

View File

@@ -40,11 +40,11 @@ import org.eclipse.hawkbit.repository.jpa.model.JpaTargetInfo;
import org.eclipse.hawkbit.repository.model.Action;
import org.eclipse.hawkbit.repository.model.Action.ActionType;
import org.eclipse.hawkbit.repository.model.Action.Status;
import org.eclipse.hawkbit.repository.test.util.WithUser;
import org.eclipse.hawkbit.repository.model.ActionStatus;
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.test.util.WithUser;
import org.eclipse.hawkbit.rest.AbstractRestIntegrationTest;
import org.eclipse.hawkbit.rest.exception.MessageNotReadableException;
import org.eclipse.hawkbit.rest.json.model.ExceptionInfo;
@@ -109,8 +109,8 @@ public class MgmtTargetResourceTest extends AbstractRestIntegrationTest {
final String knownTargetId = "targetId";
final List<Action> actions = generateTargetWithTwoUpdatesWithOneOverride(knownTargetId);
actions.get(0).setStatus(Status.FINISHED);
controllerManagament.addUpdateActionStatus(entityFactory.generateActionStatus(actions.get(0),
Status.FINISHED, System.currentTimeMillis(), "testmessage"));
controllerManagament.addUpdateActionStatus(entityFactory.generateActionStatus(actions.get(0), Status.FINISHED,
System.currentTimeMillis(), "testmessage"));
final PageRequest pageRequest = new PageRequest(0, 1000, Direction.ASC, ActionFields.ID.getFieldName());
final ActionStatus status = deploymentManagement
@@ -682,6 +682,7 @@ public class MgmtTargetResourceTest extends AbstractRestIntegrationTest {
final Target test1 = entityFactory.generateTarget("id1");
test1.setDescription("testid1");
test1.setName("testname1");
test1.getTargetInfo().setAddress("amqp://test123/foobar");
final Target test2 = entityFactory.generateTarget("id2");
test2.setDescription("testid2");
test2.setName("testname2");
@@ -704,6 +705,7 @@ public class MgmtTargetResourceTest extends AbstractRestIntegrationTest {
.andExpect(jsonPath("[0].description", equalTo("testid1")))
.andExpect(jsonPath("[0].createdAt", not(equalTo(0))))
.andExpect(jsonPath("[0].createdBy", equalTo("bumlux")))
.andExpect(jsonPath("[0].address", equalTo("amqp://test123/foobar")))
.andExpect(jsonPath("[1].name", equalTo("testname2")))
.andExpect(jsonPath("[1].createdBy", equalTo("bumlux")))
.andExpect(jsonPath("[1].controllerId", equalTo("id2")))

View File

@@ -0,0 +1,37 @@
/**
* 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.repository;
import org.eclipse.hawkbit.repository.model.ActionStatus;
import org.springframework.boot.context.properties.ConfigurationProperties;
/**
* Configuration properties for the repository.
*
*/
@ConfigurationProperties("hawkbit.server.repository")
public class RepositoryProperties {
/**
* Set to <code>true</code> if the repository has to reject
* {@link ActionStatus} entries for actions that are closed. Note: if this
* is enforced you have to make sure that the feedback channel from the
* devices i in order.
*/
private boolean rejectActionStatusForClosedAction = false;
public boolean isRejectActionStatusForClosedAction() {
return rejectActionStatusForClosedAction;
}
public void setRejectActionStatusForClosedAction(final boolean rejectActionStatusForClosedAction) {
this.rejectActionStatusForClosedAction = rejectActionStatusForClosedAction;
}
}

View File

@@ -179,25 +179,6 @@ public interface TargetManagement {
@PreAuthorize(SpringEvalExpressions.HAS_AUTH_CREATE_TARGET)
List<Target> createTargets(@NotNull Collection<Target> targets);
/**
* creating a new {@link Target} including poll status data. useful
* especially in plug and play scenarios.
*
* @param targets
* to be created *
* @param status
* of the target
* @param lastTargetQuery
* if a plug and play case
* @param address
* if a plug and play case
*
* @return newly created target
*/
@PreAuthorize(SpringEvalExpressions.HAS_AUTH_CREATE_TARGET)
List<Target> createTargets(@NotNull Collection<Target> targets, @NotNull TargetUpdateStatus status,
Long lastTargetQuery, URI address);
/**
* Deletes all targets with the given IDs.
*

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
*/
package org.eclipse.hawkbit.repository.exception;
import org.eclipse.hawkbit.exception.SpServerError;
import org.eclipse.hawkbit.exception.SpServerRtException;
/**
* Exception which is thrown when trying to set an invalid target address.
*/
public class InvalidTargetAddressException extends SpServerRtException {
/**
*
*/
private static final long serialVersionUID = 1L;
/**
* @param message
* the message for this exception
*/
public InvalidTargetAddressException(final String message) {
super(message, SpServerError.SP_REPO_INVALID_TARGET_ADDRESS);
}
/**
*
* @param message
* the message for this exception
* @param cause
* the cause for this exception
*/
public InvalidTargetAddressException(final String message, final Throwable cause) {
super(message, SpServerError.SP_REPO_INVALID_TARGET_ADDRESS, cause);
}
}

View File

@@ -16,10 +16,19 @@ import java.util.concurrent.TimeUnit;
public interface TargetInfo extends Serializable {
/**
* @return the address under whioch the target can be reached
* @return the address under which the target can be reached
*/
URI getAddress();
/**
* @param address
* the target address to set
*
* @throws IllegalArgumentException
* If the given string violates RFC&nbsp;2396
*/
void setAddress(String address);
/**
* @return {@link Target} this info element belongs to.
*/

View File

@@ -17,6 +17,7 @@ import org.eclipse.hawkbit.repository.DeploymentManagement;
import org.eclipse.hawkbit.repository.DistributionSetManagement;
import org.eclipse.hawkbit.repository.EntityFactory;
import org.eclipse.hawkbit.repository.ReportManagement;
import org.eclipse.hawkbit.repository.RepositoryProperties;
import org.eclipse.hawkbit.repository.RolloutGroupManagement;
import org.eclipse.hawkbit.repository.RolloutManagement;
import org.eclipse.hawkbit.repository.SoftwareManagement;
@@ -56,6 +57,7 @@ import org.eclipse.hawkbit.tenancy.TenantAware;
import org.springframework.boot.autoconfigure.EnableAutoConfiguration;
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
import org.springframework.boot.autoconfigure.orm.jpa.JpaBaseConfiguration;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
@@ -70,7 +72,7 @@ import org.springframework.transaction.annotation.EnableTransactionManagement;
import org.springframework.validation.beanvalidation.MethodValidationPostProcessor;
/**
* General configuration for the SP Repository.
* General configuration for hawkBit's Repository.
*
*/
@EnableJpaRepositories(basePackages = { "org.eclipse.hawkbit.repository.jpa" })
@@ -80,6 +82,7 @@ import org.springframework.validation.beanvalidation.MethodValidationPostProcess
@Configuration
@ComponentScan
@EnableAutoConfiguration
@EnableConfigurationProperties(RepositoryProperties.class)
@EnableScheduling
public class RepositoryApplicationConfiguration extends JpaBaseConfiguration {
/**

View File

@@ -21,6 +21,7 @@ import javax.validation.constraints.NotNull;
import org.eclipse.hawkbit.repository.ControllerManagement;
import org.eclipse.hawkbit.repository.RepositoryConstants;
import org.eclipse.hawkbit.repository.RepositoryProperties;
import org.eclipse.hawkbit.repository.TargetManagement;
import org.eclipse.hawkbit.repository.TenantConfigurationManagement;
import org.eclipse.hawkbit.repository.exception.EntityNotFoundException;
@@ -92,6 +93,9 @@ public class JpaControllerManagement implements ControllerManagement {
@Autowired
private HawkbitSecurityProperties securityProperties;
@Autowired
private RepositoryProperties repositoryProperties;
@Autowired
private TenantConfigurationRepository tenantConfigurationRepository;
@@ -249,7 +253,12 @@ public class JpaControllerManagement implements ControllerManagement {
public Action addUpdateActionStatus(@NotNull final ActionStatus actionStatus) {
final JpaAction action = (JpaAction) actionStatus.getAction();
if (!action.isActive()) {
// if action is already closed we accept further status updates if
// permitted so by configuration. This is especially useful if the
// action status feedback channel order from the device cannot be
// guaranteed. However, if an action is closed we do not accept further
// close messages.
if (actionIsNotActiveButIntermediateFeedbackStillAllowed(actionStatus, action)) {
LOG.debug("Update of actionStatus {} for action {} not possible since action not active anymore.",
actionStatus.getId(), action.getId());
return action;
@@ -257,6 +266,12 @@ public class JpaControllerManagement implements ControllerManagement {
return handleAddUpdateActionStatus((JpaActionStatus) actionStatus, action);
}
private boolean actionIsNotActiveButIntermediateFeedbackStillAllowed(final ActionStatus actionStatus,
final JpaAction action) {
return !action.isActive() && (repositoryProperties.isRejectActionStatusForClosedAction()
|| (Status.ERROR.equals(actionStatus.getStatus()) || Status.FINISHED.equals(actionStatus.getStatus())));
}
/**
* Sets {@link TargetUpdateStatus} based on given {@link ActionStatus}.
*
@@ -284,8 +299,7 @@ public class JpaControllerManagement implements ControllerManagement {
case CANCELED:
case WARNING:
case RUNNING:
DeploymentHelper.updateTargetInfo(mergedTarget, TargetUpdateStatus.PENDING, false, targetInfoRepository,
entityManager);
handleIntermediateFeedback(mergedAction, mergedTarget);
break;
default:
break;
@@ -298,6 +312,16 @@ public class JpaControllerManagement implements ControllerManagement {
return actionRepository.save(mergedAction);
}
private void handleIntermediateFeedback(final JpaAction mergedAction, final JpaTarget mergedTarget) {
// we change the target state only if the action is still running
// otherwise this is considered as late feedback that does not have
// an impact on the state anymore.
if (mergedAction.isActive()) {
DeploymentHelper.updateTargetInfo(mergedTarget, TargetUpdateStatus.PENDING, false, targetInfoRepository,
entityManager);
}
}
private void handleErrorOnAction(final JpaAction mergedAction, final JpaTarget mergedTarget) {
mergedAction.setActive(false);
mergedAction.setStatus(Status.ERROR);

View File

@@ -606,24 +606,7 @@ public class JpaTargetManagement implements TargetManagement {
}
final List<Target> savedTargets = new ArrayList<>();
for (final Target t : targets) {
final Target myTarget = createTarget(t);
savedTargets.add(myTarget);
}
return savedTargets;
}
@Override
@Modifying
@Transactional(isolation = Isolation.READ_UNCOMMITTED)
public List<Target> createTargets(final Collection<Target> targets, final TargetUpdateStatus status,
final Long lastTargetQuery, final URI address) {
if (targetRepository.countByControllerIdIn(
targets.stream().map(target -> target.getControllerId()).collect(Collectors.toList())) > 0) {
throw new EntityAlreadyExistsException();
}
final List<Target> savedTargets = new ArrayList<>();
for (final Target t : targets) {
final Target myTarget = createTarget(t, status, lastTargetQuery, address);
final Target myTarget = createTarget(t, TargetUpdateStatus.UNKNOWN, null, t.getTargetInfo().getAddress());
savedTargets.add(myTarget);
}
return savedTargets;

View File

@@ -37,6 +37,7 @@ import javax.persistence.OneToOne;
import javax.persistence.Table;
import javax.persistence.Transient;
import org.eclipse.hawkbit.repository.exception.InvalidTargetAddressException;
import org.eclipse.hawkbit.repository.jpa.model.helper.SystemSecurityContextHolder;
import org.eclipse.hawkbit.repository.jpa.model.helper.TenantConfigurationManagementHolder;
import org.eclipse.hawkbit.repository.model.DistributionSet;
@@ -168,15 +169,21 @@ public class JpaTargetInfo implements Persistable<Long>, TargetInfo {
/**
* @param address
* the ipAddress to set
* the target address to set
*
* @throws IllegalArgumentException
* If the given string violates RFC&nbsp;2396
*/
@Override
public void setAddress(final String address) {
// check if this is a real URI
if (address != null) {
URI.create(address);
try {
URI.create(address);
} catch (final IllegalArgumentException e) {
throw new InvalidTargetAddressException(
"The given address " + address + " violates the RFC-2396 specification", e);
}
}
this.address = address;

View File

@@ -17,6 +17,7 @@ import java.util.List;
import javax.validation.ConstraintViolationException;
import org.apache.commons.lang3.RandomStringUtils;
import org.eclipse.hawkbit.repository.RepositoryProperties;
import org.eclipse.hawkbit.repository.jpa.model.JpaActionStatus;
import org.eclipse.hawkbit.repository.jpa.model.JpaTarget;
import org.eclipse.hawkbit.repository.model.Action;
@@ -26,6 +27,7 @@ import org.eclipse.hawkbit.repository.model.DistributionSet;
import org.eclipse.hawkbit.repository.model.Target;
import org.eclipse.hawkbit.repository.model.TargetUpdateStatus;
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;
@@ -34,6 +36,8 @@ import ru.yandex.qatools.allure.annotations.Stories;
@Features("Component Tests - Repository")
@Stories("Controller Management")
public class ControllerManagementTest extends AbstractJpaIntegrationTest {
@Autowired
private RepositoryProperties repositoryProperties;
@Test
@Description("Controller adds a new action status.")
@@ -94,7 +98,7 @@ public class ControllerManagementTest extends AbstractJpaIntegrationTest {
@Test
@Description("Controller trys to finish an update process after it has been finished by an error action status.")
public void tryToFinishUpdateProcessMoreThenOnce() {
public void tryToFinishUpdateProcessMoreThanOnce() {
// mock
final Target target = new JpaTarget("Rabbit");
@@ -120,16 +124,101 @@ public class ControllerManagementTest extends AbstractJpaIntegrationTest {
assertThat(targetManagement.findTargetByControllerID("Rabbit").getTargetInfo().getUpdateStatus())
.isEqualTo(TargetUpdateStatus.ERROR);
// try with disabled late feedback
repositoryProperties.setRejectActionStatusForClosedAction(true);
final ActionStatus actionStatusMessage3 = new JpaActionStatus(savedAction, Action.Status.FINISHED,
System.currentTimeMillis());
actionStatusMessage3.addMessage("finish");
controllerManagament.addUpdateActionStatus(actionStatusMessage3);
savedAction = controllerManagament.addUpdateActionStatus(actionStatusMessage3);
targetManagement.findTargetByControllerID("Rabbit").getTargetInfo().getUpdateStatus();
// test
assertThat(targetManagement.findTargetByControllerID("Rabbit").getTargetInfo().getUpdateStatus())
.isEqualTo(TargetUpdateStatus.ERROR);
// try with enabled late feedback
repositoryProperties.setRejectActionStatusForClosedAction(false);
final ActionStatus actionStatusMessage4 = new JpaActionStatus(savedAction, Action.Status.FINISHED,
System.currentTimeMillis());
actionStatusMessage4.addMessage("finish");
controllerManagament.addUpdateActionStatus(actionStatusMessage3);
// test
assertThat(targetManagement.findTargetByControllerID("Rabbit").getTargetInfo().getUpdateStatus())
.isEqualTo(TargetUpdateStatus.ERROR);
}
@Test
@Description("Controller trys to send an update feedback after it has been finished which is reject as the repository is "
+ "configured to reject that.")
public void sendUpdatesForFinishUpdateProcessDropedIfDisabled() {
repositoryProperties.setRejectActionStatusForClosedAction(true);
final Action action = prepareFinishedUpdate("Rabbit");
final ActionStatus actionStatusMessage1 = new JpaActionStatus(action, Action.Status.RUNNING,
System.currentTimeMillis());
actionStatusMessage1.addMessage("got some additional feedback");
controllerManagament.addUpdateActionStatus(actionStatusMessage1);
// nothing changed as "feedback after close" is disabled
assertThat(targetManagement.findTargetByControllerID("Rabbit").getTargetInfo().getUpdateStatus())
.isEqualTo(TargetUpdateStatus.IN_SYNC);
assertThat(actionStatusRepository.findAll(pageReq).getNumberOfElements()).isEqualTo(3);
assertThat(deploymentManagement.findActionStatusByAction(pageReq, action).getNumberOfElements()).isEqualTo(3);
}
@Test
@Description("Controller trys to send an update feedback after it has been finished which is actepted as the repository is "
+ "configured to accept them.")
public void sendUpdatesForFinishUpdateProcessAcceptedIfEnabled() {
repositoryProperties.setRejectActionStatusForClosedAction(false);
Action action = prepareFinishedUpdate("Rabbit");
final ActionStatus actionStatusMessage1 = new JpaActionStatus(action, Action.Status.RUNNING,
System.currentTimeMillis());
actionStatusMessage1.addMessage("got some additional feedback");
action = controllerManagament.addUpdateActionStatus(actionStatusMessage1);
// nothing changed as "feedback after close" is disabled
assertThat(targetManagement.findTargetByControllerID("Rabbit").getTargetInfo().getUpdateStatus())
.isEqualTo(TargetUpdateStatus.IN_SYNC);
assertThat(actionStatusRepository.findAll(pageReq).getNumberOfElements()).isEqualTo(4);
assertThat(deploymentManagement.findActionStatusByAction(pageReq, action).getNumberOfElements()).isEqualTo(4);
}
private Action prepareFinishedUpdate(final String controllerId) {
// mock
final Target target = new JpaTarget(controllerId);
final DistributionSet ds = testdataFactory.createDistributionSet("");
Target savedTarget = targetManagement.createTarget(target);
final List<Target> toAssign = new ArrayList<>();
toAssign.add(savedTarget);
savedTarget = deploymentManagement.assignDistributionSet(ds, toAssign).getAssignedEntity().iterator().next();
Action savedAction = deploymentManagement.findActiveActionsByTarget(savedTarget).get(0);
// test and verify
final ActionStatus actionStatusMessage = new JpaActionStatus(savedAction, Action.Status.RUNNING,
System.currentTimeMillis());
actionStatusMessage.addMessage("running");
savedAction = controllerManagament.addUpdateActionStatus(actionStatusMessage);
assertThat(targetManagement.findTargetByControllerID(controllerId).getTargetInfo().getUpdateStatus())
.isEqualTo(TargetUpdateStatus.PENDING);
final ActionStatus actionStatusMessage2 = new JpaActionStatus(savedAction, Action.Status.FINISHED,
System.currentTimeMillis());
actionStatusMessage2.addMessage("finish");
savedAction = controllerManagament.addUpdateActionStatus(actionStatusMessage2);
// test
assertThat(targetManagement.findTargetByControllerID(controllerId).getTargetInfo().getUpdateStatus())
.isEqualTo(TargetUpdateStatus.IN_SYNC);
assertThat(actionStatusRepository.findAll(pageReq).getNumberOfElements()).isEqualTo(3);
assertThat(deploymentManagement.findActionStatusByAction(pageReq, savedAction).getNumberOfElements())
.isEqualTo(3);
return savedAction;
}
}

View File

@@ -67,6 +67,7 @@ public class ResponseExceptionHandler {
ERROR_TO_HTTP_STATUS.put(SpServerError.SP_ROLLOUT_ILLEGAL_STATE, HttpStatus.BAD_REQUEST);
ERROR_TO_HTTP_STATUS.put(SpServerError.SP_CONFIGURATION_VALUE_INVALID, HttpStatus.BAD_REQUEST);
ERROR_TO_HTTP_STATUS.put(SpServerError.SP_CONFIGURATION_KEY_INVALID, HttpStatus.BAD_REQUEST);
ERROR_TO_HTTP_STATUS.put(SpServerError.SP_REPO_INVALID_TARGET_ADDRESS, HttpStatus.BAD_REQUEST);
}
private static HttpStatus getStatusOrDefault(final SpServerError error) {

View File

@@ -41,9 +41,9 @@ public abstract class JsonBuilder {
try {
builder.append(new JSONObject().put("name", module.getName())
.put("description", module.getDescription()).put("type", module.getType().getKey())
.put("id", Long.MAX_VALUE).put("vendor", module.getVendor())
.put("version", module.getVersion()).put("createdAt", "0").put("updatedAt", "0")
.put("createdBy", "fghdfkjghdfkjh").put("updatedBy", "fghdfkjghdfkjh").toString());
.put("id", Long.MAX_VALUE).put("vendor", module.getVendor()).put("version", module.getVersion())
.put("createdAt", "0").put("updatedAt", "0").put("createdBy", "fghdfkjghdfkjh")
.put("updatedBy", "fghdfkjghdfkjh").toString());
} catch (final Exception e) {
e.printStackTrace();
}
@@ -185,15 +185,13 @@ public abstract class JsonBuilder {
final List<String> messages = new ArrayList<String>();
messages.add(message);
return new JSONObject()
.put("id", id)
.put("time", "20140511T121314")
return new JSONObject().put("id", id).put("time", "20140511T121314")
.put("status",
new JSONObject()
.put("execution", execution)
new JSONObject().put("execution", execution)
.put("result",
new JSONObject().put("finished", finished).put("progress",
new JSONObject().put("cnt", 2).put("of", 5))).put("details", messages))
new JSONObject().put("cnt", 2).put("of", 5)))
.put("details", messages))
.toString();
}
@@ -380,10 +378,13 @@ public abstract class JsonBuilder {
int i = 0;
for (final Target target : targets) {
try {
final String address = target.getTargetInfo().getAddress() != null
? target.getTargetInfo().getAddress().toString() : null;
builder.append(new JSONObject().put("controllerId", target.getControllerId())
.put("description", target.getDescription()).put("name", target.getName())
.put("createdAt", "0").put("updatedAt", "0").put("createdBy", "fghdfkjghdfkjh")
.put("updatedBy", "fghdfkjghdfkjh").toString());
.put("description", target.getDescription()).put("name", target.getName()).put("createdAt", "0")
.put("updatedAt", "0").put("createdBy", "fghdfkjghdfkjh").put("updatedBy", "fghdfkjghdfkjh")
.put("address", address).toString());
} catch (final Exception e) {
e.printStackTrace();
}
@@ -441,9 +442,7 @@ public abstract class JsonBuilder {
throws JSONException {
final List<String> messages = new ArrayList<String>();
messages.add(message);
return new JSONObject()
.put("id", id)
.put("time", "20140511T121314")
return new JSONObject().put("id", id).put("time", "20140511T121314")
.put("status",
new JSONObject().put("execution", execution)
.put("result", new JSONObject().put("finished", "success")).put("details", messages))
@@ -453,13 +452,12 @@ public abstract class JsonBuilder {
public static String configData(final String id, final Map<String, String> attributes, final String execution)
throws JSONException {
return new JSONObject()
.put("id", id)
.put("time", "20140511T121314")
return new JSONObject().put("id", id).put("time", "20140511T121314")
.put("status",
new JSONObject().put("execution", execution)
.put("result", new JSONObject().put("finished", "success"))
.put("details", new ArrayList<String>())).put("data", attributes).toString();
.put("details", new ArrayList<String>()))
.put("data", attributes).toString();
}

View File

@@ -26,6 +26,7 @@ import com.google.common.net.HttpHeaders;
*/
public final class IpUtil {
private static final String HIDDEN_IP = "***";
private static final String SCHEME_SEPERATOR = "://";
private static final String HTTP_SCHEME = "http";
private static final String AMPQP_SCHEME = "amqp";
@@ -87,7 +88,7 @@ public final class IpUtil {
ip = request.getRemoteAddr();
}
} else {
ip = "***";
ip = HIDDEN_IP;
}
return createHttpUri(ip);
@@ -178,4 +179,17 @@ public final class IpUtil {
public static boolean isAmqpUri(final URI uri) {
return uri != null && AMPQP_SCHEME.equals(uri.getScheme());
}
/**
* Check if the IP address of that {@link URI} is known, i.e. not an AQMP
* exchange in DMF case and not HIDDEN_IP in DDI case.
*
* @param uri
* the uri
* @return <code>true</code> if IP address is actually known by the server
*/
public static boolean isIpAddresKnown(final URI uri) {
return uri != null && !(AMPQP_SCHEME.equals(uri.getScheme()) || HIDDEN_IP.equals(uri.getHost()));
}
}

View File

@@ -69,6 +69,7 @@
<hibernate-validator.version>5.2.4.Final</hibernate-validator.version>
<spring-cloud-connectors.version>1.2.0.RELEASE</spring-cloud-connectors.version>
<spring-amqp.version>1.6.0.RELEASE</spring-amqp.version>
<rabbitmq.version>3.6.2</rabbitmq.version>
<spring-hateoas.version>0.18.0.RELEASE</spring-hateoas.version>
<!-- Support for MongoDB 3 -->
<spring-data-releasetrain.version>Fowler-SR1</spring-data-releasetrain.version>
@@ -526,6 +527,11 @@
<artifactId>org.eclipse.persistence.jpa</artifactId>
<version>${eclipselink.version}</version>
</dependency>
<dependency>
<groupId>com.rabbitmq</groupId>
<artifactId>amqp-client</artifactId>
<version>${rabbitmq.version}</version>
</dependency>
<!-- RSQL / FIQL parser -->
<dependency>
<groupId>cz.jirutka.rsql</groupId>