Merge pull request #161 from bsinno/feature_add_targettoken_to_dmf

Add target security token to DMF update message
This commit is contained in:
Michael Hirsch
2016-05-09 13:07:51 +02:00
33 changed files with 618 additions and 289 deletions

View File

@@ -1,4 +1,3 @@
<?xml version="1.0"?>
<!-- <!--
Copyright (c) 2015 Bosch Software Innovations GmbH and others. Copyright (c) 2015 Bosch Software Innovations GmbH and others.
@@ -9,7 +8,8 @@
http://www.eclipse.org/legal/epl-v10.html http://www.eclipse.org/legal/epl-v10.html
--> -->
<project xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd" <project
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"
xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"> xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
<modelVersion>4.0.0</modelVersion> <modelVersion>4.0.0</modelVersion>
<parent> <parent>
@@ -133,6 +133,10 @@
<artifactId>spring-boot-configuration-processor</artifactId> <artifactId>spring-boot-configuration-processor</artifactId>
<optional>true</optional> <optional>true</optional>
</dependency> </dependency>
<dependency>
<groupId>org.apache.httpcomponents</groupId>
<artifactId>httpclient</artifactId>
</dependency>
</dependencies> </dependencies>
<dependencyManagement> <dependencyManagement>
<dependencies> <dependencies>

View File

@@ -8,6 +8,8 @@
*/ */
package org.eclipse.hawkbit.simulator; package org.eclipse.hawkbit.simulator;
import org.eclipse.hawkbit.simulator.UpdateStatus.ResponseStatus;
/** /**
* The bean of a simulated device which can be stored in the * The bean of a simulated device which can be stored in the
* {@link DeviceSimulatorRepository} or shown in the UI. * {@link DeviceSimulatorRepository} or shown in the UI.
@@ -22,16 +24,15 @@ public abstract class AbstractSimulatedDevice {
private Status status; private Status status;
private double progress; private double progress;
private String swversion = "unknown"; private String swversion = "unknown";
private ResponseStatus responseStatus = ResponseStatus.SUCCESSFUL; private UpdateStatus updateStatus = new UpdateStatus(ResponseStatus.SUCCESSFUL, "Simulation complete!");
private Protocol protocol = Protocol.DMF_AMQP; private Protocol protocol = Protocol.DMF_AMQP;
private String targetSecurityToken;
private int nextPollCounterSec; private int nextPollCounterSec;
/** /**
* Enum definition of the protocol to be used for the simulated device. * Enum definition of the protocol to be used for the simulated device.
* *
* @author Michael Hirsch
*
*/ */
public enum Protocol { public enum Protocol {
/** /**
@@ -69,24 +70,6 @@ public abstract class AbstractSimulatedDevice {
ERROR; ERROR;
} }
/**
* The status to response to the hawkbit update server if an simulated
* update process should be respond with successful or failure update.
*
* @author Michael Hirsch
*
*/
public enum ResponseStatus {
/**
* updated has been successful and response the successful update.
*/
SUCCESSFUL,
/**
* updated has been not successful and response the error update.
*/
ERROR;
}
/** /**
* empty constructor. * empty constructor.
*/ */
@@ -158,12 +141,12 @@ public abstract class AbstractSimulatedDevice {
this.swversion = swversion; this.swversion = swversion;
} }
public ResponseStatus getResponseStatus() { public UpdateStatus getUpdateStatus() {
return responseStatus; return updateStatus;
} }
public void setResponseStatus(final ResponseStatus responseStatus) { public void setUpdateStatus(final UpdateStatus updateStatus) {
this.responseStatus = responseStatus; this.updateStatus = updateStatus;
} }
public Protocol getProtocol() { public Protocol getProtocol() {
@@ -177,4 +160,13 @@ public abstract class AbstractSimulatedDevice {
public void setNextPollCounterSec(final int nextPollDelayInSec) { public void setNextPollCounterSec(final int nextPollDelayInSec) {
this.nextPollCounterSec = nextPollDelayInSec; this.nextPollCounterSec = nextPollDelayInSec;
} }
public String getTargetSecurityToken() {
return targetSecurityToken;
}
public void setTargetSecurityToken(final String targetSecurityToken) {
this.targetSecurityToken = targetSecurityToken;
}
} }

View File

@@ -8,8 +8,6 @@
*/ */
package org.eclipse.hawkbit.simulator; package org.eclipse.hawkbit.simulator;
import java.util.concurrent.ScheduledExecutorService;
import org.eclipse.hawkbit.simulator.http.ControllerResource; import org.eclipse.hawkbit.simulator.http.ControllerResource;
import org.slf4j.Logger; import org.slf4j.Logger;
import org.slf4j.LoggerFactory; import org.slf4j.LoggerFactory;
@@ -18,7 +16,7 @@ import com.jayway.jsonpath.JsonPath;
import com.jayway.jsonpath.PathNotFoundException; import com.jayway.jsonpath.PathNotFoundException;
/** /**
* @author Michael Hirsch * A simulated device using the DDI API of the hawkBit update server.
* *
*/ */
public class DDISimulatedDevice extends AbstractSimulatedDevice { public class DDISimulatedDevice extends AbstractSimulatedDevice {
@@ -26,12 +24,12 @@ public class DDISimulatedDevice extends AbstractSimulatedDevice {
private static final Logger LOGGER = LoggerFactory.getLogger(DDISimulatedDevice.class); private static final Logger LOGGER = LoggerFactory.getLogger(DDISimulatedDevice.class);
private final int pollDelaySec; private final int pollDelaySec;
private final ScheduledExecutorService pollthreadpool;
private final ControllerResource controllerResource; private final ControllerResource controllerResource;
private final DeviceSimulatorUpdater deviceUpdater;
private volatile boolean removed; private volatile boolean removed;
private volatile Long currentActionId; private volatile Long currentActionId;
private final DeviceSimulatorUpdater deviceUpdater;
/** /**
* @param id * @param id
@@ -42,18 +40,14 @@ public class DDISimulatedDevice extends AbstractSimulatedDevice {
* the delay of the poll interval in sec * the delay of the poll interval in sec
* @param controllerResource * @param controllerResource
* the http controller resource * the http controller resource
* @param pollthreadpool
* the threadpool for polling endpoint
* @param deviceUpdater * @param deviceUpdater
* the service to update devices * the service to update devices
*/ */
public DDISimulatedDevice(final String id, final String tenant, final int pollDelaySec, public DDISimulatedDevice(final String id, final String tenant, final int pollDelaySec,
final ControllerResource controllerResource, final ScheduledExecutorService pollthreadpool, final ControllerResource controllerResource, final DeviceSimulatorUpdater deviceUpdater) {
final DeviceSimulatorUpdater deviceUpdater) {
super(id, tenant, Protocol.DDI_HTTP); super(id, tenant, Protocol.DDI_HTTP);
this.pollDelaySec = pollDelaySec; this.pollDelaySec = pollDelaySec;
this.controllerResource = controllerResource; this.controllerResource = controllerResource;
this.pollthreadpool = pollthreadpool;
this.deviceUpdater = deviceUpdater; this.deviceUpdater = deviceUpdater;
setNextPollCounterSec(pollDelaySec); setNextPollCounterSec(pollDelaySec);
} }
@@ -76,27 +70,12 @@ public class DDISimulatedDevice extends AbstractSimulatedDevice {
final String basePollJson = controllerResource.get(getTenant(), getId()); final String basePollJson = controllerResource.get(getTenant(), getId());
try { try {
final String href = JsonPath.parse(basePollJson).read("_links.deploymentBase.href"); final String href = JsonPath.parse(basePollJson).read("_links.deploymentBase.href");
final long actionId = Long.parseLong(href.substring(href.lastIndexOf("/") + 1, href.indexOf("?"))); final long actionId = Long.parseLong(href.substring(href.lastIndexOf('/') + 1, href.indexOf('?')));
if (currentActionId == null) { if (currentActionId == null) {
final String deploymentJson = controllerResource.getDeployment(getTenant(), getId(), actionId); final String deploymentJson = controllerResource.getDeployment(getTenant(), getId(), actionId);
final String swVersion = JsonPath.parse(deploymentJson).read("deployment.chunks[0].version"); final String swVersion = JsonPath.parse(deploymentJson).read("deployment.chunks[0].version");
currentActionId = actionId; currentActionId = actionId;
deviceUpdater.startUpdate(getTenant(), getId(), actionId, swVersion, (device, actionId1) -> { startDdiUpdate(actionId, swVersion);
switch (device.getResponseStatus()) {
case SUCCESSFUL:
controllerResource.postSuccessFeedback(getTenant(), getId(),
actionId1);
break;
case ERROR:
controllerResource.postErrorFeedback(getTenant(), getId(),
actionId1);
break;
default:
throw new IllegalStateException(
"simulated device has an unknown response status + " + device.getResponseStatus());
}
currentActionId = null;
});
} }
} catch (final PathNotFoundException e) { } catch (final PathNotFoundException e) {
// href might not be in the json response, so ignore // href might not be in the json response, so ignore
@@ -106,4 +85,21 @@ public class DDISimulatedDevice extends AbstractSimulatedDevice {
} }
} }
private void startDdiUpdate(final long actionId, final String swVersion) {
deviceUpdater.startUpdate(getTenant(), getId(), actionId, swVersion, null, null, (device, actionId1) -> {
switch (device.getUpdateStatus().getResponseStatus()) {
case SUCCESSFUL:
controllerResource.postSuccessFeedback(getTenant(), getId(), actionId1);
break;
case ERROR:
controllerResource.postErrorFeedback(getTenant(), getId(), actionId1);
break;
default:
throw new IllegalStateException("simulated device has an unknown response status + "
+ device.getUpdateStatus().getResponseStatus());
}
currentActionId = null;
});
}
} }

View File

@@ -9,10 +9,7 @@
package org.eclipse.hawkbit.simulator; package org.eclipse.hawkbit.simulator;
/** /**
* An simulated device using the DMF API of the hawkbit update server. * A simulated device using the DMF API of the hawkBit update server.
*
* @author Michael Hirsch
*
*/ */
public class DMFSimulatedDevice extends AbstractSimulatedDevice { public class DMFSimulatedDevice extends AbstractSimulatedDevice {

View File

@@ -8,27 +8,52 @@
*/ */
package org.eclipse.hawkbit.simulator; package org.eclipse.hawkbit.simulator;
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.security.DigestOutputStream;
import java.security.KeyManagementException;
import java.security.KeyStoreException;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.security.SecureRandom; import java.security.SecureRandom;
import java.util.ArrayList;
import java.util.List;
import java.util.Random; import java.util.Random;
import java.util.concurrent.Executors; import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledExecutorService; import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.TimeUnit; import java.util.concurrent.TimeUnit;
import java.util.stream.Collectors;
import org.apache.http.client.methods.CloseableHttpResponse;
import org.apache.http.client.methods.HttpGet;
import org.apache.http.conn.ssl.SSLConnectionSocketFactory;
import org.apache.http.conn.ssl.SSLContextBuilder;
import org.apache.http.impl.client.CloseableHttpClient;
import org.apache.http.impl.client.HttpClients;
import org.eclipse.hawkbit.dmf.json.model.Artifact;
import org.eclipse.hawkbit.dmf.json.model.SoftwareModule;
import org.eclipse.hawkbit.simulator.AbstractSimulatedDevice.Protocol; import org.eclipse.hawkbit.simulator.AbstractSimulatedDevice.Protocol;
import org.eclipse.hawkbit.simulator.UpdateStatus.ResponseStatus;
import org.eclipse.hawkbit.simulator.amqp.SpSenderService; import org.eclipse.hawkbit.simulator.amqp.SpSenderService;
import org.eclipse.hawkbit.simulator.event.InitUpdate; import org.eclipse.hawkbit.simulator.event.InitUpdate;
import org.eclipse.hawkbit.simulator.event.ProgressUpdate; import org.eclipse.hawkbit.simulator.event.ProgressUpdate;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service; import org.springframework.stereotype.Service;
import com.google.common.eventbus.EventBus; import com.google.common.eventbus.EventBus;
import com.google.common.io.BaseEncoding;
import com.google.common.io.ByteStreams;
/** /**
* @author Michael Hirsch * Update simulation handler.
* *
*/ */
@Service @Service
public class DeviceSimulatorUpdater { public class DeviceSimulatorUpdater {
private static final Logger LOGGER = LoggerFactory.getLogger(DeviceSimulatorUpdater.class);
private static final ScheduledExecutorService threadPool = Executors.newScheduledThreadPool(4); private static final ScheduledExecutorService threadPool = Executors.newScheduledThreadPool(4);
@@ -54,14 +79,18 @@ public class DeviceSimulatorUpdater {
* @param actionId * @param actionId
* the actionId from the hawkbit update server to start the * the actionId from the hawkbit update server to start the
* update. * update.
* @param swVersion * @param modules
* the software module version from the hawkbit update server * the software module version from the hawkbit update server
* @param swVersion
* the software version as static value in case modules is null
* @param targetSecurityToken
* the target security token for download authentication
* @param callback * @param callback
* the callback which gets called when the simulated update * the callback which gets called when the simulated update
* process has been finished * process has been finished
*/ */
public void startUpdate(final String tenant, final String id, final long actionId, final String swVersion, public void startUpdate(final String tenant, final String id, final long actionId, final String swVersion,
final UpdaterCallback callback) { final List<SoftwareModule> modules, final String targetSecurityToken, final UpdaterCallback callback) {
AbstractSimulatedDevice device = repository.get(tenant, id); AbstractSimulatedDevice device = repository.get(tenant, id);
// plug and play - non existing device will be auto created // plug and play - non existing device will be auto created
@@ -70,11 +99,18 @@ public class DeviceSimulatorUpdater {
} }
device.setProgress(0.0); device.setProgress(0.0);
if (modules == null || modules.isEmpty()) {
device.setSwversion(swVersion); device.setSwversion(swVersion);
} else {
device.setSwversion(modules.stream().map(sm -> sm.getModuleVersion()).collect(Collectors.joining(", ")));
}
device.setTargetSecurityToken(targetSecurityToken);
eventbus.post(new InitUpdate(device)); eventbus.post(new InitUpdate(device));
threadPool.schedule(new DeviceSimulatorUpdateThread(device, spSenderService, actionId, eventbus, callback), threadPool.schedule(
2_000, TimeUnit.MILLISECONDS); new DeviceSimulatorUpdateThread(device, spSenderService, actionId, eventbus, callback, modules), 2_000,
TimeUnit.MILLISECONDS);
} }
private static final class DeviceSimulatorUpdateThread implements Runnable { private static final class DeviceSimulatorUpdateThread implements Runnable {
@@ -85,38 +121,139 @@ public class DeviceSimulatorUpdater {
private final long actionId; private final long actionId;
private final EventBus eventbus; private final EventBus eventbus;
private final UpdaterCallback callback; private final UpdaterCallback callback;
private final List<SoftwareModule> modules;
private DeviceSimulatorUpdateThread(final AbstractSimulatedDevice device, final SpSenderService spSenderService, private DeviceSimulatorUpdateThread(final AbstractSimulatedDevice device, final SpSenderService spSenderService,
final long actionId, final EventBus eventbus, final UpdaterCallback callback) { final long actionId, final EventBus eventbus, final UpdaterCallback callback,
final List<SoftwareModule> modules) {
this.device = device; this.device = device;
this.spSenderService = spSenderService; this.spSenderService = spSenderService;
this.actionId = actionId; this.actionId = actionId;
this.eventbus = eventbus; this.eventbus = eventbus;
this.callback = callback; this.callback = callback;
this.modules = modules;
} }
@Override @Override
public void run() { public void run() {
if (device.getProgress() <= 0 && modules != null) {
device.setUpdateStatus(simulateDownloads(device.getTargetSecurityToken()));
if (isErrorResponse(device.getUpdateStatus())) {
callback.updateFinished(device, actionId);
eventbus.post(new ProgressUpdate(device));
return;
}
}
final double newProgress = device.getProgress() + 0.2; final double newProgress = device.getProgress() + 0.2;
device.setProgress(newProgress); device.setProgress(newProgress);
if (newProgress < 1.0) { if (newProgress < 1.0) {
threadPool.schedule( threadPool.schedule(
new DeviceSimulatorUpdateThread(device, spSenderService, actionId, eventbus, callback), new DeviceSimulatorUpdateThread(device, spSenderService, actionId, eventbus, callback, modules),
rndSleep.nextInt(5_000), TimeUnit.MILLISECONDS); rndSleep.nextInt(5_000), TimeUnit.MILLISECONDS);
} else { } else {
callback.updateFinished(device, actionId); callback.updateFinished(device, actionId);
} }
eventbus.post(new ProgressUpdate(device)); eventbus.post(new ProgressUpdate(device));
} }
private UpdateStatus simulateDownloads(final String targetToken) {
final List<UpdateStatus> status = new ArrayList<>();
LOGGER.info("Simulate downloads for {}", device.getId());
modules.forEach(module -> module.getArtifacts()
.forEach(artifact -> handleArtifacts(targetToken, status, artifact)));
final UpdateStatus result = new UpdateStatus(ResponseStatus.SUCCESSFUL);
result.getStatusMessages().add("Simulation complete!");
status.forEach(download -> {
result.getStatusMessages().addAll(download.getStatusMessages());
if (isErrorResponse(download)) {
result.setResponseStatus(ResponseStatus.ERROR);
}
});
LOGGER.info("Download simulations complete for {}", device.getId());
return result;
}
private boolean isErrorResponse(final UpdateStatus status) {
if (status == null) {
return false;
}
return ResponseStatus.ERROR.equals(status.getResponseStatus());
}
private static void handleArtifacts(final String targetToken, final List<UpdateStatus> status,
final Artifact artifact) {
artifact.getUrls().entrySet().forEach(entry -> {
switch (entry.getKey()) {
case HTTP:
case HTTPS:
status.add(downloadUrl(entry.getValue(), targetToken, artifact.getHashes().getSha1()));
break;
default:
// not supported yet
break;
}
});
}
private static UpdateStatus downloadUrl(final String url, final String targetToken, final String sha1Hash) {
LOGGER.debug("Downloading {}", url);
long overallread = 0;
try {
final CloseableHttpClient httpclient = createHttpClientThatAcceptsAllServerCerts();
final HttpGet request = new HttpGet(url);
request.addHeader("TargetToken", targetToken);
final String sha1HashResult;
try (final CloseableHttpResponse response = httpclient.execute(request)) {
final File tempFile = File.createTempFile("uploadFile", null);
final MessageDigest md = MessageDigest.getInstance("SHA-1");
try (final DigestOutputStream dos = new DigestOutputStream(new FileOutputStream(tempFile), md)) {
overallread = ByteStreams.copy(response.getEntity().getContent(), dos);
sha1HashResult = BaseEncoding.base16().lowerCase().encode(md.digest());
} finally {
tempFile.delete();
}
}
if (!sha1Hash.equals(sha1HashResult)) {
final String message = "Download " + url + " failed with SHA1 hash missmatch (Expected: " + sha1Hash
+ " but got: " + sha1HashResult + ")";
LOGGER.debug(message);
return new UpdateStatus(ResponseStatus.ERROR, message);
}
} catch (IOException | KeyManagementException | NoSuchAlgorithmException | KeyStoreException e) {
LOGGER.error("Failed to download {}", url, e);
return new UpdateStatus(ResponseStatus.ERROR, "Failed to download " + url + ": " + e.getMessage());
}
final String message = "Downloaded " + url + " (" + overallread + " bytes)";
LOGGER.debug(message);
return new UpdateStatus(ResponseStatus.SUCCESSFUL, message);
}
private static CloseableHttpClient createHttpClientThatAcceptsAllServerCerts()
throws NoSuchAlgorithmException, KeyStoreException, KeyManagementException {
final SSLContextBuilder builder = new SSLContextBuilder();
builder.loadTrustMaterial(null, (chain, authType) -> true);
final SSLConnectionSocketFactory sslsf = new SSLConnectionSocketFactory(builder.build());
return HttpClients.custom().setSSLSocketFactory(sslsf).build();
}
} }
/** /**
* Callback interface which is called when the simulated update process has * Callback interface which is called when the simulated update process has
* been finished and the caller of starting the simulated update process can * been finished and the caller of starting the simulated update process can
* send the result to the hawkbit update server back. * send the result back to the hawkBit update server.
*
* @author Michael Hirsch
*
*/ */
@FunctionalInterface @FunctionalInterface
public interface UpdaterCallback { public interface UpdaterCallback {

View File

@@ -26,9 +26,6 @@ import com.google.common.eventbus.EventBus;
/** /**
* Poll time trigger which executes the {@link DDISimulatedDevice#poll()} every * Poll time trigger which executes the {@link DDISimulatedDevice#poll()} every
* second. * second.
*
* @author Michael Hirsch
*
*/ */
@Component @Component
public class NextPollTimeController { public class NextPollTimeController {
@@ -59,8 +56,7 @@ public class NextPollTimeController {
devices.forEach(device -> { devices.forEach(device -> {
int nextCounter = device.getNextPollCounterSec() - 1; int nextCounter = device.getNextPollCounterSec() - 1;
if (nextCounter < 0) { if (nextCounter < 0 && device instanceof DDISimulatedDevice) {
if (device instanceof DDISimulatedDevice) {
try { try {
pollService.submit(() -> ((DDISimulatedDevice) device).poll()); pollService.submit(() -> ((DDISimulatedDevice) device).poll());
} catch (final IllegalStateException e) { } catch (final IllegalStateException e) {
@@ -68,7 +64,7 @@ public class NextPollTimeController {
} }
nextCounter = ((DDISimulatedDevice) device).getPollDelaySec(); nextCounter = ((DDISimulatedDevice) device).getPollDelaySec();
} }
}
device.setNextPollCounterSec(nextCounter); device.setNextPollCounterSec(nextCounter);
}); });
eventBus.post(new NextPollCounterUpdate(devices)); eventBus.post(new NextPollCounterUpdate(devices));

View File

@@ -9,8 +9,6 @@
package org.eclipse.hawkbit.simulator; package org.eclipse.hawkbit.simulator;
import java.net.URL; import java.net.URL;
import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledExecutorService;
import org.eclipse.hawkbit.simulator.AbstractSimulatedDevice.Protocol; import org.eclipse.hawkbit.simulator.AbstractSimulatedDevice.Protocol;
import org.eclipse.hawkbit.simulator.http.ControllerResource; import org.eclipse.hawkbit.simulator.http.ControllerResource;
@@ -24,15 +22,9 @@ import feign.Logger;
/** /**
* The simulated device factory to create either {@link DMFSimulatedDevice} or * The simulated device factory to create either {@link DMFSimulatedDevice} or
* {@link DDISimulatedDevice#}. * {@link DDISimulatedDevice#}.
*
* @author Michael Hirsch
*
*/ */
@Service @Service
public class SimulatedDeviceFactory { public class SimulatedDeviceFactory {
private static final ScheduledExecutorService pollThreadPool = Executors.newScheduledThreadPool(4);
@Autowired @Autowired
private DeviceSimulatorUpdater deviceUpdater; private DeviceSimulatorUpdater deviceUpdater;
@@ -47,7 +39,8 @@ public class SimulatedDeviceFactory {
* the protocol of the device * the protocol of the device
* @return the created simulated device * @return the created simulated device
*/ */
public AbstractSimulatedDevice createSimulatedDevice(final String id, final String tenant, final Protocol protocol) { 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, 30, null, null);
} }
@@ -80,7 +73,7 @@ public class SimulatedDeviceFactory {
final ControllerResource controllerResource = Feign.builder().logger(new Logger.ErrorLogger()) final ControllerResource controllerResource = Feign.builder().logger(new Logger.ErrorLogger())
.requestInterceptor(new GatewayTokenInterceptor(gatewayToken)).logLevel(Logger.Level.BASIC) .requestInterceptor(new GatewayTokenInterceptor(gatewayToken)).logLevel(Logger.Level.BASIC)
.target(ControllerResource.class, baseEndpoint.toString()); .target(ControllerResource.class, baseEndpoint.toString());
return new DDISimulatedDevice(id, tenant, pollDelaySec, controllerResource, pollThreadPool, deviceUpdater); return new DDISimulatedDevice(id, tenant, pollDelaySec, controllerResource, deviceUpdater);
default: default:
throw new IllegalArgumentException("Protocol " + protocol + " unknown"); throw new IllegalArgumentException("Protocol " + protocol + " unknown");
} }

View File

@@ -0,0 +1,78 @@
/**
* Copyright (c) 2015 Bosch Software Innovations GmbH and others.
*
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0
* which accompanies this distribution, and is available at
* http://www.eclipse.org/legal/epl-v10.html
*/
package org.eclipse.hawkbit.simulator;
import java.util.ArrayList;
import java.util.List;
/**
* Update status of the simulated update.
*
*/
public class UpdateStatus {
private ResponseStatus responseStatus;
private List<String> statusMessages;
/**
* Constructor.
*
* @param responseStatus
* of the update
*/
public UpdateStatus(final ResponseStatus responseStatus) {
this.responseStatus = responseStatus;
}
/**
* Constructor including status message.
*
* @param responseStatus
* of the update
* @param message
* of the update status
*/
public UpdateStatus(final ResponseStatus responseStatus, final String message) {
this(responseStatus);
statusMessages = new ArrayList<>();
statusMessages.add(message);
}
public ResponseStatus getResponseStatus() {
return responseStatus;
}
public void setResponseStatus(final ResponseStatus responseStatus) {
this.responseStatus = responseStatus;
}
public List<String> getStatusMessages() {
if (statusMessages == null) {
statusMessages = new ArrayList<>();
}
return statusMessages;
}
/**
* The status to response to the hawkBit update server if an simulated
* update process should be respond with successful or failure update.
*/
public enum ResponseStatus {
/**
* Update has been successful and response the successful update.
*/
SUCCESSFUL,
/**
* Update has been not successful and response the error update.
*/
ERROR;
}
}

View File

@@ -25,6 +25,8 @@ import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.messaging.handler.annotation.Header; import org.springframework.messaging.handler.annotation.Header;
import org.springframework.stereotype.Component; import org.springframework.stereotype.Component;
import com.google.common.collect.Lists;
/** /**
* Handle all incoming Messages from hawkBit update server. * Handle all incoming Messages from hawkBit update server.
* *
@@ -111,7 +113,7 @@ public class SpReceiverService extends ReceiverService {
final Long actionId = convertMessage(message, Long.class); final Long actionId = convertMessage(message, Long.class);
final SimulatedUpdate update = new SimulatedUpdate(tenant, thingId, actionId); final SimulatedUpdate update = new SimulatedUpdate(tenant, thingId, actionId);
spSenderService.finishUpdateProcess(update, "Simulation canceled"); spSenderService.finishUpdateProcess(update, Lists.newArrayList("Simulation canceled"));
} }
private void handleUpdateProcess(final Message message, final String thingId) { private void handleUpdateProcess(final Message message, final String thingId) {
@@ -122,19 +124,20 @@ public class SpReceiverService extends ReceiverService {
final DownloadAndUpdateRequest downloadAndUpdateRequest = convertMessage(message, final DownloadAndUpdateRequest downloadAndUpdateRequest = convertMessage(message,
DownloadAndUpdateRequest.class); DownloadAndUpdateRequest.class);
final Long actionId = downloadAndUpdateRequest.getActionId(); final Long actionId = downloadAndUpdateRequest.getActionId();
final String targetSecurityToken = downloadAndUpdateRequest.getTargetSecurityToken();
deviceUpdater.startUpdate(tenant, thingId, actionId, deviceUpdater.startUpdate(tenant, thingId, actionId, null, downloadAndUpdateRequest.getSoftwareModules(),
downloadAndUpdateRequest.getSoftwareModules().get(0).getModuleVersion(), (device, actionId1) -> { targetSecurityToken, (device, actionId1) -> {
switch (device.getResponseStatus()) { switch (device.getUpdateStatus().getResponseStatus()) {
case SUCCESSFUL: case SUCCESSFUL:
spSenderService.finishUpdateProcess( spSenderService.finishUpdateProcess(
new SimulatedUpdate(device.getTenant(), device.getId(), actionId1), new SimulatedUpdate(device.getTenant(), device.getId(), actionId1),
"Simulation complete!"); device.getUpdateStatus().getStatusMessages());
break; break;
case ERROR: case ERROR:
spSenderService.finishUpdateProcessWithError( spSenderService.finishUpdateProcessWithError(
new SimulatedUpdate(device.getTenant(), device.getId(), actionId1), new SimulatedUpdate(device.getTenant(), device.getId(), actionId1),
"Simulation complete with error!"); device.getUpdateStatus().getStatusMessages());
break; break;
default: default:
break; break;

View File

@@ -8,6 +8,7 @@
*/ */
package org.eclipse.hawkbit.simulator.amqp; package org.eclipse.hawkbit.simulator.amqp;
import java.util.List;
import java.util.Map; import java.util.Map;
import org.eclipse.hawkbit.dmf.amqp.api.AmqpSettings; import org.eclipse.hawkbit.dmf.amqp.api.AmqpSettings;
@@ -23,13 +24,9 @@ import org.springframework.amqp.core.MessageProperties;
import org.springframework.amqp.rabbit.core.RabbitTemplate; import org.springframework.amqp.rabbit.core.RabbitTemplate;
import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service; import org.springframework.stereotype.Service;
import org.springframework.util.StringUtils;
/** /**
* Sender service to send message to SP. * Sender service to send messages to update server.
*
*
*
*/ */
@Service @Service
public class SpSenderService extends SenderService { public class SpSenderService extends SenderService {
@@ -59,8 +56,9 @@ public class SpSenderService extends SenderService {
* @param description * @param description
* a description according the update process * a description according the update process
*/ */
public void finishUpdateProcess(final SimulatedUpdate update, final String description) { public void finishUpdateProcess(final SimulatedUpdate update, final List<String> updateResultMessages) {
final Message updateResultMessage = createUpdateResultMessage(update, ActionStatus.FINISHED, description); final Message updateResultMessage = createUpdateResultMessage(update, ActionStatus.FINISHED,
updateResultMessages);
sendMessage(spExchange, updateResultMessage); sendMessage(spExchange, updateResultMessage);
} }
@@ -72,9 +70,9 @@ public class SpSenderService extends SenderService {
* @param messageDescription * @param messageDescription
* a description according the update process * a description according the update process
*/ */
public void finishUpdateProcessWithError(final SimulatedUpdate update, final String messageDescription) { public void finishUpdateProcessWithError(final SimulatedUpdate update, final List<String> updateResultMessages) {
sendErrorgMessage(update, messageDescription); sendErrorgMessage(update, updateResultMessages);
LOGGER.debug("Update process finished with error \"{}\" reported by thing {}", messageDescription, LOGGER.debug("Update process finished with error \"{}\" reported by thing {}", updateResultMessages,
update.getThingId()); update.getThingId());
} }
@@ -88,8 +86,8 @@ public class SpSenderService extends SenderService {
* @param actionId * @param actionId
* the ID of the action for the error message * the ID of the action for the error message
*/ */
public void sendErrorMessage(final String tenant, final String messageDescription, final Long actionId) { public void sendErrorMessage(final String tenant, final List<String> updateResultMessages, final Long actionId) {
final Message message = createActionStatusMessage(tenant, ActionStatus.ERROR, messageDescription, actionId); final Message message = createActionStatusMessage(tenant, ActionStatus.ERROR, updateResultMessages, actionId);
sendMessage(spExchange, message); sendMessage(spExchange, message);
} }
@@ -101,8 +99,8 @@ public class SpSenderService extends SenderService {
* @param warningMessage * @param warningMessage
* a warning description * a warning description
*/ */
public void sendWarningMessage(final SimulatedUpdate update, final String warningMessage) { public void sendWarningMessage(final SimulatedUpdate update, final List<String> updateResultMessages) {
final Message message = createActionStatusMessage(update, warningMessage, ActionStatus.WARNING); final Message message = createActionStatusMessage(update, updateResultMessages, ActionStatus.WARNING);
sendMessage(spExchange, message); sendMessage(spExchange, message);
} }
@@ -119,8 +117,8 @@ public class SpSenderService extends SenderService {
* the cached value * the cached value
*/ */
public void sendActionStatusMessage(final String tenant, final ActionStatus actionStatus, public void sendActionStatusMessage(final String tenant, final ActionStatus actionStatus,
final String actionMessage, final Long actionId) { final List<String> updateResultMessages, final Long actionId) {
final Message message = createActionStatusMessage(tenant, actionStatus, actionMessage, actionId); final Message message = createActionStatusMessage(tenant, actionStatus, updateResultMessages, actionId);
sendMessage(message); sendMessage(message);
} }
@@ -162,11 +160,11 @@ public class SpSenderService extends SenderService {
* *
* @param context * @param context
* the current context * the current context
* @param messageDescription * @param updateResultMessages
* a description according the update process * a list of descriptions according the update process
*/ */
private void sendErrorgMessage(final SimulatedUpdate update, final String messageDescription) { private void sendErrorgMessage(final SimulatedUpdate update, final List<String> updateResultMessages) {
final Message message = createActionStatusMessage(update, messageDescription, ActionStatus.ERROR); final Message message = createActionStatusMessage(update, updateResultMessages, ActionStatus.ERROR);
sendMessage(spExchange, message); sendMessage(spExchange, message);
} }
@@ -183,7 +181,7 @@ public class SpSenderService extends SenderService {
* the cacheValue value * the cacheValue value
*/ */
private Message createActionStatusMessage(final String tenant, final ActionStatus actionStatus, private Message createActionStatusMessage(final String tenant, final ActionStatus actionStatus,
final String actionMessage, final Long actionId) { final List<String> updateResultMessages, final Long actionId) {
final MessageProperties messageProperties = new MessageProperties(); final MessageProperties messageProperties = new MessageProperties();
final Map<String, Object> headers = messageProperties.getHeaders(); final Map<String, Object> headers = messageProperties.getHeaders();
final ActionUpdateStatus actionUpdateStatus = new ActionUpdateStatus(); final ActionUpdateStatus actionUpdateStatus = new ActionUpdateStatus();
@@ -192,15 +190,14 @@ public class SpSenderService extends SenderService {
headers.put(MessageHeaderKey.TENANT, tenant); headers.put(MessageHeaderKey.TENANT, tenant);
headers.put(MessageHeaderKey.TOPIC, EventTopic.UPDATE_ACTION_STATUS.name()); headers.put(MessageHeaderKey.TOPIC, EventTopic.UPDATE_ACTION_STATUS.name());
headers.put(MessageHeaderKey.CONTENT_TYPE, MessageProperties.CONTENT_TYPE_JSON); headers.put(MessageHeaderKey.CONTENT_TYPE, MessageProperties.CONTENT_TYPE_JSON);
if (!StringUtils.isEmpty(actionMessage)) { actionUpdateStatus.getMessage().addAll(updateResultMessages);
actionUpdateStatus.getMessage().add(actionMessage);
}
actionUpdateStatus.setActionId(actionId); actionUpdateStatus.setActionId(actionId);
return convertMessage(actionUpdateStatus, messageProperties); return convertMessage(actionUpdateStatus, messageProperties);
} }
private Message createUpdateResultMessage(final SimulatedUpdate cacheValue, final ActionStatus actionStatus, private Message createUpdateResultMessage(final SimulatedUpdate cacheValue, final ActionStatus actionStatus,
final String updateResultMessage) { final List<String> updateResultMessages) {
final MessageProperties messageProperties = new MessageProperties(); final MessageProperties messageProperties = new MessageProperties();
final Map<String, Object> headers = messageProperties.getHeaders(); final Map<String, Object> headers = messageProperties.getHeaders();
final ActionUpdateStatus actionUpdateStatus = new ActionUpdateStatus(); final ActionUpdateStatus actionUpdateStatus = new ActionUpdateStatus();
@@ -209,14 +206,14 @@ public class SpSenderService extends SenderService {
headers.put(MessageHeaderKey.TENANT, cacheValue.getTenant()); headers.put(MessageHeaderKey.TENANT, cacheValue.getTenant());
headers.put(MessageHeaderKey.TOPIC, EventTopic.UPDATE_ACTION_STATUS.name()); headers.put(MessageHeaderKey.TOPIC, EventTopic.UPDATE_ACTION_STATUS.name());
headers.put(MessageHeaderKey.CONTENT_TYPE, MessageProperties.CONTENT_TYPE_JSON); headers.put(MessageHeaderKey.CONTENT_TYPE, MessageProperties.CONTENT_TYPE_JSON);
actionUpdateStatus.getMessage().add(updateResultMessage); actionUpdateStatus.getMessage().addAll(updateResultMessages);
actionUpdateStatus.setActionId(cacheValue.getActionId()); actionUpdateStatus.setActionId(cacheValue.getActionId());
return convertMessage(actionUpdateStatus, messageProperties); return convertMessage(actionUpdateStatus, messageProperties);
} }
private Message createActionStatusMessage(final SimulatedUpdate update, final String messageDescription, private Message createActionStatusMessage(final SimulatedUpdate update, final List<String> updateResultMessages,
final ActionStatus status) { final ActionStatus status) {
return createActionStatusMessage(update.getTenant(), status, messageDescription, update.getActionId()); return createActionStatusMessage(update.getTenant(), status, updateResultMessages, update.getActionId());
} }
} }

View File

@@ -14,8 +14,6 @@ import org.eclipse.hawkbit.simulator.AbstractSimulatedDevice;
* Event definition object which is published if the simulated device updated * Event definition object which is published if the simulated device updated
* its update progress. * its update progress.
* *
* @author Michael Hirsch
*
*/ */
public class ProgressUpdate { public class ProgressUpdate {

View File

@@ -8,21 +8,19 @@
*/ */
package org.eclipse.hawkbit.simulator.ui; package org.eclipse.hawkbit.simulator.ui;
import java.net.URL;
import java.util.List; import java.util.List;
import java.util.Locale; import java.util.Locale;
import org.eclipse.hawkbit.simulator.AbstractSimulatedDevice; import org.eclipse.hawkbit.simulator.AbstractSimulatedDevice;
import org.eclipse.hawkbit.simulator.AbstractSimulatedDevice.Protocol; import org.eclipse.hawkbit.simulator.AbstractSimulatedDevice.Protocol;
import org.eclipse.hawkbit.simulator.AbstractSimulatedDevice.ResponseStatus;
import org.eclipse.hawkbit.simulator.AbstractSimulatedDevice.Status; import org.eclipse.hawkbit.simulator.AbstractSimulatedDevice.Status;
import org.eclipse.hawkbit.simulator.DeviceSimulatorRepository; import org.eclipse.hawkbit.simulator.DeviceSimulatorRepository;
import org.eclipse.hawkbit.simulator.SimulatedDeviceFactory; import org.eclipse.hawkbit.simulator.SimulatedDeviceFactory;
import org.eclipse.hawkbit.simulator.UpdateStatus.ResponseStatus;
import org.eclipse.hawkbit.simulator.amqp.SpSenderService; import org.eclipse.hawkbit.simulator.amqp.SpSenderService;
import org.eclipse.hawkbit.simulator.event.InitUpdate; import org.eclipse.hawkbit.simulator.event.InitUpdate;
import org.eclipse.hawkbit.simulator.event.NextPollCounterUpdate; import org.eclipse.hawkbit.simulator.event.NextPollCounterUpdate;
import org.eclipse.hawkbit.simulator.event.ProgressUpdate; import org.eclipse.hawkbit.simulator.event.ProgressUpdate;
import org.eclipse.hawkbit.simulator.ui.GenerateDialog.GenerateDialogCallback;
import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Autowired;
import com.google.common.collect.Lists; import com.google.common.collect.Lists;
@@ -53,12 +51,26 @@ import com.vaadin.ui.renderers.ProgressBarRenderer;
* current simulated devices in a grid with their current status and update * current simulated devices in a grid with their current status and update
* progress. * progress.
* *
* @author Michael Hirsch
*
*/ */
@SpringView(name = "") @SpringView(name = "")
public class SimulatorView extends VerticalLayout implements View { public class SimulatorView extends VerticalLayout implements View {
private static final String NEXT_POLL_COUNTER_SEC_COL = "nextPollCounterSec";
private static final String RESPONSE_STATUS_COL = "responseStatus";
private static final String PROTOCOL_COL = "protocol";
private static final String TENANT_COL = "tenant";
private static final String PROGRESS_COL = "progress";
private static final String SWVERSION_COL = "swversion";
private static final String STATUS_COL = "status";
private static final String ID_COL = "id";
private static final long serialVersionUID = 1L; private static final long serialVersionUID = 1L;
@Autowired @Autowired
@@ -79,6 +91,7 @@ public class SimulatorView extends VerticalLayout implements View {
private BeanContainer<String, AbstractSimulatedDevice> beanContainer; private BeanContainer<String, AbstractSimulatedDevice> beanContainer;
@SuppressWarnings("unchecked")
@Override @Override
public void enter(final ViewChangeEvent event) { public void enter(final ViewChangeEvent event) {
eventbus.register(this); eventbus.register(this);
@@ -91,7 +104,7 @@ public class SimulatorView extends VerticalLayout implements View {
createToolbar(); createToolbar();
beanContainer = new BeanContainer<>(AbstractSimulatedDevice.class); beanContainer = new BeanContainer<>(AbstractSimulatedDevice.class);
beanContainer.setBeanIdProperty("id"); beanContainer.setBeanIdProperty(ID_COL);
grid.setSizeFull(); grid.setSizeFull();
grid.setCellStyleGenerator(new CellStyleGenerator() { grid.setCellStyleGenerator(new CellStyleGenerator() {
@@ -100,28 +113,28 @@ public class SimulatorView extends VerticalLayout implements View {
@Override @Override
public String getStyle(final CellReference cellReference) { public String getStyle(final CellReference cellReference) {
return cellReference.getPropertyId().equals("status") ? "centeralign" : null; return cellReference.getPropertyId().equals(STATUS_COL) ? "centeralign" : null;
} }
}); });
grid.setSelectionMode(SelectionMode.NONE); grid.setSelectionMode(SelectionMode.NONE);
grid.setContainerDataSource(beanContainer); grid.setContainerDataSource(beanContainer);
grid.appendHeaderRow().getCell("responseStatus").setComponent(responseComboBox); grid.appendHeaderRow().getCell(RESPONSE_STATUS_COL).setComponent(responseComboBox);
grid.setColumnOrder("id", "status", "swversion", "progress", "tenant", "protocol", "responseStatus", grid.setColumnOrder(ID_COL, STATUS_COL, SWVERSION_COL, PROGRESS_COL, TENANT_COL, PROTOCOL_COL,
"nextPollCounterSec"); RESPONSE_STATUS_COL, NEXT_POLL_COUNTER_SEC_COL);
// header widths // header widths
grid.getColumn("status").setMaximumWidth(80); grid.getColumn(STATUS_COL).setMaximumWidth(80);
grid.getColumn("protocol").setMaximumWidth(180); grid.getColumn(PROTOCOL_COL).setMaximumWidth(180);
grid.getColumn("responseStatus").setMaximumWidth(240); grid.getColumn(RESPONSE_STATUS_COL).setMaximumWidth(240);
grid.getColumn("nextPollCounterSec").setMaximumWidth(210); grid.getColumn(NEXT_POLL_COUNTER_SEC_COL).setMaximumWidth(210);
grid.getColumn("nextPollCounterSec").setHeaderCaption("Next Poll in (sec)"); grid.getColumn(NEXT_POLL_COUNTER_SEC_COL).setHeaderCaption("Next Poll in (sec)");
grid.getColumn("swversion").setHeaderCaption("SW Version"); grid.getColumn(SWVERSION_COL).setHeaderCaption("SW Version");
grid.getColumn("responseStatus").setHeaderCaption("Response Update Status"); grid.getColumn(RESPONSE_STATUS_COL).setHeaderCaption("Response Update Status");
grid.getColumn("progress").setRenderer(new ProgressBarRenderer()); grid.getColumn(PROGRESS_COL).setRenderer(new ProgressBarRenderer());
grid.getColumn("protocol").setConverter(createProtocolConverter()); grid.getColumn(PROTOCOL_COL).setConverter(createProtocolConverter());
grid.getColumn("status").setRenderer(new HtmlRenderer(), createStatusConverter()); grid.getColumn(STATUS_COL).setRenderer(new HtmlRenderer(), createStatusConverter());
grid.removeColumn("tenant"); grid.removeColumn(TENANT_COL);
// grid combobox // grid combobox
responseComboBox.setItemIcon(ResponseStatus.SUCCESSFUL, FontAwesome.CHECK_CIRCLE); responseComboBox.setItemIcon(ResponseStatus.SUCCESSFUL, FontAwesome.CHECK_CIRCLE);
@@ -129,8 +142,8 @@ public class SimulatorView extends VerticalLayout implements View {
responseComboBox.setNullSelectionAllowed(false); responseComboBox.setNullSelectionAllowed(false);
responseComboBox.setValue(ResponseStatus.SUCCESSFUL); responseComboBox.setValue(ResponseStatus.SUCCESSFUL);
responseComboBox.addValueChangeListener(valueChangeEvent -> { responseComboBox.addValueChangeListener(valueChangeEvent -> {
beanContainer.getItemIds().forEach(itemId -> beanContainer.getItem(itemId).getItemProperty("responseStatus") beanContainer.getItemIds().forEach(itemId -> beanContainer.getItem(itemId)
.setValue(valueChangeEvent.getProperty().getValue())); .getItemProperty(RESPONSE_STATUS_COL).setValue(valueChangeEvent.getProperty().getValue()));
}); });
// add all components // add all components
@@ -141,7 +154,7 @@ public class SimulatorView extends VerticalLayout implements View {
setExpandRatio(grid, 1.0F); setExpandRatio(grid, 1.0F);
// load beans // load beans
repository.getAll().forEach(device -> beanContainer.addBean(device)); repository.getAll().forEach(beanContainer::addBean);
} }
@Override @Override
@@ -150,21 +163,16 @@ public class SimulatorView extends VerticalLayout implements View {
eventbus.unregister(this); eventbus.unregister(this);
} }
@SuppressWarnings("unchecked")
@Subscribe @Subscribe
public void pollCounterUpdate(final NextPollCounterUpdate update) { public void pollCounterUpdate(final NextPollCounterUpdate update) {
final List<AbstractSimulatedDevice> devices = update.getDevices(); final List<AbstractSimulatedDevice> devices = update.getDevices();
this.getUI().access(new Runnable() { this.getUI().access(() -> devices.forEach(device -> {
@Override
public void run() {
devices.forEach(device -> {
final BeanItem<AbstractSimulatedDevice> item = beanContainer.getItem(device.getId()); final BeanItem<AbstractSimulatedDevice> item = beanContainer.getItem(device.getId());
if (item != null) { if (item != null) {
item.getItemProperty("nextPollCounterSec").setValue(device.getNextPollCounterSec()); item.getItemProperty(NEXT_POLL_COUNTER_SEC_COL).setValue(device.getNextPollCounterSec());
} }
}); }));
}
});
} }
/** /**
@@ -173,21 +181,19 @@ public class SimulatorView extends VerticalLayout implements View {
* @param update * @param update
* the update event posted on the event bus * the update event posted on the event bus
*/ */
@SuppressWarnings("unchecked")
@Subscribe @Subscribe
public void initUpdate(final InitUpdate update) { public void initUpdate(final InitUpdate update) {
final AbstractSimulatedDevice device = update.getDevice(); final AbstractSimulatedDevice device = update.getDevice();
this.getUI().access(new Runnable() { this.getUI().access(() -> {
@Override
public void run() {
final BeanItem<AbstractSimulatedDevice> item = beanContainer.getItem(device.getId()); final BeanItem<AbstractSimulatedDevice> item = beanContainer.getItem(device.getId());
if (item != null) { if (item == null) {
item.getItemProperty("progress").setValue(device.getProgress()); return;
item.getItemProperty("status").setValue(Status.PEDNING);
item.getItemProperty("swversion").setValue(device.getSwversion());
} }
} item.getItemProperty(PROGRESS_COL).setValue(device.getProgress());
item.getItemProperty(STATUS_COL).setValue(Status.PEDNING);
item.getItemProperty(SWVERSION_COL).setValue(device.getSwversion());
}); });
} }
@@ -197,35 +203,37 @@ public class SimulatorView extends VerticalLayout implements View {
* @param update * @param update
* the update event posted on the event bus * the update event posted on the event bus
*/ */
@SuppressWarnings("unchecked")
@Subscribe @Subscribe
public void progessUpdate(final ProgressUpdate update) { public void progessUpdate(final ProgressUpdate update) {
final AbstractSimulatedDevice device = update.getDevice(); final AbstractSimulatedDevice device = update.getDevice();
this.getUI().access(new Runnable() { this.getUI().access(() -> {
@Override
public void run() {
final BeanItem<AbstractSimulatedDevice> item = beanContainer.getItem(device.getId()); final BeanItem<AbstractSimulatedDevice> item = beanContainer.getItem(device.getId());
if (item != null) { if (item != null) {
item.getItemProperty("progress").setValue(device.getProgress()); item.getItemProperty(PROGRESS_COL).setValue(device.getProgress());
if (device.getProgress() >= 1) { setStatusColumn(device, item);
switch (device.getResponseStatus()) {
case SUCCESSFUL:
item.getItemProperty("status").setValue(Status.FINISH);
break;
case ERROR:
item.getItemProperty("status").setValue(Status.ERROR);
break;
default:
item.getItemProperty("status").setValue(Status.UNKNWON);
}
} else {
item.getItemProperty("status").setValue(Status.PEDNING);
}
}
} }
}); });
} }
@SuppressWarnings("unchecked")
private void setStatusColumn(final AbstractSimulatedDevice device, final BeanItem<AbstractSimulatedDevice> item) {
if (device.getProgress() >= 1) {
switch (device.getUpdateStatus().getResponseStatus()) {
case SUCCESSFUL:
item.getItemProperty(STATUS_COL).setValue(Status.FINISH);
break;
case ERROR:
item.getItemProperty(STATUS_COL).setValue(Status.ERROR);
break;
default:
item.getItemProperty(STATUS_COL).setValue(Status.UNKNWON);
}
} else {
item.getItemProperty(STATUS_COL).setValue(Status.PEDNING);
}
}
private void createToolbar() { private void createToolbar() {
final Button createDevicesButton = new Button("generate..."); final Button createDevicesButton = new Button("generate...");
createDevicesButton.setIcon(FontAwesome.GEARS); createDevicesButton.setIcon(FontAwesome.GEARS);
@@ -246,17 +254,14 @@ public class SimulatorView extends VerticalLayout implements View {
} }
private void openGenerateDialog() { private void openGenerateDialog() {
UI.getCurrent().addWindow(new GenerateDialog(new GenerateDialogCallback() { UI.getCurrent().addWindow(
@Override new GenerateDialog((namePrefix, tenant, amount, pollDelay, basePollUrl, gatewayToken, protocol) -> {
public void okButton(final String namePrefix, final String tenant, final int amount, final int pollDelay,
final URL basePollUrl, final String gatewayToken, final Protocol protocol) {
for (int index = 0; index < amount; index++) { for (int index = 0; index < amount; index++) {
final String deviceId = namePrefix + index; final String deviceId = namePrefix + index;
beanContainer.addBean(repository.add(deviceFactory.createSimulatedDevice(deviceId, beanContainer.addBean(repository.add(deviceFactory.createSimulatedDevice(deviceId,
tenant.toLowerCase(), protocol, pollDelay, basePollUrl, gatewayToken))); tenant.toLowerCase(), protocol, pollDelay, basePollUrl, gatewayToken)));
spSenderService.createOrUpdateThing(tenant, deviceId); spSenderService.createOrUpdateThing(tenant, deviceId);
} }
}
})); }));
} }

View File

@@ -8,3 +8,9 @@
# #
vaadin.servlet.productionMode=true vaadin.servlet.productionMode=true
hawkbit.artifact.url.coap.enabled=false
hawkbit.artifact.url.http.enabled=false
hawkbit.artifact.url.https.enabled=true
hawkbit.artifact.url.https.pattern={protocol}://{hostname}/{tenant}/controller/v1/{targetId}/softwaremodules/{softwareModuleId}/artifacts/{artifactFileName}
hawkbit.artifact.url.https.hostname=hawkbit.eu-gb.mybluemix.net

View File

@@ -7,15 +7,22 @@
# http://www.eclipse.org/legal/epl-v10.html # http://www.eclipse.org/legal/epl-v10.html
# #
# DDI authentication configuration
hawkbit.server.ddi.security.authentication.anonymous.enabled=true hawkbit.server.ddi.security.authentication.anonymous.enabled=true
hawkbit.server.ddi.security.authentication.targettoken.enabled=false hawkbit.server.ddi.security.authentication.targettoken.enabled=true
hawkbit.server.ddi.security.authentication.gatewaytoken.enabled=false hawkbit.server.ddi.security.authentication.gatewaytoken.enabled=true
spring.profiles.active=amqp # Download URL generation config
hawkbit.artifact.url.coap.enabled=false
hawkbit.artifact.url.http.enabled=true
hawkbit.artifact.url.http.port=8080
hawkbit.artifact.url.https.enabled=false
## Vaadin configuration
vaadin.servlet.productionMode=false vaadin.servlet.productionMode=false
## Configuration for RabbitMQ integration ## Configuration for DMF/RabbitMQ integration
spring.profiles.active=amqp
spring.rabbitmq.username=guest spring.rabbitmq.username=guest
spring.rabbitmq.password=guest spring.rabbitmq.password=guest
spring.rabbitmq.virtualHost=/ spring.rabbitmq.virtualHost=/

View File

@@ -18,7 +18,7 @@
</parent> </parent>
<packaging>jar</packaging> <packaging>jar</packaging>
<artifactId>hawkbit-mgmt-api-client</artifactId> <artifactId>hawkbit-mgmt-api-client</artifactId>
<name>hawkBit Management API example client</name> <name>hawkBit :: Management API example client</name>
<build> <build>
<plugins> <plugins>

View File

@@ -200,9 +200,9 @@ public class ArtifactStore implements ArtifactRepository {
String sha1Hash; String sha1Hash;
// compute digest // compute digest
final MessageDigest md = MessageDigest.getInstance("SHA-1"); final MessageDigest md = MessageDigest.getInstance("SHA-1");
final DigestOutputStream dos = new DigestOutputStream(os, md); try (final DigestOutputStream dos = new DigestOutputStream(os, md)) {
ByteStreams.copy(stream, dos); ByteStreams.copy(stream, dos);
dos.close(); }
sha1Hash = BaseEncoding.base16().lowerCase().encode(md.digest()); sha1Hash = BaseEncoding.base16().lowerCase().encode(md.digest());
if (providedSHA1Sum != null && !providedSHA1Sum.equalsIgnoreCase(sha1Hash)) { if (providedSHA1Sum != null && !providedSHA1Sum.equalsIgnoreCase(sha1Hash)) {
throw new HashNotMatchException( throw new HashNotMatchException(

View File

@@ -13,7 +13,6 @@ package org.eclipse.hawkbit.api;
* URLs to specific artifacts. * URLs to specific artifacts.
* *
*/ */
@FunctionalInterface
public interface ArtifactUrlHandler { public interface ArtifactUrlHandler {
/** /**
@@ -34,4 +33,11 @@ public interface ArtifactUrlHandler {
*/ */
String getUrl(String controllerId, final Long softwareModuleId, final String filename, final String sha1Hash, String getUrl(String controllerId, final Long softwareModuleId, final String filename, final String sha1Hash,
final UrlProtocol protocol); final UrlProtocol protocol);
/**
* @param protocol
* to check support for
* @return <code>true</code> of the handler supports given protocol.
*/
boolean protocolSupported(UrlProtocol protocol);
} }

View File

@@ -78,6 +78,12 @@ public class ArtifactUrlHandlerProperties {
* @return the pattern to build the URL. * @return the pattern to build the URL.
*/ */
String getPattern(); String getPattern();
/**
* @return <code>true</code> if the {@link ProtocolProperties} is
* enabled.
*/
boolean isEnabled();
} }
/** /**
@@ -93,6 +99,20 @@ public class ArtifactUrlHandlerProperties {
*/ */
private String pattern = "{protocol}://{hostname}:{port}/{tenant}/controller/v1/{targetId}/softwaremodules/{softwareModuleId}/artifacts/{artifactFileName}"; private String pattern = "{protocol}://{hostname}:{port}/{tenant}/controller/v1/{targetId}/softwaremodules/{softwareModuleId}/artifacts/{artifactFileName}";
/**
* Enables HTTP URI generation in DDI and DMF.
*/
private boolean enabled = true;
@Override
public boolean isEnabled() {
return enabled;
}
public void setEnabled(final boolean enabled) {
this.enabled = enabled;
}
@Override @Override
public String getHostname() { public String getHostname() {
return hostname; return hostname;
@@ -143,6 +163,20 @@ public class ArtifactUrlHandlerProperties {
*/ */
private String pattern = "{protocol}://{hostname}:{port}/{tenant}/controller/v1/{targetId}/softwaremodules/{softwareModuleId}/artifacts/{artifactFileName}"; private String pattern = "{protocol}://{hostname}:{port}/{tenant}/controller/v1/{targetId}/softwaremodules/{softwareModuleId}/artifacts/{artifactFileName}";
/**
* Enables HTTPS URI generation in DDI and DMF.
*/
private boolean enabled = true;
@Override
public boolean isEnabled() {
return enabled;
}
public void setEnabled(final boolean enabled) {
this.enabled = enabled;
}
@Override @Override
public String getHostname() { public String getHostname() {
return hostname; return hostname;
@@ -193,6 +227,20 @@ public class ArtifactUrlHandlerProperties {
*/ */
private String pattern = "{protocol}://{ip}:{port}/fw/{tenant}/{targetId}/sha1/{artifactSHA1}"; private String pattern = "{protocol}://{ip}:{port}/fw/{tenant}/{targetId}/sha1/{artifactSHA1}";
/**
* Enables CoAP URI generation in DMF.
*/
private boolean enabled = true;
@Override
public boolean isEnabled() {
return enabled;
}
public void setEnabled(final boolean enabled) {
this.enabled = enabled;
}
@Override @Override
public String getHostname() { public String getHostname() {
return hostname; return hostname;

View File

@@ -84,4 +84,15 @@ public class PropertyBasedArtifactUrlHandler implements ArtifactUrlHandler {
return replaceMap; return replaceMap;
} }
@Override
public boolean protocolSupported(final UrlProtocol protocol) {
final String protocolString = protocol.name().toLowerCase();
final ProtocolProperties properties = urlHandlerProperties.getProperties(protocolString);
if (properties == null || properties.getPattern() == null) {
return false;
}
return properties.isEnabled();
}
} }

View File

@@ -82,6 +82,7 @@ public class AmqpMessageDispatcherService extends BaseAmqpService {
.getSoftwareModules(); .getSoftwareModules();
final DownloadAndUpdateRequest downloadAndUpdateRequest = new DownloadAndUpdateRequest(); final DownloadAndUpdateRequest downloadAndUpdateRequest = new DownloadAndUpdateRequest();
downloadAndUpdateRequest.setActionId(targetAssignDistributionSetEvent.getActionId()); downloadAndUpdateRequest.setActionId(targetAssignDistributionSetEvent.getActionId());
downloadAndUpdateRequest.setTargetSecurityToken(targetAssignDistributionSetEvent.getTargetToken());
for (final org.eclipse.hawkbit.repository.model.SoftwareModule softwareModule : modules) { for (final org.eclipse.hawkbit.repository.model.SoftwareModule softwareModule : modules) {
final SoftwareModule amqpSoftwareModule = convertToAmqpSoftwareModule(controllerId, softwareModule); final SoftwareModule amqpSoftwareModule = convertToAmqpSoftwareModule(controllerId, softwareModule);
@@ -154,18 +155,26 @@ public class AmqpMessageDispatcherService extends BaseAmqpService {
private Artifact convertArtifact(final String targetId, final LocalArtifact localArtifact) { private Artifact convertArtifact(final String targetId, final LocalArtifact localArtifact) {
final Artifact artifact = new Artifact(); final Artifact artifact = new Artifact();
if (artifactUrlHandler.protocolSupported(UrlProtocol.COAP)) {
artifact.getUrls().put(Artifact.UrlProtocol.COAP, artifact.getUrls().put(Artifact.UrlProtocol.COAP,
artifactUrlHandler.getUrl(targetId, localArtifact.getSoftwareModule().getId(), artifactUrlHandler.getUrl(targetId, localArtifact.getSoftwareModule().getId(),
localArtifact.getFilename(), localArtifact.getSha1Hash(), UrlProtocol.COAP)); localArtifact.getFilename(), localArtifact.getSha1Hash(), UrlProtocol.COAP));
}
if (artifactUrlHandler.protocolSupported(UrlProtocol.HTTP)) {
artifact.getUrls().put(Artifact.UrlProtocol.HTTP, artifact.getUrls().put(Artifact.UrlProtocol.HTTP,
artifactUrlHandler.getUrl(targetId, localArtifact.getSoftwareModule().getId(), artifactUrlHandler.getUrl(targetId, localArtifact.getSoftwareModule().getId(),
localArtifact.getFilename(), localArtifact.getSha1Hash(), UrlProtocol.HTTP)); localArtifact.getFilename(), localArtifact.getSha1Hash(), UrlProtocol.HTTP));
}
if (artifactUrlHandler.protocolSupported(UrlProtocol.HTTPS)) {
artifact.getUrls().put(Artifact.UrlProtocol.HTTPS, artifact.getUrls().put(Artifact.UrlProtocol.HTTPS,
artifactUrlHandler.getUrl(targetId, localArtifact.getSoftwareModule().getId(), artifactUrlHandler.getUrl(targetId, localArtifact.getSoftwareModule().getId(),
localArtifact.getFilename(), localArtifact.getSha1Hash(), UrlProtocol.HTTPS)); localArtifact.getFilename(), localArtifact.getSha1Hash(), UrlProtocol.HTTPS));
}
artifact.setFilename(localArtifact.getFilename()); artifact.setFilename(localArtifact.getFilename());
artifact.setHashes(new ArtifactHash(localArtifact.getSha1Hash(), null)); artifact.setHashes(new ArtifactHash(localArtifact.getSha1Hash(), localArtifact.getMd5Hash()));
artifact.setSize(localArtifact.getSize()); artifact.setSize(localArtifact.getSize());
return artifact; return artifact;
} }

View File

@@ -305,7 +305,8 @@ public class AmqpMessageHandlerService extends BaseAmqpService {
final List<SoftwareModule> softwareModuleList = controllerManagement final List<SoftwareModule> softwareModuleList = controllerManagement
.findSoftwareModulesByDistributionSet(distributionSet); .findSoftwareModulesByDistributionSet(distributionSet);
eventBus.post(new TargetAssignDistributionSetEvent(target.getOptLockRevision(), target.getTenant(), eventBus.post(new TargetAssignDistributionSetEvent(target.getOptLockRevision(), target.getTenant(),
target.getControllerId(), action.getId(), softwareModuleList, target.getTargetInfo().getAddress())); target.getControllerId(), action.getId(), softwareModuleList, target.getTargetInfo().getAddress(),
target.getSecurityToken()));
} }
@@ -336,9 +337,8 @@ public class AmqpMessageHandlerService extends BaseAmqpService {
final Action action = checkActionExist(message, actionUpdateStatus); final Action action = checkActionExist(message, actionUpdateStatus);
final ActionStatus actionStatus = new ActionStatus(); final ActionStatus actionStatus = new ActionStatus();
final List<String> messageText = actionUpdateStatus.getMessage(); actionUpdateStatus.getMessage().forEach(actionStatus::addMessage);
final String messageString = String.join(", ", messageText);
actionStatus.addMessage(messageString);
actionStatus.setAction(action); actionStatus.setAction(action);
actionStatus.setOccurredAt(System.currentTimeMillis()); actionStatus.setOccurredAt(System.currentTimeMillis());

View File

@@ -8,8 +8,8 @@
*/ */
package org.eclipse.hawkbit.amqp; package org.eclipse.hawkbit.amqp;
import static org.fest.assertions.api.Assertions.assertThat;
import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertNotNull; import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertTrue; import static org.junit.Assert.assertTrue;
import static org.mockito.Matchers.any; import static org.mockito.Matchers.any;
@@ -23,6 +23,7 @@ import static org.mockito.Mockito.when;
import java.net.URI; import java.net.URI;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.List; import java.util.List;
import java.util.Optional;
import org.eclipse.hawkbit.AbstractIntegrationTestWithMongoDB; import org.eclipse.hawkbit.AbstractIntegrationTestWithMongoDB;
import org.eclipse.hawkbit.TestDataUtil; import org.eclipse.hawkbit.TestDataUtil;
@@ -58,6 +59,12 @@ import ru.yandex.qatools.allure.annotations.Stories;
@Stories("AmqpMessage Dispatcher Service Test") @Stories("AmqpMessage Dispatcher Service Test")
public class AmqpMessageDispatcherServiceTest extends AbstractIntegrationTestWithMongoDB { public class AmqpMessageDispatcherServiceTest extends AbstractIntegrationTestWithMongoDB {
private static final String TENANT = "default";
private static final URI AMQP_URI = IpUtil.createAmqpUri("vHost", "mytest");
private static final String TEST_TOKEN = "testToken";
private AmqpMessageDispatcherService amqpMessageDispatcherService; private AmqpMessageDispatcherService amqpMessageDispatcherService;
private RabbitTemplate rabbitTemplate; private RabbitTemplate rabbitTemplate;
@@ -89,8 +96,7 @@ public class AmqpMessageDispatcherServiceTest extends AbstractIntegrationTestWit
@Description("Verfies that download and install event with no software modul works") @Description("Verfies that download and install event with no software modul works")
public void testSendDownloadRequesWithEmptySoftwareModules() { public void testSendDownloadRequesWithEmptySoftwareModules() {
final TargetAssignDistributionSetEvent targetAssignDistributionSetEvent = new TargetAssignDistributionSetEvent( final TargetAssignDistributionSetEvent targetAssignDistributionSetEvent = new TargetAssignDistributionSetEvent(
1L, "default", CONTROLLER_ID, 1l, new ArrayList<SoftwareModule>(), 1L, TENANT, CONTROLLER_ID, 1L, new ArrayList<SoftwareModule>(), AMQP_URI, TEST_TOKEN);
IpUtil.createAmqpUri("vHost", "mytest"));
amqpMessageDispatcherService.targetAssignDistributionSet(targetAssignDistributionSetEvent); amqpMessageDispatcherService.targetAssignDistributionSet(targetAssignDistributionSetEvent);
final Message sendMessage = createArgumentCapture(targetAssignDistributionSetEvent.getTargetAdress()); final Message sendMessage = createArgumentCapture(targetAssignDistributionSetEvent.getTargetAdress());
final DownloadAndUpdateRequest downloadAndUpdateRequest = assertDownloadAndInstallMessage(sendMessage); final DownloadAndUpdateRequest downloadAndUpdateRequest = assertDownloadAndInstallMessage(sendMessage);
@@ -104,7 +110,7 @@ public class AmqpMessageDispatcherServiceTest extends AbstractIntegrationTestWit
final DistributionSet dsA = TestDataUtil.generateDistributionSet("", softwareManagement, final DistributionSet dsA = TestDataUtil.generateDistributionSet("", softwareManagement,
distributionSetManagement); distributionSetManagement);
final TargetAssignDistributionSetEvent targetAssignDistributionSetEvent = new TargetAssignDistributionSetEvent( final TargetAssignDistributionSetEvent targetAssignDistributionSetEvent = new TargetAssignDistributionSetEvent(
1L, "default", CONTROLLER_ID, 1l, dsA.getModules(), IpUtil.createAmqpUri("vHost", "mytest")); 1L, TENANT, CONTROLLER_ID, 1L, dsA.getModules(), AMQP_URI, TEST_TOKEN);
amqpMessageDispatcherService.targetAssignDistributionSet(targetAssignDistributionSetEvent); amqpMessageDispatcherService.targetAssignDistributionSet(targetAssignDistributionSetEvent);
final Message sendMessage = createArgumentCapture(targetAssignDistributionSetEvent.getTargetAdress()); final Message sendMessage = createArgumentCapture(targetAssignDistributionSetEvent.getTargetAdress());
final DownloadAndUpdateRequest downloadAndUpdateRequest = assertDownloadAndInstallMessage(sendMessage); final DownloadAndUpdateRequest downloadAndUpdateRequest = assertDownloadAndInstallMessage(sendMessage);
@@ -143,7 +149,7 @@ public class AmqpMessageDispatcherServiceTest extends AbstractIntegrationTestWit
Mockito.when(rabbitTemplate.convertSendAndReceive(any())).thenReturn(receivedList); Mockito.when(rabbitTemplate.convertSendAndReceive(any())).thenReturn(receivedList);
final TargetAssignDistributionSetEvent targetAssignDistributionSetEvent = new TargetAssignDistributionSetEvent( final TargetAssignDistributionSetEvent targetAssignDistributionSetEvent = new TargetAssignDistributionSetEvent(
1L, "default", CONTROLLER_ID, 1l, dsA.getModules(), IpUtil.createAmqpUri("vHost", "mytest")); 1L, TENANT, CONTROLLER_ID, 1L, dsA.getModules(), AMQP_URI, TEST_TOKEN);
amqpMessageDispatcherService.targetAssignDistributionSet(targetAssignDistributionSetEvent); amqpMessageDispatcherService.targetAssignDistributionSet(targetAssignDistributionSetEvent);
final Message sendMessage = createArgumentCapture(targetAssignDistributionSetEvent.getTargetAdress()); final Message sendMessage = createArgumentCapture(targetAssignDistributionSetEvent.getTargetAdress());
final DownloadAndUpdateRequest downloadAndUpdateRequest = assertDownloadAndInstallMessage(sendMessage); final DownloadAndUpdateRequest downloadAndUpdateRequest = assertDownloadAndInstallMessage(sendMessage);
@@ -154,7 +160,19 @@ public class AmqpMessageDispatcherServiceTest extends AbstractIntegrationTestWit
if (!softwareModule.getModuleId().equals(module.getId())) { if (!softwareModule.getModuleId().equals(module.getId())) {
continue; continue;
} }
assertFalse("The software module artifacts should not be empty", softwareModule.getArtifacts().isEmpty()); assertThat(softwareModule.getArtifacts().size()).isEqualTo(module.getArtifacts().size()).isGreaterThan(0);
module.getArtifacts().forEach(dbArtifact -> {
final Optional<org.eclipse.hawkbit.dmf.json.model.Artifact> found = softwareModule.getArtifacts()
.stream().filter(dmfartifact -> dmfartifact.getFilename()
.equals(((LocalArtifact) dbArtifact).getFilename()))
.findFirst();
assertTrue("The artifact should exist in message", found.isPresent());
assertThat(found.get().getSize()).isEqualTo(dbArtifact.getSize());
assertThat(found.get().getHashes().getMd5()).isEqualTo(dbArtifact.getMd5Hash());
assertThat(found.get().getHashes().getSha1()).isEqualTo(dbArtifact.getSha1Hash());
});
} }
} }
@@ -162,7 +180,7 @@ public class AmqpMessageDispatcherServiceTest extends AbstractIntegrationTestWit
@Description("Verfies that send cancel event works") @Description("Verfies that send cancel event works")
public void testSendCancelRequest() { public void testSendCancelRequest() {
final CancelTargetAssignmentEvent cancelTargetAssignmentDistributionSetEvent = new CancelTargetAssignmentEvent( final CancelTargetAssignmentEvent cancelTargetAssignmentDistributionSetEvent = new CancelTargetAssignmentEvent(
1L, "default", CONTROLLER_ID, 1l, IpUtil.createAmqpUri("vHost", "mytest")); 1L, TENANT, CONTROLLER_ID, 1L, AMQP_URI);
amqpMessageDispatcherService amqpMessageDispatcherService
.targetCancelAssignmentToDistributionSet(cancelTargetAssignmentDistributionSetEvent); .targetCancelAssignmentToDistributionSet(cancelTargetAssignmentDistributionSetEvent);
final Message sendMessage = createArgumentCapture(cancelTargetAssignmentDistributionSetEvent.getTargetAdress()); final Message sendMessage = createArgumentCapture(cancelTargetAssignmentDistributionSetEvent.getTargetAdress());
@@ -187,13 +205,12 @@ public class AmqpMessageDispatcherServiceTest extends AbstractIntegrationTestWit
downloadAndUpdateRequest.getActionId(), Long.valueOf(1)); downloadAndUpdateRequest.getActionId(), Long.valueOf(1));
assertEquals("The topic of the event shuold contain DOWNLOAD_AND_INSTALL", EventTopic.DOWNLOAD_AND_INSTALL, assertEquals("The topic of the event shuold contain DOWNLOAD_AND_INSTALL", EventTopic.DOWNLOAD_AND_INSTALL,
sendMessage.getMessageProperties().getHeaders().get(MessageHeaderKey.TOPIC)); sendMessage.getMessageProperties().getHeaders().get(MessageHeaderKey.TOPIC));
assertEquals("Security token of target", downloadAndUpdateRequest.getTargetSecurityToken(), TEST_TOKEN);
return downloadAndUpdateRequest; return downloadAndUpdateRequest;
} }
/**
* @param sendMessage
*/
private void assertEventMessage(final Message sendMessage) { private void assertEventMessage(final Message sendMessage) {
assertNotNull("The message should not be null", sendMessage); assertNotNull("The message should not be null", sendMessage);

View File

@@ -333,20 +333,22 @@ public class AmqpMessageHandlerServiceTest {
assertThat(downloadResponse.getResponseCode()).as("Message body response code is wrong") assertThat(downloadResponse.getResponseCode()).as("Message body response code is wrong")
.isEqualTo(HttpStatus.OK.value()); .isEqualTo(HttpStatus.OK.value());
assertThat(downloadResponse.getArtifact().getSize()).as("Wrong artifact size in message body").isEqualTo(1L); assertThat(downloadResponse.getArtifact().getSize()).as("Wrong artifact size in message body").isEqualTo(1L);
assertThat(downloadResponse.getArtifact().getHashes().getSha1()).as("Wrong sha1 hash").isEqualTo("sha1");
assertThat(downloadResponse.getArtifact().getHashes().getMd5()).as("Wrong md5 hash").isEqualTo("md5");
assertThat(downloadResponse.getDownloadUrl()).as("download url is wrong") assertThat(downloadResponse.getDownloadUrl()).as("download url is wrong")
.startsWith("http://localhost/api/v1/downloadserver/downloadId/"); .startsWith("http://localhost/api/v1/downloadserver/downloadId/");
} }
@Test @Test
@Description("Tests TODO") @Description("Tests TODO")
public void lookupNextUpdateActionAfterFinished() throws IllegalArgumentException, IllegalAccessException { public void lookupNextUpdateActionAfterFinished() throws IllegalAccessException {
// Mock // Mock
final Action action = createActionWithTarget(22L, Status.FINISHED); final Action action = createActionWithTarget(22L, Status.FINISHED);
when(controllerManagementMock.findActionWithDetails(Matchers.any())).thenReturn(action); when(controllerManagementMock.findActionWithDetails(Matchers.any())).thenReturn(action);
when(controllerManagementMock.addUpdateActionStatus(Matchers.any(), Matchers.any())).thenReturn(action); when(controllerManagementMock.addUpdateActionStatus(Matchers.any(), Matchers.any())).thenReturn(action);
// for the test the same action can be used // for the test the same action can be used
final List<Action> actionList = new ArrayList<Action>(); final List<Action> actionList = new ArrayList<>();
actionList.add(action); actionList.add(action);
when(controllerManagementMock.findActionByTargetAndActive(Matchers.any())).thenReturn(actionList); when(controllerManagementMock.findActionByTargetAndActive(Matchers.any())).thenReturn(actionList);
@@ -372,6 +374,8 @@ public class AmqpMessageHandlerServiceTest {
assertThat(targetAssignDistributionSetEvent.getControllerId()).as("event has wrong controller id") assertThat(targetAssignDistributionSetEvent.getControllerId()).as("event has wrong controller id")
.isEqualTo("target1"); .isEqualTo("target1");
assertThat(targetAssignDistributionSetEvent.getTargetToken()).as("targetoken not filled correctly")
.isEqualTo(action.getTarget().getSecurityToken());
assertThat(targetAssignDistributionSetEvent.getActionId()).as("event has wrong action id").isEqualTo(22L); assertThat(targetAssignDistributionSetEvent.getActionId()).as("event has wrong action id").isEqualTo(22L);
assertThat(targetAssignDistributionSetEvent.getSoftwareModules()).as("event has wrong sofware modules") assertThat(targetAssignDistributionSetEvent.getSoftwareModules()).as("event has wrong sofware modules")
.isEqualTo(softwareModuleList); .isEqualTo(softwareModuleList);
@@ -379,7 +383,7 @@ public class AmqpMessageHandlerServiceTest {
} }
private ActionUpdateStatus createActionUpdateStatus(final ActionStatus status) { private ActionUpdateStatus createActionUpdateStatus(final ActionStatus status) {
return createActionUpdateStatus(status, 2l); return createActionUpdateStatus(status, 2L);
} }
private ActionUpdateStatus createActionUpdateStatus(final ActionStatus status, final Long id) { private ActionUpdateStatus createActionUpdateStatus(final ActionStatus status, final Long id) {
@@ -404,15 +408,14 @@ public class AmqpMessageHandlerServiceTest {
} }
private List<SoftwareModule> createSoftwareModuleList() { private List<SoftwareModule> createSoftwareModuleList() {
final List<SoftwareModule> softwareModuleList = new ArrayList<SoftwareModule>(); final List<SoftwareModule> softwareModuleList = new ArrayList<>();
final SoftwareModule softwareModule = new SoftwareModule(); final SoftwareModule softwareModule = new SoftwareModule();
softwareModule.setId(777L); softwareModule.setId(777L);
softwareModuleList.add(softwareModule); softwareModuleList.add(softwareModule);
return softwareModuleList; return softwareModuleList;
} }
private Action createActionWithTarget(final Long targetId, final Status status) private Action createActionWithTarget(final Long targetId, final Status status) throws IllegalAccessException {
throws IllegalArgumentException, IllegalAccessException {
// is needed for the creation of targets // is needed for the creation of targets
initalizeSecurityTokenGenerator(); initalizeSecurityTokenGenerator();
@@ -427,7 +430,7 @@ public class AmqpMessageHandlerServiceTest {
return action; return action;
} }
private void initalizeSecurityTokenGenerator() throws IllegalArgumentException, IllegalAccessException { private void initalizeSecurityTokenGenerator() throws IllegalAccessException {
final SecurityTokenGeneratorHolder instance = SecurityTokenGeneratorHolder.getInstance(); final SecurityTokenGeneratorHolder instance = SecurityTokenGeneratorHolder.getInstance();
final Field[] fields = instance.getClass().getDeclaredFields(); final Field[] fields = instance.getClass().getDeclaredFields();
for (final Field field : fields) { for (final Field field : fields) {

View File

@@ -52,6 +52,8 @@ public class BaseAmqpServiceTest {
final ActionUpdateStatus actionUpdateStatus = new ActionUpdateStatus(); final ActionUpdateStatus actionUpdateStatus = new ActionUpdateStatus();
actionUpdateStatus.setActionId(1L); actionUpdateStatus.setActionId(1L);
actionUpdateStatus.setSoftwareModuleId(2L); actionUpdateStatus.setSoftwareModuleId(2L);
actionUpdateStatus.getMessage().add("Message 1");
actionUpdateStatus.getMessage().add("Message 2");
final Message message = rabbitTemplate.getMessageConverter().toMessage(actionUpdateStatus, final Message message = rabbitTemplate.getMessageConverter().toMessage(actionUpdateStatus,
new MessageProperties()); new MessageProperties());

View File

@@ -13,10 +13,6 @@ import com.fasterxml.jackson.annotation.JsonProperty;
/** /**
* JSON representation of artifact hash. * JSON representation of artifact hash.
*
*
*
*
*/ */
public class ArtifactHash { public class ArtifactHash {

View File

@@ -19,15 +19,16 @@ import com.fasterxml.jackson.annotation.JsonProperty;
/** /**
* JSON representation of download and update request. * JSON representation of download and update request.
* *
*
*
*
*/ */
@JsonInclude(Include.NON_NULL) @JsonInclude(Include.NON_NULL)
@JsonIgnoreProperties(ignoreUnknown = true) @JsonIgnoreProperties(ignoreUnknown = true)
public class DownloadAndUpdateRequest { public class DownloadAndUpdateRequest {
@JsonProperty @JsonProperty
private Long actionId; private Long actionId;
@JsonProperty
private String targetSecurityToken;
@JsonProperty @JsonProperty
private final List<SoftwareModule> softwareModules = new LinkedList<>(); private final List<SoftwareModule> softwareModules = new LinkedList<>();
@@ -39,6 +40,14 @@ public class DownloadAndUpdateRequest {
this.actionId = correlator; this.actionId = correlator;
} }
public String getTargetSecurityToken() {
return targetSecurityToken;
}
public void setTargetSecurityToken(final String targetSecurityToken) {
this.targetSecurityToken = targetSecurityToken;
}
public List<SoftwareModule> getSoftwareModules() { public List<SoftwareModule> getSoftwareModules() {
return softwareModules; return softwareModules;
} }

View File

@@ -16,8 +16,6 @@ import org.eclipse.hawkbit.repository.model.SoftwareModule;
/** /**
* Event that gets sent when a distribution set gets assigned to a target. * Event that gets sent when a distribution set gets assigned to a target.
* *
*
*
*/ */
public class TargetAssignDistributionSetEvent extends AbstractEvent { public class TargetAssignDistributionSetEvent extends AbstractEvent {
@@ -25,6 +23,7 @@ public class TargetAssignDistributionSetEvent extends AbstractEvent {
private final String controllerId; private final String controllerId;
private final Long actionId; private final Long actionId;
private final URI targetAdress; private final URI targetAdress;
private final String targetToken;
/** /**
* Creates a new {@link TargetAssignDistributionSetEvent}. * Creates a new {@link TargetAssignDistributionSetEvent}.
@@ -41,14 +40,18 @@ public class TargetAssignDistributionSetEvent extends AbstractEvent {
* the software modules which have been assigned to the target * the software modules which have been assigned to the target
* @param targetAdress * @param targetAdress
* the targetAdress of the target * the targetAdress of the target
* @param targetToken
* the authentication token of the target
*/ */
public TargetAssignDistributionSetEvent(final long revision, final String tenant, final String controllerId, public TargetAssignDistributionSetEvent(final long revision, final String tenant, final String controllerId,
final Long actionId, final Collection<SoftwareModule> softwareModules, final URI targetAdress) { final Long actionId, final Collection<SoftwareModule> softwareModules, final URI targetAdress,
final String targetToken) {
super(revision, tenant); super(revision, tenant);
this.controllerId = controllerId; this.controllerId = controllerId;
this.actionId = actionId; this.actionId = actionId;
this.softwareModules = softwareModules; this.softwareModules = softwareModules;
this.targetAdress = targetAdress; this.targetAdress = targetAdress;
this.targetToken = targetToken;
} }
/** /**
@@ -77,4 +80,7 @@ public class TargetAssignDistributionSetEvent extends AbstractEvent {
return targetAdress; return targetAdress;
} }
public String getTargetToken() {
return targetToken;
}
} }

View File

@@ -64,6 +64,8 @@ public class ControllerManagement {
private static final Logger LOG = LoggerFactory.getLogger(ControllerManagement.class); private static final Logger LOG = LoggerFactory.getLogger(ControllerManagement.class);
private static final Logger LOG_DOS = LoggerFactory.getLogger("server-security.dos"); private static final Logger LOG_DOS = LoggerFactory.getLogger("server-security.dos");
public static final String SERVER_MESSAGE_PREFIX = "Update Server: ";
@Autowired @Autowired
private EntityManager entityManager; private EntityManager entityManager;
@@ -342,13 +344,14 @@ public class ControllerManagement {
break; break;
case CANCELED: case CANCELED:
case FINISHED: case FINISHED:
// in case of successful cancelation we also report the success at // in case of successful cancellation we also report the success at
// the canceled action itself. // the canceled action itself.
actionStatus.addMessage("Cancelation completion is finished sucessfully."); actionStatus.addMessage(
ControllerManagement.SERVER_MESSAGE_PREFIX + "Cancellation completion is finished sucessfully.");
deploymentManagement.successCancellation(action); deploymentManagement.successCancellation(action);
break; break;
case RETRIEVED: case RETRIEVED:
actionStatus.addMessage("Cancelation request retrieved"); actionStatus.addMessage(ControllerManagement.SERVER_MESSAGE_PREFIX + "Cancellation request retrieved.");
break; break;
default: default:
} }

View File

@@ -425,13 +425,14 @@ public class DeploymentManagement {
afterCommit.afterCommit(() -> { afterCommit.afterCommit(() -> {
eventBus.post(new TargetInfoUpdateEvent(target.getTargetInfo())); eventBus.post(new TargetInfoUpdateEvent(target.getTargetInfo()));
eventBus.post(new TargetAssignDistributionSetEvent(target.getOptLockRevision(), target.getTenant(), eventBus.post(new TargetAssignDistributionSetEvent(target.getOptLockRevision(), target.getTenant(),
target.getControllerId(), actionId, softwareModules, target.getTargetInfo().getAddress())); target.getControllerId(), actionId, softwareModules, target.getTargetInfo().getAddress(),
target.getSecurityToken()));
}); });
} }
/** /**
* Removes {@link UpdateAction}s that are no longer necessary and sends * Removes {@link UpdateAction}s that are no longer necessary and sends
* cancelations to the controller. * cancellations to the controller.
* *
* @param myTarget * @param myTarget
* to override {@link UpdateAction}s * to override {@link UpdateAction}s

View File

@@ -79,7 +79,7 @@ public class TestConfiguration implements AsyncConfigurer {
} }
/** /**
* Bean for the downlod id cache. * Bean for the download id cache.
* *
* @return the cache * @return the cache
*/ */

View File

@@ -143,9 +143,11 @@ public class ArtifactStoreController {
actionStatus.setStatus(Status.DOWNLOAD); actionStatus.setStatus(Status.DOWNLOAD);
if (range != null) { if (range != null) {
actionStatus.addMessage("It is a partial download request: " + range); actionStatus.addMessage(ControllerManagement.SERVER_MESSAGE_PREFIX + "Target downloads range " + range
+ " of: " + request.getRequestURI());
} else { } else {
actionStatus.addMessage("Target downloads"); actionStatus.addMessage(
ControllerManagement.SERVER_MESSAGE_PREFIX + "Target downloads: " + request.getRequestURI());
} }
controllerManagement.addActionStatusMessage(actionStatus); controllerManagement.addActionStatusMessage(actionStatus);
return action; return action;

View File

@@ -31,7 +31,6 @@ import org.eclipse.hawkbit.repository.model.LocalArtifact;
import org.eclipse.hawkbit.repository.model.Target; import org.eclipse.hawkbit.repository.model.Target;
import org.eclipse.hawkbit.rest.resource.model.artifact.ArtifactHash; import org.eclipse.hawkbit.rest.resource.model.artifact.ArtifactHash;
import org.eclipse.hawkbit.tenancy.TenantAware; import org.eclipse.hawkbit.tenancy.TenantAware;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.hateoas.Link; import org.springframework.hateoas.Link;
import com.google.common.base.Charsets; import com.google.common.base.Charsets;
@@ -40,10 +39,6 @@ import com.google.common.base.Charsets;
* Utility class for the Controller API. * Utility class for the Controller API.
*/ */
public final class DataConversionHelper { public final class DataConversionHelper {
@Autowired
ArtifactUrlHandler artifactUrlHandler;
// utility class, private constructor. // utility class, private constructor.
private DataConversionHelper() { private DataConversionHelper() {
@@ -55,7 +50,6 @@ public final class DataConversionHelper {
.map(module -> new Chunk(mapChunkLegacyKeys(module.getType().getKey()), module.getVersion(), .map(module -> new Chunk(mapChunkLegacyKeys(module.getType().getKey()), module.getVersion(),
module.getName(), createArtifacts(targetid, module, artifactUrlHandler))) module.getName(), createArtifacts(targetid, module, artifactUrlHandler)))
.collect(Collectors.toList()); .collect(Collectors.toList());
} }
private static String mapChunkLegacyKeys(final String key) { private static String mapChunkLegacyKeys(final String key) {
@@ -76,29 +70,39 @@ public final class DataConversionHelper {
* of the target * of the target
* @param module * @param module
* the software module * the software module
*
* @return a list of artifacts or a empty list. Cannot be <null>. * @return a list of artifacts or a empty list. Cannot be <null>.
*/ */
public static List<Artifact> createArtifacts(final String targetid, public static List<Artifact> createArtifacts(final String targetid,
final org.eclipse.hawkbit.repository.model.SoftwareModule module, final org.eclipse.hawkbit.repository.model.SoftwareModule module,
final ArtifactUrlHandler artifactUrlHandler) { final ArtifactUrlHandler artifactUrlHandler) {
final List<Artifact> files = new ArrayList<>(); final List<Artifact> files = new ArrayList<>();
module.getLocalArtifacts().forEach(artifact -> { module.getLocalArtifacts()
.forEach(artifact -> files.add(createArtifact(targetid, artifactUrlHandler, artifact)));
return files;
}
private static Artifact createArtifact(final String targetid, final ArtifactUrlHandler artifactUrlHandler,
final LocalArtifact artifact) {
final Artifact file = new Artifact(); final Artifact file = new Artifact();
file.setHashes(new ArtifactHash(artifact.getSha1Hash(), artifact.getMd5Hash())); file.setHashes(new ArtifactHash(artifact.getSha1Hash(), artifact.getMd5Hash()));
file.setFilename(artifact.getFilename()); file.setFilename(artifact.getFilename());
file.setSize(artifact.getSize()); file.setSize(artifact.getSize());
if (artifactUrlHandler.protocolSupported(UrlProtocol.HTTP)) {
final String linkHttp = artifactUrlHandler.getUrl(targetid, artifact.getSoftwareModule().getId(), final String linkHttp = artifactUrlHandler.getUrl(targetid, artifact.getSoftwareModule().getId(),
artifact.getFilename(), artifact.getSha1Hash(), UrlProtocol.HTTP); artifact.getFilename(), artifact.getSha1Hash(), UrlProtocol.HTTP);
file.add(new Link(linkHttp).withRel("download-http"));
file.add(new Link(linkHttp + ControllerConstants.ARTIFACT_MD5_DWNL_SUFFIX).withRel("md5sum-http"));
}
if (artifactUrlHandler.protocolSupported(UrlProtocol.HTTPS)) {
final String linkHttps = artifactUrlHandler.getUrl(targetid, artifact.getSoftwareModule().getId(), final String linkHttps = artifactUrlHandler.getUrl(targetid, artifact.getSoftwareModule().getId(),
artifact.getFilename(), artifact.getSha1Hash(), UrlProtocol.HTTPS); artifact.getFilename(), artifact.getSha1Hash(), UrlProtocol.HTTPS);
file.add(new Link(linkHttps).withRel("download")); file.add(new Link(linkHttps).withRel("download"));
file.add(new Link(linkHttps + ControllerConstants.ARTIFACT_MD5_DWNL_SUFFIX).withRel("md5sum")); file.add(new Link(linkHttps + ControllerConstants.ARTIFACT_MD5_DWNL_SUFFIX).withRel("md5sum"));
file.add(new Link(linkHttp).withRel("download-http")); }
file.add(new Link(linkHttp + ControllerConstants.ARTIFACT_MD5_DWNL_SUFFIX).withRel("md5sum-http")); return file;
files.add(file);
});
return files;
} }
static ControllerBase fromTarget(final Target target, final List<Action> actions, static ControllerBase fromTarget(final Target target, final List<Action> actions,

View File

@@ -216,9 +216,11 @@ public class RootController {
statusMessage.setStatus(Status.DOWNLOAD); statusMessage.setStatus(Status.DOWNLOAD);
if (range != null) { if (range != null) {
statusMessage.addMessage("It is a partial download request: " + range); statusMessage.addMessage(ControllerManagement.SERVER_MESSAGE_PREFIX + "Target downloads range " + range
+ " of: " + request.getRequestURI());
} else { } else {
statusMessage.addMessage("Controller downloads"); statusMessage.addMessage(
ControllerManagement.SERVER_MESSAGE_PREFIX + "Target downloads " + request.getRequestURI());
} }
controllerManagement.addActionStatusMessage(statusMessage); controllerManagement.addActionStatusMessage(statusMessage);
return action; return action;
@@ -388,13 +390,13 @@ public class RootController {
LOG.debug("Controller confirmed cancel (actionid: {}, targetid: {}) as we got {} report.", actionid, LOG.debug("Controller confirmed cancel (actionid: {}, targetid: {}) as we got {} report.", actionid,
targetid, feedback.getStatus().getExecution()); targetid, feedback.getStatus().getExecution());
actionStatus.setStatus(Status.CANCELED); actionStatus.setStatus(Status.CANCELED);
actionStatus.addMessage("Controller confirmed cancelation"); actionStatus.addMessage(ControllerManagement.SERVER_MESSAGE_PREFIX + "Target confirmed cancelation.");
break; break;
case REJECTED: case REJECTED:
LOG.info("Controller reported internal error (actionid: {}, targetid: {}) as we got {} report.", actionid, LOG.info("Controller reported internal error (actionid: {}, targetid: {}) as we got {} report.", actionid,
targetid, feedback.getStatus().getExecution()); targetid, feedback.getStatus().getExecution());
actionStatus.setStatus(Status.WARNING); actionStatus.setStatus(Status.WARNING);
actionStatus.addMessage("Controller reported internal ERROR and REJECTED update."); actionStatus.addMessage(ControllerManagement.SERVER_MESSAGE_PREFIX + "Target REJECTED update.");
break; break;
case CLOSED: case CLOSED:
handleClosedUpdateStatus(feedback, targetid, actionid, actionStatus); handleClosedUpdateStatus(feedback, targetid, actionid, actionStatus);
@@ -421,7 +423,8 @@ public class RootController {
LOG.debug("Controller reported intermediate status (actionid: {}, targetid: {}) as we got {} report.", actionid, LOG.debug("Controller reported intermediate status (actionid: {}, targetid: {}) as we got {} report.", actionid,
targetid, feedback.getStatus().getExecution()); targetid, feedback.getStatus().getExecution());
actionStatus.setStatus(Status.RUNNING); actionStatus.setStatus(Status.RUNNING);
actionStatus.addMessage("Controller reported: " + feedback.getStatus().getExecution()); actionStatus.addMessage(
ControllerManagement.SERVER_MESSAGE_PREFIX + "Target reported " + feedback.getStatus().getExecution());
} }
private static void handleClosedUpdateStatus(final ActionFeedback feedback, final String targetid, private static void handleClosedUpdateStatus(final ActionFeedback feedback, final String targetid,
@@ -430,10 +433,10 @@ public class RootController {
feedback.getStatus().getExecution()); feedback.getStatus().getExecution());
if (feedback.getStatus().getResult().getFinished() == FinalResult.FAILURE) { if (feedback.getStatus().getResult().getFinished() == FinalResult.FAILURE) {
actionStatus.setStatus(Status.ERROR); actionStatus.setStatus(Status.ERROR);
actionStatus.addMessage("Controller reported CLOSED with ERROR!"); actionStatus.addMessage(ControllerManagement.SERVER_MESSAGE_PREFIX + "Target reported CLOSED with ERROR!");
} else { } else {
actionStatus.setStatus(Status.FINISHED); actionStatus.setStatus(Status.FINISHED);
actionStatus.addMessage("Controller reported CLOSED with OK!"); actionStatus.addMessage(ControllerManagement.SERVER_MESSAGE_PREFIX + "Target reported CLOSED with OK!");
} }
} }