Code format hawkbit (#1948)
Signed-off-by: Marinov Avgustin <Avgustin.Marinov@bosch.com>
This commit is contained in:
@@ -9,6 +9,17 @@
|
||||
*/
|
||||
package org.eclipse.hawkbit.sdk.device;
|
||||
|
||||
import java.time.LocalTime;
|
||||
import java.time.temporal.ChronoField;
|
||||
import java.util.AbstractMap;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Objects;
|
||||
import java.util.Optional;
|
||||
import java.util.concurrent.ScheduledExecutorService;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
import lombok.Getter;
|
||||
import lombok.Setter;
|
||||
import lombok.experimental.Accessors;
|
||||
@@ -28,17 +39,6 @@ import org.springframework.hateoas.Link;
|
||||
import org.springframework.http.HttpStatus;
|
||||
import org.springframework.http.ResponseEntity;
|
||||
|
||||
import java.time.LocalTime;
|
||||
import java.time.temporal.ChronoField;
|
||||
import java.util.AbstractMap;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Objects;
|
||||
import java.util.Optional;
|
||||
import java.util.concurrent.ScheduledExecutorService;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
/**
|
||||
* Class representing DDI device connecting directly to hawkBit.
|
||||
*/
|
||||
@@ -81,10 +81,10 @@ public class DdiController {
|
||||
* @param tenant the tenant of the device belongs to
|
||||
* @param controller the controller
|
||||
* @param hawkbitClient a factory for creating to {@link DdiRootControllerRestApi} (and used)
|
||||
* for communication to hawkBit
|
||||
* for communication to hawkBit
|
||||
*/
|
||||
public DdiController(final Tenant tenant, final Controller controller,
|
||||
final UpdateHandler updateHandler, final HawkbitClient hawkbitClient) {
|
||||
final UpdateHandler updateHandler, final HawkbitClient hawkbitClient) {
|
||||
this.tenantId = tenant.getTenantId();
|
||||
gatewayToken = tenant.getGatewayToken();
|
||||
downloadAuthenticationEnabled = tenant.isDownloadAuthenticationEnabled();
|
||||
@@ -112,58 +112,88 @@ public class DdiController {
|
||||
currentActionId = null;
|
||||
}
|
||||
|
||||
public void updateAttribute(final String mode, final String key, final String value) {
|
||||
final DdiUpdateMode updateMode = switch (mode.toLowerCase()) {
|
||||
case "replace" -> DdiUpdateMode.REPLACE;
|
||||
case "remove" -> DdiUpdateMode.REMOVE;
|
||||
default -> DdiUpdateMode.MERGE;
|
||||
};
|
||||
|
||||
final DdiConfigData configData = new DdiConfigData(Collections.singletonMap(key, value), updateMode);
|
||||
|
||||
getDdiApi().putConfigData(configData, getTenantId(), getControllerId());
|
||||
}
|
||||
|
||||
void sendFeedback(final UpdateStatus updateStatus) {
|
||||
log.debug(LOG_PREFIX + "Send feedback {} -> {}", getTenantId(), getControllerId(), currentActionId, updateStatus);
|
||||
try {
|
||||
getDdiApi().postDeploymentBaseActionFeedback(updateStatus.feedback(), getTenantId(), getControllerId(),
|
||||
currentActionId);
|
||||
} catch (final RuntimeException e) {
|
||||
log.error(LOG_PREFIX + "Failed to send feedback {} -> {}", getTenantId(), getControllerId(),
|
||||
currentActionId, updateStatus, e);
|
||||
}
|
||||
|
||||
if (updateStatus.status() == UpdateStatus.Status.SUCCESSFUL ||
|
||||
updateStatus.status() == UpdateStatus.Status.FAILURE) {
|
||||
lastActionId = currentActionId;
|
||||
currentActionId = null;
|
||||
}
|
||||
}
|
||||
|
||||
private void poll() {
|
||||
log.debug(LOG_PREFIX + " Polling ...", tenantId, controllerId);
|
||||
Optional.ofNullable(executorService).ifPresent(executor ->
|
||||
getControllerBase().ifPresentOrElse(
|
||||
controllerBase -> {
|
||||
final Optional<Link> confirmationBaseLink = getRequiredLink(controllerBase, CONFIRMATION_BASE_LINK);
|
||||
if (confirmationBaseLink.isPresent()) {
|
||||
final long actionId = getActionId(confirmationBaseLink.get());
|
||||
log.info(LOG_PREFIX + "Confirmation is required for action {}!", getTenantId(),
|
||||
getControllerId(), actionId);
|
||||
// TODO - confirmation handler
|
||||
sendConfirmationFeedback(actionId);
|
||||
executor.schedule(this::poll, IMMEDIATE_MS, TimeUnit.MILLISECONDS);
|
||||
} else {
|
||||
getRequiredLink(controllerBase, DEPLOYMENT_BASE_LINK).flatMap(this::getActionWithDeployment).ifPresentOrElse(actionWithDeployment -> {
|
||||
final long actionId = actionWithDeployment.getKey();
|
||||
if (currentActionId == null) {
|
||||
if (lastActionId != null && lastActionId == actionId) {
|
||||
log.info(LOG_PREFIX + "Still receive the last action {}",
|
||||
getTenantId(), getControllerId(), actionId);
|
||||
return;
|
||||
}
|
||||
getControllerBase().ifPresentOrElse(
|
||||
controllerBase -> {
|
||||
final Optional<Link> confirmationBaseLink = getRequiredLink(controllerBase, CONFIRMATION_BASE_LINK);
|
||||
if (confirmationBaseLink.isPresent()) {
|
||||
final long actionId = getActionId(confirmationBaseLink.get());
|
||||
log.info(LOG_PREFIX + "Confirmation is required for action {}!", getTenantId(),
|
||||
getControllerId(), actionId);
|
||||
// TODO - confirmation handler
|
||||
sendConfirmationFeedback(actionId);
|
||||
executor.schedule(this::poll, IMMEDIATE_MS, TimeUnit.MILLISECONDS);
|
||||
} else {
|
||||
getRequiredLink(controllerBase, DEPLOYMENT_BASE_LINK).flatMap(this::getActionWithDeployment)
|
||||
.ifPresentOrElse(actionWithDeployment -> {
|
||||
final long actionId = actionWithDeployment.getKey();
|
||||
if (currentActionId == null) {
|
||||
if (lastActionId != null && lastActionId == actionId) {
|
||||
log.info(LOG_PREFIX + "Still receive the last action {}",
|
||||
getTenantId(), getControllerId(), actionId);
|
||||
return;
|
||||
}
|
||||
|
||||
log.info(LOG_PREFIX + "Process action {}", getTenantId(), getControllerId(),
|
||||
actionId);
|
||||
final DdiDeployment deployment = actionWithDeployment.getValue().getDeployment();
|
||||
final DdiDeployment.HandlingType updateType = deployment.getUpdate();
|
||||
final List<DdiChunk> modules = deployment.getChunks();
|
||||
log.info(LOG_PREFIX + "Process action {}", getTenantId(), getControllerId(),
|
||||
actionId);
|
||||
final DdiDeployment deployment = actionWithDeployment.getValue().getDeployment();
|
||||
final DdiDeployment.HandlingType updateType = deployment.getUpdate();
|
||||
final List<DdiChunk> modules = deployment.getChunks();
|
||||
|
||||
currentActionId = actionId;
|
||||
executor.submit(
|
||||
updateHandler.getUpdateProcessor(this, updateType, modules));
|
||||
} else if (currentActionId != actionId) {
|
||||
// TODO - cancel and start new one?
|
||||
log.info(LOG_PREFIX + "Action {} is canceled while in process (new {})!", getTenantId(),
|
||||
getControllerId(), currentActionId, actionId);
|
||||
} // else same action - already processing
|
||||
}, () -> {
|
||||
if (currentActionId != null) {
|
||||
// TODO - cancel current?
|
||||
log.info(LOG_PREFIX + "Action {} is canceled while in process (not returned)!", getTenantId(),
|
||||
getControllerId(), getCurrentActionId());
|
||||
}
|
||||
});
|
||||
executor.schedule(this::poll, getPollMillis(controllerBase), TimeUnit.MILLISECONDS);
|
||||
currentActionId = actionId;
|
||||
executor.submit(
|
||||
updateHandler.getUpdateProcessor(this, updateType, modules));
|
||||
} else if (currentActionId != actionId) {
|
||||
// TODO - cancel and start new one?
|
||||
log.info(LOG_PREFIX + "Action {} is canceled while in process (new {})!", getTenantId(),
|
||||
getControllerId(), currentActionId, actionId);
|
||||
} // else same action - already processing
|
||||
}, () -> {
|
||||
if (currentActionId != null) {
|
||||
// TODO - cancel current?
|
||||
log.info(LOG_PREFIX + "Action {} is canceled while in process (not returned)!", getTenantId(),
|
||||
getControllerId(), getCurrentActionId());
|
||||
}
|
||||
});
|
||||
executor.schedule(this::poll, getPollMillis(controllerBase), TimeUnit.MILLISECONDS);
|
||||
}
|
||||
},
|
||||
() -> {
|
||||
// error has occurred or no controller base hasn't been acquired
|
||||
executor.schedule(this::poll, DEFAULT_POLL_MS, TimeUnit.MILLISECONDS);
|
||||
}
|
||||
},
|
||||
() -> {
|
||||
// error has occurred or no controller base hasn't been acquired
|
||||
executor.schedule(this::poll, DEFAULT_POLL_MS, TimeUnit.MILLISECONDS);
|
||||
}
|
||||
));
|
||||
));
|
||||
}
|
||||
|
||||
private Optional<DdiControllerBase> getControllerBase() {
|
||||
@@ -211,42 +241,14 @@ public class DdiController {
|
||||
final ResponseEntity<DdiDeploymentBase> action = getDdiApi()
|
||||
.getControllerDeploymentBaseAction(getTenantId(), getControllerId(), actionId, -1, null);
|
||||
if (action.getStatusCode() != HttpStatus.OK) {
|
||||
log.warn(LOG_PREFIX + "Fail to get deployment action: {} -> {}", getTenantId(), getControllerId(), actionId, action.getStatusCode());
|
||||
log.warn(LOG_PREFIX + "Fail to get deployment action: {} -> {}", getTenantId(), getControllerId(), actionId,
|
||||
action.getStatusCode());
|
||||
return Optional.empty();
|
||||
}
|
||||
|
||||
return Optional.ofNullable(action.getBody() == null ? null : new AbstractMap.SimpleEntry<>(actionId, action.getBody()));
|
||||
}
|
||||
|
||||
public void updateAttribute(final String mode, final String key, final String value) {
|
||||
final DdiUpdateMode updateMode = switch (mode.toLowerCase()) {
|
||||
case "replace" -> DdiUpdateMode.REPLACE;
|
||||
case "remove" -> DdiUpdateMode.REMOVE;
|
||||
default -> DdiUpdateMode.MERGE;
|
||||
};
|
||||
|
||||
final DdiConfigData configData = new DdiConfigData(Collections.singletonMap(key, value), updateMode);
|
||||
|
||||
getDdiApi().putConfigData(configData, getTenantId(), getControllerId());
|
||||
}
|
||||
|
||||
void sendFeedback(final UpdateStatus updateStatus) {
|
||||
log.debug(LOG_PREFIX + "Send feedback {} -> {}", getTenantId(), getControllerId(), currentActionId, updateStatus);
|
||||
try {
|
||||
getDdiApi().postDeploymentBaseActionFeedback(updateStatus.feedback(), getTenantId(), getControllerId(),
|
||||
currentActionId);
|
||||
} catch (final RuntimeException e) {
|
||||
log.error(LOG_PREFIX + "Failed to send feedback {} -> {}", getTenantId(), getControllerId(),
|
||||
currentActionId, updateStatus, e);
|
||||
}
|
||||
|
||||
if (updateStatus.status() == UpdateStatus.Status.SUCCESSFUL ||
|
||||
updateStatus.status() == UpdateStatus.Status.FAILURE) {
|
||||
lastActionId = currentActionId;
|
||||
currentActionId = null;
|
||||
}
|
||||
}
|
||||
|
||||
private void sendConfirmationFeedback(final long actionId) {
|
||||
final DdiConfirmationFeedback ddiConfirmationFeedback = new DdiConfirmationFeedback(
|
||||
DdiConfirmationFeedback.Confirmation.CONFIRMED, 0, Collections.singletonList(
|
||||
|
||||
@@ -9,15 +9,15 @@
|
||||
*/
|
||||
package org.eclipse.hawkbit.sdk.device;
|
||||
|
||||
import java.util.Map;
|
||||
import java.util.Optional;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
|
||||
import lombok.Getter;
|
||||
import org.eclipse.hawkbit.sdk.Controller;
|
||||
import org.eclipse.hawkbit.sdk.HawkbitClient;
|
||||
import org.eclipse.hawkbit.sdk.Tenant;
|
||||
|
||||
import java.util.Map;
|
||||
import java.util.Optional;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
|
||||
/**
|
||||
* An in-memory simulated DDI Tenant to hold the controller twins in
|
||||
* memory and be able to retrieve them again.
|
||||
|
||||
@@ -9,6 +9,22 @@
|
||||
*/
|
||||
package org.eclipse.hawkbit.sdk.device;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
import java.security.KeyManagementException;
|
||||
import java.security.KeyStoreException;
|
||||
import java.security.MessageDigest;
|
||||
import java.security.NoSuchAlgorithmException;
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashMap;
|
||||
import java.util.HexFormat;
|
||||
import java.util.LinkedList;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.apache.hc.client5.http.classic.methods.HttpGet;
|
||||
import org.apache.hc.client5.http.impl.classic.CloseableHttpClient;
|
||||
@@ -28,22 +44,6 @@ import org.springframework.util.CollectionUtils;
|
||||
import org.springframework.util.ObjectUtils;
|
||||
import org.springframework.util.StringUtils;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
import java.security.KeyManagementException;
|
||||
import java.security.KeyStoreException;
|
||||
import java.security.MessageDigest;
|
||||
import java.security.NoSuchAlgorithmException;
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashMap;
|
||||
import java.util.HexFormat;
|
||||
import java.util.LinkedList;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
/**
|
||||
* Update handler provide plug-in endpoint allowing for customization of the update processing.
|
||||
*/
|
||||
@@ -67,20 +67,16 @@ public interface UpdateHandler {
|
||||
@Slf4j
|
||||
class UpdateProcessor implements Runnable {
|
||||
|
||||
protected final Map<String, Path> downloads = new HashMap<>();
|
||||
private static final String LOG_PREFIX = "[{}:{}] ";
|
||||
|
||||
private static final String DOWNLOAD_LOG_MESSAGE = "Download ";
|
||||
private static final String EXPECTED = "(Expected: ";
|
||||
private static final String BUT_GOT_LOG_MESSAGE = " but got: ";
|
||||
private static final int MINIMUM_TOKEN_LENGTH_FOR_HINT = 6;
|
||||
|
||||
private final DdiController ddiController;
|
||||
|
||||
private final DdiDeployment.HandlingType updateType;
|
||||
private final List<DdiChunk> modules;
|
||||
|
||||
private final ArtifactHandler artifactHandler;
|
||||
protected final Map<String, Path> downloads = new HashMap<>();
|
||||
|
||||
public UpdateProcessor(
|
||||
final DdiController ddiController,
|
||||
@@ -186,101 +182,6 @@ public interface UpdateHandler {
|
||||
log.debug(LOG_PREFIX + "Cleaned up", ddiController.getTenantId(), ddiController.getControllerId());
|
||||
}
|
||||
|
||||
private void handleArtifact(
|
||||
final String targetToken, final String gatewayToken,
|
||||
final List<UpdateStatus> status, final DdiArtifact artifact) {
|
||||
artifact.getLink("download").ifPresentOrElse(
|
||||
// HTTPS
|
||||
link -> status.add(downloadUrl(link.getHref(), gatewayToken, targetToken,
|
||||
artifact.getHashes(), artifact.getSize())),
|
||||
// HTTP
|
||||
() -> status.add(downloadUrl(
|
||||
artifact.getLink("download-http")
|
||||
.map(Link::getHref)
|
||||
.orElseThrow(() -> new IllegalArgumentException("Nor https nor http found!")),
|
||||
gatewayToken, targetToken,
|
||||
artifact.getHashes(), artifact.getSize()))
|
||||
);
|
||||
}
|
||||
|
||||
private UpdateStatus downloadUrl(
|
||||
final String url, final String gatewayToken, final String targetToken,
|
||||
final DdiArtifactHash hash, final long size) {
|
||||
if (log.isDebugEnabled()) {
|
||||
log.debug(LOG_PREFIX + "Downloading {} with token {}, expected hash {} and size {}",
|
||||
ddiController.getTenantId(), ddiController.getControllerId(), url,
|
||||
hideTokenDetails(targetToken), hash, size);
|
||||
}
|
||||
|
||||
try {
|
||||
return readAndCheckDownloadUrl(url, gatewayToken, targetToken, hash, size);
|
||||
} catch (final IOException | KeyManagementException | NoSuchAlgorithmException | KeyStoreException e) {
|
||||
log.error(LOG_PREFIX + "Failed to download {}",
|
||||
ddiController.getTenantId(), ddiController.getControllerId(), url, e);
|
||||
return new UpdateStatus(
|
||||
UpdateStatus.Status.FAILURE,
|
||||
List.of("Failed to download " + url + ": " + e.getMessage()));
|
||||
}
|
||||
}
|
||||
|
||||
private UpdateStatus readAndCheckDownloadUrl(final String url, final String gatewayToken,
|
||||
final String targetToken, final DdiArtifactHash hash, final long size)
|
||||
throws NoSuchAlgorithmException, KeyStoreException, KeyManagementException, IOException {
|
||||
final Validator sizeValidator = sizeValidator(size);
|
||||
final Validator hashValidator = hashValidator(hash);
|
||||
final ArtifactHandler.DownloadHandler downloadHandler = artifactHandler.getDownloadHandler(url);
|
||||
|
||||
try (final CloseableHttpClient httpclient = createHttpClientThatAcceptsAllServerCerts()) {
|
||||
final HttpGet request = new HttpGet(url);
|
||||
if (StringUtils.hasLength(targetToken)) {
|
||||
request.addHeader(HttpHeaders.AUTHORIZATION, "TargetToken " + targetToken);
|
||||
} else if (StringUtils.hasLength(gatewayToken)) {
|
||||
request.addHeader(HttpHeaders.AUTHORIZATION, "GatewayToken " + gatewayToken);
|
||||
}
|
||||
|
||||
return httpclient.execute(request, response -> {
|
||||
try {
|
||||
if (response.getCode() != HttpStatus.OK.value()) {
|
||||
throw new IllegalStateException("Unexpected status code: " + response.getCode());
|
||||
}
|
||||
|
||||
if (response.getEntity().getContentLength() != size) {
|
||||
throw new IllegalArgumentException("Wrong content length " + EXPECTED + size + BUT_GOT_LOG_MESSAGE + response.getEntity()
|
||||
.getContentLength() + ")!");
|
||||
}
|
||||
|
||||
final byte[] buff = new byte[32 * 1024];
|
||||
try (final InputStream is = response.getEntity().getContent()) {
|
||||
for (int read; (read = is.read(buff)) != -1; ) {
|
||||
sizeValidator.read(buff, read);
|
||||
hashValidator.read(buff, read);
|
||||
downloadHandler.read(buff, 0, read);
|
||||
}
|
||||
}
|
||||
sizeValidator.validate();
|
||||
hashValidator.validate();
|
||||
|
||||
final String message = "Downloaded " + url + " (" + size + " bytes)";
|
||||
log.debug(LOG_PREFIX + message, ddiController.getTenantId(), ddiController.getControllerId());
|
||||
downloadHandler.finished(ArtifactHandler.DownloadHandler.Status.SUCCESS);
|
||||
downloadHandler.download().ifPresent(path -> downloads.put(url, path));
|
||||
return new UpdateStatus(UpdateStatus.Status.SUCCESSFUL, List.of(message));
|
||||
} catch (final Exception e) {
|
||||
final String message = e.getMessage();
|
||||
if (log.isTraceEnabled()) {
|
||||
log.error(LOG_PREFIX + DOWNLOAD_LOG_MESSAGE + url + " failed: " + message,
|
||||
ddiController.getTenantId(), ddiController.getControllerId(), e);
|
||||
} else {
|
||||
log.error(LOG_PREFIX + DOWNLOAD_LOG_MESSAGE + url + " failed: " + message,
|
||||
ddiController.getTenantId(), ddiController.getControllerId());
|
||||
}
|
||||
downloadHandler.finished(ArtifactHandler.DownloadHandler.Status.ERROR);
|
||||
return new UpdateStatus(UpdateStatus.Status.FAILURE, List.of(message));
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
private static String hideTokenDetails(final String targetToken) {
|
||||
if (targetToken == null) {
|
||||
return "<NULL!>";
|
||||
@@ -314,14 +215,6 @@ public interface UpdateHandler {
|
||||
.build();
|
||||
}
|
||||
|
||||
|
||||
private interface Validator {
|
||||
|
||||
void read(final byte[] buff, final int len);
|
||||
|
||||
void validate();
|
||||
}
|
||||
|
||||
private static Validator sizeValidator(final long size) {
|
||||
return new Validator() {
|
||||
|
||||
@@ -384,6 +277,7 @@ public interface UpdateHandler {
|
||||
}
|
||||
|
||||
return new Validator() {
|
||||
|
||||
@Override
|
||||
public void read(final byte[] buff, final int len) {
|
||||
hashValidators.forEach(hashValidator -> hashValidator.update(buff, len));
|
||||
@@ -395,5 +289,108 @@ public interface UpdateHandler {
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
private void handleArtifact(
|
||||
final String targetToken, final String gatewayToken,
|
||||
final List<UpdateStatus> status, final DdiArtifact artifact) {
|
||||
artifact.getLink("download").ifPresentOrElse(
|
||||
// HTTPS
|
||||
link -> status.add(downloadUrl(link.getHref(), gatewayToken, targetToken,
|
||||
artifact.getHashes(), artifact.getSize())),
|
||||
// HTTP
|
||||
() -> status.add(downloadUrl(
|
||||
artifact.getLink("download-http")
|
||||
.map(Link::getHref)
|
||||
.orElseThrow(() -> new IllegalArgumentException("Nor https nor http found!")),
|
||||
gatewayToken, targetToken,
|
||||
artifact.getHashes(), artifact.getSize()))
|
||||
);
|
||||
}
|
||||
|
||||
private UpdateStatus downloadUrl(
|
||||
final String url, final String gatewayToken, final String targetToken,
|
||||
final DdiArtifactHash hash, final long size) {
|
||||
if (log.isDebugEnabled()) {
|
||||
log.debug(LOG_PREFIX + "Downloading {} with token {}, expected hash {} and size {}",
|
||||
ddiController.getTenantId(), ddiController.getControllerId(), url,
|
||||
hideTokenDetails(targetToken), hash, size);
|
||||
}
|
||||
|
||||
try {
|
||||
return readAndCheckDownloadUrl(url, gatewayToken, targetToken, hash, size);
|
||||
} catch (final IOException | KeyManagementException | NoSuchAlgorithmException | KeyStoreException e) {
|
||||
log.error(LOG_PREFIX + "Failed to download {}",
|
||||
ddiController.getTenantId(), ddiController.getControllerId(), url, e);
|
||||
return new UpdateStatus(
|
||||
UpdateStatus.Status.FAILURE,
|
||||
List.of("Failed to download " + url + ": " + e.getMessage()));
|
||||
}
|
||||
}
|
||||
|
||||
private UpdateStatus readAndCheckDownloadUrl(final String url, final String gatewayToken,
|
||||
final String targetToken, final DdiArtifactHash hash, final long size)
|
||||
throws NoSuchAlgorithmException, KeyStoreException, KeyManagementException, IOException {
|
||||
final Validator sizeValidator = sizeValidator(size);
|
||||
final Validator hashValidator = hashValidator(hash);
|
||||
final ArtifactHandler.DownloadHandler downloadHandler = artifactHandler.getDownloadHandler(url);
|
||||
|
||||
try (final CloseableHttpClient httpclient = createHttpClientThatAcceptsAllServerCerts()) {
|
||||
final HttpGet request = new HttpGet(url);
|
||||
if (StringUtils.hasLength(targetToken)) {
|
||||
request.addHeader(HttpHeaders.AUTHORIZATION, "TargetToken " + targetToken);
|
||||
} else if (StringUtils.hasLength(gatewayToken)) {
|
||||
request.addHeader(HttpHeaders.AUTHORIZATION, "GatewayToken " + gatewayToken);
|
||||
}
|
||||
|
||||
return httpclient.execute(request, response -> {
|
||||
try {
|
||||
if (response.getCode() != HttpStatus.OK.value()) {
|
||||
throw new IllegalStateException("Unexpected status code: " + response.getCode());
|
||||
}
|
||||
|
||||
if (response.getEntity().getContentLength() != size) {
|
||||
throw new IllegalArgumentException(
|
||||
"Wrong content length " + EXPECTED + size + BUT_GOT_LOG_MESSAGE + response.getEntity()
|
||||
.getContentLength() + ")!");
|
||||
}
|
||||
|
||||
final byte[] buff = new byte[32 * 1024];
|
||||
try (final InputStream is = response.getEntity().getContent()) {
|
||||
for (int read; (read = is.read(buff)) != -1; ) {
|
||||
sizeValidator.read(buff, read);
|
||||
hashValidator.read(buff, read);
|
||||
downloadHandler.read(buff, 0, read);
|
||||
}
|
||||
}
|
||||
sizeValidator.validate();
|
||||
hashValidator.validate();
|
||||
|
||||
final String message = "Downloaded " + url + " (" + size + " bytes)";
|
||||
log.debug(LOG_PREFIX + message, ddiController.getTenantId(), ddiController.getControllerId());
|
||||
downloadHandler.finished(ArtifactHandler.DownloadHandler.Status.SUCCESS);
|
||||
downloadHandler.download().ifPresent(path -> downloads.put(url, path));
|
||||
return new UpdateStatus(UpdateStatus.Status.SUCCESSFUL, List.of(message));
|
||||
} catch (final Exception e) {
|
||||
final String message = e.getMessage();
|
||||
if (log.isTraceEnabled()) {
|
||||
log.error(LOG_PREFIX + DOWNLOAD_LOG_MESSAGE + url + " failed: " + message,
|
||||
ddiController.getTenantId(), ddiController.getControllerId(), e);
|
||||
} else {
|
||||
log.error(LOG_PREFIX + DOWNLOAD_LOG_MESSAGE + url + " failed: " + message,
|
||||
ddiController.getTenantId(), ddiController.getControllerId());
|
||||
}
|
||||
downloadHandler.finished(ArtifactHandler.DownloadHandler.Status.ERROR);
|
||||
return new UpdateStatus(UpdateStatus.Status.FAILURE, List.of(message));
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
private interface Validator {
|
||||
|
||||
void read(final byte[] buff, final int len);
|
||||
|
||||
void validate();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -9,14 +9,19 @@
|
||||
*/
|
||||
package org.eclipse.hawkbit.sdk.device;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
import org.eclipse.hawkbit.ddi.json.model.DdiActionFeedback;
|
||||
import org.eclipse.hawkbit.ddi.json.model.DdiResult;
|
||||
import org.eclipse.hawkbit.ddi.json.model.DdiStatus;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
public record UpdateStatus(Status status, List<String> messages) {
|
||||
|
||||
DdiActionFeedback feedback() {
|
||||
return new DdiActionFeedback(null,
|
||||
new DdiStatus(status.executionStatus, new DdiResult(status.finalResult, null), status.code, messages));
|
||||
}
|
||||
|
||||
/**
|
||||
* The status to response to the hawkBit update server if an simulated update process should be respond with
|
||||
* successful or failure update.
|
||||
@@ -59,9 +64,4 @@ public record UpdateStatus(Status status, List<String> messages) {
|
||||
this.code = code;
|
||||
}
|
||||
}
|
||||
|
||||
DdiActionFeedback feedback() {
|
||||
return new DdiActionFeedback(null,
|
||||
new DdiStatus(status.executionStatus, new DdiResult(status.finalResult, null), status.code, messages));
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user