Support for action cancellation in ddi controller sdk (#2846)

* Support for action cancellation in ddi controller sdk

Signed-off-by: strailov <Stanislav.Trailov@bosch.io>

* minor refactor

Signed-off-by: strailov <Stanislav.Trailov@bosch.io>

* add comment for cancel action

Signed-off-by: strailov <Stanislav.Trailov@bosch.io>

* minor refactor

Signed-off-by: strailov <Stanislav.Trailov@bosch.io>

---------

Signed-off-by: strailov <Stanislav.Trailov@bosch.io>
This commit is contained in:
Stanislav Trailov
2025-12-16 14:06:18 +02:00
committed by GitHub
parent 556bad652f
commit bfc0e7e550

View File

@@ -24,12 +24,16 @@ import lombok.Getter;
import lombok.Setter;
import lombok.experimental.Accessors;
import lombok.extern.slf4j.Slf4j;
import org.eclipse.hawkbit.ddi.json.model.DdiActionFeedback;
import org.eclipse.hawkbit.ddi.json.model.DdiChunk;
import org.eclipse.hawkbit.ddi.json.model.DdiConfigData;
import org.eclipse.hawkbit.ddi.json.model.DdiConfirmationFeedback;
import org.eclipse.hawkbit.ddi.json.model.DdiControllerBase;
import org.eclipse.hawkbit.ddi.json.model.DdiDeployment;
import org.eclipse.hawkbit.ddi.json.model.DdiDeploymentBase;
import org.eclipse.hawkbit.ddi.json.model.DdiProgress;
import org.eclipse.hawkbit.ddi.json.model.DdiResult;
import org.eclipse.hawkbit.ddi.json.model.DdiStatus;
import org.eclipse.hawkbit.ddi.json.model.DdiUpdateMode;
import org.eclipse.hawkbit.ddi.rest.api.DdiRootControllerRestApi;
import org.eclipse.hawkbit.sdk.Certificate;
@@ -55,6 +59,7 @@ public class DdiController {
private static final String DEPLOYMENT_BASE_LINK = "deploymentBase";
private static final String CONFIRMATION_BASE_LINK = "confirmationBase";
private static final String CANCEL_ACTION_LINK = "cancelAction";
private final Tenant tenant;
private final Controller controller;
@@ -147,6 +152,19 @@ public class DdiController {
}
}
public void sendCancelFeedback(long actionId) {
try {
log.info(LOG_PREFIX + "Sending cancelation feedback for action with id : {}", getTenantId(), getControllerId(), actionId);
getDdiApi().postCancelActionFeedback(new DdiActionFeedback(
new DdiStatus(DdiStatus.ExecutionStatus.CLOSED, new DdiResult(DdiResult.FinalResult.SUCCESS,
new DdiProgress(100, 100)), 200, List.of("Successfully cancelled by the controller."))
), getTenantId(), getControllerId(), actionId);
} catch (Exception ex) {
log.error(LOG_PREFIX + "Failed to send cancelation feedback {}", getTenantId(), getControllerId(),
actionId, ex);
}
}
private void poll() {
log.debug(LOG_PREFIX + " Polling ...", getTenantId(), getControllerId());
Optional.ofNullable(executorService).ifPresent(executor ->
@@ -165,31 +183,21 @@ public class DdiController {
.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();
currentActionId = actionId;
executor.submit(updateHandler.getUpdateProcessor(this, updateType, modules));
processAction(actionId, actionWithDeployment, executor);
} else if (currentActionId != actionId) {
// TODO - cancel and start new one?
// currentActionId had failed to be processed and new one had been initiated
// try cancel current and process new one
log.info(LOG_PREFIX + "Action {} is canceled while in process (new {})!", getTenantId(),
getControllerId(), currentActionId, actionId);
cancelActionByCancellationLink(controllerBase, currentActionId);
currentActionId = null;
// then process the new one
poll();
} // 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());
}
cancelActionByCancellationLink(controllerBase, -1);
// reset current action id - on next poll should be available deploymentBase
currentActionId = null;
});
executor.schedule(this::poll, getPollMillis(controllerBase), TimeUnit.MILLISECONDS);
}
@@ -198,6 +206,23 @@ public class DdiController {
executor.schedule(this::poll, DEFAULT_POLL_MS, TimeUnit.MILLISECONDS)));
}
private void processAction(final long actionId, final Map.Entry<Long,DdiDeploymentBase> actionWithDeployment, final ScheduledExecutorService executor) {
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();
currentActionId = actionId;
executor.submit(updateHandler.getUpdateProcessor(this, updateType, modules));
}
private Optional<DdiControllerBase> getControllerBase() {
log.trace(LOG_PREFIX + "Polling ...", getTenantId(), getControllerId());
final ResponseEntity<DdiControllerBase> poll;
@@ -216,6 +241,16 @@ public class DdiController {
return Optional.ofNullable(poll.getBody());
}
private void cancelActionByCancellationLink(DdiControllerBase controllerBase, long actionToBeCanceled) {
getRequiredLink(controllerBase, CANCEL_ACTION_LINK).ifPresentOrElse(link -> {
// action is in CANCELING state - send cancel feedback
final long actionId = actionToBeCanceled == -1 ? getActionIdFromCancellationLink(link) : actionToBeCanceled;
log.info(LOG_PREFIX + "Cancelling current action {}", getTenantId(), getControllerId(), actionId);
sendCancelFeedback(actionId);
}, () -> log.info(LOG_PREFIX + "Action {} is canceled while in process (not returned)!", getTenantId(), getControllerId(), getCurrentActionId())
);
}
private Optional<Link> getRequiredLink(final DdiControllerBase controllerBase, final String nameOfTheLink) {
final Optional<Link> link = controllerBase != null ? controllerBase.getLink(nameOfTheLink) : Optional.empty();
link.ifPresentOrElse(
@@ -262,4 +297,10 @@ public class DdiController {
final String href = link.getHref();
return Long.parseLong(href.substring(href.lastIndexOf('/') + 1, href.indexOf('?')));
}
private long getActionIdFromCancellationLink(final Link link) {
final String href = link.getHref();
final String[] split = href.split("/");
return Long.parseLong(split[split.length - 1]);
}
}