Merge remote-tracking branch 'origin/master' into fix_Introduce_consitent_button_icon_for_update_create_dialogs

This commit is contained in:
Melanie Retter
2016-06-27 10:16:34 +02:00
113 changed files with 1977 additions and 954 deletions

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -99,7 +99,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);

View File

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

View File

@@ -11,6 +11,7 @@ package org.eclipse.hawkbit.simulator;
import java.net.URL;
import org.eclipse.hawkbit.simulator.AbstractSimulatedDevice.Protocol;
import org.eclipse.hawkbit.simulator.amqp.SpSenderService;
import org.eclipse.hawkbit.simulator.http.ControllerResource;
import org.eclipse.hawkbit.simulator.http.GatewayTokenInterceptor;
import org.springframework.beans.factory.annotation.Autowired;
@@ -28,6 +29,9 @@ public class SimulatedDeviceFactory {
@Autowired
private DeviceSimulatorUpdater deviceUpdater;
@Autowired
private SpSenderService spSenderService;
/**
* Creating a simulated devices.
*
@@ -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)

View File

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

View File

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

View File

@@ -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<ContextRefreshedEve
} catch (final MalformedURLException e) {
LOGGER.error("Creation of simulated device at startup failed.", e);
}
if (autostart.getApi() == Protocol.DMF_AMQP) {
spSenderService.createOrUpdateThing(autostart.getTenant(), deviceId);
}
}
});
}

View File

@@ -12,52 +12,103 @@ import java.time.Duration;
import java.util.HashMap;
import java.util.Map;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.amqp.core.Binding;
import org.springframework.amqp.core.BindingBuilder;
import org.springframework.amqp.core.FanoutExchange;
import org.springframework.amqp.core.Queue;
import org.springframework.amqp.core.QueueBuilder;
import org.springframework.amqp.rabbit.config.SimpleRabbitListenerContainerFactory;
import org.springframework.amqp.rabbit.connection.CachingConnectionFactory;
import org.springframework.amqp.rabbit.connection.ConnectionFactory;
import org.springframework.amqp.rabbit.core.RabbitTemplate;
import org.springframework.amqp.rabbit.listener.SimpleMessageListenerContainer;
import org.springframework.amqp.support.converter.Jackson2JsonMessageConverter;
import org.springframework.amqp.support.converter.MessageConverter;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.autoconfigure.amqp.RabbitProperties;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.retry.backoff.ExponentialBackOffPolicy;
import org.springframework.retry.support.RetryTemplate;
/**
* The spring AMQP configuration to use a AMQP for communication with SP update
* server.
*
*
*
*/
@Configuration
@EnableConfigurationProperties(AmqpProperties.class)
public class AmqpConfiguration {
private static final Logger LOGGER = LoggerFactory.getLogger(AmqpConfiguration.class);
@Autowired
protected AmqpProperties amqpProperties;
@Autowired
private ConnectionFactory connectionFactory;
@Autowired
private RabbitTemplate rabbitTemplate;
/**
* Create jackson message converter bean.
*
* @return the jackson message converter
* @return {@link RabbitTemplate} with automatic retry, published confirms
* and {@link Jackson2JsonMessageConverter}.
*/
@Bean
public MessageConverter jsonMessageConverter() {
final Jackson2JsonMessageConverter jackson2JsonMessageConverter = new Jackson2JsonMessageConverter();
rabbitTemplate.setMessageConverter(jackson2JsonMessageConverter);
return jackson2JsonMessageConverter;
public RabbitTemplate rabbitTemplate() {
final RabbitTemplate rabbitTemplate = new RabbitTemplate(connectionFactory);
rabbitTemplate.setMessageConverter(new Jackson2JsonMessageConverter());
final RetryTemplate retryTemplate = new RetryTemplate();
retryTemplate.setBackOffPolicy(new ExponentialBackOffPolicy());
rabbitTemplate.setRetryTemplate(retryTemplate);
rabbitTemplate.setConfirmCallback((correlationData, ack, cause) -> {
if (ack) {
LOGGER.debug("Message with correlation ID {} confirmed by broker.", correlationData.getId());
} else {
LOGGER.error("Broker is unable to handle message with correlation ID {} : {}", correlationData.getId(),
cause);
}
});
return rabbitTemplate;
}
@Configuration
protected static class RabbitConnectionFactoryCreator {
/**
* {@link ConnectionFactory} with enabled publisher confirms and
* heartbeat.
*
* @param config
* with standard {@link RabbitProperties}
* @return {@link ConnectionFactory}
*/
@Bean
public ConnectionFactory rabbitConnectionFactory(final RabbitProperties config) {
final CachingConnectionFactory factory = new CachingConnectionFactory();
factory.setRequestedHeartBeat(60);
factory.setPublisherConfirms(true);
final String addresses = config.getAddresses();
factory.setAddresses(addresses);
if (config.getHost() != null) {
factory.setHost(config.getHost());
factory.setPort(config.getPort());
}
if (config.getUsername() != null) {
factory.setUsername(config.getUsername());
}
if (config.getPassword() != null) {
factory.setPassword(config.getPassword());
}
if (config.getVirtualHost() != null) {
factory.setVirtualHost(config.getVirtualHost());
}
return factory;
}
}
/**
@@ -71,8 +122,8 @@ public class AmqpConfiguration {
final Map<String, Object> 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;
}

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -1,135 +0,0 @@
/**
* Copyright (c) 2015 Bosch Software Innovations GmbH and others.
*
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0
* which accompanies this distribution, and is available at
* http://www.eclipse.org/legal/epl-v10.html
*/
package org.eclipse.hawkbit.mgmt.client.scenarios;
import java.util.List;
import org.eclipse.hawkbit.mgmt.client.resource.MgmtDistributionSetClientResource;
import org.eclipse.hawkbit.mgmt.client.resource.MgmtDistributionSetTypeClientResource;
import org.eclipse.hawkbit.mgmt.client.resource.MgmtSoftwareModuleClientResource;
import org.eclipse.hawkbit.mgmt.client.resource.MgmtSoftwareModuleTypeClientResource;
import org.eclipse.hawkbit.mgmt.client.resource.builder.DistributionSetBuilder;
import org.eclipse.hawkbit.mgmt.client.resource.builder.DistributionSetTypeBuilder;
import org.eclipse.hawkbit.mgmt.client.resource.builder.SoftwareModuleAssigmentBuilder;
import org.eclipse.hawkbit.mgmt.client.resource.builder.SoftwareModuleBuilder;
import org.eclipse.hawkbit.mgmt.client.resource.builder.SoftwareModuleTypeBuilder;
import org.eclipse.hawkbit.mgmt.json.model.distributionset.MgmtDistributionSet;
import org.eclipse.hawkbit.mgmt.json.model.softwaremodule.MgmtSoftwareModule;
import org.eclipse.hawkbit.mgmt.json.model.softwaremoduletype.MgmtSoftwareModuleType;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
/**
*
* Default getting started scenario.
*
*/
public class GettingStartedDefaultScenario {
private static final Logger LOGGER = LoggerFactory.getLogger(GettingStartedDefaultScenario.class);
/* known software module type name and key */
private static final String SM_MODULE_TYPE = "gettingstarted";
/* known distribution set type name and key */
private static final String DS_MODULE_TYPE = SM_MODULE_TYPE;
/* known distribution name of this getting started example */
private static final String SM_EXAMPLE_NAME = "gettingstarted-example";
/* known distribution name of this getting started example */
private static final String DS_EXAMPLE_NAME = SM_EXAMPLE_NAME;
@Autowired
private MgmtDistributionSetClientResource distributionSetResource;
@Autowired
private MgmtDistributionSetTypeClientResource distributionSetTypeResource;
@Autowired
private MgmtSoftwareModuleClientResource softwareModuleResource;
@Autowired
private MgmtSoftwareModuleTypeClientResource softwareModuleTypeResource;
/**
* Run the default getting started scenario.
*/
public void run() {
LOGGER.info("Running Getting-Started-Scenario...");
// create one SoftwareModuleTypes
LOGGER.info("Creating software module type {}", SM_MODULE_TYPE);
final List<MgmtSoftwareModuleType> createdSoftwareModuleTypes = softwareModuleTypeResource
.createSoftwareModuleTypes(new SoftwareModuleTypeBuilder().key(SM_MODULE_TYPE).name(SM_MODULE_TYPE)
.maxAssignments(1).build())
.getBody();
// create one DistributionSetType
LOGGER.info("Creating distribution set type {}", DS_MODULE_TYPE);
distributionSetTypeResource.createDistributionSetTypes(new DistributionSetTypeBuilder().key(DS_MODULE_TYPE)
.name(DS_MODULE_TYPE).mandatorymodules(createdSoftwareModuleTypes.get(0).getModuleId()).build());
// create three DistributionSet
final String dsVersion1 = "1.0.0";
final String dsVersion2 = "2.0.0";
final String dsVersion3 = "2.1.0";
LOGGER.info("Creating distribution set {}:{}", DS_EXAMPLE_NAME, dsVersion1);
final List<MgmtDistributionSet> distributionSetsRest1 = distributionSetResource.createDistributionSets(
new DistributionSetBuilder().name(DS_EXAMPLE_NAME).version(dsVersion1).type(DS_MODULE_TYPE).build())
.getBody();
LOGGER.info("Creating distribution set {}:{}", DS_EXAMPLE_NAME, dsVersion2);
final List<MgmtDistributionSet> distributionSetsRest2 = distributionSetResource.createDistributionSets(
new DistributionSetBuilder().name(DS_EXAMPLE_NAME).version(dsVersion2).type(DS_MODULE_TYPE).build())
.getBody();
LOGGER.info("Creating distribution set {}:{}", DS_EXAMPLE_NAME, dsVersion3);
final List<MgmtDistributionSet> distributionSetsRest3 = distributionSetResource.createDistributionSets(
new DistributionSetBuilder().name(DS_EXAMPLE_NAME).version(dsVersion3).type(DS_MODULE_TYPE).build())
.getBody();
// create three SoftwareModules
final String swVersion1 = "1";
final String swVersion2 = "2";
final String swVersion3 = "3";
LOGGER.info("Creating distribution set {}:{}", SM_EXAMPLE_NAME, swVersion1);
final List<MgmtSoftwareModule> softwareModulesRest1 = softwareModuleResource.createSoftwareModules(
new SoftwareModuleBuilder().name(SM_EXAMPLE_NAME).version(swVersion1).type(SM_MODULE_TYPE).build())
.getBody();
LOGGER.info("Creating distribution set {}:{}", SM_EXAMPLE_NAME, swVersion2);
final List<MgmtSoftwareModule> softwareModulesRest2 = softwareModuleResource.createSoftwareModules(
new SoftwareModuleBuilder().name(SM_EXAMPLE_NAME).version(swVersion2).type(SM_MODULE_TYPE).build())
.getBody();
LOGGER.info("Creating distribution set {}:{}", SM_EXAMPLE_NAME, swVersion3);
final List<MgmtSoftwareModule> softwareModulesRest3 = softwareModuleResource.createSoftwareModules(
new SoftwareModuleBuilder().name(SM_EXAMPLE_NAME).version(swVersion3).type(SM_MODULE_TYPE).build())
.getBody();
// Assign SoftwareModules to DistributionSet
LOGGER.info("Assign software module {}:{} to distribution set {}:{}", SM_EXAMPLE_NAME, swVersion1,
DS_EXAMPLE_NAME, dsVersion1);
distributionSetResource.assignSoftwareModules(distributionSetsRest1.get(0).getDsId(),
new SoftwareModuleAssigmentBuilder().id(softwareModulesRest1.get(0).getModuleId()).build());
LOGGER.info("Assign software module {}:{} to distribution set {}:{}", SM_EXAMPLE_NAME, swVersion2,
DS_EXAMPLE_NAME, dsVersion2);
distributionSetResource.assignSoftwareModules(distributionSetsRest2.get(0).getDsId(),
new SoftwareModuleAssigmentBuilder().id(softwareModulesRest2.get(0).getModuleId()).build());
LOGGER.info("Assign software module {}:{} to distribution set {}:{}", SM_EXAMPLE_NAME, swVersion3,
DS_EXAMPLE_NAME, dsVersion3);
distributionSetResource.assignSoftwareModules(distributionSetsRest3.get(0).getDsId(),
new SoftwareModuleAssigmentBuilder().id(softwareModulesRest3.get(0).getModuleId()).build());
}
}

View File

@@ -0,0 +1,108 @@
/**
* Copyright (c) 2015 Bosch Software Innovations GmbH and others.
*
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0
* which accompanies this distribution, and is available at
* http://www.eclipse.org/legal/epl-v10.html
*/
package org.eclipse.hawkbit.mgmt.client.scenarios.upload;
import java.io.ByteArrayInputStream;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.util.Optional;
import org.springframework.util.Assert;
import org.springframework.util.FileCopyUtils;
import org.springframework.web.multipart.MultipartFile;
/**
* Implementation for {@link MultipartFile} for hawkBit artifact upload.
*
*/
public class ArtifactFile implements MultipartFile {
private final String name;
private final String originalFilename;
private final String contentType;
private final byte[] content;
/**
* Create a new ArtifactFile with the given content.
*
* @param name
* the name of the file
* @param content
* the content of the file
*/
public ArtifactFile(final String name, final byte[] content) {
this(name, "", null, content);
}
/**
* Create a new ArtifactFile with the given content.
*
* @param name
* of the file
* @param originalFilename
* the original filename (as on the client's machine)
* @param contentType
* the content type
* @param content
* of the file
*/
public ArtifactFile(final String name, final String originalFilename, final String contentType,
final byte[] content) {
Assert.hasLength(name, "Name must not be null");
this.name = name;
this.originalFilename = Optional.ofNullable(originalFilename).orElse("");
this.contentType = contentType;
this.content = Optional.ofNullable(content).orElse(new byte[0]);
}
@Override
public String getName() {
return this.name;
}
@Override
public String getOriginalFilename() {
return this.originalFilename;
}
@Override
public String getContentType() {
return this.contentType;
}
@Override
public boolean isEmpty() {
return this.content.length == 0;
}
@Override
public long getSize() {
return this.content.length;
}
@Override
public byte[] getBytes() throws IOException {
return this.content;
}
@Override
public InputStream getInputStream() throws IOException {
return new ByteArrayInputStream(this.content);
}
@Override
public void transferTo(final File dest) throws IOException {
FileCopyUtils.copy(this.content, dest);
}
}

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -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
hawkbit.dmf.rabbitmq.receiverQueue=dmf_receiver
hawkbit.dmf.rabbitmq.authenticationReceiverQueue=authentication_receiver

View File

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

View File

@@ -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,6 +21,7 @@ import org.springframework.amqp.core.Binding;
import org.springframework.amqp.core.BindingBuilder;
import org.springframework.amqp.core.FanoutExchange;
import org.springframework.amqp.core.Queue;
import org.springframework.amqp.core.QueueBuilder;
import org.springframework.amqp.rabbit.config.SimpleRabbitListenerContainerFactory;
import org.springframework.amqp.rabbit.connection.CachingConnectionFactory;
import org.springframework.amqp.rabbit.connection.ConnectionFactory;
@@ -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());
}
/**
@@ -236,10 +270,20 @@ public class AmqpConfiguration {
@Bean(name = { "listenerContainerFactory" })
public SimpleRabbitListenerContainerFactory listenerContainerFactory() {
final SimpleRabbitListenerContainerFactory containerFactory = new SimpleRabbitListenerContainerFactory();
containerFactory.setDefaultRequeueRejected(false);
containerFactory.setDefaultRequeueRejected(true);
containerFactory.setConnectionFactory(rabbitConnectionFactory);
containerFactory.setMissingQueuesFatal(amqpProperties.isMissingQueuesFatal());
containerFactory.setConcurrentConsumers(amqpProperties.getInitialConcurrentConsumers());
containerFactory.setMaxConcurrentConsumers(amqpProperties.getMaxConcurrentConsumers());
containerFactory.setPrefetchCount(amqpProperties.getPrefetchCount());
return containerFactory;
}
private static Map<String, Object> getTTLMaxArgsAuthenticationQueue() {
final Map<String, Object> args = new HashMap<>();
args.put("x-message-ttl", Duration.ofSeconds(30).toMillis());
args.put("x-max-length", 1_000);
return args;
}
}

View File

@@ -14,6 +14,7 @@ import java.util.Collections;
import java.util.List;
import java.util.UUID;
import org.apache.commons.lang3.ArrayUtils;
import org.apache.commons.lang3.StringUtils;
import org.eclipse.hawkbit.api.HostnameResolver;
import org.eclipse.hawkbit.artifact.repository.model.DbArtifact;
@@ -35,8 +36,10 @@ import org.eclipse.hawkbit.im.authentication.TenantAwareAuthenticationDetails;
import org.eclipse.hawkbit.repository.ArtifactManagement;
import org.eclipse.hawkbit.repository.ControllerManagement;
import org.eclipse.hawkbit.repository.EntityFactory;
import org.eclipse.hawkbit.repository.RepositoryConstants;
import org.eclipse.hawkbit.repository.eventbus.event.TargetAssignDistributionSetEvent;
import org.eclipse.hawkbit.repository.exception.EntityNotFoundException;
import org.eclipse.hawkbit.repository.exception.TenantNotExistException;
import org.eclipse.hawkbit.repository.model.Action;
import org.eclipse.hawkbit.repository.model.Action.Status;
import org.eclipse.hawkbit.repository.model.ActionStatus;
@@ -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,30 @@ 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 <null> no message is send back to sender
*/
@RabbitListener(queues = "${hawkbit.dmf.rabbitmq.receiverQueue}", containerFactory = "listenerContainerFactory")
public Message onMessage(final Message message, @Header(MessageHeaderKey.TYPE) final String type,
@Header(MessageHeaderKey.TENANT) final String tenant) {
return onMessage(message, type, tenant, getRabbitTemplate().getConnectionFactory().getVirtualHost());
}
@RabbitListener(queues = "${hawkbit.dmf.rabbitmq.authenticationReceiverQueue}", containerFactory = "listenerContainerFactory")
public Message onAuthenticationRequest(final Message message,
@Header(MessageHeaderKey.TENANT) final String tenant) {
return onAuthenticationRequest(message);
}
public Message onMessage(final Message message, final String type, final String tenant, final String virtualHost) {
checkContentTypeJson(message);
final SecurityContext oldContext = SecurityContextHolder.getContext();
@@ -148,17 +159,34 @@ 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);
}
return null;
}
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);
}
}
private Message handleAuthentifiactionMessage(final Message message) {
final DownloadResponse authentificationResponse = new DownloadResponse();
final MessageProperties messageProperties = message.getMessageProperties();
@@ -308,9 +336,10 @@ public class AmqpMessageHandlerService extends BaseAmqpService {
final DistributionSet distributionSet = action.getDistributionSet();
final List<SoftwareModule> 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 +369,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 +407,21 @@ public class AmqpMessageHandlerService extends BaseAmqpService {
}
}
private ActionStatus createActionStatus(final Message message, final ActionUpdateStatus actionUpdateStatus,
final Action action) {
final ActionStatus actionStatus = entityFactory.generateActionStatus();
actionUpdateStatus.getMessage().forEach(actionStatus::addMessage);
if (ArrayUtils.isNotEmpty(message.getMessageProperties().getCorrelationId())) {
actionStatus.addMessage(RepositoryConstants.SERVER_MESSAGE_PREFIX + "DMF message correlation-id "
+ message.getMessageProperties().getCorrelationId());
}
actionStatus.setAction(action);
actionStatus.setOccurredAt(System.currentTimeMillis());
return actionStatus;
}
private Action getUpdateActionStatus(final ActionStatus actionStatus) {
if (actionStatus.getStatus().equals(Status.CANCELED)) {
return controllerManagement.addCancelActionStatus(actionStatus);
@@ -421,7 +461,7 @@ public class AmqpMessageHandlerService extends BaseAmqpService {
}
}
private void checkContentTypeJson(final Message message) {
private static void checkContentTypeJson(final Message message) {
final MessageProperties messageProperties = message.getMessageProperties();
if (messageProperties.getContentType() != null && messageProperties.getContentType().contains("json")) {
return;
@@ -457,4 +497,7 @@ public class AmqpMessageHandlerService extends BaseAmqpService {
this.entityFactory = entityFactory;
}
void setSystemSecurityContext(final SystemSecurityContext systemSecurityContext) {
this.systemSecurityContext = systemSecurityContext;
}
}

View File

@@ -31,20 +31,75 @@ 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;
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 +161,6 @@ public class AmqpProperties {
return receiverQueue;
}
public void setReceiverQueue(final String receiverQueue) {
this.receiverQueue = receiverQueue;
}
public int getRequestedHeartBeat() {
return requestedHeartBeat;
}
@@ -118,4 +169,8 @@ public class AmqpProperties {
this.requestedHeartBeat = requestedHeartBeat;
}
public void setReceiverQueue(final String receiverQueue) {
this.receiverQueue = receiverQueue;
}
}

View File

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

View File

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

View File

@@ -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<Runnable> blockingQueue = new ArrayBlockingQueue<>(10);
final ThreadPoolExecutor threadPoolExecutor = new ThreadPoolExecutor(1, 10, 1000, TimeUnit.MILLISECONDS,
blockingQueue, new ThreadFactoryBuilder().setNameFormat("central-executor-pool-%d").build());

View File

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

View File

@@ -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);
}
@@ -170,7 +176,7 @@ public class AmqpMessageHandlerServiceTest {
try {
amqpMessageHandlerService.onMessage(message, MessageType.THING_CREATED.name(), TENANT, "vHost");
fail("IllegalArgumentException was excepeted since no replyTo header was set");
} catch (final IllegalArgumentException exception) {
} catch (final AmqpRejectAndDontRequeueException exception) {
// test ok - exception was excepted
}
@@ -184,7 +190,7 @@ public class AmqpMessageHandlerServiceTest {
try {
amqpMessageHandlerService.onMessage(message, MessageType.THING_CREATED.name(), TENANT, "vHost");
fail("IllegalArgumentException was excepeted since no thingID was set");
} catch (final IllegalArgumentException exception) {
} catch (final AmqpRejectAndDontRequeueException exception) {
// test ok - exception was excepted
}
}
@@ -200,7 +206,7 @@ public class AmqpMessageHandlerServiceTest {
try {
amqpMessageHandlerService.onMessage(message, type, TENANT, "vHost");
fail("IllegalArgumentException was excepeted due to unknown message type");
} catch (final IllegalArgumentException exception) {
} catch (final AmqpRejectAndDontRequeueException exception) {
// test ok - exception was excepted
}
}
@@ -213,21 +219,21 @@ public class AmqpMessageHandlerServiceTest {
try {
amqpMessageHandlerService.onMessage(message, MessageType.EVENT.name(), TENANT, "vHost");
fail("IllegalArgumentException was excepeted due to unknown message type");
} catch (final IllegalArgumentException e) {
} catch (final AmqpRejectAndDontRequeueException e) {
}
try {
messageProperties.setHeader(MessageHeaderKey.TOPIC, "wrongTopic");
amqpMessageHandlerService.onMessage(message, MessageType.EVENT.name(), TENANT, "vHost");
fail("IllegalArgumentException was excepeted due to unknown topic");
} catch (final IllegalArgumentException e) {
} catch (final AmqpRejectAndDontRequeueException e) {
}
messageProperties.setHeader(MessageHeaderKey.TOPIC, EventTopic.CANCEL_DOWNLOAD.name());
try {
amqpMessageHandlerService.onMessage(message, MessageType.EVENT.name(), TENANT, "vHost");
fail("IllegalArgumentException was excepeted because there was no event topic");
} catch (final IllegalArgumentException exception) {
} catch (final AmqpRejectAndDontRequeueException exception) {
// test ok - exception was excepted
}
@@ -246,7 +252,7 @@ public class AmqpMessageHandlerServiceTest {
try {
amqpMessageHandlerService.onMessage(message, MessageType.EVENT.name(), TENANT, "vHost");
fail("IllegalArgumentException was excepeted since no action id was set");
} catch (final IllegalArgumentException exception) {
} catch (final AmqpRejectAndDontRequeueException exception) {
// test ok - exception was excepted
}
}
@@ -263,7 +269,7 @@ public class AmqpMessageHandlerServiceTest {
try {
amqpMessageHandlerService.onMessage(message, MessageType.EVENT.name(), TENANT, "vHost");
fail("IllegalArgumentException was excepeted since no action id was set");
} catch (final IllegalArgumentException exception) {
} catch (final AmqpRejectAndDontRequeueException exception) {
// test ok - exception was excepted
}
@@ -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);

View File

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

View File

@@ -18,6 +18,8 @@ public final class AmqpSettings {
public static final String DMF_EXCHANGE = "dmf.exchange";
public static final String AUTHENTICATION_EXCHANGE = "authentication.exchange";
private AmqpSettings() {
}

View File

@@ -26,9 +26,4 @@ public enum MessageType {
*/
THING_CREATED,
/**
* The authentication type.
*/
AUTHENTIFICATION,
}

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -0,0 +1,37 @@
/**
* Copyright (c) 2015 Bosch Software Innovations GmbH and others.
*
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0
* which accompanies this distribution, and is available at
* http://www.eclipse.org/legal/epl-v10.html
*/
package org.eclipse.hawkbit.repository;
import org.eclipse.hawkbit.repository.model.ActionStatus;
import org.springframework.boot.context.properties.ConfigurationProperties;
/**
* Configuration properties for the repository.
*
*/
@ConfigurationProperties("hawkbit.server.repository")
public class RepositoryProperties {
/**
* Set to <code>true</code> if the repository has to reject
* {@link ActionStatus} entries for actions that are closed. Note: if this
* is enforced you have to make sure that the feedback channel from the
* devices i in order.
*/
private boolean rejectActionStatusForClosedAction = false;
public boolean isRejectActionStatusForClosedAction() {
return rejectActionStatusForClosedAction;
}
public void setRejectActionStatusForClosedAction(final boolean rejectActionStatusForClosedAction) {
this.rejectActionStatusForClosedAction = rejectActionStatusForClosedAction;
}
}

View File

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

View File

@@ -0,0 +1,42 @@
/**
* Copyright (c) 2015 Bosch Software Innovations GmbH and others.
*
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0
* which accompanies this distribution, and is available at
* http://www.eclipse.org/legal/epl-v10.html
*/
package org.eclipse.hawkbit.repository.exception;
import org.eclipse.hawkbit.exception.SpServerError;
import org.eclipse.hawkbit.exception.SpServerRtException;
/**
* Exception which is thrown when trying to set an invalid target address.
*/
public class InvalidTargetAddressException extends SpServerRtException {
/**
*
*/
private static final long serialVersionUID = 1L;
/**
* @param message
* the message for this exception
*/
public InvalidTargetAddressException(final String message) {
super(message, SpServerError.SP_REPO_INVALID_TARGET_ADDRESS);
}
/**
*
* @param message
* the message for this exception
* @param cause
* the cause for this exception
*/
public InvalidTargetAddressException(final String message, final Throwable cause) {
super(message, SpServerError.SP_REPO_INVALID_TARGET_ADDRESS, cause);
}
}

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -13,9 +13,10 @@ import java.util.Map;
import javax.servlet.http.HttpServletRequest;
import org.apache.tomcat.util.http.fileupload.FileUploadBase.FileSizeLimitExceededException;
import org.apache.tomcat.util.http.fileupload.FileUploadException;
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;
@@ -28,10 +29,6 @@ import org.springframework.web.multipart.MultipartException;
/**
* General controller advice for exception handling.
*
*
*
*
*/
@ControllerAdvice
public class ResponseExceptionHandler {
@@ -67,6 +64,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 +86,11 @@ public class ResponseExceptionHandler {
@ExceptionHandler(SpServerRtException.class)
public ResponseEntity<ExceptionInfo> 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 +112,8 @@ public class ResponseExceptionHandler {
@ExceptionHandler(HttpMessageNotReadableException.class)
public ResponseEntity<ExceptionInfo> 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 +130,48 @@ public class ResponseExceptionHandler {
* as entity.
*/
@ExceptionHandler(MultipartException.class)
public ResponseEntity<ExceptionInfo> handleFileLimitExceededException(final HttpServletRequest request,
public ResponseEntity<ExceptionInfo> handleMultipartException(final HttpServletRequest request,
final Exception ex) {
LOG.debug("Handling exception {} of request {}", ex.getClass().getName(), request.getRequestURL());
final ExceptionInfo response = new ExceptionInfo();
logRequest(request, ex);
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());
Throwable responseCause = ex;
final Throwable searchForCause = searchForCause(ex, FileUploadException.class);
if (searchForCause != null) {
responseCause = searchForCause;
}
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 static Throwable searchForCause(final Throwable t, final Class<?> lookFor) {
if (t == null || t.getCause() == null) {
return null;
}
return false;
final Throwable cause = t.getCause();
if (cause.getClass().equals(lookFor)) {
return cause;
}
return searchForCause(cause, lookFor);
}
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 response;
}
}

View File

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

View File

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

View File

@@ -396,6 +396,12 @@ public final class SpPermission {
public static final String HAS_AUTH_TENANT_CONFIGURATION = HAS_AUTH_PREFIX + TENANT_CONFIGURATION
+ HAS_AUTH_SUFFIX;
/**
* Spring security eval hasAuthority expression to check if spring
* context contains {@link SpPermission#SYSTEM_MONITOR}
*/
public static final String HAS_AUTH_SYSTEM_MONITOR = HAS_AUTH_PREFIX + SYSTEM_MONITOR + HAS_AUTH_SUFFIX;
private SpringEvalExpressions() {
// utility class
}

View File

@@ -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<String> TENANT_THREAD_LOCAL = new ThreadLocal<>();
private static final ThreadLocal<AtomicInteger> 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> T runAsTenant(final String tenant, final TenantRunner<T> 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<? extends GrantedAuthority> 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);
}
}
}

View File

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

View File

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

View File

@@ -36,7 +36,8 @@ public final class PermissionTest {
final Collection<String> allAuthorities = SpPermission.getAllAuthorities();
final List<GrantedAuthority> 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);

View File

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

View File

@@ -210,4 +210,5 @@ public class SoftwareModuleTable extends AbstractNamedVersionTable<SoftwareModul
protected void setDataAvailable(final boolean available) {
artifactUploadState.setNoDataAvilableSoftwareModule(!available);
}
}

View File

@@ -17,10 +17,7 @@ import com.vaadin.spring.annotation.SpringComponent;
import com.vaadin.spring.annotation.ViewScope;
/**
* Software module table layout.
*
*
*
* Software module table layout. (Upload Management)
*/
@SpringComponent
@ViewScope
@@ -44,4 +41,5 @@ public class SoftwareModuleTableLayout extends AbstractTableLayout {
void init() {
super.init(smTableHeader, smTable, softwareModuleDetails);
}
}

View File

@@ -34,10 +34,6 @@ import com.vaadin.ui.themes.ValoTheme;
/**
* Parent class for filter button layout.
*
*
*
*
*/
public abstract class AbstractFilterButtons extends Table {

View File

@@ -28,6 +28,16 @@ public abstract class AbstractNamedVersionTable<E extends NamedVersionedEntity,
private static final long serialVersionUID = 780050712209750719L;
/**
* Initialize the component.
*/
@Override
protected void init() {
super.init();
setMultiSelect(true);
setSelectable(true);
}
@Override
protected List<TableColumn> getTableVisibleColumns() {
final List<TableColumn> columnList = super.getTableVisibleColumns();

View File

@@ -153,6 +153,16 @@ public abstract class AbstractTable<E extends NamedEntity, I> extends Table {
}
}
/**
* Select all rows in the table.
*/
protected void selectAll() {
if (isMultiSelect()) {
// only contains the ItemIds of the visible items in the table
setValue(getItemIds());
}
}
private void setColumnProperties() {
final List<TableColumn> columnList = getTableVisibleColumns();
final List<Object> swColumnIds = new ArrayList<>();

View File

@@ -36,10 +36,6 @@ import com.vaadin.ui.VerticalLayout;
/**
* Parent class for table header.
*
*
*
*
*/
public abstract class AbstractTableHeader extends VerticalLayout {

View File

@@ -9,8 +9,11 @@
package org.eclipse.hawkbit.ui.common.table;
import org.eclipse.hawkbit.ui.common.detailslayout.AbstractTableDetailsLayout;
import org.eclipse.hawkbit.ui.utils.ShortCutModifierUtils;
import com.vaadin.event.Action;
import com.vaadin.event.Action.Handler;
import com.vaadin.event.ShortcutAction;
import com.vaadin.ui.Alignment;
import com.vaadin.ui.Panel;
import com.vaadin.ui.VerticalLayout;
@@ -18,23 +21,19 @@ import com.vaadin.ui.themes.ValoTheme;
/**
* Parent class for table layout.
*
*
*
*
*/
public abstract class AbstractTableLayout extends VerticalLayout {
private static final long serialVersionUID = 8611248179949245460L;
private static final long serialVersionUID = 1L;
private AbstractTableHeader tableHeader;
private AbstractTable table;
private AbstractTable<?, ?> table;
private AbstractTableDetailsLayout detailsLayout;
private AbstractTableDetailsLayout<?> detailsLayout;
protected void init(final AbstractTableHeader tableHeader, final AbstractTable table,
final AbstractTableDetailsLayout detailsLayout) {
protected void init(final AbstractTableHeader tableHeader, final AbstractTable<?, ?> table,
final AbstractTableDetailsLayout<?> detailsLayout) {
this.tableHeader = tableHeader;
this.table = table;
this.detailsLayout = detailsLayout;
@@ -58,33 +57,33 @@ public abstract class AbstractTableLayout extends VerticalLayout {
if (isShortCutKeysRequired()) {
final Panel tablePanel = new Panel();
tablePanel.setStyleName("table-panel");
tablePanel.setHeight(100.0f, Unit.PERCENTAGE);
tablePanel.setHeight(100.0F, Unit.PERCENTAGE);
tablePanel.setContent(table);
tablePanel.addActionHandler(getShortCutKeysHandler());
tablePanel.addStyleName(ValoTheme.PANEL_BORDERLESS);
tableHeaderLayout.addComponent(tablePanel);
tableHeaderLayout.setComponentAlignment(tablePanel, Alignment.TOP_CENTER);
tableHeaderLayout.setExpandRatio(tablePanel, 1.0f);
tableHeaderLayout.setExpandRatio(tablePanel, 1.0F);
} else {
tableHeaderLayout.addComponent(table);
tableHeaderLayout.setComponentAlignment(table, Alignment.TOP_CENTER);
tableHeaderLayout.setExpandRatio(table, 1.0f);
tableHeaderLayout.setExpandRatio(table, 1.0F);
}
addComponent(tableHeaderLayout);
addComponent(detailsLayout);
setComponentAlignment(tableHeaderLayout, Alignment.TOP_CENTER);
setComponentAlignment(detailsLayout, Alignment.TOP_CENTER);
setExpandRatio(tableHeaderLayout, 1.0f);
setExpandRatio(tableHeaderLayout, 1.0F);
}
/**
* If any short cut keys required on the table.
*
* @return true if required else false. Default is 'false'.
* @return true if required else false. Default is 'true'.
*/
protected boolean isShortCutKeysRequired() {
return false;
return true;
}
/**
@@ -94,11 +93,38 @@ public abstract class AbstractTableLayout extends VerticalLayout {
* Default is null.
*/
protected Handler getShortCutKeysHandler() {
return null;
return new TableShortCutHandler();
}
protected void publishEvent() {
// can be override by subclasses
}
public void setShowFilterButtonVisible(final boolean visible) {
tableHeader.setFilterButtonsIconVisible(visible);
}
private class TableShortCutHandler implements Handler {
private static final String SELECT_ALL_TEXT = "Select All";
private final ShortcutAction selectAllAction = new ShortcutAction(SELECT_ALL_TEXT, ShortcutAction.KeyCode.A,
new int[] { ShortCutModifierUtils.getCtrlOrMetaModifier() });
private static final long serialVersionUID = 1L;
@Override
public void handleAction(final Action action, final Object sender, final Object target) {
if (!selectAllAction.equals(action)) {
return;
}
table.selectAll();
publishEvent();
}
@Override
public Action[] getActions(final Object target, final Object sender) {
return new Action[] { selectAllAction };
}
}
}

View File

@@ -31,9 +31,6 @@ import com.vaadin.spring.annotation.ViewScope;
/**
* Distribution Set Type filter buttons.
*
*
*
*/
@SpringComponent
@ViewScope

View File

@@ -65,7 +65,6 @@ import com.vaadin.ui.UI;
/**
* Distribution set table.
*
*/
@SpringComponent
@ViewScope
@@ -468,7 +467,6 @@ public class DistributionSetTable extends AbstractNamedVersionTable<Distribution
@Override
protected void setDataAvailable(final boolean available) {
manageDistUIState.setNoDataAvailableDist(!available);
}
}

View File

@@ -17,16 +17,14 @@ import com.vaadin.spring.annotation.SpringComponent;
import com.vaadin.spring.annotation.ViewScope;
/**
* Software module table layout.
*
*
*
* DistributionSet table layout
*/
@SpringComponent
@ViewScope
public class DistributionSetTableLayout extends AbstractTableLayout {
private static final long serialVersionUID = 6464291374980641235L;
/**
* Details to be autowired before table as details listens to value change
* of table.

Some files were not shown because too many files have changed in this diff Show More