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/DeviceSimulatorUpdater.java b/examples/hawkbit-device-simulator/src/main/java/org/eclipse/hawkbit/simulator/DeviceSimulatorUpdater.java index 0931f6f3e..69ed4fd4a 100644 --- a/examples/hawkbit-device-simulator/src/main/java/org/eclipse/hawkbit/simulator/DeviceSimulatorUpdater.java +++ b/examples/hawkbit-device-simulator/src/main/java/org/eclipse/hawkbit/simulator/DeviceSimulatorUpdater.java @@ -10,8 +10,6 @@ package org.eclipse.hawkbit.simulator; import java.io.BufferedInputStream; import java.io.BufferedOutputStream; -import java.io.File; -import java.io.FileOutputStream; import java.io.IOException; import java.security.DigestOutputStream; import java.security.KeyManagementException; @@ -34,6 +32,7 @@ import org.apache.http.conn.ssl.SSLContextBuilder; import org.apache.http.impl.client.CloseableHttpClient; import org.apache.http.impl.client.HttpClients; import org.eclipse.hawkbit.dmf.json.model.Artifact; +import org.eclipse.hawkbit.dmf.json.model.Artifact.UrlProtocol; import org.eclipse.hawkbit.dmf.json.model.SoftwareModule; import org.eclipse.hawkbit.simulator.AbstractSimulatedDevice.Protocol; import org.eclipse.hawkbit.simulator.UpdateStatus.ResponseStatus; @@ -59,7 +58,7 @@ import com.google.common.io.ByteStreams; public class DeviceSimulatorUpdater { private static final Logger LOGGER = LoggerFactory.getLogger(DeviceSimulatorUpdater.class); - private static final ScheduledExecutorService threadPool = Executors.newScheduledThreadPool(4); + private static final ScheduledExecutorService threadPool = Executors.newScheduledThreadPool(8); @Autowired private SpSenderService spSenderService; @@ -99,7 +98,8 @@ public class DeviceSimulatorUpdater { // plug and play - non existing device will be auto created if (device == null) { - device = repository.add(deviceFactory.createSimulatedDevice(id, tenant, Protocol.DMF_AMQP, -1, null, null)); + device = repository + .add(deviceFactory.createSimulatedDevice(id, tenant, Protocol.DMF_AMQP, 1800, null, null)); } device.setProgress(0.0); @@ -197,18 +197,14 @@ public class DeviceSimulatorUpdater { private static void handleArtifacts(final String targetToken, final List status, final Artifact artifact) { - artifact.getUrls().entrySet().forEach(entry -> { - switch (entry.getKey()) { - case HTTP: - case HTTPS: - status.add(downloadUrl(entry.getValue(), targetToken, artifact.getHashes().getSha1(), - artifact.getSize())); - break; - default: - // not supported yet - break; - } - }); + + if (artifact.getUrls().containsKey(UrlProtocol.HTTPS)) { + status.add(downloadUrl(artifact.getUrls().get(UrlProtocol.HTTPS), targetToken, + artifact.getHashes().getSha1(), artifact.getSize())); + } else if (artifact.getUrls().containsKey(UrlProtocol.HTTP)) { + status.add(downloadUrl(artifact.getUrls().get(UrlProtocol.HTTP), targetToken, + artifact.getHashes().getSha1(), artifact.getSize())); + } } private static UpdateStatus downloadUrl(final String url, final String targetToken, final String sha1Hash, @@ -235,22 +231,15 @@ public class DeviceSimulatorUpdater { return new UpdateStatus(ResponseStatus.ERROR, message); } - final File tempFile = File.createTempFile("uploadFile", null); - // Exception squid:S2070 - not used for hashing sensitive // data @SuppressWarnings("squid:S2070") final MessageDigest md = MessageDigest.getInstance("SHA-1"); - try (final DigestOutputStream dos = new DigestOutputStream(new FileOutputStream(tempFile), md)) { - try (final BufferedOutputStream bdos = new BufferedOutputStream(dos)) { - try (BufferedInputStream bis = new BufferedInputStream(response.getEntity().getContent())) { - overallread = ByteStreams.copy(bis, bdos); - } - } - } finally { - if (tempFile != null && !tempFile.delete()) { - LOGGER.error("Could not delete temporary file: {}", tempFile); + try (final BufferedOutputStream bdos = new BufferedOutputStream( + new DigestOutputStream(ByteStreams.nullOutputStream(), md))) { + try (BufferedInputStream bis = new BufferedInputStream(response.getEntity().getContent())) { + overallread = ByteStreams.copy(bis, bdos); } } 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..8d5c5e0b1 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. * @@ -41,7 +45,7 @@ public class SimulatedDeviceFactory { */ public AbstractSimulatedDevice createSimulatedDevice(final String id, final String tenant, final Protocol protocol) { - return createSimulatedDevice(id, tenant, protocol, 30, null, null); + return createSimulatedDevice(id, tenant, protocol, 1800, null, null); } /** @@ -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..43f954c00 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 { @@ -86,10 +86,6 @@ public class SimulationController { final String deviceId = name + i; repository.add(deviceFactory.createSimulatedDevice(deviceId, tenant, protocol, pollDelay, new URL(endpoint), gatewayToken)); - - if (protocol == Protocol.DMF_AMQP) { - spSenderService.createOrUpdateThing(tenant, deviceId); - } } return ResponseEntity.ok("Updated " + amount + " DMF connected targets!"); 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/SimulatorStartup.java b/examples/hawkbit-device-simulator/src/main/java/org/eclipse/hawkbit/simulator/SimulatorStartup.java index 19367a9d4..486eb4b5e 100644 --- a/examples/hawkbit-device-simulator/src/main/java/org/eclipse/hawkbit/simulator/SimulatorStartup.java +++ b/examples/hawkbit-device-simulator/src/main/java/org/eclipse/hawkbit/simulator/SimulatorStartup.java @@ -11,7 +11,6 @@ package org.eclipse.hawkbit.simulator; import java.net.MalformedURLException; import java.net.URL; -import org.eclipse.hawkbit.simulator.AbstractSimulatedDevice.Protocol; import org.eclipse.hawkbit.simulator.amqp.SpSenderService; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -54,10 +53,6 @@ public class SimulatorStartup implements ApplicationListener { + 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; + } } /** @@ -71,8 +122,8 @@ public class AmqpConfiguration { final Map arguments = getDeadLetterExchangeArgs(); arguments.putAll(getTTLMaxArgs()); - return QueueBuilder.nonDurable(amqpProperties.getReceiverConnectorQueueFromSp()).withArguments(arguments) - .build(); + return QueueBuilder.nonDurable(amqpProperties.getReceiverConnectorQueueFromSp()).autoDelete() + .withArguments(arguments).build(); } /** @@ -82,12 +133,12 @@ public class AmqpConfiguration { */ @Bean public FanoutExchange exchangeQueueToConnector() { - return new FanoutExchange(amqpProperties.getSenderForSpExchange()); + return new FanoutExchange(amqpProperties.getSenderForSpExchange(), false, true); } /** * Create the Binding - * {@link AmqpConfiguration#receiverConnectorQueueFromSp()} to + * {@link AmqpConfiguration#receiverConnectorQueueFromHawkBit()} to * {@link AmqpConfiguration#exchangeQueueToConnector()}. * * @return the binding and create the queue and exchange @@ -114,7 +165,7 @@ public class AmqpConfiguration { */ @Bean public FanoutExchange exchangeDeadLetter() { - return new FanoutExchange(amqpProperties.getDeadLetterExchange()); + return new FanoutExchange(amqpProperties.getDeadLetterExchange(), false, true); } /** @@ -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-autoconfigure/src/main/java/org/eclipse/hawkbit/autoconfigure/scheduling/AsyncConfigurerThreadpoolProperties.java b/hawkbit-autoconfigure/src/main/java/org/eclipse/hawkbit/autoconfigure/scheduling/AsyncConfigurerThreadpoolProperties.java index 4425c7278..d6f3ca430 100644 --- a/hawkbit-autoconfigure/src/main/java/org/eclipse/hawkbit/autoconfigure/scheduling/AsyncConfigurerThreadpoolProperties.java +++ b/hawkbit-autoconfigure/src/main/java/org/eclipse/hawkbit/autoconfigure/scheduling/AsyncConfigurerThreadpoolProperties.java @@ -32,6 +32,11 @@ public class AsyncConfigurerThreadpoolProperties { */ private Integer maxthreads = 20; + /** + * Core processing threads for scheduled event executor. + */ + private Integer schedulerThreads = 3; + /** * When the number of threads is greater than the core, this is the maximum * time that excess idle threads will wait for new tasks before terminating. @@ -70,4 +75,12 @@ public class AsyncConfigurerThreadpoolProperties { this.idletimeout = idletimeout; } + public Integer getSchedulerThreads() { + return schedulerThreads; + } + + public void setSchedulerThreads(final Integer schedulerThreads) { + this.schedulerThreads = schedulerThreads; + } + } diff --git a/hawkbit-autoconfigure/src/main/java/org/eclipse/hawkbit/autoconfigure/scheduling/ExecutorAutoConfiguration.java b/hawkbit-autoconfigure/src/main/java/org/eclipse/hawkbit/autoconfigure/scheduling/ExecutorAutoConfiguration.java index 0aeaf75bb..917e4e4ef 100644 --- a/hawkbit-autoconfigure/src/main/java/org/eclipse/hawkbit/autoconfigure/scheduling/ExecutorAutoConfiguration.java +++ b/hawkbit-autoconfigure/src/main/java/org/eclipse/hawkbit/autoconfigure/scheduling/ExecutorAutoConfiguration.java @@ -11,6 +11,8 @@ package org.eclipse.hawkbit.autoconfigure.scheduling; import java.util.concurrent.ArrayBlockingQueue; import java.util.concurrent.BlockingQueue; import java.util.concurrent.Executor; +import java.util.concurrent.ExecutorService; +import java.util.concurrent.Executors; import java.util.concurrent.ScheduledExecutorService; import java.util.concurrent.ThreadPoolExecutor; import java.util.concurrent.ThreadPoolExecutor.CallerRunsPolicy; @@ -24,9 +26,12 @@ import org.springframework.boot.context.properties.EnableConfigurationProperties import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.core.task.TaskExecutor; +import org.springframework.scheduling.TaskScheduler; import org.springframework.scheduling.concurrent.ConcurrentTaskExecutor; -import org.springframework.scheduling.concurrent.ThreadPoolTaskScheduler; +import org.springframework.scheduling.concurrent.ConcurrentTaskScheduler; import org.springframework.security.concurrent.DelegatingSecurityContextExecutor; +import org.springframework.security.concurrent.DelegatingSecurityContextExecutorService; +import org.springframework.security.concurrent.DelegatingSecurityContextScheduledExecutorService; import com.google.common.util.concurrent.ThreadFactoryBuilder; @@ -45,20 +50,28 @@ public class ExecutorAutoConfiguration { /** * @return ExecutorService with security context availability in thread - * execution.. + * execution. + */ + @Bean(destroyMethod = "shutdown") + @ConditionalOnMissingBean + public ExecutorService asyncExecutor() { + return new DelegatingSecurityContextExecutorService(threadPoolExecutor()); + } + + /** + * @return {@link TaskExecutor} for task execution */ @Bean @ConditionalOnMissingBean - public Executor asyncExecutor() { - return new DelegatingSecurityContextExecutor(threadPoolExecutor()); + public TaskExecutor taskExecutor() { + return new ConcurrentTaskExecutor(asyncExecutor()); } /** * @return central ThreadPoolExecutor for general purpose multi threaded * operations. Tries an orderly shutdown when destroyed. */ - @Bean(destroyMethod = "shutdown") - public ThreadPoolExecutor threadPoolExecutor() { + private ThreadPoolExecutor threadPoolExecutor() { final BlockingQueue blockingQueue = new ArrayBlockingQueue<>( asyncConfigurerProperties.getQueuesize()); return new ThreadPoolExecutor(asyncConfigurerProperties.getCorethreads(), @@ -92,31 +105,24 @@ public class ExecutorAutoConfiguration { } /** - * @return {@link TaskExecutor} for task execution + * @return {@link ScheduledExecutorService} with security context + * availability in thread execution. */ - @Bean - @ConditionalOnMissingBean - public TaskExecutor taskExecutor() { - return new ConcurrentTaskExecutor(asyncExecutor()); - } - - /** - * @return {@link ScheduledExecutorService} based on - * {@link #threadPoolTaskScheduler()}. - */ - @Bean + @Bean(destroyMethod = "shutdown") @ConditionalOnMissingBean public ScheduledExecutorService scheduledExecutorService() { - return threadPoolTaskScheduler().getScheduledExecutor(); + return new DelegatingSecurityContextScheduledExecutorService( + Executors.newScheduledThreadPool(asyncConfigurerProperties.getSchedulerThreads(), + new ThreadFactoryBuilder().setNameFormat("central-scheduled-executor-pool-%d").build())); } /** - * @return {@link ThreadPoolTaskScheduler} for scheduled operations. + * @return {@link TaskScheduler} for task execution */ @Bean @ConditionalOnMissingBean - public ThreadPoolTaskScheduler threadPoolTaskScheduler() { - return new ThreadPoolTaskScheduler(); + public TaskScheduler taskScheduler() { + return new ConcurrentTaskScheduler(scheduledExecutorService()); } } diff --git a/hawkbit-autoconfigure/src/main/java/org/eclipse/hawkbit/autoconfigure/security/SecurityManagedConfiguration.java b/hawkbit-autoconfigure/src/main/java/org/eclipse/hawkbit/autoconfigure/security/SecurityManagedConfiguration.java index fc7a0a1e4..1e4dbfb27 100644 --- a/hawkbit-autoconfigure/src/main/java/org/eclipse/hawkbit/autoconfigure/security/SecurityManagedConfiguration.java +++ b/hawkbit-autoconfigure/src/main/java/org/eclipse/hawkbit/autoconfigure/security/SecurityManagedConfiguration.java @@ -83,6 +83,7 @@ import org.springframework.security.web.header.writers.frameoptions.StaticAllowF import org.springframework.security.web.header.writers.frameoptions.XFrameOptionsHeaderWriter; import org.springframework.security.web.header.writers.frameoptions.XFrameOptionsHeaderWriter.XFrameOptionsMode; import org.springframework.security.web.session.HttpSessionEventPublisher; +import org.springframework.security.web.session.SessionManagementFilter; import org.vaadin.spring.security.VaadinSecurityContext; import org.vaadin.spring.security.annotation.EnableVaadinSecurity; import org.vaadin.spring.security.web.VaadinDefaultRedirectStrategy; @@ -270,20 +271,6 @@ public class SecurityManagedConfiguration { return filterRegBean; } - /** - * Security configuration for the REST management API of the health url. - */ - @Configuration - @Order(310) - public static class HealthSecurityConfigurationAdapter extends WebSecurityConfigurerAdapter { - - @Override - protected void configure(final HttpSecurity http) throws Exception { - http.regexMatcher("/system/health").csrf().disable().httpBasic().and().sessionManagement() - .sessionCreationPolicy(SessionCreationPolicy.STATELESS); - } - } - /** * Security configuration for the REST management API. */ @@ -309,7 +296,7 @@ public class SecurityManagedConfiguration { final BasicAuthenticationEntryPoint basicAuthEntryPoint = new BasicAuthenticationEntryPoint(); basicAuthEntryPoint.setRealmName(springSecurityProperties.getBasic().getRealm()); - HttpSecurity httpSec = http.regexMatcher("\\/rest.*|\\/system.*").csrf().disable(); + HttpSecurity httpSec = http.regexMatcher("\\/rest.*|\\/system/admin.*").csrf().disable(); if (springSecurityProperties.isRequireSsl()) { httpSec = httpSec.requiresChannel().anyRequest().requiresSecure().and(); } @@ -333,12 +320,10 @@ public class SecurityManagedConfiguration { }, RequestHeaderAuthenticationFilter.class) .addFilterAfter( new AuthenticationSuccessTenantMetadataCreationFilter(tenantAware, systemManagement), - RequestHeaderAuthenticationFilter.class) + SessionManagementFilter.class) .authorizeRequests().anyRequest().authenticated() .antMatchers(MgmtRestConstants.BASE_SYSTEM_MAPPING + "/admin/**") - .hasAnyAuthority(SpPermission.SYSTEM_ADMIN) - .antMatchers(MgmtRestConstants.BASE_SYSTEM_MAPPING + "/**") - .hasAnyAuthority(SpPermission.SYSTEM_DIAG); + .hasAnyAuthority(SpPermission.SYSTEM_ADMIN); httpSec.httpBasic().and().exceptionHandling().authenticationEntryPoint(basicAuthEntryPoint); } diff --git a/hawkbit-autoconfigure/src/main/resources/hawkbitdefaults.properties b/hawkbit-autoconfigure/src/main/resources/hawkbitdefaults.properties index 9c197fbb3..cb7793168 100644 --- a/hawkbit-autoconfigure/src/main/resources/hawkbitdefaults.properties +++ b/hawkbit-autoconfigure/src/main/resources/hawkbitdefaults.properties @@ -41,4 +41,5 @@ hawkbit.controller.minPollingTime=00:00:30 # Configuration for RabbitMQ integration hawkbit.dmf.rabbitmq.deadLetterQueue=dmf_connector_deadletter_ttl hawkbit.dmf.rabbitmq.deadLetterExchange=dmf.connector.deadletter -hawkbit.dmf.rabbitmq.receiverQueue=dmf_receiver \ No newline at end of file +hawkbit.dmf.rabbitmq.receiverQueue=dmf_receiver +hawkbit.dmf.rabbitmq.authenticationReceiverQueue=authentication_receiver 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..5acd11463 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,147 +24,124 @@ 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_INSUFFICIENT_PERMISSION("hawkbit.server.error.insufficientpermission", "Insufficient Permission"), /** * */ - 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 +151,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 +165,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-ddi-resource/src/main/java/org/eclipse/hawkbit/ddi/rest/resource/DataConversionHelper.java b/hawkbit-ddi-resource/src/main/java/org/eclipse/hawkbit/ddi/rest/resource/DataConversionHelper.java index b07e2557c..d87de67d0 100644 --- a/hawkbit-ddi-resource/src/main/java/org/eclipse/hawkbit/ddi/rest/resource/DataConversionHelper.java +++ b/hawkbit-ddi-resource/src/main/java/org/eclipse/hawkbit/ddi/rest/resource/DataConversionHelper.java @@ -79,12 +79,12 @@ public final class DataConversionHelper { final org.eclipse.hawkbit.repository.model.SoftwareModule module, final ArtifactUrlHandler artifactUrlHandler) { final List files = new ArrayList<>(); - + module.getLocalArtifacts() - .forEach(artifact -> files.add(createArtifact(targetid, artifactUrlHandler, artifact))); + .forEach(artifact -> files.add(createArtifact(targetid, artifactUrlHandler, artifact))); return files; } - + private static DdiArtifact createArtifact(final String targetid, final ArtifactUrlHandler artifactUrlHandler, final LocalArtifact artifact) { final DdiArtifact file = new DdiArtifact(); @@ -125,7 +125,7 @@ public final class DataConversionHelper { // response because of eTags. result.add(linkTo(methodOn(DdiRootController.class, tenantAware.getCurrentTenant()) .getControllerBasedeploymentAction(target.getControllerId(), action.getId(), - actions.hashCode())).withRel(DdiRestConstants.DEPLOYMENT_BASE_ACTION)); + calculateEtag(action))).withRel(DdiRestConstants.DEPLOYMENT_BASE_ACTION)); addedUpdate = true; } else if (action.isCancelingOrCanceled() && !addedCancel) { result.add(linkTo(methodOn(DdiRootController.class, tenantAware.getCurrentTenant()) @@ -142,6 +142,22 @@ public final class DataConversionHelper { return result; } + /** + * Calculates an etag for the given {@link Action} based on the entities + * hashcode and the {@link Action#isHitAutoForceTime(long)} to reflect a + * force switch. + * + * @param action + * to calculate the etag for + * @return the etag + */ + private static int calculateEtag(final Action action) { + final int prime = 31; + int result = action.hashCode(); + result = prime * result + (action.isHitAutoForceTime(System.currentTimeMillis()) ? 1231 : 1237); + return result; + } + static void writeMD5FileResponse(final String fileName, final HttpServletResponse response, final LocalArtifact artifact) throws IOException { final StringBuilder builder = new StringBuilder(); diff --git a/hawkbit-ddi-resource/src/test/java/org/eclipse/hawkbit/ddi/rest/resource/DdiDeploymentBaseTest.java b/hawkbit-ddi-resource/src/test/java/org/eclipse/hawkbit/ddi/rest/resource/DdiDeploymentBaseTest.java index 33d470b3d..0ec0d0d7e 100644 --- a/hawkbit-ddi-resource/src/test/java/org/eclipse/hawkbit/ddi/rest/resource/DdiDeploymentBaseTest.java +++ b/hawkbit-ddi-resource/src/test/java/org/eclipse/hawkbit/ddi/rest/resource/DdiDeploymentBaseTest.java @@ -23,15 +23,17 @@ import static org.springframework.test.web.servlet.result.MockMvcResultMatchers. import java.io.ByteArrayInputStream; import java.util.ArrayList; import java.util.List; +import java.util.concurrent.TimeUnit; import org.apache.commons.lang3.RandomUtils; +import org.eclipse.hawkbit.repository.DistributionSetAssignmentResult; 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.model.ActionStatus; -import org.eclipse.hawkbit.repository.model.RepositoryModelConstants; import org.eclipse.hawkbit.repository.model.DistributionSet; import org.eclipse.hawkbit.repository.model.LocalArtifact; +import org.eclipse.hawkbit.repository.model.RepositoryModelConstants; import org.eclipse.hawkbit.repository.model.Target; import org.eclipse.hawkbit.repository.model.TargetUpdateStatus; import org.eclipse.hawkbit.rest.AbstractRestIntegrationTestWithMongoDB; @@ -43,6 +45,9 @@ import org.springframework.data.domain.PageRequest; import org.springframework.data.domain.Sort.Direction; import org.springframework.hateoas.MediaTypes; import org.springframework.http.MediaType; +import org.springframework.test.web.servlet.MvcResult; + +import com.jayway.jsonpath.JsonPath; import ru.yandex.qatools.allure.annotations.Description; import ru.yandex.qatools.allure.annotations.Features; @@ -229,6 +234,45 @@ public class DdiDeploymentBaseTest extends AbstractRestIntegrationTestWithMongoD assertThat(actionStatusMessage.getStatus()).isEqualTo(Status.RETRIEVED); } + @Test + @Description("Checks that the deployementBase URL changes when the action is switched from soft to forced in TIMEFORCED case.") + public void changeEtagIfActionSwitchesFromSoftToForced() throws Exception { + // Prepare test data + final Target target = targetManagement.createTarget(entityFactory.generateTarget("4712")); + final DistributionSet ds = testdataFactory.createDistributionSet("", true); + + final DistributionSetAssignmentResult result = deploymentManagement.assignDistributionSet(ds.getId(), + ActionType.TIMEFORCED, System.currentTimeMillis() + 1_000, target.getControllerId()); + + final Action action = deploymentManagement.findActiveActionsByTarget(result.getAssignedEntity().get(0)).get(0); + + MvcResult mvcResult = mvc.perform(get("/{tenant}/controller/v1/4712", tenantAware.getCurrentTenant())) + .andDo(MockMvcResultPrinter.print()).andExpect(status().isOk()) + .andExpect(content().contentType(MediaTypes.HAL_JSON)).andReturn(); + + final String urlBeforeSwitch = JsonPath.compile("_links.deploymentBase.href") + .read(mvcResult.getResponse().getContentAsString()).toString(); + + // Time is not yet over, so we should see the same URL + mvcResult = mvc.perform(get("/{tenant}/controller/v1/4712", tenantAware.getCurrentTenant())) + .andDo(MockMvcResultPrinter.print()).andExpect(status().isOk()) + .andExpect(content().contentType(MediaTypes.HAL_JSON)).andReturn(); + assertThat(JsonPath.compile("_links.deploymentBase.href").read(mvcResult.getResponse().getContentAsString()) + .toString()).isEqualTo(urlBeforeSwitch) + .startsWith("http://localhost/" + tenantAware.getCurrentTenant() + + "/controller/v1/4712/deploymentBase/" + action.getId()); + + // After the time is over we should see a new etag + TimeUnit.MILLISECONDS.sleep(1_000); + + mvcResult = mvc.perform(get("/{tenant}/controller/v1/4712", tenantAware.getCurrentTenant())) + .andDo(MockMvcResultPrinter.print()).andExpect(status().isOk()) + .andExpect(content().contentType(MediaTypes.HAL_JSON)).andReturn(); + + assertThat(JsonPath.compile("_links.deploymentBase.href").read(mvcResult.getResponse().getContentAsString()) + .toString()).isNotEqualTo(urlBeforeSwitch); + } + @Test @Description("Attempt/soft deployment to a controller. Checks if the resource reponse payload for a given deployment is as expected.") public void deplomentAttemptAction() throws Exception { diff --git a/hawkbit-ddi-resource/src/test/resources/logback.xml b/hawkbit-ddi-resource/src/test/resources/logback.xml new file mode 100644 index 000000000..447712338 --- /dev/null +++ b/hawkbit-ddi-resource/src/test/resources/logback.xml @@ -0,0 +1,21 @@ + + + + + + + + + + + + \ No newline at end of file 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..11f892654 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 @@ -8,8 +8,11 @@ */ package org.eclipse.hawkbit.amqp; +import java.time.Duration; +import java.util.HashMap; +import java.util.Map; +import java.util.concurrent.Executor; import java.util.concurrent.ScheduledExecutorService; -import java.util.concurrent.ThreadPoolExecutor; import org.eclipse.hawkbit.dmf.amqp.api.AmqpSettings; import org.slf4j.Logger; @@ -18,11 +21,12 @@ import org.springframework.amqp.core.Binding; import org.springframework.amqp.core.BindingBuilder; import org.springframework.amqp.core.FanoutExchange; import org.springframework.amqp.core.Queue; -import org.springframework.amqp.rabbit.config.SimpleRabbitListenerContainerFactory; +import org.springframework.amqp.core.QueueBuilder; import org.springframework.amqp.rabbit.connection.CachingConnectionFactory; import org.springframework.amqp.rabbit.connection.ConnectionFactory; import org.springframework.amqp.rabbit.core.RabbitAdmin; import org.springframework.amqp.rabbit.core.RabbitTemplate; +import org.springframework.amqp.rabbit.listener.RabbitListenerContainerFactory; import org.springframework.amqp.rabbit.listener.SimpleMessageListenerContainer; import org.springframework.amqp.support.converter.Jackson2JsonMessageConverter; import org.springframework.beans.factory.annotation.Autowired; @@ -51,10 +55,6 @@ public class AmqpConfiguration { @Autowired private AmqpDeadletterProperties amqpDeadletterProperties; - @Autowired - @Qualifier("threadPoolExecutor") - private ThreadPoolExecutor threadPoolExecutor; - @Autowired private ConnectionFactory rabbitConnectionFactory; @@ -66,8 +66,8 @@ public class AmqpConfiguration { private AmqpProperties amqpProperties; @Autowired - @Qualifier("threadPoolExecutor") - private ThreadPoolExecutor threadPoolExecutor; + @Qualifier("asyncExecutor") + private Executor threadPoolExecutor; @Autowired private ScheduledExecutorService scheduledExecutorService; @@ -145,26 +145,71 @@ public class AmqpConfiguration { } /** - * Create the sp receiver queue. + * Create the DMF API receiver queue for retrieving DMF messages. * * @return the receiver queue */ @Bean - public Queue receiverQueue() { + public Queue dmfReceiverQueue() { return new Queue(amqpProperties.getReceiverQueue(), true, false, false, amqpDeadletterProperties.getDeadLetterExchangeArgs(amqpProperties.getDeadLetterExchange())); } /** - * Create the dead letter fanout exchange. + * Create the DMF API receiver queue for authentication requests called by + * 3rd party artifact storages for download authorization by devices. + * + * @return the receiver queue + */ + @Bean + public Queue authenticationReceiverQueue() { + return QueueBuilder.nonDurable(amqpProperties.getAuthenticationReceiverQueue()).autoDelete() + .withArguments(getTTLMaxArgsAuthenticationQueue()).build(); + } + + /** + * Create DMF exchange. * * @return the fanout exchange */ @Bean - public FanoutExchange senderExchange() { + public FanoutExchange dmfSenderExchange() { return new FanoutExchange(AmqpSettings.DMF_EXCHANGE); } + /** + * Create the Binding {@link AmqpConfiguration#dmfReceiverQueue()} to + * {@link AmqpConfiguration#dmfSenderExchange()}. + * + * @return the binding and create the queue and exchange + */ + @Bean + public Binding bindDmfSenderExchangeToDmfQueue() { + return BindingBuilder.bind(dmfReceiverQueue()).to(dmfSenderExchange()); + } + + /** + * Create authentication exchange. + * + * @return the fanout exchange + */ + @Bean + public FanoutExchange authenticationExchange() { + return new FanoutExchange(AmqpSettings.AUTHENTICATION_EXCHANGE, false, true); + } + + /** + * Create the Binding + * {@link AmqpConfiguration#authenticationReceiverQueue()} to + * {@link AmqpConfiguration#authenticationExchange()}. + * + * @return the binding and create the queue and exchange + */ + @Bean + public Binding bindAuthenticationSenderExchangeToAuthenticationQueue() { + return BindingBuilder.bind(authenticationReceiverQueue()).to(authenticationExchange()); + } + /** * Create dead letter queue. * @@ -181,29 +226,18 @@ public class AmqpConfiguration { * @return the fanout exchange */ @Bean - public FanoutExchange exchangeDeadLetter() { + public FanoutExchange deadLetterExchange() { return new FanoutExchange(amqpProperties.getDeadLetterExchange()); } /** - * Create the Binding deadLetterQueue to exchangeDeadLetter. + * Create the Binding deadLetterQueue to deadLetterExchange. * * @return the binding */ @Bean - public Binding bindDeadLetterQueueToLwm2mExchange() { - return BindingBuilder.bind(deadLetterQueue()).to(exchangeDeadLetter()); - } - - /** - * Create the Binding {@link AmqpConfiguration#receiverQueueFromSp()} to - * {@link AmqpConfiguration#senderConnectorToSpExchange()}. - * - * @return the binding and create the queue and exchange - */ - @Bean - public Binding bindSenderExchangeToSpQueue() { - return BindingBuilder.bind(receiverQueue()).to(senderExchange()); + public Binding bindDeadLetterQueueToDeadLetterExchange() { + return BindingBuilder.bind(deadLetterQueue()).to(deadLetterExchange()); } /** @@ -234,12 +268,15 @@ public class AmqpConfiguration { * AMQP messages */ @Bean(name = { "listenerContainerFactory" }) - public SimpleRabbitListenerContainerFactory listenerContainerFactory() { - final SimpleRabbitListenerContainerFactory containerFactory = new SimpleRabbitListenerContainerFactory(); - containerFactory.setDefaultRequeueRejected(false); - containerFactory.setConnectionFactory(rabbitConnectionFactory); - containerFactory.setMissingQueuesFatal(amqpProperties.isMissingQueuesFatal()); - return containerFactory; + public RabbitListenerContainerFactory listenerContainerFactory() { + return new ConfigurableRabbitListenerContainerFactory(amqpProperties, rabbitConnectionFactory); + } + + private static Map getTTLMaxArgsAuthenticationQueue() { + final Map args = new HashMap<>(); + args.put("x-message-ttl", Duration.ofSeconds(30).toMillis()); + args.put("x-max-length", 1_000); + return args; } } 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..d1bf85103 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; @@ -44,9 +47,11 @@ import org.eclipse.hawkbit.repository.model.DistributionSet; import org.eclipse.hawkbit.repository.model.LocalArtifact; import org.eclipse.hawkbit.repository.model.SoftwareModule; import org.eclipse.hawkbit.repository.model.Target; +import org.eclipse.hawkbit.security.SystemSecurityContext; 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; @@ -101,6 +106,9 @@ public class AmqpMessageHandlerService extends BaseAmqpService { @Autowired private EntityFactory entityFactory; + @Autowired + private SystemSecurityContext systemSecurityContext; + /** * Constructor. * @@ -111,27 +119,39 @@ 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. + * Method to handle all incoming DMF amqp messages. * * @param message * incoming message * @param type * the message type - * @param contentType - * the contentType of the message * @param tenant * the contentType of the message - * @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()); + } + + @RabbitListener(queues = "${hawkbit.dmf.rabbitmq.authenticationReceiverQueue}", containerFactory = "listenerContainerFactory") + public Message onAuthenticationRequest(final Message message) { + checkContentTypeJson(message); + final SecurityContext oldContext = SecurityContextHolder.getContext(); + try { + return handleAuthentifiactionMessage(message); + } catch (final IllegalArgumentException ex) { + throw new AmqpRejectAndDontRequeueException("Invalid message!", ex); + } catch (final TenantNotExistException teex) { + throw new AmqpRejectAndDontRequeueException(teex); + } finally { + SecurityContextHolder.setContext(oldContext); + } + } + public Message onMessage(final Message message, final String type, final String tenant, final String virtualHost) { checkContentTypeJson(message); final SecurityContext oldContext = SecurityContextHolder.getContext(); @@ -148,11 +168,13 @@ public class AmqpMessageHandlerService extends BaseAmqpService { final EventTopic eventTopic = EventTopic.valueOf(topicValue); handleIncomingEvent(message, eventTopic); break; - case AUTHENTIFICATION: - return handleAuthentifiactionMessage(message); default: logAndThrowMessageError(message, "No handle method was found for the given message type."); } + } catch (final IllegalArgumentException ex) { + throw new AmqpRejectAndDontRequeueException("Invalid message!", ex); + } catch (final TenantNotExistException teex) { + throw new AmqpRejectAndDontRequeueException(teex); } finally { SecurityContextHolder.setContext(oldContext); } @@ -308,9 +330,10 @@ public class AmqpMessageHandlerService extends BaseAmqpService { final DistributionSet distributionSet = action.getDistributionSet(); final List softwareModuleList = controllerManagement .findSoftwareModulesByDistributionSet(distributionSet); + final String targetSecurityToken = systemSecurityContext.runAsSystem(() -> target.getSecurityToken()); eventBus.post(new TargetAssignDistributionSetEvent(target.getOptLockRevision(), target.getTenant(), target.getControllerId(), action.getId(), softwareModuleList, target.getTargetInfo().getAddress(), - target.getSecurityToken())); + targetSecurityToken)); } @@ -340,11 +363,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 +401,25 @@ 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 " + + convertCorrelationId(message)); + } + + actionStatus.setAction(action); + actionStatus.setOccurredAt(System.currentTimeMillis()); + return actionStatus; + } + + private static String convertCorrelationId(final Message message) { + return new String(message.getMessageProperties().getCorrelationId()); + } + private Action getUpdateActionStatus(final ActionStatus actionStatus) { if (actionStatus.getStatus().equals(Status.CANCELED)) { return controllerManagement.addCancelActionStatus(actionStatus); @@ -421,12 +459,12 @@ 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; } - throw new IllegalArgumentException("Content-Type is not JSON compatible"); + throw new AmqpRejectAndDontRequeueException("Content-Type is not JSON compatible"); } void setControllerManagement(final ControllerManagement controllerManagement) { @@ -457,4 +495,7 @@ public class AmqpMessageHandlerService extends BaseAmqpService { this.entityFactory = entityFactory; } + void setSystemSecurityContext(final SystemSecurityContext systemSecurityContext) { + this.systemSecurityContext = systemSecurityContext; + } } 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..56aa37772 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 @@ -31,20 +31,98 @@ public class AmqpProperties { private String deadLetterExchange = "dmf.connector.deadletter"; /** - * DMF API receiving queue. + * DMF API receiving queue for EVENT or THING_CREATED message. */ private String receiverQueue = "dmf_receiver"; + /** + * Authentication request called by 3rd party artifact storages for download + * authorizations. + */ + private String authenticationReceiverQueue = "authentication_receiver"; + /** * Missing queue fatal. */ - private boolean missingQueuesFatal = 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; + + /** + * The number of retry attempts when passive queue declaration fails. + * Passive queue declaration occurs when the consumer starts or, when + * consuming from multiple queues, when not all queues were available during + * initialization. + */ + private int declarationRetries = 50; + + /** + * @return the declarationRetries + */ + public int getDeclarationRetries() { + return declarationRetries; + } + + /** + * @param declarationRetries + * the declarationRetries to set + */ + public void setDeclarationRetries(final int declarationRetries) { + this.declarationRetries = declarationRetries; + } + + public String getAuthenticationReceiverQueue() { + return authenticationReceiverQueue; + } + + public void setAuthenticationReceiverQueue(final String authenticationReceiverQueue) { + this.authenticationReceiverQueue = authenticationReceiverQueue; + } + + public int getPrefetchCount() { + return prefetchCount; + } + + public void setPrefetchCount(final int prefetchCount) { + this.prefetchCount = prefetchCount; + } + + public int getInitialConcurrentConsumers() { + return initialConcurrentConsumers; + } + + public void setInitialConcurrentConsumers(final int initialConcurrentConsumers) { + this.initialConcurrentConsumers = initialConcurrentConsumers; + } + + public int getMaxConcurrentConsumers() { + return maxConcurrentConsumers; + } + + public void setMaxConcurrentConsumers(final int maxConcurrentConsumers) { + this.maxConcurrentConsumers = maxConcurrentConsumers; + } + /** * Is missingQueuesFatal enabled * @@ -106,10 +184,6 @@ public class AmqpProperties { return receiverQueue; } - public void setReceiverQueue(final String receiverQueue) { - this.receiverQueue = receiverQueue; - } - public int getRequestedHeartBeat() { return requestedHeartBeat; } @@ -118,4 +192,8 @@ public class AmqpProperties { this.requestedHeartBeat = requestedHeartBeat; } + public void setReceiverQueue(final String receiverQueue) { + this.receiverQueue = receiverQueue; + } + } 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/ConfigurableRabbitListenerContainerFactory.java b/hawkbit-dmf-amqp/src/main/java/org/eclipse/hawkbit/amqp/ConfigurableRabbitListenerContainerFactory.java new file mode 100644 index 000000000..14e6f8fcb --- /dev/null +++ b/hawkbit-dmf-amqp/src/main/java/org/eclipse/hawkbit/amqp/ConfigurableRabbitListenerContainerFactory.java @@ -0,0 +1,52 @@ +/** + * Copyright (c) 2015 Bosch Software Innovations GmbH and others. + * + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * http://www.eclipse.org/legal/epl-v10.html + */ +package org.eclipse.hawkbit.amqp; + +import org.springframework.amqp.rabbit.config.SimpleRabbitListenerContainerFactory; +import org.springframework.amqp.rabbit.connection.ConnectionFactory; +import org.springframework.amqp.rabbit.listener.RabbitListenerContainerFactory; +import org.springframework.amqp.rabbit.listener.SimpleMessageListenerContainer; + +/** + * {@link RabbitListenerContainerFactory} that can be configured through + * hawkBit's {@link AmqpProperties}. + * + */ +public class ConfigurableRabbitListenerContainerFactory extends SimpleRabbitListenerContainerFactory { + private final AmqpProperties amqpProperties; + + /** + * Constructor. + * + * @param rabbitConnectionFactory + * for the container factory + * @param amqpProperties + * to configure the container factory + */ + public ConfigurableRabbitListenerContainerFactory(final AmqpProperties amqpProperties, + final ConnectionFactory rabbitConnectionFactory) { + this.amqpProperties = amqpProperties; + setDefaultRequeueRejected(true); + setConnectionFactory(rabbitConnectionFactory); + setMissingQueuesFatal(amqpProperties.isMissingQueuesFatal()); + setConcurrentConsumers(amqpProperties.getInitialConcurrentConsumers()); + setMaxConcurrentConsumers(amqpProperties.getMaxConcurrentConsumers()); + setPrefetchCount(amqpProperties.getPrefetchCount()); + + } + + @Override + // Exception squid:UnusedProtectedMethod - called by + // AbstractRabbitListenerContainerFactory + @SuppressWarnings("squid:UnusedProtectedMethod") + protected void initializeContainer(final SimpleMessageListenerContainer instance) { + super.initializeContainer(instance); + instance.setDeclarationRetries(amqpProperties.getDeclarationRetries()); + } +} 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/AmqpTestConfiguration.java b/hawkbit-dmf-amqp/src/test/java/org/eclipse/hawkbit/AmqpTestConfiguration.java index 075313a19..467c650be 100644 --- a/hawkbit-dmf-amqp/src/test/java/org/eclipse/hawkbit/AmqpTestConfiguration.java +++ b/hawkbit-dmf-amqp/src/test/java/org/eclipse/hawkbit/AmqpTestConfiguration.java @@ -31,7 +31,7 @@ import org.springframework.context.annotation.Configuration; import org.springframework.core.task.TaskExecutor; import org.springframework.scheduling.concurrent.ConcurrentTaskExecutor; import org.springframework.scheduling.concurrent.ThreadPoolTaskScheduler; -import org.springframework.security.concurrent.DelegatingSecurityContextExecutor; +import org.springframework.security.concurrent.DelegatingSecurityContextExecutorService; import com.google.common.util.concurrent.ThreadFactoryBuilder; @@ -78,18 +78,17 @@ public class AmqpTestConfiguration { * @return ExecutorService with security context availability in thread * execution.. */ - @Bean + @Bean(destroyMethod = "shutdown") @ConditionalOnMissingBean public Executor asyncExecutor() { - return new DelegatingSecurityContextExecutor(threadPoolExecutor()); + return new DelegatingSecurityContextExecutorService(threadPoolExecutor()); } /** * @return central ThreadPoolExecutor for general purpose multi threaded * operations. Tries an orderly shutdown when destroyed. */ - @Bean(destroyMethod = "shutdown") - public ThreadPoolExecutor threadPoolExecutor() { + private ThreadPoolExecutor threadPoolExecutor() { final BlockingQueue blockingQueue = new ArrayBlockingQueue<>(10); final ThreadPoolExecutor threadPoolExecutor = new ThreadPoolExecutor(1, 10, 1000, TimeUnit.MILLISECONDS, blockingQueue, new ThreadFactoryBuilder().setNameFormat("central-executor-pool-%d").build()); diff --git a/hawkbit-dmf-amqp/src/test/java/org/eclipse/hawkbit/amqp/AmqpControllerAuthenticationTest.java b/hawkbit-dmf-amqp/src/test/java/org/eclipse/hawkbit/amqp/AmqpControllerAuthenticationTest.java index 881260579..4e08e1852 100644 --- a/hawkbit-dmf-amqp/src/test/java/org/eclipse/hawkbit/amqp/AmqpControllerAuthenticationTest.java +++ b/hawkbit-dmf-amqp/src/test/java/org/eclipse/hawkbit/amqp/AmqpControllerAuthenticationTest.java @@ -158,7 +158,7 @@ public class AmqpControllerAuthenticationTest { @Test @Description("Tests authentication message without principal") public void testAuthenticationMessageBadCredantialsWithoutPricipal() { - final MessageProperties messageProperties = createMessageProperties(MessageType.AUTHENTIFICATION); + final MessageProperties messageProperties = createMessageProperties(null); final TenantSecurityToken securityToken = new TenantSecurityToken(TENANT, CONTROLLLER_ID, FileResource.sha1("12345")); @@ -166,8 +166,7 @@ public class AmqpControllerAuthenticationTest { messageProperties); // test - final Message onMessage = amqpMessageHandlerService.onMessage(message, MessageType.AUTHENTIFICATION.name(), - TENANT, "vHost"); + final Message onMessage = amqpMessageHandlerService.onAuthenticationRequest(message); // verify final DownloadResponse downloadResponse = (DownloadResponse) messageConverter.fromMessage(onMessage); @@ -178,7 +177,7 @@ public class AmqpControllerAuthenticationTest { @Test @Description("Tests authentication message without wrong credential") public void testAuthenticationMessageBadCredantialsWithWrongCredential() { - final MessageProperties messageProperties = createMessageProperties(MessageType.AUTHENTIFICATION); + final MessageProperties messageProperties = createMessageProperties(null); final TenantSecurityToken securityToken = new TenantSecurityToken(TENANT, CONTROLLLER_ID, FileResource.sha1("12345")); when(tenantConfigurationManagement.getConfigurationValue( @@ -189,8 +188,7 @@ public class AmqpControllerAuthenticationTest { messageProperties); // test - final Message onMessage = amqpMessageHandlerService.onMessage(message, MessageType.AUTHENTIFICATION.name(), - TENANT, "vHost"); + final Message onMessage = amqpMessageHandlerService.onAuthenticationRequest(message); // verify final DownloadResponse downloadResponse = (DownloadResponse) messageConverter.fromMessage(onMessage); @@ -201,7 +199,7 @@ public class AmqpControllerAuthenticationTest { @Test @Description("Tests authentication message successfull") public void testSuccessfullMessageAuthentication() { - final MessageProperties messageProperties = createMessageProperties(MessageType.AUTHENTIFICATION); + final MessageProperties messageProperties = createMessageProperties(null); final TenantSecurityToken securityToken = new TenantSecurityToken(TENANT, CONTROLLLER_ID, FileResource.sha1("12345")); when(tenantConfigurationManagement.getConfigurationValue( @@ -212,8 +210,7 @@ public class AmqpControllerAuthenticationTest { messageProperties); // test - final Message onMessage = amqpMessageHandlerService.onMessage(message, MessageType.AUTHENTIFICATION.name(), - TENANT, "vHost"); + final Message onMessage = amqpMessageHandlerService.onAuthenticationRequest(message); // verify final DownloadResponse downloadResponse = (DownloadResponse) messageConverter.fromMessage(onMessage); @@ -232,7 +229,9 @@ public class AmqpControllerAuthenticationTest { private MessageProperties createMessageProperties(final MessageType type, final String replyTo) { final MessageProperties messageProperties = new MessageProperties(); - messageProperties.setHeader(MessageHeaderKey.TYPE, type.name()); + if (type != null) { + messageProperties.setHeader(MessageHeaderKey.TYPE, type.name()); + } messageProperties.setHeader(MessageHeaderKey.TENANT, TENANT); messageProperties.setContentType(MessageProperties.CONTENT_TYPE_JSON); messageProperties.setReplyTo(replyTo); 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..2627731c6 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 @@ -52,6 +52,7 @@ import org.eclipse.hawkbit.repository.model.LocalArtifact; import org.eclipse.hawkbit.repository.model.SoftwareModule; import org.eclipse.hawkbit.repository.model.TargetInfo; import org.eclipse.hawkbit.security.SecurityTokenGenerator; +import org.eclipse.hawkbit.security.SystemSecurityContext; import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; @@ -59,6 +60,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; @@ -111,6 +113,9 @@ public class AmqpMessageHandlerServiceTest { @Mock private RabbitTemplate rabbitTemplate; + @Mock + private SystemSecurityContext systemSecurityContextMock; + @Before public void before() throws Exception { messageConverter = new Jackson2JsonMessageConverter(); @@ -123,6 +128,7 @@ public class AmqpMessageHandlerServiceTest { amqpMessageHandlerService.setHostnameResolver(hostnameResolverMock); amqpMessageHandlerService.setEventBus(eventBus); amqpMessageHandlerService.setEntityFactory(entityFactoryMock); + amqpMessageHandlerService.setSystemSecurityContext(systemSecurityContextMock); } @@ -134,8 +140,8 @@ public class AmqpMessageHandlerServiceTest { final Message message = new Message(new byte[0], messageProperties); try { amqpMessageHandlerService.onMessage(message, MessageType.THING_CREATED.name(), TENANT, "vHost"); - fail("IllegalArgumentException was excepeted due to worng content type"); - } catch (final IllegalArgumentException e) { + fail("AmqpRejectAndDontRequeueException was excepeted due to worng content type"); + } catch (final AmqpRejectAndDontRequeueException e) { } } @@ -169,8 +175,8 @@ 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) { + fail("AmqpRejectAndDontRequeueException was excepeted since no replyTo header was set"); + } catch (final AmqpRejectAndDontRequeueException exception) { // test ok - exception was excepted } @@ -183,8 +189,8 @@ public class AmqpMessageHandlerServiceTest { final Message message = messageConverter.toMessage(new byte[0], messageProperties); try { amqpMessageHandlerService.onMessage(message, MessageType.THING_CREATED.name(), TENANT, "vHost"); - fail("IllegalArgumentException was excepeted since no thingID was set"); - } catch (final IllegalArgumentException exception) { + fail("AmqpRejectAndDontRequeueException was excepeted since no thingID was set"); + } catch (final AmqpRejectAndDontRequeueException exception) { // test ok - exception was excepted } } @@ -199,8 +205,8 @@ public class AmqpMessageHandlerServiceTest { try { amqpMessageHandlerService.onMessage(message, type, TENANT, "vHost"); - fail("IllegalArgumentException was excepeted due to unknown message type"); - } catch (final IllegalArgumentException exception) { + fail("AmqpRejectAndDontRequeueException was excepeted due to unknown message type"); + } catch (final AmqpRejectAndDontRequeueException exception) { // test ok - exception was excepted } } @@ -212,22 +218,22 @@ public class AmqpMessageHandlerServiceTest { final Message message = new Message(new byte[0], messageProperties); try { amqpMessageHandlerService.onMessage(message, MessageType.EVENT.name(), TENANT, "vHost"); - fail("IllegalArgumentException was excepeted due to unknown message type"); - } catch (final IllegalArgumentException e) { + fail("AmqpRejectAndDontRequeueException was excepeted due to unknown message type"); + } catch (final AmqpRejectAndDontRequeueException e) { } try { messageProperties.setHeader(MessageHeaderKey.TOPIC, "wrongTopic"); amqpMessageHandlerService.onMessage(message, MessageType.EVENT.name(), TENANT, "vHost"); - fail("IllegalArgumentException was excepeted due to unknown topic"); - } catch (final IllegalArgumentException e) { + fail("AmqpRejectAndDontRequeueException was excepeted due to unknown topic"); + } catch (final AmqpRejectAndDontRequeueException e) { } messageProperties.setHeader(MessageHeaderKey.TOPIC, EventTopic.CANCEL_DOWNLOAD.name()); try { amqpMessageHandlerService.onMessage(message, MessageType.EVENT.name(), TENANT, "vHost"); - fail("IllegalArgumentException was excepeted because there was no event topic"); - } catch (final IllegalArgumentException exception) { + fail("AmqpRejectAndDontRequeueException was excepeted because there was no event topic"); + } catch (final AmqpRejectAndDontRequeueException exception) { // test ok - exception was excepted } @@ -245,8 +251,8 @@ 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) { + fail("AmqpRejectAndDontRequeueException was excepeted since no action id was set"); + } catch (final AmqpRejectAndDontRequeueException exception) { // test ok - exception was excepted } } @@ -262,8 +268,8 @@ 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) { + fail("AmqpRejectAndDontRequeueException was excepeted since no action id was set"); + } catch (final AmqpRejectAndDontRequeueException exception) { // test ok - exception was excepted } @@ -272,14 +278,13 @@ public class AmqpMessageHandlerServiceTest { @Test @Description("Tests that an download request is denied for an artifact which does not exists") public void authenticationRequestDeniedForArtifactWhichDoesNotExists() { - final MessageProperties messageProperties = createMessageProperties(MessageType.AUTHENTIFICATION); + final MessageProperties messageProperties = createMessageProperties(null); final TenantSecurityToken securityToken = new TenantSecurityToken(TENANT, "123", FileResource.sha1("12345")); final Message message = amqpMessageHandlerService.getMessageConverter().toMessage(securityToken, messageProperties); // test - final Message onMessage = amqpMessageHandlerService.onMessage(message, MessageType.AUTHENTIFICATION.name(), - TENANT, "vHost"); + final Message onMessage = amqpMessageHandlerService.onAuthenticationRequest(message); // verify final DownloadResponse downloadResponse = (DownloadResponse) messageConverter.fromMessage(onMessage); @@ -291,7 +296,7 @@ public class AmqpMessageHandlerServiceTest { @Test @Description("Tests that an download request is denied for an artifact which is not assigned to the requested target") public void authenticationRequestDeniedForArtifactWhichIsNotAssignedToTarget() { - final MessageProperties messageProperties = createMessageProperties(MessageType.AUTHENTIFICATION); + final MessageProperties messageProperties = createMessageProperties(null); final TenantSecurityToken securityToken = new TenantSecurityToken(TENANT, "123", FileResource.sha1("12345")); final Message message = amqpMessageHandlerService.getMessageConverter().toMessage(securityToken, messageProperties); @@ -302,8 +307,7 @@ public class AmqpMessageHandlerServiceTest { .thenThrow(EntityNotFoundException.class); // test - final Message onMessage = amqpMessageHandlerService.onMessage(message, MessageType.AUTHENTIFICATION.name(), - TENANT, "vHost"); + final Message onMessage = amqpMessageHandlerService.onAuthenticationRequest(message); // verify final DownloadResponse downloadResponse = (DownloadResponse) messageConverter.fromMessage(onMessage); @@ -315,7 +319,7 @@ public class AmqpMessageHandlerServiceTest { @Test @Description("Tests that an download request is allowed for an artifact which exists and assigned to the requested target") public void authenticationRequestAllowedForArtifactWhichExistsAndAssignedToTarget() throws MalformedURLException { - final MessageProperties messageProperties = createMessageProperties(MessageType.AUTHENTIFICATION); + final MessageProperties messageProperties = createMessageProperties(null); final TenantSecurityToken securityToken = new TenantSecurityToken(TENANT, "123", FileResource.sha1("12345")); final Message message = amqpMessageHandlerService.getMessageConverter().toMessage(securityToken, messageProperties); @@ -333,8 +337,7 @@ public class AmqpMessageHandlerServiceTest { when(hostnameResolverMock.resolveHostname()).thenReturn(new URL("http://localhost")); // test - final Message onMessage = amqpMessageHandlerService.onMessage(message, MessageType.AUTHENTIFICATION.name(), - TENANT, "vHost"); + final Message onMessage = amqpMessageHandlerService.onAuthenticationRequest(message); // verify final DownloadResponse downloadResponse = (DownloadResponse) messageConverter.fromMessage(onMessage); @@ -366,6 +369,8 @@ public class AmqpMessageHandlerServiceTest { when(controllerManagementMock.findSoftwareModulesByDistributionSet(Matchers.any())) .thenReturn(softwareModuleList); + when(systemSecurityContextMock.runAsSystem(anyObject())).thenReturn("securityToken"); + final MessageProperties messageProperties = createMessageProperties(MessageType.EVENT); messageProperties.setHeader(MessageHeaderKey.TOPIC, EventTopic.UPDATE_ACTION_STATUS.name()); final ActionUpdateStatus actionUpdateStatus = createActionUpdateStatus(ActionStatus.FINISHED, 23L); @@ -410,7 +415,9 @@ public class AmqpMessageHandlerServiceTest { private MessageProperties createMessageProperties(final MessageType type, final String replyTo) { final MessageProperties messageProperties = new MessageProperties(); - messageProperties.setHeader(MessageHeaderKey.TYPE, type.name()); + if (type != null) { + messageProperties.setHeader(MessageHeaderKey.TYPE, type.name()); + } messageProperties.setHeader(MessageHeaderKey.TENANT, TENANT); messageProperties.setContentType(MessageProperties.CONTENT_TYPE_JSON); messageProperties.setReplyTo(replyTo); 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-dmf-api/src/main/java/org/eclipse/hawkbit/dmf/amqp/api/AmqpSettings.java b/hawkbit-dmf-api/src/main/java/org/eclipse/hawkbit/dmf/amqp/api/AmqpSettings.java index 40ba04419..364f36aa5 100644 --- a/hawkbit-dmf-api/src/main/java/org/eclipse/hawkbit/dmf/amqp/api/AmqpSettings.java +++ b/hawkbit-dmf-api/src/main/java/org/eclipse/hawkbit/dmf/amqp/api/AmqpSettings.java @@ -18,6 +18,8 @@ public final class AmqpSettings { public static final String DMF_EXCHANGE = "dmf.exchange"; + public static final String AUTHENTICATION_EXCHANGE = "authentication.exchange"; + private AmqpSettings() { } diff --git a/hawkbit-dmf-api/src/main/java/org/eclipse/hawkbit/dmf/amqp/api/MessageType.java b/hawkbit-dmf-api/src/main/java/org/eclipse/hawkbit/dmf/amqp/api/MessageType.java index 8cca32b06..e66a0c8c6 100644 --- a/hawkbit-dmf-api/src/main/java/org/eclipse/hawkbit/dmf/amqp/api/MessageType.java +++ b/hawkbit-dmf-api/src/main/java/org/eclipse/hawkbit/dmf/amqp/api/MessageType.java @@ -26,9 +26,4 @@ public enum MessageType { */ THING_CREATED, - /** - * The authentication type. - */ - AUTHENTIFICATION, - } 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..f7a297be1 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,20 @@ public class MgmtTargetRequestBody { @JsonProperty(required = true) private String controllerId; + @JsonProperty + private String address; + + @JsonProperty + private String securityToken; + + public String getSecurityToken() { + return securityToken; + } + + public void setSecurityToken(final String securityToken) { + this.securityToken = securityToken; + } + /** * @return the name */ @@ -66,4 +80,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/MgmtSoftwareModuleResource.java b/hawkbit-mgmt-resource/src/main/java/org/eclipse/hawkbit/mgmt/rest/resource/MgmtSoftwareModuleResource.java index b4f1e8adb..b475c473f 100644 --- a/hawkbit-mgmt-resource/src/main/java/org/eclipse/hawkbit/mgmt/rest/resource/MgmtSoftwareModuleResource.java +++ b/hawkbit-mgmt-resource/src/main/java/org/eclipse/hawkbit/mgmt/rest/resource/MgmtSoftwareModuleResource.java @@ -68,28 +68,24 @@ public class MgmtSoftwareModuleResource implements MgmtSoftwareModuleRestApi { @RequestParam(value = "md5sum", required = false) final String md5Sum, @RequestParam(value = "sha1sum", required = false) final String sha1Sum) { - Artifact result; - if (!file.isEmpty()) { - String fileName = optionalFileName; - - if (null == fileName) { - fileName = file.getOriginalFilename(); - } - - try { - result = artifactManagement.createLocalArtifact(file.getInputStream(), softwareModuleId, fileName, - md5Sum == null ? null : md5Sum.toLowerCase(), sha1Sum == null ? null : sha1Sum.toLowerCase(), - false, file.getContentType()); - } catch (final IOException e) { - LOG.error("Failed to store artifact", e); - return new ResponseEntity<>(HttpStatus.INTERNAL_SERVER_ERROR); - } - } else { + if (file.isEmpty()) { return new ResponseEntity<>(HttpStatus.BAD_REQUEST); } + String fileName = optionalFileName; - return new ResponseEntity<>(MgmtSoftwareModuleMapper.toResponse(result), HttpStatus.CREATED); + if (fileName == null) { + fileName = file.getOriginalFilename(); + } + try { + final Artifact result = artifactManagement.createLocalArtifact(file.getInputStream(), softwareModuleId, + fileName, md5Sum == null ? null : md5Sum.toLowerCase(), + sha1Sum == null ? null : sha1Sum.toLowerCase(), false, file.getContentType()); + return new ResponseEntity<>(MgmtSoftwareModuleMapper.toResponse(result), HttpStatus.CREATED); + } catch (final IOException e) { + LOG.error("Failed to store artifact", e); + return new ResponseEntity<>(HttpStatus.INTERNAL_SERVER_ERROR); + } } @Override 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..a889edc35 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()); } @@ -179,9 +182,11 @@ public final class MgmtTargetMapper { } static Target fromRequest(final EntityFactory entityFactory, final MgmtTargetRequestBody targetRest) { - final Target target = entityFactory.generateTarget(targetRest.getControllerId()); + final Target target = entityFactory.generateTarget(targetRest.getControllerId(), targetRest.getSecurityToken()); 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..5ddb74314 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()); @@ -125,22 +125,29 @@ public class MgmtTargetResource implements MgmtTargetRestApi { if (targetRest.getName() != null) { existingTarget.setName(targetRest.getName()); } + if (targetRest.getAddress() != null) { + existingTarget.getTargetInfo().setAddress(targetRest.getAddress()); + } + if (targetRest.getSecurityToken() != null) { + existingTarget.setSecurityToken(targetRest.getSecurityToken()); + } + final Target updateTarget = this.targetManagement.updateTarget(existingTarget); return new ResponseEntity<>(MgmtTargetMapper.toResponse(updateTarget), HttpStatus.OK); } @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 +160,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 +185,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 +201,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 +221,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 +240,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 +269,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 +283,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 +317,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..a528438bb 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 @@ -355,6 +355,54 @@ public class MgmtTargetResourceTest extends AbstractRestIntegrationTest { assertThat(findTargetByControllerID.getName()).isEqualTo(knownNameNotModiy); } + @Test + @Description("Ensures that target update request is reflected by repository.") + public void updateTargetSecurityToken() throws Exception { + final String knownControllerId = "123"; + final String knownNewToken = "6567576565"; + final String knownNameNotModiy = "nameNotModiy"; + final String body = new JSONObject().put("securityToken", knownNewToken).toString(); + + // prepare + final Target t = entityFactory.generateTarget(knownControllerId); + t.setName(knownNameNotModiy); + targetManagement.createTarget(t); + + mvc.perform(put(MgmtRestConstants.TARGET_V1_REQUEST_MAPPING + "/" + knownControllerId).content(body) + .contentType(MediaType.APPLICATION_JSON)).andDo(MockMvcResultPrinter.print()).andExpect(status().isOk()) + .andExpect(jsonPath("$.controllerId", equalTo(knownControllerId))) + .andExpect(jsonPath("$.securityToken", equalTo(knownNewToken))) + .andExpect(jsonPath("$.name", equalTo(knownNameNotModiy))); + + final Target findTargetByControllerID = targetManagement.findTargetByControllerID(knownControllerId); + assertThat(findTargetByControllerID.getSecurityToken()).isEqualTo(knownNewToken); + assertThat(findTargetByControllerID.getName()).isEqualTo(knownNameNotModiy); + } + + @Test + @Description("Ensures that target update request is reflected by repository.") + public void updateTargetAddress() throws Exception { + final String knownControllerId = "123"; + final String knownNewAddress = "amqp://test123/foobar"; + final String knownNameNotModiy = "nameNotModiy"; + final String body = new JSONObject().put("address", knownNewAddress).toString(); + + // prepare + final Target t = entityFactory.generateTarget(knownControllerId); + t.setName(knownNameNotModiy); + targetManagement.createTarget(t); + + mvc.perform(put(MgmtRestConstants.TARGET_V1_REQUEST_MAPPING + "/" + knownControllerId).content(body) + .contentType(MediaType.APPLICATION_JSON)).andDo(MockMvcResultPrinter.print()).andExpect(status().isOk()) + .andExpect(jsonPath("$.controllerId", equalTo(knownControllerId))) + .andExpect(jsonPath("$.address", equalTo(knownNewAddress))) + .andExpect(jsonPath("$.name", equalTo(knownNameNotModiy))); + + final Target findTargetByControllerID = targetManagement.findTargetByControllerID(knownControllerId); + assertThat(findTargetByControllerID.getTargetInfo().getAddress().toString()).isEqualTo(knownNewAddress); + assertThat(findTargetByControllerID.getName()).isEqualTo(knownNameNotModiy); + } + @Test @Description("Ensures that target query returns list of targets in defined format.") public void getTargetWithoutAddtionalRequestParameters() throws Exception { @@ -679,9 +727,10 @@ public class MgmtTargetResourceTest extends AbstractRestIntegrationTest { @Test public void createTargetsListReturnsSuccessful() throws Exception { - final Target test1 = entityFactory.generateTarget("id1"); + final Target test1 = entityFactory.generateTarget("id1", "token"); test1.setDescription("testid1"); test1.setName("testname1"); + test1.getTargetInfo().setAddress("amqp://test123/foobar"); final Target test2 = entityFactory.generateTarget("id2"); test2.setDescription("testid2"); test2.setName("testname2"); @@ -695,7 +744,7 @@ public class MgmtTargetResourceTest extends AbstractRestIntegrationTest { targets.add(test3); final MvcResult mvcResult = mvc - .perform(post("/rest/v1/targets/").content(JsonBuilder.targets(targets)) + .perform(post("/rest/v1/targets/").content(JsonBuilder.targets(targets, true)) .contentType(MediaType.APPLICATION_JSON).accept(MediaType.APPLICATION_JSON)) .andDo(MockMvcResultPrinter.print()).andExpect(status().isCreated()) .andExpect(content().contentType(MediaType.APPLICATION_JSON)) @@ -704,6 +753,8 @@ 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].securityToken", equalTo("token"))) + .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"))) @@ -729,6 +780,9 @@ public class MgmtTargetResourceTest extends AbstractRestIntegrationTest { assertThat(targetManagement.findTargetByControllerID("id1")).isNotNull(); assertThat(targetManagement.findTargetByControllerID("id1").getName()).isEqualTo("testname1"); assertThat(targetManagement.findTargetByControllerID("id1").getDescription()).isEqualTo("testid1"); + assertThat(targetManagement.findTargetByControllerID("id1").getSecurityToken()).isEqualTo("token"); + assertThat(targetManagement.findTargetByControllerID("id1").getTargetInfo().getAddress().toString()) + .isEqualTo("amqp://test123/foobar"); assertThat(targetManagement.findTargetByControllerID("id2")).isNotNull(); assertThat(targetManagement.findTargetByControllerID("id2").getName()).isEqualTo("testname2"); assertThat(targetManagement.findTargetByControllerID("id2").getDescription()).isEqualTo("testid2"); diff --git a/hawkbit-repository/hawkbit-repository-api/src/main/java/org/eclipse/hawkbit/repository/DeploymentManagement.java b/hawkbit-repository/hawkbit-repository-api/src/main/java/org/eclipse/hawkbit/repository/DeploymentManagement.java index 9dc5f74c8..270de3e74 100644 --- a/hawkbit-repository/hawkbit-repository-api/src/main/java/org/eclipse/hawkbit/repository/DeploymentManagement.java +++ b/hawkbit-repository/hawkbit-repository-api/src/main/java/org/eclipse/hawkbit/repository/DeploymentManagement.java @@ -269,8 +269,7 @@ public interface DeploymentManagement { * @return the actions referring a specific rollout and a specific parent * rollout group in a specific status */ - @PreAuthorize(SpringEvalExpressions.HAS_AUTH_READ_TARGET + SpringEvalExpressions.HAS_AUTH_OR - + SpringEvalExpressions.IS_SYSTEM_CODE) + @PreAuthorize(SpringEvalExpressions.HAS_AUTH_READ_TARGET) List findActionsByRolloutGroupParentAndStatus(@NotNull Rollout rollout, @NotNull RolloutGroup rolloutGroupParent, @NotNull Action.Status actionStatus); @@ -496,8 +495,7 @@ public interface DeploymentManagement { * the action to start now. * @return the action which has been started */ - @PreAuthorize(SpringEvalExpressions.HAS_AUTH_READ_TARGET + SpringEvalExpressions.HAS_AUTH_OR - + SpringEvalExpressions.IS_SYSTEM_CODE) + @PreAuthorize(SpringEvalExpressions.HAS_AUTH_READ_TARGET) Action startScheduledAction(@NotNull Action action); /** diff --git a/hawkbit-repository/hawkbit-repository-api/src/main/java/org/eclipse/hawkbit/repository/EntityFactory.java b/hawkbit-repository/hawkbit-repository-api/src/main/java/org/eclipse/hawkbit/repository/EntityFactory.java index 7542cd2ce..87919729e 100644 --- a/hawkbit-repository/hawkbit-repository-api/src/main/java/org/eclipse/hawkbit/repository/EntityFactory.java +++ b/hawkbit-repository/hawkbit-repository-api/src/main/java/org/eclipse/hawkbit/repository/EntityFactory.java @@ -287,6 +287,7 @@ public interface EntityFactory { /** * Generates an empty {@link Target} without persisting it. + * {@link Target#getSecurityToken()} is generated. * * @param controllerID * of the {@link Target} @@ -295,6 +296,19 @@ public interface EntityFactory { */ Target generateTarget(@NotEmpty String controllerID); + /** + * Generates an empty {@link Target} without persisting it. + * + * @param controllerID + * of the {@link Target} + * @param securityToken + * of the {@link Target} for authentication if enabled on tenant. + * Generates one if empty or null. + * + * @return {@link Target} object + */ + Target generateTarget(@NotEmpty String controllerID, @NotEmpty String securityToken); + /** * Generates an empty {@link TargetFilterQuery} without persisting it. * 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/RolloutManagement.java b/hawkbit-repository/hawkbit-repository-api/src/main/java/org/eclipse/hawkbit/repository/RolloutManagement.java index 2c6e85dda..337475318 100644 --- a/hawkbit-repository/hawkbit-repository-api/src/main/java/org/eclipse/hawkbit/repository/RolloutManagement.java +++ b/hawkbit-repository/hawkbit-repository-api/src/main/java/org/eclipse/hawkbit/repository/RolloutManagement.java @@ -61,8 +61,7 @@ public interface RolloutManagement { * this check. This check is only applied if the last check is * less than (lastcheck-delay). */ - @PreAuthorize(SpringEvalExpressions.HAS_AUTH_ROLLOUT_MANAGEMENT_WRITE + SpringEvalExpressions.HAS_AUTH_OR - + SpringEvalExpressions.IS_SYSTEM_CODE) + @PreAuthorize(SpringEvalExpressions.HAS_AUTH_ROLLOUT_MANAGEMENT_WRITE) void checkRunningRollouts(long delayBetweenChecks); /** @@ -266,8 +265,7 @@ public interface RolloutManagement { * if given rollout is not in {@link RolloutStatus#RUNNING}. * Only running rollouts can be paused. */ - @PreAuthorize(SpringEvalExpressions.HAS_AUTH_ROLLOUT_MANAGEMENT_WRITE + SpringEvalExpressions.HAS_AUTH_OR - + SpringEvalExpressions.IS_SYSTEM_CODE) + @PreAuthorize(SpringEvalExpressions.HAS_AUTH_ROLLOUT_MANAGEMENT_WRITE) void pauseRollout(@NotNull Rollout rollout); /** @@ -281,8 +279,7 @@ public interface RolloutManagement { * if given rollout is not in {@link RolloutStatus#PAUSED}. Only * paused rollouts can be resumed. */ - @PreAuthorize(SpringEvalExpressions.HAS_AUTH_ROLLOUT_MANAGEMENT_WRITE + SpringEvalExpressions.HAS_AUTH_OR - + SpringEvalExpressions.IS_SYSTEM_CODE) + @PreAuthorize(SpringEvalExpressions.HAS_AUTH_ROLLOUT_MANAGEMENT_WRITE) void resumeRollout(@NotNull Rollout rollout); /** @@ -303,8 +300,7 @@ public interface RolloutManagement { * if given rollout is not in {@link RolloutStatus#READY}. Only * ready rollouts can be started. */ - @PreAuthorize(SpringEvalExpressions.HAS_AUTH_ROLLOUT_MANAGEMENT_WRITE + SpringEvalExpressions.HAS_AUTH_OR - + SpringEvalExpressions.IS_SYSTEM_CODE) + @PreAuthorize(SpringEvalExpressions.HAS_AUTH_ROLLOUT_MANAGEMENT_WRITE) Rollout startRollout(@NotNull Rollout rollout); /** @@ -326,8 +322,7 @@ public interface RolloutManagement { * if given rollout is not in {@link RolloutStatus#READY}. Only * ready rollouts can be started. */ - @PreAuthorize(SpringEvalExpressions.HAS_AUTH_ROLLOUT_MANAGEMENT_WRITE + SpringEvalExpressions.HAS_AUTH_OR - + SpringEvalExpressions.IS_SYSTEM_CODE) + @PreAuthorize(SpringEvalExpressions.HAS_AUTH_ROLLOUT_MANAGEMENT_WRITE) Rollout startRolloutAsync(@NotNull Rollout rollout); /** diff --git a/hawkbit-repository/hawkbit-repository-api/src/main/java/org/eclipse/hawkbit/repository/SystemManagement.java b/hawkbit-repository/hawkbit-repository-api/src/main/java/org/eclipse/hawkbit/repository/SystemManagement.java index 039e4ae13..a44b13d92 100644 --- a/hawkbit-repository/hawkbit-repository-api/src/main/java/org/eclipse/hawkbit/repository/SystemManagement.java +++ b/hawkbit-repository/hawkbit-repository-api/src/main/java/org/eclipse/hawkbit/repository/SystemManagement.java @@ -39,16 +39,14 @@ public interface SystemManagement { * @param tenant * to delete */ - @PreAuthorize(SpringEvalExpressions.HAS_AUTH_SYSTEM_ADMIN + SpringEvalExpressions.HAS_AUTH_OR - + SpringEvalExpressions.IS_SYSTEM_CODE) + @PreAuthorize(SpringEvalExpressions.HAS_AUTH_SYSTEM_ADMIN) void deleteTenant(@NotNull String tenant); /** * * @return list of all tenant names in the system. */ - @PreAuthorize(SpringEvalExpressions.HAS_AUTH_SYSTEM_ADMIN + SpringEvalExpressions.HAS_AUTH_OR - + SpringEvalExpressions.IS_SYSTEM_CODE) + @PreAuthorize(SpringEvalExpressions.HAS_AUTH_SYSTEM_ADMIN) List findTenants(); /** @@ -68,8 +66,8 @@ public interface SystemManagement { /** * Returns {@link TenantMetaData} of given and current tenant. Creates for * new tenants also two {@link SoftwareModuleType} (os and app) and - * {@link RepositoryConstants#DEFAULT_DS_TYPES_IN_TENANT} {@link DistributionSetType}s - * (os and os_app). + * {@link RepositoryConstants#DEFAULT_DS_TYPES_IN_TENANT} + * {@link DistributionSetType}s (os and os_app). * * DISCLAIMER: this variant is used during initial login (where the tenant * is not yet in the session). Please user {@link #getTenantMetadata()} for 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/TenantConfigurationManagement.java b/hawkbit-repository/hawkbit-repository-api/src/main/java/org/eclipse/hawkbit/repository/TenantConfigurationManagement.java index 734704f05..9c467f9dd 100644 --- a/hawkbit-repository/hawkbit-repository-api/src/main/java/org/eclipse/hawkbit/repository/TenantConfigurationManagement.java +++ b/hawkbit-repository/hawkbit-repository-api/src/main/java/org/eclipse/hawkbit/repository/TenantConfigurationManagement.java @@ -54,8 +54,7 @@ public interface TenantConfigurationManagement { * @return if no default value is set and no database value available * or returns the tenant configuration value */ - @PreAuthorize(value = SpringEvalExpressions.HAS_AUTH_TENANT_CONFIGURATION + SpringEvalExpressions.HAS_AUTH_OR - + SpringEvalExpressions.IS_SYSTEM_CODE) + @PreAuthorize(value = SpringEvalExpressions.HAS_AUTH_TENANT_CONFIGURATION) TenantConfigurationValue buildTenantConfigurationValueByKey(TenantConfigurationKey configurationKey, Class propertyType, TenantConfiguration tenantConfiguration); @@ -87,8 +86,7 @@ public interface TenantConfigurationManagement { * if the property cannot be converted to the given * {@code propertyType} */ - @PreAuthorize(value = SpringEvalExpressions.HAS_AUTH_TENANT_CONFIGURATION + SpringEvalExpressions.HAS_AUTH_OR - + SpringEvalExpressions.IS_SYSTEM_CODE) + @PreAuthorize(value = SpringEvalExpressions.HAS_AUTH_TENANT_CONFIGURATION) TenantConfigurationValue getConfigurationValue(TenantConfigurationKey configurationKey); /** @@ -114,8 +112,7 @@ public interface TenantConfigurationManagement { * if the property cannot be converted to the given * {@code propertyType} */ - @PreAuthorize(value = SpringEvalExpressions.HAS_AUTH_TENANT_CONFIGURATION + SpringEvalExpressions.HAS_AUTH_OR - + SpringEvalExpressions.IS_SYSTEM_CODE) + @PreAuthorize(value = SpringEvalExpressions.HAS_AUTH_TENANT_CONFIGURATION) TenantConfigurationValue getConfigurationValue(TenantConfigurationKey configurationKey, Class propertyType); @@ -139,7 +136,6 @@ public interface TenantConfigurationManagement { * if the property cannot be converted to the given * {@code propertyType} */ - @PreAuthorize(value = SpringEvalExpressions.HAS_AUTH_TENANT_CONFIGURATION + SpringEvalExpressions.HAS_AUTH_OR - + SpringEvalExpressions.IS_SYSTEM_CODE) + @PreAuthorize(value = SpringEvalExpressions.HAS_AUTH_TENANT_CONFIGURATION) T getGlobalConfigurationValue(TenantConfigurationKey configurationKey, Class propertyType); } 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/exception/MultiPartFileUploadException.java b/hawkbit-repository/hawkbit-repository-api/src/main/java/org/eclipse/hawkbit/repository/exception/MultiPartFileUploadException.java new file mode 100644 index 000000000..e37786e19 --- /dev/null +++ b/hawkbit-repository/hawkbit-repository-api/src/main/java/org/eclipse/hawkbit/repository/exception/MultiPartFileUploadException.java @@ -0,0 +1,30 @@ +/** + * 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; + +/** + * Thrown if a multi part exception occurred. + * + */ +public final class MultiPartFileUploadException extends SpServerRtException { + + private static final long serialVersionUID = 1L; + + /** + * @param cause + * for the exception + */ + public MultiPartFileUploadException(final Throwable cause) { + super(cause.getMessage(), SpServerError.SP_ARTIFACT_UPLOAD_FAILED, cause); + } + +} diff --git a/hawkbit-repository/hawkbit-repository-api/src/main/java/org/eclipse/hawkbit/repository/model/Target.java b/hawkbit-repository/hawkbit-repository-api/src/main/java/org/eclipse/hawkbit/repository/model/Target.java index 56ab89ad3..c09b28a2f 100644 --- a/hawkbit-repository/hawkbit-repository-api/src/main/java/org/eclipse/hawkbit/repository/model/Target.java +++ b/hawkbit-repository/hawkbit-repository-api/src/main/java/org/eclipse/hawkbit/repository/model/Target.java @@ -58,4 +58,10 @@ public interface Target extends NamedEntity { */ String getSecurityToken(); + /** + * @param token + * new securityToken + */ + void setSecurityToken(String token); + } 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/JpaEntityFactory.java b/hawkbit-repository/hawkbit-repository-jpa/src/main/java/org/eclipse/hawkbit/repository/jpa/JpaEntityFactory.java index a3792de54..51b59f08f 100644 --- a/hawkbit-repository/hawkbit-repository-jpa/src/main/java/org/eclipse/hawkbit/repository/jpa/JpaEntityFactory.java +++ b/hawkbit-repository/hawkbit-repository-jpa/src/main/java/org/eclipse/hawkbit/repository/jpa/JpaEntityFactory.java @@ -10,6 +10,7 @@ package org.eclipse.hawkbit.repository.jpa; import java.util.Collection; +import org.apache.commons.lang3.StringUtils; import org.eclipse.hawkbit.repository.EntityFactory; import org.eclipse.hawkbit.repository.jpa.model.JpaAction; import org.eclipse.hawkbit.repository.jpa.model.JpaActionStatus; @@ -87,6 +88,14 @@ public class JpaEntityFactory implements EntityFactory { return new JpaTarget(controllerId); } + @Override + public Target generateTarget(final String controllerId, final String securityToken) { + if (StringUtils.isEmpty(securityToken)) { + return new JpaTarget(controllerId); + } + return new JpaTarget(controllerId, securityToken); + } + @Override public TargetTag generateTargetTag() { return new JpaTargetTag(); diff --git a/hawkbit-repository/hawkbit-repository-jpa/src/main/java/org/eclipse/hawkbit/repository/jpa/JpaSoftwareManagement.java b/hawkbit-repository/hawkbit-repository-jpa/src/main/java/org/eclipse/hawkbit/repository/jpa/JpaSoftwareManagement.java index 50127e227..b561667a9 100644 --- a/hawkbit-repository/hawkbit-repository-jpa/src/main/java/org/eclipse/hawkbit/repository/jpa/JpaSoftwareManagement.java +++ b/hawkbit-repository/hawkbit-repository-jpa/src/main/java/org/eclipse/hawkbit/repository/jpa/JpaSoftwareManagement.java @@ -132,7 +132,7 @@ public class JpaSoftwareManagement implements SoftwareManagement { final JpaSoftwareModuleType type = softwareModuleTypeRepository.findOne(sm.getId()); boolean updated = false; - if (sm.getDescription() != null && !sm.getDescription().equals(type.getDescription())) { + if (sm.getDescription() == null || !sm.getDescription().equals(type.getDescription())) { type.setDescription(sm.getDescription()); updated = true; } 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/JpaTarget.java b/hawkbit-repository/hawkbit-repository-jpa/src/main/java/org/eclipse/hawkbit/repository/jpa/model/JpaTarget.java index 94f50ff0d..183405470 100644 --- a/hawkbit-repository/hawkbit-repository-jpa/src/main/java/org/eclipse/hawkbit/repository/jpa/model/JpaTarget.java +++ b/hawkbit-repository/hawkbit-repository-jpa/src/main/java/org/eclipse/hawkbit/repository/jpa/model/JpaTarget.java @@ -115,9 +115,21 @@ public class JpaTarget extends AbstractJpaNamedEntity 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-repository/hawkbit-repository-jpa/src/test/java/org/eclipse/hawkbit/repository/jpa/TargetManagementTest.java b/hawkbit-repository/hawkbit-repository-jpa/src/test/java/org/eclipse/hawkbit/repository/jpa/TargetManagementTest.java index 4a83b1054..06f853db6 100644 --- a/hawkbit-repository/hawkbit-repository-jpa/src/test/java/org/eclipse/hawkbit/repository/jpa/TargetManagementTest.java +++ b/hawkbit-repository/hawkbit-repository-jpa/src/test/java/org/eclipse/hawkbit/repository/jpa/TargetManagementTest.java @@ -38,13 +38,13 @@ import org.eclipse.hawkbit.repository.jpa.model.JpaTarget; import org.eclipse.hawkbit.repository.jpa.model.JpaTargetInfo; import org.eclipse.hawkbit.repository.jpa.model.JpaTargetTag; import org.eclipse.hawkbit.repository.model.Action.Status; -import org.eclipse.hawkbit.repository.test.util.WithSpringAuthorityRule; -import org.eclipse.hawkbit.repository.test.util.WithUser; import org.eclipse.hawkbit.repository.model.DistributionSet; import org.eclipse.hawkbit.repository.model.Tag; import org.eclipse.hawkbit.repository.model.Target; import org.eclipse.hawkbit.repository.model.TargetIdName; import org.eclipse.hawkbit.repository.model.TargetTag; +import org.eclipse.hawkbit.repository.test.util.WithSpringAuthorityRule; +import org.eclipse.hawkbit.repository.test.util.WithUser; import org.junit.Test; import org.springframework.data.domain.PageRequest; @@ -61,7 +61,7 @@ public class TargetManagementTest extends AbstractJpaIntegrationTest { @Test @Description("Ensures that retrieving the target security is only permitted with the necessary permissions.") public void getTargetSecurityTokenOnlyWithCorrectPermission() throws Exception { - final Target createdTarget = targetManagement.createTarget(new JpaTarget("targetWithSecurityToken")); + final Target createdTarget = targetManagement.createTarget(new JpaTarget("targetWithSecurityToken", "token")); // retrieve security token only with READ_TARGET_SEC_TOKEN permission final String securityTokenWithReadPermission = securityRule.runAs(WithSpringAuthorityRule @@ -80,7 +80,7 @@ public class TargetManagementTest extends AbstractJpaIntegrationTest { return createdTarget.getSecurityToken(); }); - assertThat(createdTarget.getSecurityToken()).isNotNull(); + assertThat(createdTarget.getSecurityToken()).isEqualTo("token"); assertThat(securityTokenWithReadPermission).isNotNull(); assertThat(securityTokenAsSystemCode).isNotNull(); diff --git a/hawkbit-repository/hawkbit-repository-test/src/main/java/org/eclipse/hawkbit/repository/test/util/TestConfiguration.java b/hawkbit-repository/hawkbit-repository-test/src/main/java/org/eclipse/hawkbit/repository/test/util/TestConfiguration.java index e6a8e7686..3a7a2f73d 100644 --- a/hawkbit-repository/hawkbit-repository-test/src/main/java/org/eclipse/hawkbit/repository/test/util/TestConfiguration.java +++ b/hawkbit-repository/hawkbit-repository-test/src/main/java/org/eclipse/hawkbit/repository/test/util/TestConfiguration.java @@ -31,7 +31,7 @@ import org.springframework.context.annotation.Configuration; import org.springframework.context.annotation.Profile; import org.springframework.data.domain.AuditorAware; import org.springframework.scheduling.annotation.AsyncConfigurer; -import org.springframework.security.concurrent.DelegatingSecurityContextExecutor; +import org.springframework.security.concurrent.DelegatingSecurityContextExecutorService; import org.springframework.security.config.annotation.method.configuration.EnableGlobalMethodSecurity; import com.google.common.eventbus.AsyncEventBus; @@ -99,7 +99,7 @@ public class TestConfiguration implements AsyncConfigurer { @Bean public Executor asyncExecutor() { - return new DelegatingSecurityContextExecutor(Executors.newSingleThreadExecutor()); + return new DelegatingSecurityContextExecutorService(Executors.newSingleThreadExecutor()); } @Bean 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..02a48ef78 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 @@ -9,13 +9,15 @@ package org.eclipse.hawkbit.rest.exception; import java.util.EnumMap; +import java.util.List; import java.util.Map; import javax.servlet.http.HttpServletRequest; -import org.apache.tomcat.util.http.fileupload.FileUploadBase.FileSizeLimitExceededException; +import org.apache.commons.lang3.exception.ExceptionUtils; import org.eclipse.hawkbit.exception.SpServerError; import org.eclipse.hawkbit.exception.SpServerRtException; +import org.eclipse.hawkbit.repository.exception.MultiPartFileUploadException; import org.eclipse.hawkbit.rest.json.model.ExceptionInfo; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -26,12 +28,10 @@ import org.springframework.web.bind.annotation.ControllerAdvice; import org.springframework.web.bind.annotation.ExceptionHandler; import org.springframework.web.multipart.MultipartException; +import com.google.common.collect.Iterables; + /** * General controller advice for exception handling. - * - * - * - * */ @ControllerAdvice public class ResponseExceptionHandler { @@ -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) { @@ -88,14 +89,11 @@ public class ResponseExceptionHandler { @ExceptionHandler(SpServerRtException.class) public ResponseEntity handleSpServerRtExceptions(final HttpServletRequest request, final Exception ex) { - LOG.debug("Handling exception of request {}", request.getRequestURL()); - final ExceptionInfo response = new ExceptionInfo(); + logRequest(request, ex); + final ExceptionInfo response = createExceptionInfo(ex); final HttpStatus responseStatus; - response.setMessage(ex.getMessage()); - response.setExceptionClass(ex.getClass().getName()); if (ex instanceof SpServerRtException) { responseStatus = getStatusOrDefault(((SpServerRtException) ex).getError()); - response.setErrorCode(((SpServerRtException) ex).getError().getKey()); } else { responseStatus = DEFAULT_RESPONSE_STATUS; } @@ -117,11 +115,8 @@ public class ResponseExceptionHandler { @ExceptionHandler(HttpMessageNotReadableException.class) public ResponseEntity handleHttpMessageNotReadableException(final HttpServletRequest request, final Exception ex) { - LOG.debug("Handling exception {} of request {}", ex.getClass().getName(), request.getRequestURL()); - final ExceptionInfo response = new ExceptionInfo(); - response.setErrorCode(SpServerError.SP_REST_BODY_NOT_READABLE.getKey()); - response.setMessage(SpServerError.SP_REST_BODY_NOT_READABLE.getMessage()); - response.setExceptionClass(MessageNotReadableException.class.getName()); + logRequest(request, ex); + final ExceptionInfo response = createExceptionInfo(new MessageNotReadableException()); return new ResponseEntity<>(response, HttpStatus.BAD_REQUEST); } @@ -138,35 +133,30 @@ public class ResponseExceptionHandler { * as entity. */ @ExceptionHandler(MultipartException.class) - public ResponseEntity handleFileLimitExceededException(final HttpServletRequest request, + public ResponseEntity handleMultipartException(final HttpServletRequest request, final Exception ex) { - LOG.debug("Handling exception {} of request {}", ex.getClass().getName(), request.getRequestURL()); - final ExceptionInfo response = new ExceptionInfo(); - - if (searchForCause(ex, FileSizeLimitExceededException.class)) { - response.setErrorCode(SpServerError.SP_ARTIFACT_UPLOAD_FILE_LIMIT_EXCEEDED.getKey()); - response.setMessage(SpServerError.SP_ARTIFACT_UPLOAD_FILE_LIMIT_EXCEEDED.getMessage()); - response.setExceptionClass(FileSizeLimitExceededException.class.getName()); - } else { - response.setErrorCode(SpServerError.SP_ARTIFACT_UPLOAD_FAILED.getKey()); - response.setMessage(SpServerError.SP_ARTIFACT_UPLOAD_FAILED.getMessage()); - response.setExceptionClass(MultipartException.class.getName()); - } + logRequest(request, ex); + final List throwables = ExceptionUtils.getThrowableList(ex); + final Throwable responseCause = Iterables.getLast(throwables); + final ExceptionInfo response = createExceptionInfo(new MultiPartFileUploadException(responseCause)); return new ResponseEntity<>(response, HttpStatus.BAD_REQUEST); } - private static boolean searchForCause(final Throwable t, final Class lookFor) { - if (t != null && t.getCause() != null) { - if (t.getCause().getClass().isAssignableFrom(lookFor)) { - return true; - } else { - return searchForCause(t.getCause(), lookFor); - } + private void logRequest(final HttpServletRequest request, final Exception ex) { + LOG.debug("Handling exception {} of request {}", ex.getClass().getName(), request.getRequestURL()); + } + + private ExceptionInfo createExceptionInfo(final Exception ex) { + final ExceptionInfo response = new ExceptionInfo(); + response.setMessage(ex.getMessage()); + response.setExceptionClass(ex.getClass().getName()); + if (ex instanceof SpServerRtException) { + response.setErrorCode(((SpServerRtException) ex).getError().getKey()); } - return false; + return response; } } 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..0ee2c846d 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(); } @@ -369,21 +367,22 @@ public abstract class JsonBuilder { } - /** - * @param targets - * @return - */ - public static String targets(final List targets) { + public static String targets(final List targets, final boolean withToken) { final StringBuilder builder = new StringBuilder(); builder.append("["); int i = 0; for (final Target target : targets) { try { + final String address = target.getTargetInfo().getAddress() != null + ? target.getTargetInfo().getAddress().toString() : null; + + final String token = withToken ? target.getSecurityToken() : 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).put("securityToken", token).toString()); } catch (final Exception e) { e.printStackTrace(); } @@ -441,9 +440,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 +450,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/im/authentication/PermissionUtils.java b/hawkbit-security-core/src/main/java/org/eclipse/hawkbit/im/authentication/PermissionUtils.java index ae8b604bb..98cde9642 100644 --- a/hawkbit-security-core/src/main/java/org/eclipse/hawkbit/im/authentication/PermissionUtils.java +++ b/hawkbit-security-core/src/main/java/org/eclipse/hawkbit/im/authentication/PermissionUtils.java @@ -36,6 +36,9 @@ public final class PermissionUtils { for (final String role : roles) { authorities.add(new SimpleGrantedAuthority(role)); + // add spring security ROLE authority which is indicated by the + // `ROLE_` prefix + authorities.add(new SimpleGrantedAuthority("ROLE_" + role)); } return authorities; diff --git a/hawkbit-security-core/src/main/java/org/eclipse/hawkbit/im/authentication/SpPermission.java b/hawkbit-security-core/src/main/java/org/eclipse/hawkbit/im/authentication/SpPermission.java index ce6bc7c40..daf5f0dd9 100644 --- a/hawkbit-security-core/src/main/java/org/eclipse/hawkbit/im/authentication/SpPermission.java +++ b/hawkbit-security-core/src/main/java/org/eclipse/hawkbit/im/authentication/SpPermission.java @@ -224,8 +224,10 @@ public final class SpPermission { /* * Spring security eval expressions. */ - private static final String HAS_AUTH_PREFIX = "hasAuthority('"; - private static final String HAS_AUTH_SUFFIX = "')"; + private static final String BRACKET_OPEN = "("; + private static final String BRACKET_CLOSE = ")"; + private static final String HAS_AUTH_PREFIX = "hasAuthority" + BRACKET_OPEN + "'"; + private static final String HAS_AUTH_SUFFIX = "'" + BRACKET_CLOSE; private static final String HAS_AUTH_AND = " and "; /** @@ -257,99 +259,6 @@ public final class SpPermission { */ public static final String HAS_AUTH_OR = " or "; - /** - * Spring security eval hasAuthority expression to check if spring - * context contains {@link SpPermission#UPDATE_TARGET}. - */ - public static final String HAS_AUTH_UPDATE_TARGET = HAS_AUTH_PREFIX + UPDATE_TARGET + HAS_AUTH_SUFFIX; - - /** - * Spring security eval hasAuthority expression to check if spring - * context contains {@link SpPermission#SYSTEM_ADMIN}. - */ - public static final String HAS_AUTH_SYSTEM_ADMIN = HAS_AUTH_PREFIX + SYSTEM_ADMIN + HAS_AUTH_SUFFIX; - - /** - * Spring security eval hasAuthority expression to check if spring - * context contains {@link SpPermission#READ_TARGET}. - */ - public static final String HAS_AUTH_READ_TARGET = HAS_AUTH_PREFIX + READ_TARGET + HAS_AUTH_SUFFIX; - - /** - * Spring security eval hasAuthority expression to check if spring - * context contains {@link SpPermission#CREATE_TARGET}. - */ - public static final String HAS_AUTH_CREATE_TARGET = HAS_AUTH_PREFIX + CREATE_TARGET + HAS_AUTH_SUFFIX; - - /** - * Spring security eval hasAuthority expression to check if spring - * context contains {@link SpPermission#DELETE_TARGET}. - */ - public static final String HAS_AUTH_DELETE_TARGET = HAS_AUTH_PREFIX + DELETE_TARGET + HAS_AUTH_SUFFIX; - - /** - * Spring security eval hasAuthority expression to check if spring - * context contains {@link SpPermission#READ_REPOSITORY} and - * {@link SpPermission#UPDATE_TARGET}. - */ - public static final String HAS_AUTH_READ_REPOSITORY_AND_UPDATE_TARGET = HAS_AUTH_PREFIX + READ_REPOSITORY - + HAS_AUTH_SUFFIX + HAS_AUTH_AND + HAS_AUTH_PREFIX + UPDATE_TARGET + HAS_AUTH_SUFFIX; - - /** - * Spring security eval hasAuthority expression to check if spring - * context contains {@link SpPermission#CREATE_REPOSITORY}. - */ - public static final String HAS_AUTH_CREATE_REPOSITORY = HAS_AUTH_PREFIX + CREATE_REPOSITORY + HAS_AUTH_SUFFIX; - - /** - * Spring security eval hasAuthority expression to check if spring - * context contains {@link SpPermission#DELETE_REPOSITORY}. - */ - public static final String HAS_AUTH_DELETE_REPOSITORY = HAS_AUTH_PREFIX + DELETE_REPOSITORY + HAS_AUTH_SUFFIX; - - /** - * Spring security eval hasAuthority expression to check if spring - * context contains {@link SpPermission#READ_REPOSITORY}. - */ - public static final String HAS_AUTH_READ_REPOSITORY = HAS_AUTH_PREFIX + READ_REPOSITORY + HAS_AUTH_SUFFIX; - - /** - * Spring security eval hasAuthority expression to check if spring - * context contains {@link SpPermission#UPDATE_REPOSITORY}. - */ - public static final String HAS_AUTH_UPDATE_REPOSITORY = HAS_AUTH_PREFIX + UPDATE_REPOSITORY + HAS_AUTH_SUFFIX; - - /** - * Spring security eval hasAuthority expression to check if spring - * context contains {@link SpPermission#READ_REPOSITORY} and - * {@link SpPermission#READ_TARGET}. - */ - public static final String HAS_AUTH_READ_REPOSITORY_AND_READ_TARGET = HAS_AUTH_PREFIX + READ_REPOSITORY - + HAS_AUTH_SUFFIX + HAS_AUTH_AND + HAS_AUTH_PREFIX + READ_TARGET + HAS_AUTH_SUFFIX; - - /** - * Spring security eval hasAuthority expression to check if spring - * context contains {@link SpPermission#DOWNLOAD_REPOSITORY_ARTIFACT}. - */ - public static final String HAS_AUTH_DOWNLOAD_ARTIFACT = HAS_AUTH_PREFIX + DOWNLOAD_REPOSITORY_ARTIFACT - + HAS_AUTH_SUFFIX; - - /** - * Spring security eval hasAnyRole expression to check if the spring - * context contains the anoynmous role or the controller specific role - * {@link SpPermission#CONTROLLER_ROLE}. - */ - public static final String IS_CONTROLLER = "hasAnyRole('" + CONTROLLER_ROLE_ANONYMOUS + "', '" + CONTROLLER_ROLE - + "')"; - - /** - * Spring security eval hasAuthority expression to check if the spring - * context contains the role to allow controllers to download specific - * role {@link SpPermission#CONTROLLER_DOWNLOAD_ROLE}. - */ - public static final String HAS_CONTROLLER_DOWNLOAD = HAS_AUTH_PREFIX + CONTROLLER_DOWNLOAD_ROLE - + HAS_AUTH_SUFFIX; - /** * Spring security eval hasAnyRole expression to check if the spring * context contains system code role @@ -359,42 +268,168 @@ public final class SpPermission { /** * Spring security eval hasAuthority expression to check if spring - * context contains {@link SpPermission#CREATE_REPOSITORY} and - * {@link SpPermission#CREATE_TARGET}. + * context contains {@link SpPermission#UPDATE_TARGET} or + * {@link #IS_SYSTEM_CODE}. */ - public static final String HAS_AUTH_CREATE_REPOSITORY_AND_CREATE_TARGET = HAS_AUTH_PREFIX + CREATE_REPOSITORY - + HAS_AUTH_SUFFIX + HAS_AUTH_AND + HAS_AUTH_PREFIX + CREATE_TARGET + HAS_AUTH_SUFFIX; + public static final String HAS_AUTH_UPDATE_TARGET = HAS_AUTH_PREFIX + UPDATE_TARGET + HAS_AUTH_SUFFIX + + HAS_AUTH_OR + IS_SYSTEM_CODE; /** * Spring security eval hasAuthority expression to check if spring - * context contains {@link SpPermission#ROLLOUT_MANAGEMENT} + * context contains {@link SpPermission#SYSTEM_ADMIN} or + * {@link #IS_SYSTEM_CODE}. + */ + public static final String HAS_AUTH_SYSTEM_ADMIN = HAS_AUTH_PREFIX + SYSTEM_ADMIN + HAS_AUTH_SUFFIX + + HAS_AUTH_OR + IS_SYSTEM_CODE; + + /** + * Spring security eval hasAuthority expression to check if spring + * context contains {@link SpPermission#READ_TARGET} or + * {@link #IS_SYSTEM_CODE}. + */ + public static final String HAS_AUTH_READ_TARGET = HAS_AUTH_PREFIX + READ_TARGET + HAS_AUTH_SUFFIX + HAS_AUTH_OR + + IS_SYSTEM_CODE; + + /** + * Spring security eval hasAuthority expression to check if spring + * context contains {@link SpPermission#CREATE_TARGET} or + * {@link #IS_SYSTEM_CODE}. + */ + public static final String HAS_AUTH_CREATE_TARGET = HAS_AUTH_PREFIX + CREATE_TARGET + HAS_AUTH_SUFFIX + + HAS_AUTH_OR + IS_SYSTEM_CODE; + + /** + * Spring security eval hasAuthority expression to check if spring + * context contains {@link SpPermission#DELETE_TARGET} or + * {@link #IS_SYSTEM_CODE}. + */ + public static final String HAS_AUTH_DELETE_TARGET = HAS_AUTH_PREFIX + DELETE_TARGET + HAS_AUTH_SUFFIX + + HAS_AUTH_OR + IS_SYSTEM_CODE; + + /** + * Spring security eval hasAuthority expression to check if spring + * context contains {@link SpPermission#READ_REPOSITORY} and + * {@link SpPermission#UPDATE_TARGET} or {@link #IS_SYSTEM_CODE}. + */ + public static final String HAS_AUTH_READ_REPOSITORY_AND_UPDATE_TARGET = BRACKET_OPEN + HAS_AUTH_PREFIX + + READ_REPOSITORY + HAS_AUTH_SUFFIX + HAS_AUTH_AND + HAS_AUTH_PREFIX + UPDATE_TARGET + HAS_AUTH_SUFFIX + + BRACKET_CLOSE + HAS_AUTH_OR + IS_SYSTEM_CODE; + + /** + * Spring security eval hasAuthority expression to check if spring + * context contains {@link SpPermission#CREATE_REPOSITORY} or + * {@link #IS_SYSTEM_CODE}. + */ + public static final String HAS_AUTH_CREATE_REPOSITORY = HAS_AUTH_PREFIX + CREATE_REPOSITORY + HAS_AUTH_SUFFIX + + HAS_AUTH_OR + IS_SYSTEM_CODE; + + /** + * Spring security eval hasAuthority expression to check if spring + * context contains {@link SpPermission#DELETE_REPOSITORY} or + * {@link #IS_SYSTEM_CODE}. + */ + public static final String HAS_AUTH_DELETE_REPOSITORY = HAS_AUTH_PREFIX + DELETE_REPOSITORY + HAS_AUTH_SUFFIX + + HAS_AUTH_OR + IS_SYSTEM_CODE; + + /** + * Spring security eval hasAuthority expression to check if spring + * context contains {@link SpPermission#READ_REPOSITORY} or + * {@link #IS_SYSTEM_CODE}. + */ + public static final String HAS_AUTH_READ_REPOSITORY = HAS_AUTH_PREFIX + READ_REPOSITORY + HAS_AUTH_SUFFIX + + HAS_AUTH_OR + IS_SYSTEM_CODE; + + /** + * Spring security eval hasAuthority expression to check if spring + * context contains {@link SpPermission#UPDATE_REPOSITORY} or + * {@link #IS_SYSTEM_CODE}. + */ + public static final String HAS_AUTH_UPDATE_REPOSITORY = HAS_AUTH_PREFIX + UPDATE_REPOSITORY + HAS_AUTH_SUFFIX + + HAS_AUTH_OR + IS_SYSTEM_CODE; + + /** + * Spring security eval hasAuthority expression to check if spring + * context contains {@link SpPermission#READ_REPOSITORY} and + * {@link SpPermission#READ_TARGET} or {@link #IS_SYSTEM_CODE}. + */ + public static final String HAS_AUTH_READ_REPOSITORY_AND_READ_TARGET = BRACKET_OPEN + HAS_AUTH_PREFIX + + READ_REPOSITORY + HAS_AUTH_SUFFIX + HAS_AUTH_AND + HAS_AUTH_PREFIX + READ_TARGET + HAS_AUTH_SUFFIX + + BRACKET_CLOSE + HAS_AUTH_OR + IS_SYSTEM_CODE; + + /** + * Spring security eval hasAuthority expression to check if spring + * context contains {@link SpPermission#DOWNLOAD_REPOSITORY_ARTIFACT} or + * {@link #IS_SYSTEM_CODE}. + */ + public static final String HAS_AUTH_DOWNLOAD_ARTIFACT = HAS_AUTH_PREFIX + DOWNLOAD_REPOSITORY_ARTIFACT + + HAS_AUTH_SUFFIX + HAS_AUTH_OR + IS_SYSTEM_CODE; + + /** + * Spring security eval hasAnyRole expression to check if the spring + * context contains the anoynmous role or the controller specific role + * {@link SpringEvalExpressions#CONTROLLER_ROLE}. + */ + public static final String IS_CONTROLLER = "hasAnyRole('" + CONTROLLER_ROLE_ANONYMOUS + "', '" + CONTROLLER_ROLE + + "')"; + + /** + * Spring security eval hasAuthority expression to check if the spring + * context contains the role to allow controllers to download specific + * role {@link SpringEvalExpressions#CONTROLLER_DOWNLOAD_ROLE} + */ + public static final String HAS_CONTROLLER_DOWNLOAD = HAS_AUTH_PREFIX + CONTROLLER_DOWNLOAD_ROLE + + HAS_AUTH_SUFFIX; + + /** + * Spring security eval hasAuthority expression to check if spring + * context contains {@link SpPermission#CREATE_REPOSITORY} and + * {@link SpPermission#CREATE_TARGET} or {@link #IS_SYSTEM_CODE}. + */ + public static final String HAS_AUTH_CREATE_REPOSITORY_AND_CREATE_TARGET = BRACKET_OPEN + HAS_AUTH_PREFIX + + CREATE_REPOSITORY + HAS_AUTH_SUFFIX + HAS_AUTH_AND + HAS_AUTH_PREFIX + CREATE_TARGET + HAS_AUTH_SUFFIX + + BRACKET_CLOSE + HAS_AUTH_OR + IS_SYSTEM_CODE; + + /** + * Spring security eval hasAuthority expression to check if spring + * context contains {@link SpPermission#ROLLOUT_MANAGEMENT} or + * {@link #IS_SYSTEM_CODE}. */ public static final String HAS_AUTH_ROLLOUT_MANAGEMENT_READ = HAS_AUTH_PREFIX + ROLLOUT_MANAGEMENT - + HAS_AUTH_SUFFIX; + + HAS_AUTH_SUFFIX + HAS_AUTH_OR + IS_SYSTEM_CODE; /** * Spring security eval hasAuthority expression to check if spring * context contains {@link SpPermission#ROLLOUT_MANAGEMENT} and - * {@link SpPermission#READ_TARGET} + * {@link SpPermission#READ_TARGET} or {@link #IS_SYSTEM_CODE}. */ - public static final String HAS_AUTH_ROLLOUT_MANAGEMENT_READ_AND_TARGET_READ = HAS_AUTH_PREFIX - + ROLLOUT_MANAGEMENT + HAS_AUTH_SUFFIX + HAS_AUTH_AND + HAS_AUTH_PREFIX + READ_TARGET - + HAS_AUTH_SUFFIX;; + public static final String HAS_AUTH_ROLLOUT_MANAGEMENT_READ_AND_TARGET_READ = BRACKET_OPEN + HAS_AUTH_PREFIX + + ROLLOUT_MANAGEMENT + HAS_AUTH_SUFFIX + HAS_AUTH_AND + HAS_AUTH_PREFIX + READ_TARGET + HAS_AUTH_SUFFIX + + BRACKET_CLOSE + HAS_AUTH_OR + IS_SYSTEM_CODE; /** * Spring security eval hasAuthority expression to check if spring * context contains {@link SpPermission#ROLLOUT_MANAGEMENT} and - * {@link SpPermission#UPDATE_TARGET}. + * {@link SpPermission#UPDATE_TARGET} or {@link #IS_SYSTEM_CODE}. */ - public static final String HAS_AUTH_ROLLOUT_MANAGEMENT_WRITE = HAS_AUTH_PREFIX + ROLLOUT_MANAGEMENT - + HAS_AUTH_SUFFIX + HAS_AUTH_AND + HAS_AUTH_PREFIX + UPDATE_TARGET + HAS_AUTH_SUFFIX; + public static final String HAS_AUTH_ROLLOUT_MANAGEMENT_WRITE = BRACKET_OPEN + HAS_AUTH_PREFIX + + ROLLOUT_MANAGEMENT + HAS_AUTH_SUFFIX + HAS_AUTH_AND + HAS_AUTH_PREFIX + UPDATE_TARGET + + HAS_AUTH_SUFFIX + BRACKET_CLOSE + HAS_AUTH_OR + IS_SYSTEM_CODE; /** * Spring security eval hasAuthority expression to check if spring - * context contains {@link SpPermission#TENANT_CONFIGURATION} + * context contains {@link SpPermission#TENANT_CONFIGURATION} or + * {@link #IS_SYSTEM_CODE}. */ public static final String HAS_AUTH_TENANT_CONFIGURATION = HAS_AUTH_PREFIX + TENANT_CONFIGURATION - + HAS_AUTH_SUFFIX; + + HAS_AUTH_SUFFIX + HAS_AUTH_OR + IS_SYSTEM_CODE; + + /** + * Spring security eval hasAuthority expression to check if spring + * context contains {@link SpPermission#SYSTEM_MONITOR} or + * {@link #IS_SYSTEM_CODE}. + */ + public static final String HAS_AUTH_SYSTEM_MONITOR = HAS_AUTH_PREFIX + SYSTEM_MONITOR + HAS_AUTH_SUFFIX + + HAS_AUTH_OR + IS_SYSTEM_CODE; private SpringEvalExpressions() { // utility class diff --git a/hawkbit-security-core/src/main/java/org/eclipse/hawkbit/security/SecurityContextTenantAware.java b/hawkbit-security-core/src/main/java/org/eclipse/hawkbit/security/SecurityContextTenantAware.java index 2d8ac52df..4b5fe7224 100644 --- a/hawkbit-security-core/src/main/java/org/eclipse/hawkbit/security/SecurityContextTenantAware.java +++ b/hawkbit-security-core/src/main/java/org/eclipse/hawkbit/security/SecurityContextTenantAware.java @@ -8,13 +8,15 @@ */ package org.eclipse.hawkbit.security; -import java.util.concurrent.atomic.AtomicInteger; +import java.util.Collection; import org.eclipse.hawkbit.im.authentication.TenantAwareAuthenticationDetails; import org.eclipse.hawkbit.tenancy.TenantAware; import org.springframework.security.core.Authentication; +import org.springframework.security.core.GrantedAuthority; import org.springframework.security.core.context.SecurityContext; import org.springframework.security.core.context.SecurityContextHolder; +import org.springframework.security.core.context.SecurityContextImpl; /** * A {@link TenantAware} implemenation which retrieves the ID of the tenant from @@ -22,15 +24,9 @@ import org.springframework.security.core.context.SecurityContextHolder; * {@link Authentication#getDetails()} which holds the * {@link TenantAwareAuthenticationDetails} object. * - * - * - * */ public class SecurityContextTenantAware implements TenantAware { - private static final ThreadLocal TENANT_THREAD_LOCAL = new ThreadLocal<>(); - private static final ThreadLocal RUN_AS_DEPTH = new ThreadLocal<>(); - /* * (non-Javadoc) * @@ -38,9 +34,6 @@ public class SecurityContextTenantAware implements TenantAware { */ @Override public String getCurrentTenant() { - if (TENANT_THREAD_LOCAL.get() != null) { - return TENANT_THREAD_LOCAL.get(); - } final SecurityContext context = SecurityContextHolder.getContext(); if (context.getAuthentication() != null) { final Object authDetails = context.getAuthentication().getDetails(); @@ -51,29 +44,88 @@ public class SecurityContextTenantAware implements TenantAware { return null; } - /* - * (non-Javadoc) - * - * @see hawkbit.server.tenancy.TenantAware#runAsTenant(java.lang.String, - * java.util.concurrent.Callable) - */ @Override public T runAsTenant(final String tenant, final TenantRunner callable) { - AtomicInteger runAsDepth = RUN_AS_DEPTH.get(); - if (runAsDepth == null) { - runAsDepth = new AtomicInteger(1); - RUN_AS_DEPTH.set(runAsDepth); - } else { - runAsDepth.incrementAndGet(); - } - TENANT_THREAD_LOCAL.set(tenant); + final SecurityContext originalContext = SecurityContextHolder.getContext(); try { + SecurityContextHolder.setContext(buildSecurityContext(tenant)); return callable.run(); } finally { - if (runAsDepth.decrementAndGet() <= 0) { - RUN_AS_DEPTH.remove(); - TENANT_THREAD_LOCAL.remove(); - } + SecurityContextHolder.setContext(originalContext); + } + } + + private SecurityContext buildSecurityContext(final String tenant) { + final SecurityContextImpl securityContext = new SecurityContextImpl(); + securityContext.setAuthentication( + new AuthenticationDelegate(SecurityContextHolder.getContext().getAuthentication(), tenant)); + return securityContext; + } + + /** + * An {@link Authentication} implementation to delegate to an existing + * {@link Authentication} object except setting the details specifically for + * a specific tenant. + */ + private class AuthenticationDelegate implements Authentication { + private static final long serialVersionUID = 1L; + + private final Authentication delegate; + private final TenantAwareAuthenticationDetails tenantAwareAuthenticationDetails; + + private AuthenticationDelegate(final Authentication delegate, final String tenant) { + this.delegate = delegate; + tenantAwareAuthenticationDetails = new TenantAwareAuthenticationDetails(tenant, false); + } + + @Override + public boolean equals(final Object another) { + return delegate.equals(another); + } + + @Override + public String toString() { + return delegate.toString(); + } + + @Override + public int hashCode() { + return delegate.hashCode(); + } + + @Override + public String getName() { + return delegate.getName(); + } + + @Override + public Collection getAuthorities() { + return delegate.getAuthorities(); + } + + @Override + public Object getCredentials() { + return delegate.getCredentials(); + } + + @Override + public Object getDetails() { + return tenantAwareAuthenticationDetails; + } + + @Override + public Object getPrincipal() { + return delegate.getPrincipal(); + } + + @Override + public boolean isAuthenticated() { + return delegate.isAuthenticated(); + } + + @Override + public void setAuthenticated(final boolean isAuthenticated) throws IllegalArgumentException { + delegate.setAuthenticated(isAuthenticated); } } } diff --git a/hawkbit-security-core/src/main/java/org/eclipse/hawkbit/security/SystemSecurityContext.java b/hawkbit-security-core/src/main/java/org/eclipse/hawkbit/security/SystemSecurityContext.java index 78fb5818d..c0ecb8ceb 100644 --- a/hawkbit-security-core/src/main/java/org/eclipse/hawkbit/security/SystemSecurityContext.java +++ b/hawkbit-security-core/src/main/java/org/eclipse/hawkbit/security/SystemSecurityContext.java @@ -72,7 +72,7 @@ public class SystemSecurityContext { logger.debug("entering system code execution"); return tenantAware.runAsTenant(tenantAware.getCurrentTenant(), () -> { try { - setSystemContext(); + setSystemContext(oldContext); return callable.call(); } catch (final Exception e) { throw Throwables.propagate(e); @@ -93,9 +93,10 @@ public class SystemSecurityContext { return SecurityContextHolder.getContext().getAuthentication() instanceof SystemCodeAuthentication; } - private static void setSystemContext() { + private static void setSystemContext(final SecurityContext oldContext) { + final Authentication oldAuthentication = oldContext.getAuthentication(); final SecurityContextImpl securityContextImpl = new SecurityContextImpl(); - securityContextImpl.setAuthentication(new SystemCodeAuthentication()); + securityContextImpl.setAuthentication(new SystemCodeAuthentication(oldAuthentication)); SecurityContextHolder.setContext(securityContextImpl); } @@ -104,6 +105,11 @@ public class SystemSecurityContext { private static final long serialVersionUID = 1L; private static final List AUTHORITIES = Collections .singletonList(new SimpleGrantedAuthority(SpringEvalExpressions.SYSTEM_ROLE)); + private final Authentication oldAuthentication; + + private SystemCodeAuthentication(final Authentication oldAuthentication) { + this.oldAuthentication = oldAuthentication; + } @Override public String getName() { @@ -117,17 +123,17 @@ public class SystemSecurityContext { @Override public Object getCredentials() { - return null; + return oldAuthentication != null ? oldAuthentication.getCredentials() : null; } @Override public Object getDetails() { - return null; + return oldAuthentication != null ? oldAuthentication.getDetails() : null; } @Override public Object getPrincipal() { - return null; + return oldAuthentication != null ? oldAuthentication.getPrincipal() : null; } @Override 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/hawkbit-security-core/src/test/java/org/eclipse/hawkbit/im/authentication/PermissionTest.java b/hawkbit-security-core/src/test/java/org/eclipse/hawkbit/im/authentication/PermissionTest.java index bca7fd1c1..2c948206e 100644 --- a/hawkbit-security-core/src/test/java/org/eclipse/hawkbit/im/authentication/PermissionTest.java +++ b/hawkbit-security-core/src/test/java/org/eclipse/hawkbit/im/authentication/PermissionTest.java @@ -36,7 +36,8 @@ public final class PermissionTest { final Collection allAuthorities = SpPermission.getAllAuthorities(); final List allAuthoritiesList = PermissionUtils.createAllAuthorityList(); assertThat(allAuthorities).hasSize(allPermission); - assertThat(allAuthoritiesList).hasSize(allPermission); + // times 2 because we add also all authorities as prefix 'ROLE_'; + assertThat(allAuthoritiesList).hasSize(allPermission * 2); assertThat(allAuthoritiesList.stream().map(authority -> authority.getAuthority()).collect(Collectors.toList())) .containsAll(allAuthorities); @@ -46,7 +47,8 @@ public final class PermissionTest { .getAllAuthorities(SpPermission.SYSTEM_ADMIN, SpPermission.SYSTEM_DIAG, SpPermission.SYSTEM_MONITOR)); assertThat(authoritiesWithoutSystem).hasSize(permissionWithoutSystem); - assertThat(authoritiesListWithoutSystem).hasSize(permissionWithoutSystem); + // times 2 because we add also all authorities as prefix 'ROLE_'; + assertThat(authoritiesListWithoutSystem).hasSize(permissionWithoutSystem * 2); assertThat(authoritiesListWithoutSystem.stream().map(authority -> authority.getAuthority()) .collect(Collectors.toList())).containsAll(authoritiesWithoutSystem); diff --git a/hawkbit-ui/pom.xml b/hawkbit-ui/pom.xml index 08d225c62..b90e36e88 100644 --- a/hawkbit-ui/pom.xml +++ b/hawkbit-ui/pom.xml @@ -200,6 +200,10 @@ org.springframework.security spring-security-web + + org.apache.commons + commons-collections4 + com.vaadin diff --git a/hawkbit-ui/src/main/java/org/eclipse/hawkbit/ui/artifacts/event/SoftwareModuleTypeEvent.java b/hawkbit-ui/src/main/java/org/eclipse/hawkbit/ui/artifacts/event/SoftwareModuleTypeEvent.java index 88e076775..7262aee72 100644 --- a/hawkbit-ui/src/main/java/org/eclipse/hawkbit/ui/artifacts/event/SoftwareModuleTypeEvent.java +++ b/hawkbit-ui/src/main/java/org/eclipse/hawkbit/ui/artifacts/event/SoftwareModuleTypeEvent.java @@ -12,17 +12,11 @@ import org.eclipse.hawkbit.repository.model.SoftwareModuleType; /** * Event to represent software module type add, update or delete. - * - * - * */ public class SoftwareModuleTypeEvent { /** * Software module type events in the Upload UI. - * - * - * */ public enum SoftwareModuleTypeEnum { ADD_SOFTWARE_MODULE_TYPE, DELETE_SOFTWARE_MODULE_TYPE, UPDATE_SOFTWARE_MODULE_TYPE diff --git a/hawkbit-ui/src/main/java/org/eclipse/hawkbit/ui/artifacts/smtable/SoftwareModuleAddUpdateWindow.java b/hawkbit-ui/src/main/java/org/eclipse/hawkbit/ui/artifacts/smtable/SoftwareModuleAddUpdateWindow.java index 6a5a07a40..c8eb74313 100644 --- a/hawkbit-ui/src/main/java/org/eclipse/hawkbit/ui/artifacts/smtable/SoftwareModuleAddUpdateWindow.java +++ b/hawkbit-ui/src/main/java/org/eclipse/hawkbit/ui/artifacts/smtable/SoftwareModuleAddUpdateWindow.java @@ -10,6 +10,8 @@ package org.eclipse.hawkbit.ui.artifacts.smtable; import java.io.Serializable; +import javax.annotation.PostConstruct; + import org.eclipse.hawkbit.repository.EntityFactory; import org.eclipse.hawkbit.repository.SoftwareManagement; import org.eclipse.hawkbit.repository.model.SoftwareModule; @@ -18,18 +20,17 @@ import org.eclipse.hawkbit.ui.common.CommonDialogWindow; import org.eclipse.hawkbit.ui.common.SoftwareModuleTypeBeanQuery; import org.eclipse.hawkbit.ui.common.table.BaseEntityEventType; import org.eclipse.hawkbit.ui.components.SPUIComponentProvider; +import org.eclipse.hawkbit.ui.decorators.SPUIWindowDecorator; import org.eclipse.hawkbit.ui.utils.HawkbitCommonUtil; import org.eclipse.hawkbit.ui.utils.I18N; import org.eclipse.hawkbit.ui.utils.SPUIComponentIdProvider; import org.eclipse.hawkbit.ui.utils.SPUIDefinitions; import org.eclipse.hawkbit.ui.utils.SPUILabelDefinitions; -import org.eclipse.hawkbit.ui.utils.SPUIStyleDefinitions; import org.eclipse.hawkbit.ui.utils.UINotification; import org.springframework.beans.factory.annotation.Autowired; import org.vaadin.addons.lazyquerycontainer.BeanQueryFactory; import org.vaadin.spring.events.EventBus; -import com.vaadin.event.FieldEvents.TextChangeEvent; import com.vaadin.spring.annotation.SpringComponent; import com.vaadin.spring.annotation.ViewScope; import com.vaadin.ui.ComboBox; @@ -38,8 +39,6 @@ import com.vaadin.ui.FormLayout; import com.vaadin.ui.Label; import com.vaadin.ui.TextArea; import com.vaadin.ui.TextField; -import com.vaadin.ui.UI; -import com.vaadin.ui.Window; import com.vaadin.ui.themes.ValoTheme; /** @@ -66,8 +65,6 @@ public class SoftwareModuleAddUpdateWindow extends CustomComponent implements Se @Autowired private transient EntityFactory entityFactory; - private Label mandatoryLabel; - private TextField nameTextField; private TextField versionTextField; @@ -80,14 +77,20 @@ public class SoftwareModuleAddUpdateWindow extends CustomComponent implements Se private CommonDialogWindow window; - private String oldDescriptionValue; - - private String oldVendorValue; - private Boolean editSwModule = Boolean.FALSE; private Long baseSwModuleId; + private FormLayout formLayout; + + /** + * Initialize Distribution Add and Edit Window. + */ + @PostConstruct + void init() { + createRequiredComponents(); + } + /** * Create window for new software module. * @@ -95,11 +98,7 @@ public class SoftwareModuleAddUpdateWindow extends CustomComponent implements Se * module. */ public CommonDialogWindow createAddSoftwareModuleWindow() { - - editSwModule = Boolean.FALSE; - createRequiredComponents(); - createWindow(); - return window; + return createUpdateSoftwareModuleWindow(null); } /** @@ -110,17 +109,11 @@ public class SoftwareModuleAddUpdateWindow extends CustomComponent implements Se * @return reference of {@link com.vaadin.ui.Window} to update software * module. */ - public Window createUpdateSoftwareModuleWindow(final Long baseSwModuleId) { - - editSwModule = Boolean.TRUE; + public CommonDialogWindow createUpdateSoftwareModuleWindow(final Long baseSwModuleId) { this.baseSwModuleId = baseSwModuleId; - createRequiredComponents(); - createWindow(); - /* populate selected target values to edit. */ + resetComponents(); populateValuesOfSwModule(); - nameTextField.setEnabled(false); - versionTextField.setEnabled(false); - typeComboBox.setEnabled(false); + createWindow(); return window; } @@ -145,13 +138,6 @@ public class SoftwareModuleAddUpdateWindow extends CustomComponent implements Se ValoTheme.TEXTAREA_TINY, false, null, i18n.get("textfield.description"), SPUILabelDefinitions.TEXT_AREA_MAX_LENGTH); descTextArea.setId(SPUIComponentIdProvider.ADD_SW_MODULE_DESCRIPTION); - addDescriptionTextChangeListener(); - addVendorTextChangeListener(); - - /* Label for mandatory symbol */ - mandatoryLabel = new Label(i18n.get("label.mandatory.field")); - mandatoryLabel.setStyleName(SPUIStyleDefinitions.SP_TEXTFIELD_ERROR); - mandatoryLabel.addStyleName(ValoTheme.LABEL_SMALL); typeComboBox = SPUIComponentProvider.getComboBox(i18n.get("upload.swmodule.type"), "", "", null, null, true, null, i18n.get("upload.swmodule.type")); @@ -159,46 +145,34 @@ public class SoftwareModuleAddUpdateWindow extends CustomComponent implements Se typeComboBox.setStyleName(SPUIDefinitions.COMBO_BOX_SPECIFIC_STYLE + " " + ValoTheme.COMBOBOX_TINY); typeComboBox.setNewItemsAllowed(Boolean.FALSE); typeComboBox.setImmediate(Boolean.TRUE); - populateTypeNameCombo(); - - resetOldValues(); } - /** - * - */ private void populateTypeNameCombo() { typeComboBox.setContainerDataSource(HawkbitCommonUtil.createLazyQueryContainer( new BeanQueryFactory(SoftwareModuleTypeBeanQuery.class))); typeComboBox.setItemCaptionPropertyId(SPUILabelDefinitions.VAR_NAME); - } - private void resetOldValues() { - oldDescriptionValue = null; - oldVendorValue = null; + private void resetComponents() { + + vendorTextField.clear(); + nameTextField.clear(); + versionTextField.clear(); + descTextArea.clear(); + typeComboBox.clear(); + editSwModule = Boolean.FALSE; } - /** - * Build the window content and get an instance of customDialogWindow - * - */ private void createWindow() { - final Label madatoryStarLabel = new Label("*"); madatoryStarLabel.setStyleName("v-caption v-required-field-indicator"); madatoryStarLabel.setWidth(null); - - /* - * The main layout of the window contains mandatory info, textboxes - * (controller Id, name & description) and action buttons layout - */ addStyleName("lay-color"); setSizeUndefined(); - final FormLayout formLayout = new FormLayout(); - formLayout.addComponent(mandatoryLabel); + formLayout = new FormLayout(); + formLayout.setCaption(null); formLayout.addComponent(typeComboBox); formLayout.addComponent(nameTextField); formLayout.addComponent(versionTextField); @@ -207,24 +181,17 @@ public class SoftwareModuleAddUpdateWindow extends CustomComponent implements Se setCompositionRoot(formLayout); - /* add main layout to the window */ - window = SPUIComponentProvider.getWindow(i18n.get("upload.caption.add.new.swmodule"), null, - SPUIDefinitions.CREATE_UPDATE_WINDOW, this, event -> saveOrUpdate(), event -> closeThisWindow(), null); + window = SPUIWindowDecorator.getWindow(i18n.get("upload.caption.add.new.swmodule"), null, + SPUIDefinitions.CREATE_UPDATE_WINDOW, this, event -> saveOrUpdate(), null, null, formLayout, i18n); window.getButtonsLayout().removeStyleName("actionButtonsMargin"); - nameTextField.focus(); + + nameTextField.setEnabled(!editSwModule); + versionTextField.setEnabled(!editSwModule); + typeComboBox.setEnabled(!editSwModule); + + typeComboBox.focus(); } - private void addDescriptionTextChangeListener() { - descTextArea.addTextChangeListener(event -> window.setSaveButtonEnabled(hasDescriptionChanged(event))); - } - - private void addVendorTextChangeListener() { - vendorTextField.addTextChangeListener(event -> window.setSaveButtonEnabled(hasVendorChanged(event))); - } - - /** - * Add new SW module. - */ private void addNewBaseSoftware() { final String name = HawkbitCommonUtil.trimAndNullIfEmpty(nameTextField.getValue()); final String version = HawkbitCommonUtil.trimAndNullIfEmpty(versionTextField.getValue()); @@ -232,10 +199,6 @@ public class SoftwareModuleAddUpdateWindow extends CustomComponent implements Se final String description = HawkbitCommonUtil.trimAndNullIfEmpty(descTextArea.getValue()); final String type = typeComboBox.getValue() != null ? typeComboBox.getValue().toString() : null; - if (!mandatoryCheck(name, version, type)) { - return; - } - if (HawkbitCommonUtil.isDuplicate(name, version, type)) { uiNotifcation.displayValidationError( i18n.get("message.duplicate.softwaremodule", new Object[] { name, version })); @@ -248,8 +211,6 @@ public class SoftwareModuleAddUpdateWindow extends CustomComponent implements Se new Object[] { newBaseSoftwareModule.getName() + ":" + newBaseSoftwareModule.getVersion() })); eventBus.publish(this, new SoftwareModuleEvent(BaseEntityEventType.NEW_ENTITY, newBaseSoftwareModule)); } - // close the window - closeThisWindow(); } } @@ -269,13 +230,16 @@ public class SoftwareModuleAddUpdateWindow extends CustomComponent implements Se eventBus.publish(this, new SoftwareModuleEvent(BaseEntityEventType.UPDATED_ENTITY, newSWModule)); } - closeThisWindow(); } /** * fill the data of a softwareModule in the content of the window */ private void populateValuesOfSwModule() { + if (baseSwModuleId == null) { + return; + } + editSwModule = Boolean.TRUE; final SoftwareModule swModle = softwareManagement.findSoftwareModuleById(baseSwModuleId); nameTextField.setValue(swModle.getName()); versionTextField.setValue(swModle.getVersion()); @@ -283,49 +247,10 @@ public class SoftwareModuleAddUpdateWindow extends CustomComponent implements Se : HawkbitCommonUtil.trimAndNullIfEmpty(swModle.getVendor())); descTextArea.setValue(swModle.getDescription() == null ? HawkbitCommonUtil.SP_STRING_EMPTY : HawkbitCommonUtil.trimAndNullIfEmpty(swModle.getDescription())); - oldDescriptionValue = descTextArea.getValue(); - oldVendorValue = vendorTextField.getValue(); if (swModle.getType().isDeleted()) { typeComboBox.addItem(swModle.getType().getName()); } typeComboBox.setValue(swModle.getType().getName()); - window.setSaveButtonEnabled(Boolean.FALSE); - } - - /** - * Method to close window. - */ - private void closeThisWindow() { - window.close(); - UI.getCurrent().removeWindow(window); - } - - /** - * Validation check - Mandatory. - * - * @param name - * as String - * @param version - * as version - * @return boolena as flag - */ - private boolean mandatoryCheck(final String name, final String version, final String type) { - boolean isValid = true; - if (name == null || version == null || type == null) { - if (name == null) { - nameTextField.addStyleName(SPUIStyleDefinitions.SP_TEXTFIELD_ERROR); - } - if (version == null) { - versionTextField.addStyleName(SPUIStyleDefinitions.SP_TEXTFIELD_ERROR); - } - if (type == null) { - typeComboBox.addStyleName(SPUIStyleDefinitions.SP_COMBOFIELD_ERROR); - } - - uiNotifcation.displayValidationError(i18n.get("message.mandatory.check")); - isValid = false; - } - return isValid; } private void saveOrUpdate() { @@ -336,12 +261,8 @@ public class SoftwareModuleAddUpdateWindow extends CustomComponent implements Se } } - private boolean hasDescriptionChanged(final TextChangeEvent event) { - return !(event.getText().equals(oldDescriptionValue) && vendorTextField.getValue().equals(oldVendorValue)); - } - - private boolean hasVendorChanged(final TextChangeEvent event) { - return !(event.getText().equals(oldVendorValue) && descTextArea.getValue().equals(oldDescriptionValue)); + public FormLayout getFormLayout() { + return formLayout; } } diff --git a/hawkbit-ui/src/main/java/org/eclipse/hawkbit/ui/artifacts/smtable/SoftwareModuleTable.java b/hawkbit-ui/src/main/java/org/eclipse/hawkbit/ui/artifacts/smtable/SoftwareModuleTable.java index 6527a37be..56367f230 100644 --- a/hawkbit-ui/src/main/java/org/eclipse/hawkbit/ui/artifacts/smtable/SoftwareModuleTable.java +++ b/hawkbit-ui/src/main/java/org/eclipse/hawkbit/ui/artifacts/smtable/SoftwareModuleTable.java @@ -44,7 +44,6 @@ import com.vaadin.ui.UI; /** * Header of Software module table. - * */ @SpringComponent @ViewScope @@ -211,4 +210,5 @@ public class SoftwareModuleTable extends AbstractNamedVersionTable close(); + + private final transient Map orginalValues; + + private final List> allComponents; + + private final I18N i18n; + /** * Constructor. * @@ -72,28 +117,83 @@ public class CommonDialogWindow extends Window { * the cancelButtonClickListener */ public CommonDialogWindow(final String caption, final Component content, final String helpLink, - final ClickListener saveButtonClickListener, final ClickListener cancelButtonClickListener) { + final ClickListener saveButtonClickListener, final ClickListener cancelButtonClickListener, + final AbstractLayout layout, final I18N i18n) { checkNotNull(saveButtonClickListener); - checkNotNull(cancelButtonClickListener); this.caption = caption; this.content = content; this.helpLink = helpLink; this.saveButtonClickListener = saveButtonClickListener; this.cancelButtonClickListener = cancelButtonClickListener; - + this.orginalValues = new HashMap<>(); + this.allComponents = getAllComponents(layout); + this.i18n = i18n; init(); } + @Override + public void close() { + super.close(); + orginalValues.clear(); + removeListeners(); + allComponents.clear(); + this.saveButton.setEnabled(false); + } + + private void removeListeners() { + for (final AbstractField field : allComponents) { + removeTextListener(field); + removeValueChangeListener(field); + removeItemSetChangeistener(field); + } + } + + private void removeItemSetChangeistener(final AbstractField field) { + if (!(field instanceof Table)) { + return; + } + for (final Object listener : field.getListeners(ItemSetChangeEvent.class)) { + if (listener instanceof ChangeListener) { + ((Table) field).removeItemSetChangeListener((ChangeListener) listener); + } + } + } + + private void removeTextListener(final AbstractField field) { + if (!(field instanceof TextChangeNotifier)) { + return; + } + for (final Object listener : field.getListeners(TextChangeEvent.class)) { + if (listener instanceof ChangeListener) { + ((TextChangeNotifier) field).removeTextChangeListener((ChangeListener) listener); + } + } + } + + private void removeValueChangeListener(final AbstractField field) { + for (final Object listener : field.getListeners(ValueChangeEvent.class)) { + if (listener instanceof ChangeListener) { + field.removeValueChangeListener((ChangeListener) listener); + } + } + } + private final void init() { if (content instanceof AbstractOrderedLayout) { ((AbstractOrderedLayout) content).setSpacing(true); ((AbstractOrderedLayout) content).setMargin(true); } + if (content instanceof GridLayout) { + addStyleName("marginTop"); + } if (null != content) { mainLayout.addComponent(content); } + + createMandatoryLabel(); + final HorizontalLayout buttonLayout = createActionButtonsLayout(); mainLayout.addComponent(buttonLayout); mainLayout.setComponentAlignment(buttonLayout, Alignment.TOP_CENTER); @@ -104,6 +204,173 @@ public class CommonDialogWindow extends Window { center(); setModal(true); addStyleName("fontsize"); + setOrginaleValues(); + addListeners(); + } + + /** + * saves the original values in a Map so we can use them for detecting + * changes + */ + public final void setOrginaleValues() { + for (final AbstractField field : allComponents) { + Object value = field.getValue(); + + if (field instanceof Table) { + value = Sets.newHashSet(((Table) field).getContainerDataSource().getItemIds()); + } + orginalValues.put(field, value); + } + saveButton.setEnabled(isSaveButtonEnabledAfterValueChange(null, null)); + } + + private final void addListeners() { + for (final AbstractField field : allComponents) { + if (field instanceof TextChangeNotifier) { + ((TextChangeNotifier) field).addTextChangeListener(new ChangeListener(field)); + } + + if (field instanceof Table) { + ((Table) field).addItemSetChangeListener(new ChangeListener(field)); + } else { + field.addValueChangeListener(new ChangeListener(field)); + } + } + + saveButton.addClickListener(close); + cancelButton.addClickListener(close); + } + + private boolean isSaveButtonEnabledAfterValueChange(final Component currentChangedComponent, + final Object newValue) { + return isMandatoryFieldNotEmptyAndValid(currentChangedComponent, newValue) + && isValuesChanged(currentChangedComponent, newValue); + } + + private boolean isValuesChanged(final Component currentChangedComponent, final Object newValue) { + for (final AbstractField field : allComponents) { + Object originalValue = orginalValues.get(field); + if (field instanceof CheckBox && originalValue == null) { + originalValue = Boolean.FALSE; + } + final Object currentValue = getCurrentVaue(currentChangedComponent, newValue, field); + + if (!isValueEquals(field, originalValue, currentValue)) { + return true; + } + } + return false; + } + + private boolean isValueEquals(final AbstractField field, final Object orginalValue, final Object currentValue) { + if (Set.class.equals(field.getType())) { + return CollectionUtils.isEqualCollection(CollectionUtils.emptyIfNull((Collection) orginalValue), + CollectionUtils.emptyIfNull((Collection) currentValue)); + } + + if (String.class.equals(field.getType())) { + return Objects.equals(Strings.emptyToNull((String) orginalValue), + Strings.emptyToNull((String) currentValue)); + } + + return Objects.equals(orginalValue, currentValue); + } + + private Object getCurrentVaue(final Component currentChangedComponent, final Object newValue, + final AbstractField field) { + Object currentValue = field.getValue(); + if (field instanceof Table) { + currentValue = ((Table) field).getContainerDataSource().getItemIds(); + } + + if (field.equals(currentChangedComponent)) { + currentValue = newValue; + } + return currentValue; + } + + private boolean shouldMandatoryLabelShown() { + for (final AbstractField field : allComponents) { + if (field.isRequired()) { + return true; + } + } + + return false; + } + + private boolean isMandatoryFieldNotEmptyAndValid(final Component currentChangedComponent, final Object newValue) { + + boolean valid = true; + final List> requiredComponents = allComponents.stream().filter(field -> field.isRequired()) + .collect(Collectors.toList()); + + requiredComponents.addAll(allComponents.stream().filter(this::hasNullValidator).collect(Collectors.toList())); + + for (final AbstractField field : requiredComponents) { + Object value = getCurrentVaue(currentChangedComponent, newValue, field); + + if (String.class.equals(field.getType())) { + value = Strings.emptyToNull((String) value); + } + + if (Set.class.equals(field.getType())) { + value = emptyToNull((Collection) value); + } + + if (value == null) { + return false; + } + + // We need to loop through the entire loop for validity testing. + // Otherwise the UI will only mark the + // first field with errors and then stop. If there are several + // fields with errors, this is bad. + field.setValue(value); + if (!field.isValid()) { + valid = false; + } + } + + return valid; + } + + private static Object emptyToNull(final Collection c) { + return (c == null || c.isEmpty()) ? null : c; + } + + private boolean hasNullValidator(final Component component) { + + if (component instanceof AbstractField) { + final AbstractField fieldComponent = (AbstractField) component; + for (final Validator validator : fieldComponent.getValidators()) { + if (validator instanceof NullValidator) { + return true; + } + } + } + return false; + } + + private List> getAllComponents(final AbstractLayout abstractLayout) { + final List> components = new ArrayList<>(); + + final Iterator iterate = abstractLayout.iterator(); + while (iterate.hasNext()) { + final Component c = iterate.next(); + if (c instanceof AbstractLayout) { + components.addAll(getAllComponents((AbstractLayout) c)); + } + + if (c instanceof AbstractField) { + components.add((AbstractField) c); + } + + if (c instanceof FlexibleOptionGroupItemComponent) { + components.add(((FlexibleOptionGroupItemComponent) c).getOwner()); + } + } + return components; } private HorizontalLayout createActionButtonsLayout() { @@ -122,12 +389,34 @@ public class CommonDialogWindow extends Window { return buttonsLayout; } + private void createMandatoryLabel() { + + if (!shouldMandatoryLabelShown()) { + return; + } + + final Label mandatoryLabel = new Label(i18n.get("label.mandatory.field")); + mandatoryLabel.addStyleName(SPUIStyleDefinitions.SP_TEXTFIELD_ERROR + " " + ValoTheme.LABEL_TINY); + + if (content instanceof TargetAddUpdateWindowLayout) { + ((TargetAddUpdateWindowLayout) content).getFormLayout().addComponent(mandatoryLabel); + } else if (content instanceof SoftwareModuleAddUpdateWindow) { + ((SoftwareModuleAddUpdateWindow) content).getFormLayout().addComponent(mandatoryLabel); + } else if (content instanceof AbstractCreateUpdateTagLayout) { + ((AbstractCreateUpdateTagLayout) content).getMainLayout().addComponent(mandatoryLabel); + } + + mainLayout.addComponent(mandatoryLabel); + } + private void createCancelButton() { cancelButton = SPUIComponentProvider.getButton(SPUIComponentIdProvider.CANCEL_BUTTON, "Cancel", "", "", true, FontAwesome.TIMES, SPUIButtonStyleBorderWithIcon.class); cancelButton.setSizeUndefined(); cancelButton.addStyleName("default-color"); - cancelButton.addClickListener(cancelButtonClickListener); + if (cancelButtonClickListener != null) { + cancelButton.addClickListener(cancelButtonClickListener); + } buttonsLayout.addComponent(cancelButton); buttonsLayout.setComponentAlignment(cancelButton, Alignment.MIDDLE_LEFT); @@ -140,6 +429,7 @@ public class CommonDialogWindow extends Window { saveButton.setSizeUndefined(); saveButton.addStyleName("default-color"); saveButton.addClickListener(saveButtonClickListener); + saveButton.setEnabled(false); buttonsLayout.addComponent(saveButton); buttonsLayout.setComponentAlignment(saveButton, Alignment.MIDDLE_RIGHT); buttonsLayout.setExpandRatio(saveButton, 1.0F); @@ -155,16 +445,52 @@ public class CommonDialogWindow extends Window { buttonsLayout.setComponentAlignment(helpLinkComponent, Alignment.MIDDLE_RIGHT); } - public void setSaveButtonEnabled(final boolean enabled) { - saveButton.setEnabled(enabled); + public AbstractComponent getButtonsLayout() { + return this.buttonsLayout; } - public void setCancelButtonEnabled(final boolean enabled) { - cancelButton.setEnabled(enabled); + private class ChangeListener implements ValueChangeListener, TextChangeListener, ItemSetChangeListener { + + private final Field field; + + public ChangeListener(final Field field) { + super(); + this.field = field; + } + + @Override + public void textChange(final TextChangeEvent event) { + saveButton.setEnabled(isSaveButtonEnabledAfterValueChange(field, event.getText())); + } + + @Override + public void valueChange(final ValueChangeEvent event) { + saveButton.setEnabled(isSaveButtonEnabledAfterValueChange(field, field.getValue())); + } + + @Override + public void containerItemSetChange(final ItemSetChangeEvent event) { + if (!(field instanceof Table)) { + return; + } + final Table table = (Table) field; + saveButton.setEnabled( + isSaveButtonEnabledAfterValueChange(table, table.getContainerDataSource().getItemIds())); + } } - public HorizontalLayout getButtonsLayout() { - return buttonsLayout; + /** + * Adds the component manually to the allComponents-List and adds a + * ValueChangeListener to it. Necessary in Update Distribution Type as the + * CheckBox concerned is an ItemProperty... + * + * @param component + * AbstractField + */ + public void updateAllComponents(final AbstractField component) { + + allComponents.add(component); + component.addValueChangeListener(new ChangeListener(component)); } } diff --git a/hawkbit-ui/src/main/java/org/eclipse/hawkbit/ui/common/filterlayout/AbstractFilterButtons.java b/hawkbit-ui/src/main/java/org/eclipse/hawkbit/ui/common/filterlayout/AbstractFilterButtons.java index cc48b47db..aec0029d0 100644 --- a/hawkbit-ui/src/main/java/org/eclipse/hawkbit/ui/common/filterlayout/AbstractFilterButtons.java +++ b/hawkbit-ui/src/main/java/org/eclipse/hawkbit/ui/common/filterlayout/AbstractFilterButtons.java @@ -34,10 +34,6 @@ import com.vaadin.ui.themes.ValoTheme; /** * Parent class for filter button layout. - * - * - * - * */ public abstract class AbstractFilterButtons extends Table { diff --git a/hawkbit-ui/src/main/java/org/eclipse/hawkbit/ui/common/filterlayout/AbstractFilterMultiButtonClick.java b/hawkbit-ui/src/main/java/org/eclipse/hawkbit/ui/common/filterlayout/AbstractFilterMultiButtonClick.java index 8b3ac4cde..f7d3656b1 100644 --- a/hawkbit-ui/src/main/java/org/eclipse/hawkbit/ui/common/filterlayout/AbstractFilterMultiButtonClick.java +++ b/hawkbit-ui/src/main/java/org/eclipse/hawkbit/ui/common/filterlayout/AbstractFilterMultiButtonClick.java @@ -22,7 +22,8 @@ import com.vaadin.ui.Button.ClickEvent; */ public abstract class AbstractFilterMultiButtonClick extends AbstractFilterButtonClickBehaviour { - protected final Set