diff --git a/examples/hawkbit-device-simulator/pom.xml b/examples/hawkbit-device-simulator/pom.xml index 3c3df9486..035bcebfc 100644 --- a/examples/hawkbit-device-simulator/pom.xml +++ b/examples/hawkbit-device-simulator/pom.xml @@ -71,6 +71,10 @@ org.springframework.boot spring-boot-starter-web + + org.springframework.boot + spring-boot-starter-logging + org.springframework.security spring-security-web @@ -83,26 +87,6 @@ org.springframework.boot spring-boot-starter - - - org.apache.logging.log4j - log4j-api - - - - org.slf4j - jul-to-slf4j - - - - org.slf4j - jcl-over-slf4j - - - - org.slf4j - log4j-over-slf4j - com.vaadin vaadin-spring-boot-starter diff --git a/examples/hawkbit-device-simulator/src/main/java/org/eclipse/hawkbit/simulator/AbstractSimulatedDevice.java b/examples/hawkbit-device-simulator/src/main/java/org/eclipse/hawkbit/simulator/AbstractSimulatedDevice.java index 890f43367..a2a036e1b 100644 --- a/examples/hawkbit-device-simulator/src/main/java/org/eclipse/hawkbit/simulator/AbstractSimulatedDevice.java +++ b/examples/hawkbit-device-simulator/src/main/java/org/eclipse/hawkbit/simulator/AbstractSimulatedDevice.java @@ -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; } /** diff --git a/examples/hawkbit-device-simulator/src/main/java/org/eclipse/hawkbit/simulator/DDISimulatedDevice.java b/examples/hawkbit-device-simulator/src/main/java/org/eclipse/hawkbit/simulator/DDISimulatedDevice.java index 26e613dd9..677d77a37 100644 --- a/examples/hawkbit-device-simulator/src/main/java/org/eclipse/hawkbit/simulator/DDISimulatedDevice.java +++ b/examples/hawkbit-device-simulator/src/main/java/org/eclipse/hawkbit/simulator/DDISimulatedDevice.java @@ -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()); diff --git a/examples/hawkbit-device-simulator/src/main/java/org/eclipse/hawkbit/simulator/DMFSimulatedDevice.java b/examples/hawkbit-device-simulator/src/main/java/org/eclipse/hawkbit/simulator/DMFSimulatedDevice.java index 6b79a85b8..65227b55d 100644 --- a/examples/hawkbit-device-simulator/src/main/java/org/eclipse/hawkbit/simulator/DMFSimulatedDevice.java +++ b/examples/hawkbit-device-simulator/src/main/java/org/eclipse/hawkbit/simulator/DMFSimulatedDevice.java @@ -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()); } } diff --git a/examples/hawkbit-device-simulator/src/main/java/org/eclipse/hawkbit/simulator/DeviceSimulatorRepository.java b/examples/hawkbit-device-simulator/src/main/java/org/eclipse/hawkbit/simulator/DeviceSimulatorRepository.java index 68db9df45..66ceacc8e 100644 --- a/examples/hawkbit-device-simulator/src/main/java/org/eclipse/hawkbit/simulator/DeviceSimulatorRepository.java +++ b/examples/hawkbit-device-simulator/src/main/java/org/eclipse/hawkbit/simulator/DeviceSimulatorRepository.java @@ -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 devices = new LinkedHashMap<>(); + private final Map devices = new ConcurrentHashMap<>(); @Autowired private SimulatedDeviceFactory deviceFactory; diff --git a/examples/hawkbit-device-simulator/src/main/java/org/eclipse/hawkbit/simulator/NextPollTimeController.java b/examples/hawkbit-device-simulator/src/main/java/org/eclipse/hawkbit/simulator/NextPollTimeController.java index 956d6d36a..96b4a1e97 100644 --- a/examples/hawkbit-device-simulator/src/main/java/org/eclipse/hawkbit/simulator/NextPollTimeController.java +++ b/examples/hawkbit-device-simulator/src/main/java/org/eclipse/hawkbit/simulator/NextPollTimeController.java @@ -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 devices = repository.getAll().stream() - .filter(device -> device instanceof DDISimulatedDevice).collect(Collectors.toList()); + final Collection 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); diff --git a/examples/hawkbit-device-simulator/src/main/java/org/eclipse/hawkbit/simulator/SimulatedDeviceFactory.java b/examples/hawkbit-device-simulator/src/main/java/org/eclipse/hawkbit/simulator/SimulatedDeviceFactory.java index f29aad001..35829c673 100644 --- a/examples/hawkbit-device-simulator/src/main/java/org/eclipse/hawkbit/simulator/SimulatedDeviceFactory.java +++ b/examples/hawkbit-device-simulator/src/main/java/org/eclipse/hawkbit/simulator/SimulatedDeviceFactory.java @@ -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) diff --git a/examples/hawkbit-device-simulator/src/main/java/org/eclipse/hawkbit/simulator/SimulationController.java b/examples/hawkbit-device-simulator/src/main/java/org/eclipse/hawkbit/simulator/SimulationController.java index 649d88477..3bd6b41f2 100644 --- a/examples/hawkbit-device-simulator/src/main/java/org/eclipse/hawkbit/simulator/SimulationController.java +++ b/examples/hawkbit-device-simulator/src/main/java/org/eclipse/hawkbit/simulator/SimulationController.java @@ -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 { diff --git a/examples/hawkbit-device-simulator/src/main/java/org/eclipse/hawkbit/simulator/SimulationProperties.java b/examples/hawkbit-device-simulator/src/main/java/org/eclipse/hawkbit/simulator/SimulationProperties.java index 354263934..fb98ff67e 100644 --- a/examples/hawkbit-device-simulator/src/main/java/org/eclipse/hawkbit/simulator/SimulationProperties.java +++ b/examples/hawkbit-device-simulator/src/main/java/org/eclipse/hawkbit/simulator/SimulationProperties.java @@ -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. diff --git a/examples/hawkbit-device-simulator/src/main/java/org/eclipse/hawkbit/simulator/amqp/AmqpConfiguration.java b/examples/hawkbit-device-simulator/src/main/java/org/eclipse/hawkbit/simulator/amqp/AmqpConfiguration.java index e954cec96..5be5c7dcb 100644 --- a/examples/hawkbit-device-simulator/src/main/java/org/eclipse/hawkbit/simulator/amqp/AmqpConfiguration.java +++ b/examples/hawkbit-device-simulator/src/main/java/org/eclipse/hawkbit/simulator/amqp/AmqpConfiguration.java @@ -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; } diff --git a/examples/hawkbit-device-simulator/src/main/java/org/eclipse/hawkbit/simulator/amqp/ReceiverService.java b/examples/hawkbit-device-simulator/src/main/java/org/eclipse/hawkbit/simulator/amqp/ReceiverService.java index f5c02789e..2e8833bab 100644 --- a/examples/hawkbit-device-simulator/src/main/java/org/eclipse/hawkbit/simulator/amqp/ReceiverService.java +++ b/examples/hawkbit-device-simulator/src/main/java/org/eclipse/hawkbit/simulator/amqp/ReceiverService.java @@ -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"); } } diff --git a/examples/hawkbit-device-simulator/src/main/java/org/eclipse/hawkbit/simulator/amqp/SenderService.java b/examples/hawkbit-device-simulator/src/main/java/org/eclipse/hawkbit/simulator/amqp/SenderService.java index 6ed6ff0cb..f2c0e7fcb 100644 --- a/examples/hawkbit-device-simulator/src/main/java/org/eclipse/hawkbit/simulator/amqp/SenderService.java +++ b/examples/hawkbit-device-simulator/src/main/java/org/eclipse/hawkbit/simulator/amqp/SenderService.java @@ -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)); } /** diff --git a/examples/hawkbit-device-simulator/src/main/java/org/eclipse/hawkbit/simulator/amqp/SpReceiverService.java b/examples/hawkbit-device-simulator/src/main/java/org/eclipse/hawkbit/simulator/amqp/SpReceiverService.java index e06b2baf3..4f2981be1 100644 --- a/examples/hawkbit-device-simulator/src/main/java/org/eclipse/hawkbit/simulator/amqp/SpReceiverService.java +++ b/examples/hawkbit-device-simulator/src/main/java/org/eclipse/hawkbit/simulator/amqp/SpReceiverService.java @@ -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, diff --git a/examples/hawkbit-device-simulator/src/main/java/org/eclipse/hawkbit/simulator/event/NextPollCounterUpdate.java b/examples/hawkbit-device-simulator/src/main/java/org/eclipse/hawkbit/simulator/event/NextPollCounterUpdate.java index b9d7b9027..5e98ebb64 100644 --- a/examples/hawkbit-device-simulator/src/main/java/org/eclipse/hawkbit/simulator/event/NextPollCounterUpdate.java +++ b/examples/hawkbit-device-simulator/src/main/java/org/eclipse/hawkbit/simulator/event/NextPollCounterUpdate.java @@ -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 devices; + private final Collection 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 devices) { + public NextPollCounterUpdate(final Collection devices) { this.devices = devices; } /** * @return the devices of the event */ - public List getDevices() { + public Collection getDevices() { return devices; } diff --git a/examples/hawkbit-device-simulator/src/main/java/org/eclipse/hawkbit/simulator/ui/GenerateDialog.java b/examples/hawkbit-device-simulator/src/main/java/org/eclipse/hawkbit/simulator/ui/GenerateDialog.java index e337e86e7..272531407 100644 --- a/examples/hawkbit-device-simulator/src/main/java/org/eclipse/hawkbit/simulator/ui/GenerateDialog.java +++ b/examples/hawkbit-device-simulator/src/main/java/org/eclipse/hawkbit/simulator/ui/GenerateDialog.java @@ -76,7 +76,6 @@ public class GenerateDialog extends Window { pollDelayTextField = createRequiredTextfield("poll delay (sec)", new ObjectProperty(10), FontAwesome.CLOCK_O, new RangeValidator("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); }); diff --git a/examples/hawkbit-device-simulator/src/main/java/org/eclipse/hawkbit/simulator/ui/SimulatorView.java b/examples/hawkbit-device-simulator/src/main/java/org/eclipse/hawkbit/simulator/ui/SimulatorView.java index 4834bece9..9405edd2b 100644 --- a/examples/hawkbit-device-simulator/src/main/java/org/eclipse/hawkbit/simulator/ui/SimulatorView.java +++ b/examples/hawkbit-device-simulator/src/main/java/org/eclipse/hawkbit/simulator/ui/SimulatorView.java @@ -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 devices = update.getDevices(); + final Collection devices = update.getDevices(); this.getUI().access(() -> devices.forEach(device -> { final BeanItem item = beanContainer.getItem(device.getId()); if (item != null) { diff --git a/examples/hawkbit-device-simulator/src/main/resources/application.properties b/examples/hawkbit-device-simulator/src/main/resources/application.properties index fbe7261be..5d3f04be7 100644 --- a/examples/hawkbit-device-simulator/src/main/resources/application.properties +++ b/examples/hawkbit-device-simulator/src/main/resources/application.properties @@ -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 diff --git a/examples/hawkbit-device-simulator/src/main/resources/logback.xml b/examples/hawkbit-device-simulator/src/main/resources/logback.xml index 469c7bde3..5f3f1dbef 100644 --- a/examples/hawkbit-device-simulator/src/main/resources/logback.xml +++ b/examples/hawkbit-device-simulator/src/main/resources/logback.xml @@ -19,10 +19,7 @@ - - - - + diff --git a/examples/hawkbit-example-mgmt-feign-client/src/main/java/org/eclipse/hawkbit/mgmt/client/resource/MgmtDistributionSetClientResource.java b/examples/hawkbit-example-mgmt-feign-client/src/main/java/org/eclipse/hawkbit/mgmt/client/resource/MgmtDistributionSetClientResource.java index 7bf696a8f..40eb13b55 100644 --- a/examples/hawkbit-example-mgmt-feign-client/src/main/java/org/eclipse/hawkbit/mgmt/client/resource/MgmtDistributionSetClientResource.java +++ b/examples/hawkbit-example-mgmt-feign-client/src/main/java/org/eclipse/hawkbit/mgmt/client/resource/MgmtDistributionSetClientResource.java @@ -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 { } diff --git a/examples/hawkbit-example-mgmt-feign-client/src/main/java/org/eclipse/hawkbit/mgmt/client/resource/MgmtDistributionSetTagClientResource.java b/examples/hawkbit-example-mgmt-feign-client/src/main/java/org/eclipse/hawkbit/mgmt/client/resource/MgmtDistributionSetTagClientResource.java index 783cc09fa..1070bbd9c 100644 --- a/examples/hawkbit-example-mgmt-feign-client/src/main/java/org/eclipse/hawkbit/mgmt/client/resource/MgmtDistributionSetTagClientResource.java +++ b/examples/hawkbit-example-mgmt-feign-client/src/main/java/org/eclipse/hawkbit/mgmt/client/resource/MgmtDistributionSetTagClientResource.java @@ -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 { } diff --git a/examples/hawkbit-example-mgmt-feign-client/src/main/java/org/eclipse/hawkbit/mgmt/client/resource/MgmtDistributionSetTypeClientResource.java b/examples/hawkbit-example-mgmt-feign-client/src/main/java/org/eclipse/hawkbit/mgmt/client/resource/MgmtDistributionSetTypeClientResource.java index 35f26781f..451c53942 100644 --- a/examples/hawkbit-example-mgmt-feign-client/src/main/java/org/eclipse/hawkbit/mgmt/client/resource/MgmtDistributionSetTypeClientResource.java +++ b/examples/hawkbit-example-mgmt-feign-client/src/main/java/org/eclipse/hawkbit/mgmt/client/resource/MgmtDistributionSetTypeClientResource.java @@ -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 { } diff --git a/examples/hawkbit-example-mgmt-feign-client/src/main/java/org/eclipse/hawkbit/mgmt/client/resource/MgmtDownloadArtifactClientResource.java b/examples/hawkbit-example-mgmt-feign-client/src/main/java/org/eclipse/hawkbit/mgmt/client/resource/MgmtDownloadArtifactClientResource.java index d25a609b2..5c93edce5 100644 --- a/examples/hawkbit-example-mgmt-feign-client/src/main/java/org/eclipse/hawkbit/mgmt/client/resource/MgmtDownloadArtifactClientResource.java +++ b/examples/hawkbit-example-mgmt-feign-client/src/main/java/org/eclipse/hawkbit/mgmt/client/resource/MgmtDownloadArtifactClientResource.java @@ -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 { } diff --git a/examples/hawkbit-example-mgmt-feign-client/src/main/java/org/eclipse/hawkbit/mgmt/client/resource/MgmtDownloadClientResource.java b/examples/hawkbit-example-mgmt-feign-client/src/main/java/org/eclipse/hawkbit/mgmt/client/resource/MgmtDownloadClientResource.java index 9a1dcee61..330d3908f 100644 --- a/examples/hawkbit-example-mgmt-feign-client/src/main/java/org/eclipse/hawkbit/mgmt/client/resource/MgmtDownloadClientResource.java +++ b/examples/hawkbit-example-mgmt-feign-client/src/main/java/org/eclipse/hawkbit/mgmt/client/resource/MgmtDownloadClientResource.java @@ -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 { } diff --git a/examples/hawkbit-example-mgmt-feign-client/src/main/java/org/eclipse/hawkbit/mgmt/client/resource/MgmtRolloutClientResource.java b/examples/hawkbit-example-mgmt-feign-client/src/main/java/org/eclipse/hawkbit/mgmt/client/resource/MgmtRolloutClientResource.java index d2643a938..acc00d6fe 100644 --- a/examples/hawkbit-example-mgmt-feign-client/src/main/java/org/eclipse/hawkbit/mgmt/client/resource/MgmtRolloutClientResource.java +++ b/examples/hawkbit-example-mgmt-feign-client/src/main/java/org/eclipse/hawkbit/mgmt/client/resource/MgmtRolloutClientResource.java @@ -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 { } diff --git a/examples/hawkbit-example-mgmt-feign-client/src/main/java/org/eclipse/hawkbit/mgmt/client/resource/MgmtSoftwareModuleClientResource.java b/examples/hawkbit-example-mgmt-feign-client/src/main/java/org/eclipse/hawkbit/mgmt/client/resource/MgmtSoftwareModuleClientResource.java index 16ea188bd..7a2267e24 100644 --- a/examples/hawkbit-example-mgmt-feign-client/src/main/java/org/eclipse/hawkbit/mgmt/client/resource/MgmtSoftwareModuleClientResource.java +++ b/examples/hawkbit-example-mgmt-feign-client/src/main/java/org/eclipse/hawkbit/mgmt/client/resource/MgmtSoftwareModuleClientResource.java @@ -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 uploadArtifact(@PathVariable("softwareModuleId") final Long softwareModuleId, @Param("file") final MultipartFile file, diff --git a/examples/hawkbit-example-mgmt-feign-client/src/main/java/org/eclipse/hawkbit/mgmt/client/resource/MgmtSoftwareModuleTypeClientResource.java b/examples/hawkbit-example-mgmt-feign-client/src/main/java/org/eclipse/hawkbit/mgmt/client/resource/MgmtSoftwareModuleTypeClientResource.java index 1e9462c47..603e82f10 100644 --- a/examples/hawkbit-example-mgmt-feign-client/src/main/java/org/eclipse/hawkbit/mgmt/client/resource/MgmtSoftwareModuleTypeClientResource.java +++ b/examples/hawkbit-example-mgmt-feign-client/src/main/java/org/eclipse/hawkbit/mgmt/client/resource/MgmtSoftwareModuleTypeClientResource.java @@ -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 { } diff --git a/examples/hawkbit-example-mgmt-feign-client/src/main/java/org/eclipse/hawkbit/mgmt/client/resource/MgmtSystemClientResource.java b/examples/hawkbit-example-mgmt-feign-client/src/main/java/org/eclipse/hawkbit/mgmt/client/resource/MgmtSystemClientResource.java index 7d6967fed..e1bbd909c 100644 --- a/examples/hawkbit-example-mgmt-feign-client/src/main/java/org/eclipse/hawkbit/mgmt/client/resource/MgmtSystemClientResource.java +++ b/examples/hawkbit-example-mgmt-feign-client/src/main/java/org/eclipse/hawkbit/mgmt/client/resource/MgmtSystemClientResource.java @@ -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 { } diff --git a/examples/hawkbit-example-mgmt-feign-client/src/main/java/org/eclipse/hawkbit/mgmt/client/resource/MgmtSystemManagementClientResource.java b/examples/hawkbit-example-mgmt-feign-client/src/main/java/org/eclipse/hawkbit/mgmt/client/resource/MgmtSystemManagementClientResource.java index d1974b43f..802194fe8 100644 --- a/examples/hawkbit-example-mgmt-feign-client/src/main/java/org/eclipse/hawkbit/mgmt/client/resource/MgmtSystemManagementClientResource.java +++ b/examples/hawkbit-example-mgmt-feign-client/src/main/java/org/eclipse/hawkbit/mgmt/client/resource/MgmtSystemManagementClientResource.java @@ -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 { } diff --git a/examples/hawkbit-example-mgmt-feign-client/src/main/java/org/eclipse/hawkbit/mgmt/client/resource/MgmtTargetClientResource.java b/examples/hawkbit-example-mgmt-feign-client/src/main/java/org/eclipse/hawkbit/mgmt/client/resource/MgmtTargetClientResource.java index 872c4251a..c0a0193af 100644 --- a/examples/hawkbit-example-mgmt-feign-client/src/main/java/org/eclipse/hawkbit/mgmt/client/resource/MgmtTargetClientResource.java +++ b/examples/hawkbit-example-mgmt-feign-client/src/main/java/org/eclipse/hawkbit/mgmt/client/resource/MgmtTargetClientResource.java @@ -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 { } diff --git a/examples/hawkbit-example-mgmt-feign-client/src/main/java/org/eclipse/hawkbit/mgmt/client/resource/MgmtTargetTagClientResource.java b/examples/hawkbit-example-mgmt-feign-client/src/main/java/org/eclipse/hawkbit/mgmt/client/resource/MgmtTargetTagClientResource.java index 7b0c213af..3f9264337 100644 --- a/examples/hawkbit-example-mgmt-feign-client/src/main/java/org/eclipse/hawkbit/mgmt/client/resource/MgmtTargetTagClientResource.java +++ b/examples/hawkbit-example-mgmt-feign-client/src/main/java/org/eclipse/hawkbit/mgmt/client/resource/MgmtTargetTagClientResource.java @@ -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 { } diff --git a/examples/hawkbit-example-mgmt-feign-client/src/main/java/org/eclipse/hawkbit/mgmt/client/resource/builder/DistributionSetBuilder.java b/examples/hawkbit-example-mgmt-feign-client/src/main/java/org/eclipse/hawkbit/mgmt/client/resource/builder/DistributionSetBuilder.java index 1e58b8415..a51d96d88 100644 --- a/examples/hawkbit-example-mgmt-feign-client/src/main/java/org/eclipse/hawkbit/mgmt/client/resource/builder/DistributionSetBuilder.java +++ b/examples/hawkbit-example-mgmt-feign-client/src/main/java/org/eclipse/hawkbit/mgmt/client/resource/builder/DistributionSetBuilder.java @@ -84,32 +84,48 @@ public class DistributionSetBuilder { * @return a single entry list of {@link MgmtDistributionSetRequestBodyPost} */ public List 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 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 buildAsList(final int offset, final int count) { final ArrayList 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); diff --git a/examples/hawkbit-example-mgmt-feign-client/src/main/java/org/eclipse/hawkbit/mgmt/client/resource/builder/DistributionSetTypeBuilder.java b/examples/hawkbit-example-mgmt-feign-client/src/main/java/org/eclipse/hawkbit/mgmt/client/resource/builder/DistributionSetTypeBuilder.java index 1ce2ff270..028060f37 100644 --- a/examples/hawkbit-example-mgmt-feign-client/src/main/java/org/eclipse/hawkbit/mgmt/client/resource/builder/DistributionSetTypeBuilder.java +++ b/examples/hawkbit-example-mgmt-feign-client/src/main/java/org/eclipse/hawkbit/mgmt/client/resource/builder/DistributionSetTypeBuilder.java @@ -101,7 +101,7 @@ public class DistributionSetTypeBuilder { * {@link MgmtDistributionSetTypeRequestBodyPost} */ public List build() { - return Lists.newArrayList(doBuild(name, key)); + return Lists.newArrayList(doBuild("")); } /** @@ -118,16 +118,16 @@ public class DistributionSetTypeBuilder { public List buildAsList(final int count) { final ArrayList 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); diff --git a/examples/hawkbit-example-mgmt-feign-client/src/main/java/org/eclipse/hawkbit/mgmt/client/resource/builder/SoftwareModuleBuilder.java b/examples/hawkbit-example-mgmt-feign-client/src/main/java/org/eclipse/hawkbit/mgmt/client/resource/builder/SoftwareModuleBuilder.java index b2e544f88..db7941bfc 100644 --- a/examples/hawkbit-example-mgmt-feign-client/src/main/java/org/eclipse/hawkbit/mgmt/client/resource/builder/SoftwareModuleBuilder.java +++ b/examples/hawkbit-example-mgmt-feign-client/src/main/java/org/eclipse/hawkbit/mgmt/client/resource/builder/SoftwareModuleBuilder.java @@ -90,13 +90,13 @@ public class SoftwareModuleBuilder { * @return a single entry list of {@link MgmtSoftwareModuleRequestBodyPost} */ public List 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 buildAsList(final int count) { final ArrayList 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); diff --git a/examples/hawkbit-example-mgmt-feign-client/src/main/java/org/eclipse/hawkbit/mgmt/client/resource/builder/SoftwareModuleTypeBuilder.java b/examples/hawkbit-example-mgmt-feign-client/src/main/java/org/eclipse/hawkbit/mgmt/client/resource/builder/SoftwareModuleTypeBuilder.java index 7981e61cf..7807d0f11 100644 --- a/examples/hawkbit-example-mgmt-feign-client/src/main/java/org/eclipse/hawkbit/mgmt/client/resource/builder/SoftwareModuleTypeBuilder.java +++ b/examples/hawkbit-example-mgmt-feign-client/src/main/java/org/eclipse/hawkbit/mgmt/client/resource/builder/SoftwareModuleTypeBuilder.java @@ -69,7 +69,7 @@ public class SoftwareModuleTypeBuilder { * {@link MgmtSoftwareModuleTypeRequestBodyPost} */ public List build() { - return Lists.newArrayList(doBuild(key, name)); + return Lists.newArrayList(doBuild("")); } /** @@ -85,15 +85,15 @@ public class SoftwareModuleTypeBuilder { public List buildAsList(final int count) { final ArrayList 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; diff --git a/examples/hawkbit-example-mgmt-feign-client/src/main/java/org/eclipse/hawkbit/mgmt/client/resource/builder/TargetBuilder.java b/examples/hawkbit-example-mgmt-feign-client/src/main/java/org/eclipse/hawkbit/mgmt/client/resource/builder/TargetBuilder.java index 7bcc0af09..5fbcd0313 100644 --- a/examples/hawkbit-example-mgmt-feign-client/src/main/java/org/eclipse/hawkbit/mgmt/client/resource/builder/TargetBuilder.java +++ b/examples/hawkbit-example-mgmt-feign-client/src/main/java/org/eclipse/hawkbit/mgmt/client/resource/builder/TargetBuilder.java @@ -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 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 buildAsList(final int count) { - final ArrayList 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 buildAsList(final int offset, final int count) { + final List 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; } diff --git a/examples/hawkbit-example-mgmt-simulator/src/main/java/org/eclipse/hawkbit/mgmt/client/Application.java b/examples/hawkbit-example-mgmt-simulator/src/main/java/org/eclipse/hawkbit/mgmt/client/Application.java index 7e1f191a9..ac65455b5 100644 --- a/examples/hawkbit-example-mgmt-simulator/src/main/java/org/eclipse/hawkbit/mgmt/client/Application.java +++ b/examples/hawkbit-example-mgmt-simulator/src/main/java/org/eclipse/hawkbit/mgmt/client/Application.java @@ -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; } -} \ No newline at end of file +} diff --git a/examples/hawkbit-example-mgmt-simulator/src/main/java/org/eclipse/hawkbit/mgmt/client/ClientConfigurationProperties.java b/examples/hawkbit-example-mgmt-simulator/src/main/java/org/eclipse/hawkbit/mgmt/client/ClientConfigurationProperties.java index 68f35b550..83a0844d4 100644 --- a/examples/hawkbit-example-mgmt-simulator/src/main/java/org/eclipse/hawkbit/mgmt/client/ClientConfigurationProperties.java +++ b/examples/hawkbit-example-mgmt-simulator/src/main/java/org/eclipse/hawkbit/mgmt/client/ClientConfigurationProperties.java @@ -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 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 getScenarios() { + return scenarios; + } + public String getUrl() { return url; } diff --git a/examples/hawkbit-example-mgmt-simulator/src/main/java/org/eclipse/hawkbit/mgmt/client/scenarios/ConfigurableScenario.java b/examples/hawkbit-example-mgmt-simulator/src/main/java/org/eclipse/hawkbit/mgmt/client/scenarios/ConfigurableScenario.java new file mode 100644 index 000000000..a09459e07 --- /dev/null +++ b/examples/hawkbit-example-mgmt-simulator/src/main/java/org/eclipse/hawkbit/mgmt/client/scenarios/ConfigurableScenario.java @@ -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 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 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 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 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 addModules(final Scenario scenario, final MgmtDistributionSet dsSet, + final byte[] artifact) { + final List 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); + } +} diff --git a/examples/hawkbit-example-mgmt-simulator/src/main/java/org/eclipse/hawkbit/mgmt/client/scenarios/CreateStartedRolloutExample.java b/examples/hawkbit-example-mgmt-simulator/src/main/java/org/eclipse/hawkbit/mgmt/client/scenarios/CreateStartedRolloutExample.java index 90d61471e..b450bbde2 100644 --- a/examples/hawkbit-example-mgmt-simulator/src/main/java/org/eclipse/hawkbit/mgmt/client/scenarios/CreateStartedRolloutExample.java +++ b/examples/hawkbit-example-mgmt-simulator/src/main/java/org/eclipse/hawkbit/mgmt/client/scenarios/CreateStartedRolloutExample.java @@ -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 diff --git a/examples/hawkbit-example-mgmt-simulator/src/main/java/org/eclipse/hawkbit/mgmt/client/scenarios/GettingStartedDefaultScenario.java b/examples/hawkbit-example-mgmt-simulator/src/main/java/org/eclipse/hawkbit/mgmt/client/scenarios/GettingStartedDefaultScenario.java deleted file mode 100644 index fdb824e8e..000000000 --- a/examples/hawkbit-example-mgmt-simulator/src/main/java/org/eclipse/hawkbit/mgmt/client/scenarios/GettingStartedDefaultScenario.java +++ /dev/null @@ -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 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 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 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 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 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 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 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()); - } -} diff --git a/examples/hawkbit-example-mgmt-simulator/src/main/java/org/eclipse/hawkbit/mgmt/client/scenarios/upload/ArtifactFile.java b/examples/hawkbit-example-mgmt-simulator/src/main/java/org/eclipse/hawkbit/mgmt/client/scenarios/upload/ArtifactFile.java new file mode 100644 index 000000000..379455580 --- /dev/null +++ b/examples/hawkbit-example-mgmt-simulator/src/main/java/org/eclipse/hawkbit/mgmt/client/scenarios/upload/ArtifactFile.java @@ -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); + } + +} diff --git a/examples/hawkbit-example-mgmt-simulator/src/main/java/org/eclipse/hawkbit/mgmt/client/scenarios/upload/FeignMultipartEncoder.java b/examples/hawkbit-example-mgmt-simulator/src/main/java/org/eclipse/hawkbit/mgmt/client/scenarios/upload/FeignMultipartEncoder.java new file mode 100644 index 000000000..5d1e43e96 --- /dev/null +++ b/examples/hawkbit-example-mgmt-simulator/src/main/java/org/eclipse/hawkbit/mgmt/client/scenarios/upload/FeignMultipartEncoder.java @@ -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> 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) 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> 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 encodeMultipartFile(final MultipartFile file) { + try { + final MultiValueMap 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; + } + + } +} diff --git a/examples/hawkbit-example-mgmt-simulator/src/main/resources/application.properties b/examples/hawkbit-example-mgmt-simulator/src/main/resources/application.properties index d3a3eb969..747e8ffe9 100644 --- a/examples/hawkbit-example-mgmt-simulator/src/main/resources/application.properties +++ b/examples/hawkbit-example-mgmt-simulator/src/main/resources/application.properties @@ -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 \ No newline at end of file +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 \ No newline at end of file diff --git a/examples/hawkbit-example-mgmt-simulator/src/main/resources/logback.xml b/examples/hawkbit-example-mgmt-simulator/src/main/resources/logback.xml index 0174611e6..768f30687 100644 --- a/examples/hawkbit-example-mgmt-simulator/src/main/resources/logback.xml +++ b/examples/hawkbit-example-mgmt-simulator/src/main/resources/logback.xml @@ -10,17 +10,11 @@ --> - - - - %d{HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%n - - - + - + - + diff --git a/hawkbit-artifact-repository-mongo/src/main/java/org/eclipse/hawkbit/artifact/repository/ArtifactStore.java b/hawkbit-artifact-repository-mongo/src/main/java/org/eclipse/hawkbit/artifact/repository/ArtifactStore.java index aa9ff3409..eabd2b329 100644 --- a/hawkbit-artifact-repository-mongo/src/main/java/org/eclipse/hawkbit/artifact/repository/ArtifactStore.java +++ b/hawkbit-artifact-repository-mongo/src/main/java/org/eclipse/hawkbit/artifact/repository/ArtifactStore.java @@ -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 map(final List 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 diff --git a/hawkbit-artifact-repository-mongo/src/main/java/org/eclipse/hawkbit/artifact/repository/ArtifactStoreAutoConfiguration.java b/hawkbit-artifact-repository-mongo/src/main/java/org/eclipse/hawkbit/artifact/repository/ArtifactStoreAutoConfiguration.java index 43bcddaf0..8a1cb89a9 100644 --- a/hawkbit-artifact-repository-mongo/src/main/java/org/eclipse/hawkbit/artifact/repository/ArtifactStoreAutoConfiguration.java +++ b/hawkbit-artifact-repository-mongo/src/main/java/org/eclipse/hawkbit/artifact/repository/ArtifactStoreAutoConfiguration.java @@ -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 { diff --git a/hawkbit-core/src/main/java/org/eclipse/hawkbit/exception/SpServerError.java b/hawkbit-core/src/main/java/org/eclipse/hawkbit/exception/SpServerError.java index 003b31cd7..7d618e234 100644 --- a/hawkbit-core/src/main/java/org/eclipse/hawkbit/exception/SpServerError.java +++ b/hawkbit-core/src/main/java/org/eclipse/hawkbit/exception/SpServerError.java @@ -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; diff --git a/hawkbit-dmf-amqp/src/main/java/org/eclipse/hawkbit/amqp/AmqpConfiguration.java b/hawkbit-dmf-amqp/src/main/java/org/eclipse/hawkbit/amqp/AmqpConfiguration.java index e40d9bd3c..c26b5547e 100644 --- a/hawkbit-dmf-amqp/src/main/java/org/eclipse/hawkbit/amqp/AmqpConfiguration.java +++ b/hawkbit-dmf-amqp/src/main/java/org/eclipse/hawkbit/amqp/AmqpConfiguration.java @@ -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; } diff --git a/hawkbit-dmf-amqp/src/main/java/org/eclipse/hawkbit/amqp/AmqpMessageHandlerService.java b/hawkbit-dmf-amqp/src/main/java/org/eclipse/hawkbit/amqp/AmqpMessageHandlerService.java index 107850fc1..27cbc0f69 100644 --- a/hawkbit-dmf-amqp/src/main/java/org/eclipse/hawkbit/amqp/AmqpMessageHandlerService.java +++ b/hawkbit-dmf-amqp/src/main/java/org/eclipse/hawkbit/amqp/AmqpMessageHandlerService.java @@ -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 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; diff --git a/hawkbit-dmf-amqp/src/main/java/org/eclipse/hawkbit/amqp/AmqpProperties.java b/hawkbit-dmf-amqp/src/main/java/org/eclipse/hawkbit/amqp/AmqpProperties.java index f9b4cceb6..ace1fefa2 100644 --- a/hawkbit-dmf-amqp/src/main/java/org/eclipse/hawkbit/amqp/AmqpProperties.java +++ b/hawkbit-dmf-amqp/src/main/java/org/eclipse/hawkbit/amqp/AmqpProperties.java @@ -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 * diff --git a/hawkbit-dmf-amqp/src/main/java/org/eclipse/hawkbit/amqp/BaseAmqpService.java b/hawkbit-dmf-amqp/src/main/java/org/eclipse/hawkbit/amqp/BaseAmqpService.java index 2c8feda13..88c88f264 100644 --- a/hawkbit-dmf-amqp/src/main/java/org/eclipse/hawkbit/amqp/BaseAmqpService.java +++ b/hawkbit-dmf-amqp/src/main/java/org/eclipse/hawkbit/amqp/BaseAmqpService.java @@ -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() { diff --git a/hawkbit-dmf-amqp/src/main/java/org/eclipse/hawkbit/amqp/DefaultAmqpSenderService.java b/hawkbit-dmf-amqp/src/main/java/org/eclipse/hawkbit/amqp/DefaultAmqpSenderService.java index afb3e7ff2..d951cce5d 100644 --- a/hawkbit-dmf-amqp/src/main/java/org/eclipse/hawkbit/amqp/DefaultAmqpSenderService.java +++ b/hawkbit-dmf-amqp/src/main/java/org/eclipse/hawkbit/amqp/DefaultAmqpSenderService.java @@ -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); diff --git a/hawkbit-dmf-amqp/src/test/java/org/eclipse/hawkbit/amqp/AmqpMessageHandlerServiceTest.java b/hawkbit-dmf-amqp/src/test/java/org/eclipse/hawkbit/amqp/AmqpMessageHandlerServiceTest.java index a0515a871..338feea62 100644 --- a/hawkbit-dmf-amqp/src/test/java/org/eclipse/hawkbit/amqp/AmqpMessageHandlerServiceTest.java +++ b/hawkbit-dmf-amqp/src/test/java/org/eclipse/hawkbit/amqp/AmqpMessageHandlerServiceTest.java @@ -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 } diff --git a/hawkbit-dmf-amqp/src/test/java/org/eclipse/hawkbit/util/PropertyBasedArtifactUrlHandlerTest.java b/hawkbit-dmf-amqp/src/test/java/org/eclipse/hawkbit/util/PropertyBasedArtifactUrlHandlerTest.java index e93e1340f..8cd92d2bd 100644 --- a/hawkbit-dmf-amqp/src/test/java/org/eclipse/hawkbit/util/PropertyBasedArtifactUrlHandlerTest.java +++ b/hawkbit-dmf-amqp/src/test/java/org/eclipse/hawkbit/util/PropertyBasedArtifactUrlHandlerTest.java @@ -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/"; diff --git a/hawkbit-mgmt-api/src/main/java/org/eclipse/hawkbit/mgmt/json/model/target/MgmtTargetRequestBody.java b/hawkbit-mgmt-api/src/main/java/org/eclipse/hawkbit/mgmt/json/model/target/MgmtTargetRequestBody.java index bc40326e4..b9c0b001a 100644 --- a/hawkbit-mgmt-api/src/main/java/org/eclipse/hawkbit/mgmt/json/model/target/MgmtTargetRequestBody.java +++ b/hawkbit-mgmt-api/src/main/java/org/eclipse/hawkbit/mgmt/json/model/target/MgmtTargetRequestBody.java @@ -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; + } + } diff --git a/hawkbit-mgmt-api/src/main/java/org/eclipse/hawkbit/mgmt/rest/api/MgmtTargetRestApi.java b/hawkbit-mgmt-api/src/main/java/org/eclipse/hawkbit/mgmt/rest/api/MgmtTargetRestApi.java index bf4e169a0..97a0eae5c 100644 --- a/hawkbit-mgmt-api/src/main/java/org/eclipse/hawkbit/mgmt/rest/api/MgmtTargetRestApi.java +++ b/hawkbit-mgmt-api/src/main/java/org/eclipse/hawkbit/mgmt/rest/api/MgmtTargetRestApi.java @@ -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 getTarget(@PathVariable("targetId") final String targetId); + ResponseEntity 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 updateTarget(@PathVariable("targetId") final String targetId, + ResponseEntity 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 deleteTarget(@PathVariable("targetId") final String targetId); + ResponseEntity 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 getAttributes(@PathVariable("targetId") final String targetId); + @RequestMapping(method = RequestMethod.GET, value = "/{controllerId}/attributes", produces = { + "application/hal+json", MediaType.APPLICATION_JSON_VALUE }) + ResponseEntity 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> getActionHistory(@PathVariable("targetId") final String targetId, + ResponseEntity> 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 getAction(@PathVariable("targetId") final String targetId, + ResponseEntity 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 cancelAction(@PathVariable("targetId") final String targetId, + @RequestMapping(method = RequestMethod.DELETE, value = "/{controllerId}/actions/{actionId}") + ResponseEntity 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> getActionStatusList(@PathVariable("targetId") final String targetId, - @PathVariable("actionId") final Long actionId, + ResponseEntity> 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 getAssignedDistributionSet(@PathVariable("targetId") final String targetId); + @RequestMapping(method = RequestMethod.GET, value = "/{controllerId}/assignedDS", produces = { + "application/hal+json", MediaType.APPLICATION_JSON_VALUE }) + ResponseEntity 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 postAssignedDistributionSet(@PathVariable("targetId") final String targetId, + ResponseEntity 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 getInstalledDistributionSet(@PathVariable("targetId") final String targetId); + @RequestMapping(method = RequestMethod.GET, value = "/{controllerId}/installedDS", produces = { + "application/hal+json", MediaType.APPLICATION_JSON_VALUE }) + ResponseEntity getInstalledDistributionSet( + @PathVariable("controllerId") final String controllerId); } diff --git a/hawkbit-mgmt-resource/src/main/java/org/eclipse/hawkbit/mgmt/rest/resource/MgmtTargetMapper.java b/hawkbit-mgmt-resource/src/main/java/org/eclipse/hawkbit/mgmt/rest/resource/MgmtTargetMapper.java index f00998d29..f6cf9f54b 100644 --- a/hawkbit-mgmt-resource/src/main/java/org/eclipse/hawkbit/mgmt/rest/resource/MgmtTargetMapper.java +++ b/hawkbit-mgmt-resource/src/main/java/org/eclipse/hawkbit/mgmt/rest/resource/MgmtTargetMapper.java @@ -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; } diff --git a/hawkbit-mgmt-resource/src/main/java/org/eclipse/hawkbit/mgmt/rest/resource/MgmtTargetResource.java b/hawkbit-mgmt-resource/src/main/java/org/eclipse/hawkbit/mgmt/rest/resource/MgmtTargetResource.java index cc7bbcde6..c41dadd27 100644 --- a/hawkbit-mgmt-resource/src/main/java/org/eclipse/hawkbit/mgmt/rest/resource/MgmtTargetResource.java +++ b/hawkbit-mgmt-resource/src/main/java/org/eclipse/hawkbit/mgmt/rest/resource/MgmtTargetResource.java @@ -68,8 +68,8 @@ public class MgmtTargetResource implements MgmtTargetRestApi { private EntityFactory entityFactory; @Override - public ResponseEntity getTarget(@PathVariable("targetId") final String targetId) { - final Target findTarget = findTargetWithExceptionIfNotFound(targetId); + public ResponseEntity 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 updateTarget(@PathVariable("targetId") final String targetId, + public ResponseEntity 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 deleteTarget(@PathVariable("targetId") final String targetId) { - final Target target = findTargetWithExceptionIfNotFound(targetId); + public ResponseEntity 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 getAttributes(@PathVariable("targetId") final String targetId) { - final Target foundTarget = findTargetWithExceptionIfNotFound(targetId); + public ResponseEntity getAttributes(@PathVariable("controllerId") final String controllerId) { + final Target foundTarget = findTargetWithExceptionIfNotFound(controllerId); final Map 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> getActionHistory(@PathVariable("targetId") final String targetId, + public ResponseEntity> 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 getAction(@PathVariable("targetId") final String targetId, + public ResponseEntity 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 cancelAction(@PathVariable("targetId") final String targetId, + public ResponseEntity 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> 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 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 postAssignedDistributionSet(@PathVariable("targetId") final String targetId, + public ResponseEntity 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 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 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; } diff --git a/hawkbit-mgmt-resource/src/test/java/org/eclipse/hawkbit/mgmt/rest/resource/MgmtTargetResourceTest.java b/hawkbit-mgmt-resource/src/test/java/org/eclipse/hawkbit/mgmt/rest/resource/MgmtTargetResourceTest.java index 1375307d4..ed6ee4a93 100644 --- a/hawkbit-mgmt-resource/src/test/java/org/eclipse/hawkbit/mgmt/rest/resource/MgmtTargetResourceTest.java +++ b/hawkbit-mgmt-resource/src/test/java/org/eclipse/hawkbit/mgmt/rest/resource/MgmtTargetResourceTest.java @@ -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 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"))) diff --git a/hawkbit-repository/hawkbit-repository-api/src/main/java/org/eclipse/hawkbit/repository/RepositoryProperties.java b/hawkbit-repository/hawkbit-repository-api/src/main/java/org/eclipse/hawkbit/repository/RepositoryProperties.java new file mode 100644 index 000000000..c60089fb3 --- /dev/null +++ b/hawkbit-repository/hawkbit-repository-api/src/main/java/org/eclipse/hawkbit/repository/RepositoryProperties.java @@ -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 true 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; + } + +} diff --git a/hawkbit-repository/hawkbit-repository-api/src/main/java/org/eclipse/hawkbit/repository/TargetManagement.java b/hawkbit-repository/hawkbit-repository-api/src/main/java/org/eclipse/hawkbit/repository/TargetManagement.java index 9054127ef..5fdfb318e 100644 --- a/hawkbit-repository/hawkbit-repository-api/src/main/java/org/eclipse/hawkbit/repository/TargetManagement.java +++ b/hawkbit-repository/hawkbit-repository-api/src/main/java/org/eclipse/hawkbit/repository/TargetManagement.java @@ -179,25 +179,6 @@ public interface TargetManagement { @PreAuthorize(SpringEvalExpressions.HAS_AUTH_CREATE_TARGET) List createTargets(@NotNull Collection 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 createTargets(@NotNull Collection targets, @NotNull TargetUpdateStatus status, - Long lastTargetQuery, URI address); - /** * Deletes all targets with the given IDs. * diff --git a/hawkbit-repository/hawkbit-repository-api/src/main/java/org/eclipse/hawkbit/repository/exception/InvalidTargetAddressException.java b/hawkbit-repository/hawkbit-repository-api/src/main/java/org/eclipse/hawkbit/repository/exception/InvalidTargetAddressException.java new file mode 100644 index 000000000..70b890483 --- /dev/null +++ b/hawkbit-repository/hawkbit-repository-api/src/main/java/org/eclipse/hawkbit/repository/exception/InvalidTargetAddressException.java @@ -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); + } +} diff --git a/hawkbit-repository/hawkbit-repository-api/src/main/java/org/eclipse/hawkbit/repository/model/TargetInfo.java b/hawkbit-repository/hawkbit-repository-api/src/main/java/org/eclipse/hawkbit/repository/model/TargetInfo.java index 6317f9783..2eb2f05c7 100644 --- a/hawkbit-repository/hawkbit-repository-api/src/main/java/org/eclipse/hawkbit/repository/model/TargetInfo.java +++ b/hawkbit-repository/hawkbit-repository-api/src/main/java/org/eclipse/hawkbit/repository/model/TargetInfo.java @@ -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 2396 + */ + void setAddress(String address); + /** * @return {@link Target} this info element belongs to. */ diff --git a/hawkbit-repository/hawkbit-repository-jpa/src/main/java/org/eclipse/hawkbit/RepositoryApplicationConfiguration.java b/hawkbit-repository/hawkbit-repository-jpa/src/main/java/org/eclipse/hawkbit/RepositoryApplicationConfiguration.java index 469e84681..217fe8801 100644 --- a/hawkbit-repository/hawkbit-repository-jpa/src/main/java/org/eclipse/hawkbit/RepositoryApplicationConfiguration.java +++ b/hawkbit-repository/hawkbit-repository-jpa/src/main/java/org/eclipse/hawkbit/RepositoryApplicationConfiguration.java @@ -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 { /** diff --git a/hawkbit-repository/hawkbit-repository-jpa/src/main/java/org/eclipse/hawkbit/repository/jpa/JpaControllerManagement.java b/hawkbit-repository/hawkbit-repository-jpa/src/main/java/org/eclipse/hawkbit/repository/jpa/JpaControllerManagement.java index 255fef2ae..b9dfce105 100644 --- a/hawkbit-repository/hawkbit-repository-jpa/src/main/java/org/eclipse/hawkbit/repository/jpa/JpaControllerManagement.java +++ b/hawkbit-repository/hawkbit-repository-jpa/src/main/java/org/eclipse/hawkbit/repository/jpa/JpaControllerManagement.java @@ -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); diff --git a/hawkbit-repository/hawkbit-repository-jpa/src/main/java/org/eclipse/hawkbit/repository/jpa/JpaTargetManagement.java b/hawkbit-repository/hawkbit-repository-jpa/src/main/java/org/eclipse/hawkbit/repository/jpa/JpaTargetManagement.java index b7a2facc8..1cd2ffc3c 100644 --- a/hawkbit-repository/hawkbit-repository-jpa/src/main/java/org/eclipse/hawkbit/repository/jpa/JpaTargetManagement.java +++ b/hawkbit-repository/hawkbit-repository-jpa/src/main/java/org/eclipse/hawkbit/repository/jpa/JpaTargetManagement.java @@ -606,24 +606,7 @@ public class JpaTargetManagement implements TargetManagement { } final List 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 createTargets(final Collection 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 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; diff --git a/hawkbit-repository/hawkbit-repository-jpa/src/main/java/org/eclipse/hawkbit/repository/jpa/model/JpaTargetInfo.java b/hawkbit-repository/hawkbit-repository-jpa/src/main/java/org/eclipse/hawkbit/repository/jpa/model/JpaTargetInfo.java index 6b5a3d1af..0606def4f 100644 --- a/hawkbit-repository/hawkbit-repository-jpa/src/main/java/org/eclipse/hawkbit/repository/jpa/model/JpaTargetInfo.java +++ b/hawkbit-repository/hawkbit-repository-jpa/src/main/java/org/eclipse/hawkbit/repository/jpa/model/JpaTargetInfo.java @@ -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, TargetInfo { /** * @param address - * the ipAddress to set + * the target address to set * * @throws IllegalArgumentException * If the given string violates RFC 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; diff --git a/hawkbit-repository/hawkbit-repository-jpa/src/test/java/org/eclipse/hawkbit/repository/jpa/ControllerManagementTest.java b/hawkbit-repository/hawkbit-repository-jpa/src/test/java/org/eclipse/hawkbit/repository/jpa/ControllerManagementTest.java index f33c5c170..77adfa45b 100644 --- a/hawkbit-repository/hawkbit-repository-jpa/src/test/java/org/eclipse/hawkbit/repository/jpa/ControllerManagementTest.java +++ b/hawkbit-repository/hawkbit-repository-jpa/src/test/java/org/eclipse/hawkbit/repository/jpa/ControllerManagementTest.java @@ -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 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; + } } diff --git a/hawkbit-rest-core/src/main/java/org/eclipse/hawkbit/rest/exception/ResponseExceptionHandler.java b/hawkbit-rest-core/src/main/java/org/eclipse/hawkbit/rest/exception/ResponseExceptionHandler.java index 0f8da9ebe..31e4a3046 100644 --- a/hawkbit-rest-core/src/main/java/org/eclipse/hawkbit/rest/exception/ResponseExceptionHandler.java +++ b/hawkbit-rest-core/src/main/java/org/eclipse/hawkbit/rest/exception/ResponseExceptionHandler.java @@ -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) { diff --git a/hawkbit-rest-core/src/test/java/org/eclipse/hawkbit/rest/util/JsonBuilder.java b/hawkbit-rest-core/src/test/java/org/eclipse/hawkbit/rest/util/JsonBuilder.java index f67f15815..51cbd7a99 100644 --- a/hawkbit-rest-core/src/test/java/org/eclipse/hawkbit/rest/util/JsonBuilder.java +++ b/hawkbit-rest-core/src/test/java/org/eclipse/hawkbit/rest/util/JsonBuilder.java @@ -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 messages = new ArrayList(); 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 messages = new ArrayList(); 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 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())).put("data", attributes).toString(); + .put("details", new ArrayList())) + .put("data", attributes).toString(); } diff --git a/hawkbit-security-core/src/main/java/org/eclipse/hawkbit/util/IpUtil.java b/hawkbit-security-core/src/main/java/org/eclipse/hawkbit/util/IpUtil.java index 96fc557aa..c7778f776 100644 --- a/hawkbit-security-core/src/main/java/org/eclipse/hawkbit/util/IpUtil.java +++ b/hawkbit-security-core/src/main/java/org/eclipse/hawkbit/util/IpUtil.java @@ -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 true 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())); + } + } diff --git a/pom.xml b/pom.xml index de7a9dd6d..444883bba 100644 --- a/pom.xml +++ b/pom.xml @@ -69,6 +69,7 @@ 5.2.4.Final 1.2.0.RELEASE 1.6.0.RELEASE + 3.6.2 0.18.0.RELEASE Fowler-SR1 @@ -526,6 +527,11 @@ org.eclipse.persistence.jpa ${eclipselink.version} + + com.rabbitmq + amqp-client + ${rabbitmq.version} + cz.jirutka.rsql