diff --git a/hawkbit-rest/hawkbit-ddi-resource/README.md b/hawkbit-rest/hawkbit-ddi-resource/README.md index 3cfb443cd..bd7cc49c7 100644 --- a/hawkbit-rest/hawkbit-ddi-resource/README.md +++ b/hawkbit-rest/hawkbit-ddi-resource/README.md @@ -1,6 +1,7 @@ -# Eclipse.IoT hawkBit - DDI Resource +# Eclipse.IoT hawkBit - DDI Resource -This is the server-side implementation of the hawkBit DDI API and the hawkBit DDI Download API that is used by devices for communicating with the HawkBit Update Server through HTTP. +This is the server-side implementation of the hawkBit DDI API and the hawkBit DDI Download API that is used by devices +for communicating with the HawkBit Update Server through HTTP. # Compile diff --git a/hawkbit-rest/hawkbit-ddi-resource/pom.xml b/hawkbit-rest/hawkbit-ddi-resource/pom.xml index eedc7e93e..c53bbeea7 100644 --- a/hawkbit-rest/hawkbit-ddi-resource/pom.xml +++ b/hawkbit-rest/hawkbit-ddi-resource/pom.xml @@ -10,104 +10,105 @@ --> - - 4.0.0 - - org.eclipse.hawkbit - hawkbit-rest-parent - ${revision} - - hawkbit-ddi-resource - hawkBit :: REST :: DDI Resources + + 4.0.0 + + org.eclipse.hawkbit + hawkbit-rest-parent + ${revision} + + hawkbit-ddi-resource + hawkBit :: REST :: DDI Resources - - - org.eclipse.hawkbit - hawkbit-ddi-api - ${project.version} - - - org.eclipse.hawkbit - hawkbit-rest-core - ${project.version} - - - org.eclipse.hawkbit - hawkbit-repository-api - ${project.version} - - - org.springframework.plugin - spring-plugin-core - - - jakarta.servlet - jakarta.servlet-api - provided - + + + org.eclipse.hawkbit + hawkbit-ddi-api + ${project.version} + + + org.eclipse.hawkbit + hawkbit-rest-core + ${project.version} + + + org.eclipse.hawkbit + hawkbit-repository-api + ${project.version} + + + org.springframework.plugin + spring-plugin-core + + + jakarta.servlet + jakarta.servlet-api + provided + - - - org.eclipse.hawkbit - hawkbit-repository-test - ${project.version} - test - - - org.eclipse.hawkbit - hawkbit-repository-jpa - ${project.version} - test - - - org.eclipse.hawkbit - hawkbit-rest-core - ${project.version} - tests - test - - - javax.el - javax.el-api - test - - - org.springframework.security - spring-security-config - test - - - org.eclipse.hawkbit - hawkbit-http-security - ${project.version} - test - - - org.springframework.boot - spring-boot-starter-json - test - - - org.springframework.boot - spring-boot-starter-test - test - - - org.springframework.security - spring-security-aspects - test - - - io.qameta.allure - allure-junit5 - test - - - org.springframework - spring-context-support - test - - + + + org.eclipse.hawkbit + hawkbit-repository-test + ${project.version} + test + + + org.eclipse.hawkbit + hawkbit-repository-jpa + ${project.version} + test + + + org.eclipse.hawkbit + hawkbit-rest-core + ${project.version} + tests + test + + + javax.el + javax.el-api + test + + + org.springframework.security + spring-security-config + test + + + org.eclipse.hawkbit + hawkbit-http-security + ${project.version} + test + + + org.springframework.boot + spring-boot-starter-json + test + + + org.springframework.boot + spring-boot-starter-test + test + + + org.springframework.security + spring-security-aspects + test + + + io.qameta.allure + allure-junit5 + test + + + org.springframework + spring-context-support + test + + diff --git a/hawkbit-rest/hawkbit-ddi-resource/src/main/java/org/eclipse/hawkbit/ddi/rest/resource/DataConversionHelper.java b/hawkbit-rest/hawkbit-ddi-resource/src/main/java/org/eclipse/hawkbit/ddi/rest/resource/DataConversionHelper.java index c9cc49cfa..7e082eaef 100644 --- a/hawkbit-rest/hawkbit-ddi-resource/src/main/java/org/eclipse/hawkbit/ddi/rest/resource/DataConversionHelper.java +++ b/hawkbit-rest/hawkbit-ddi-resource/src/main/java/org/eclipse/hawkbit/ddi/rest/resource/DataConversionHelper.java @@ -45,72 +45,12 @@ import org.springframework.util.CollectionUtils; * Utility class for the DDI API. */ public final class DataConversionHelper { + // utility class, private constructor. private DataConversionHelper() { } - static List createChunks(final Target target, final Action uAction, - final ArtifactUrlHandler artifactUrlHandler, final SystemManagement systemManagement, - final HttpRequest request, final ControllerManagement controllerManagement) { - - final Map> metadata = controllerManagement - .findTargetVisibleMetaDataBySoftwareModuleId(uAction.getDistributionSet().getModules().stream() - .map(SoftwareModule::getId).collect(Collectors.toList())); - - return new ResponseList<>(uAction.getDistributionSet().getModules().stream() - .map(module -> new DdiChunk(mapChunkLegacyKeys(module.getType().getKey()), module.getVersion(), - module.getName(), module.isEncrypted() ? Boolean.TRUE : null, - createArtifacts(target, module, artifactUrlHandler, systemManagement, request), - mapMetadata(metadata.get(module.getId())))) - .collect(Collectors.toList())); - - } - - private static List mapMetadata(final List metadata) { - return CollectionUtils.isEmpty(metadata) ? null - : metadata.stream().map(md -> new DdiMetadata(md.getKey(), md.getValue())).collect(Collectors.toList()); - } - - private static String mapChunkLegacyKeys(final String key) { - if ("application".equals(key)) { - return "bApp"; - } - if ("runtime".equals(key)) { - return "jvm"; - } - - return key; - } - - static List createArtifacts(final Target target, final SoftwareModule module, - final ArtifactUrlHandler artifactUrlHandler, final SystemManagement systemManagement, - final HttpRequest request) { - - return new ResponseList<>(module.getArtifacts().stream() - .map(artifact -> createArtifact(target, artifactUrlHandler, artifact, systemManagement, request)) - .collect(Collectors.toList())); - } - - private static DdiArtifact createArtifact(final Target target, final ArtifactUrlHandler artifactUrlHandler, - final Artifact artifact, final SystemManagement systemManagement, final HttpRequest request) { - final DdiArtifact file = new DdiArtifact(); - file.setHashes(new DdiArtifactHash(artifact.getSha1Hash(), artifact.getMd5Hash(), artifact.getSha256Hash())); - file.setFilename(artifact.getFilename()); - file.setSize(artifact.getSize()); - - artifactUrlHandler - .getUrls(new URLPlaceholder(systemManagement.getTenantMetadata().getTenant(), - systemManagement.getTenantMetadata().getId(), target.getControllerId(), target.getId(), - new SoftwareData(artifact.getSoftwareModule().getId(), artifact.getFilename(), artifact.getId(), - artifact.getSha1Hash())), - ApiType.DDI, request.getURI()) - .forEach(entry -> file.add(Link.of(entry.getRef()).withRel(entry.getRel()).expand())); - - return file; - - } - public static DdiConfirmationBase createConfirmationBase(final Target target, final Action activeAction, final DdiAutoConfirmationState autoConfirmationState, final TenantAware tenantAware) { final String controllerId = target.getControllerId(); @@ -145,9 +85,9 @@ public final class DataConversionHelper { if (activeAction != null) { if (activeAction.isWaitingConfirmation()) { result.add(WebMvcLinkBuilder.linkTo(WebMvcLinkBuilder - .methodOn(DdiRootController.class, tenantAware.getCurrentTenant()) - .getConfirmationBaseAction(tenantAware.getCurrentTenant(), target.getControllerId(), - activeAction.getId(), calculateEtag(activeAction), null)) + .methodOn(DdiRootController.class, tenantAware.getCurrentTenant()) + .getConfirmationBaseAction(tenantAware.getCurrentTenant(), target.getControllerId(), + activeAction.getId(), calculateEtag(activeAction), null)) .withRel(DdiRestConstants.CONFIRMATION_BASE).expand()); } else if (activeAction.isCancelingOrCanceled()) { @@ -163,9 +103,9 @@ public final class DataConversionHelper { // change the payload of the // response because of eTags. result.add(WebMvcLinkBuilder.linkTo(WebMvcLinkBuilder - .methodOn(DdiRootController.class, tenantAware.getCurrentTenant()) - .getControllerDeploymentBaseAction(tenantAware.getCurrentTenant(), target.getControllerId(), - activeAction.getId(), calculateEtag(activeAction), null)) + .methodOn(DdiRootController.class, tenantAware.getCurrentTenant()) + .getControllerDeploymentBaseAction(tenantAware.getCurrentTenant(), target.getControllerId(), + activeAction.getId(), calculateEtag(activeAction), null)) .withRel(DdiRestConstants.DEPLOYMENT_BASE_ACTION).expand()); } } @@ -189,13 +129,73 @@ public final class DataConversionHelper { return result; } + static List createChunks(final Target target, final Action uAction, + final ArtifactUrlHandler artifactUrlHandler, final SystemManagement systemManagement, + final HttpRequest request, final ControllerManagement controllerManagement) { + + final Map> metadata = controllerManagement + .findTargetVisibleMetaDataBySoftwareModuleId(uAction.getDistributionSet().getModules().stream() + .map(SoftwareModule::getId).collect(Collectors.toList())); + + return new ResponseList<>(uAction.getDistributionSet().getModules().stream() + .map(module -> new DdiChunk(mapChunkLegacyKeys(module.getType().getKey()), module.getVersion(), + module.getName(), module.isEncrypted() ? Boolean.TRUE : null, + createArtifacts(target, module, artifactUrlHandler, systemManagement, request), + mapMetadata(metadata.get(module.getId())))) + .collect(Collectors.toList())); + + } + + static List createArtifacts(final Target target, final SoftwareModule module, + final ArtifactUrlHandler artifactUrlHandler, final SystemManagement systemManagement, + final HttpRequest request) { + + return new ResponseList<>(module.getArtifacts().stream() + .map(artifact -> createArtifact(target, artifactUrlHandler, artifact, systemManagement, request)) + .collect(Collectors.toList())); + } + + private static List mapMetadata(final List metadata) { + return CollectionUtils.isEmpty(metadata) ? null + : metadata.stream().map(md -> new DdiMetadata(md.getKey(), md.getValue())).collect(Collectors.toList()); + } + + private static String mapChunkLegacyKeys(final String key) { + if ("application".equals(key)) { + return "bApp"; + } + if ("runtime".equals(key)) { + return "jvm"; + } + + return key; + } + + private static DdiArtifact createArtifact(final Target target, final ArtifactUrlHandler artifactUrlHandler, + final Artifact artifact, final SystemManagement systemManagement, final HttpRequest request) { + final DdiArtifact file = new DdiArtifact(); + file.setHashes(new DdiArtifactHash(artifact.getSha1Hash(), artifact.getMd5Hash(), artifact.getSha256Hash())); + file.setFilename(artifact.getFilename()); + file.setSize(artifact.getSize()); + + artifactUrlHandler + .getUrls(new URLPlaceholder(systemManagement.getTenantMetadata().getTenant(), + systemManagement.getTenantMetadata().getId(), target.getControllerId(), target.getId(), + new SoftwareData(artifact.getSoftwareModule().getId(), artifact.getFilename(), artifact.getId(), + artifact.getSha1Hash())), + ApiType.DDI, request.getURI()) + .forEach(entry -> file.add(Link.of(entry.getRef()).withRel(entry.getRel()).expand())); + + return file; + + } + /** * Calculates an etag for the given {@link Action} based on the entities * hashcode and the {@link Action#isHitAutoForceTime(long)} to reflect a * force switch. - * - * @param action - * to calculate the etag for + * + * @param action to calculate the etag for * @return the etag */ private static int calculateEtag(final Action action) { diff --git a/hawkbit-rest/hawkbit-ddi-resource/src/main/java/org/eclipse/hawkbit/ddi/rest/resource/DdiApiConfiguration.java b/hawkbit-rest/hawkbit-ddi-resource/src/main/java/org/eclipse/hawkbit/ddi/rest/resource/DdiApiConfiguration.java index 7480e4aa3..e0b886c19 100644 --- a/hawkbit-rest/hawkbit-ddi-resource/src/main/java/org/eclipse/hawkbit/ddi/rest/resource/DdiApiConfiguration.java +++ b/hawkbit-rest/hawkbit-ddi-resource/src/main/java/org/eclipse/hawkbit/ddi/rest/resource/DdiApiConfiguration.java @@ -23,7 +23,7 @@ import org.springframework.stereotype.Controller; */ @Configuration @ComponentScan -@Import({RestConfiguration.class, OpenApiConfiguration.class}) +@Import({ RestConfiguration.class, OpenApiConfiguration.class }) public class DdiApiConfiguration { } diff --git a/hawkbit-rest/hawkbit-ddi-resource/src/main/java/org/eclipse/hawkbit/ddi/rest/resource/DdiRootController.java b/hawkbit-rest/hawkbit-ddi-resource/src/main/java/org/eclipse/hawkbit/ddi/rest/resource/DdiRootController.java index 85285d0ad..467380fcd 100644 --- a/hawkbit-rest/hawkbit-ddi-resource/src/main/java/org/eclipse/hawkbit/ddi/rest/resource/DdiRootController.java +++ b/hawkbit-rest/hawkbit-ddi-resource/src/main/java/org/eclipse/hawkbit/ddi/rest/resource/DdiRootController.java @@ -19,8 +19,8 @@ import java.util.Optional; import jakarta.servlet.http.HttpServletRequest; import jakarta.validation.Valid; import jakarta.validation.constraints.NotEmpty; - import jakarta.validation.constraints.NotNull; + import lombok.extern.slf4j.Slf4j; import org.eclipse.hawkbit.api.ArtifactUrlHandler; import org.eclipse.hawkbit.artifact.repository.model.DbArtifact; @@ -28,6 +28,7 @@ import org.eclipse.hawkbit.ddi.json.model.DdiActionFeedback; import org.eclipse.hawkbit.ddi.json.model.DdiActionHistory; import org.eclipse.hawkbit.ddi.json.model.DdiActivateAutoConfirmation; import org.eclipse.hawkbit.ddi.json.model.DdiArtifact; +import org.eclipse.hawkbit.ddi.json.model.DdiAssignedVersion; import org.eclipse.hawkbit.ddi.json.model.DdiAutoConfirmationState; import org.eclipse.hawkbit.ddi.json.model.DdiCancel; import org.eclipse.hawkbit.ddi.json.model.DdiCancelActionToStop; @@ -41,7 +42,6 @@ import org.eclipse.hawkbit.ddi.json.model.DdiDeployment; import org.eclipse.hawkbit.ddi.json.model.DdiDeployment.DdiMaintenanceWindowStatus; import org.eclipse.hawkbit.ddi.json.model.DdiDeployment.HandlingType; import org.eclipse.hawkbit.ddi.json.model.DdiDeploymentBase; -import org.eclipse.hawkbit.ddi.json.model.DdiAssignedVersion; import org.eclipse.hawkbit.ddi.json.model.DdiResult.FinalResult; import org.eclipse.hawkbit.ddi.json.model.DdiUpdateMode; import org.eclipse.hawkbit.ddi.rest.api.DdiRestConstants; @@ -99,7 +99,7 @@ import org.springframework.web.context.WebApplicationContext; @RestController @Scope(value = WebApplicationContext.SCOPE_REQUEST) public class DdiRootController implements DdiRootControllerRestApi { - + private static final String GIVEN_ACTION_IS_NOT_ASSIGNED_TO_GIVEN_TARGET = "given action ({}) is not assigned to given target ({})."; private static final String FALLBACK_REMARK = "Initiated using the Device Direct Integration API without providing a remark."; @@ -208,36 +208,14 @@ public class DdiRootController implements DdiRootControllerRestApi { RequestResponseContextHolder.getHttpServletRequest(), (length, shippedSinceLastEvent, total) -> eventPublisher.publishEvent(new DownloadProgressEvent( - tenantAware.getCurrentTenant(), statusId, shippedSinceLastEvent, - serviceMatcher != null ? serviceMatcher.getBusId() : bus.getId()))); + tenantAware.getCurrentTenant(), statusId, shippedSinceLastEvent, + serviceMatcher != null ? serviceMatcher.getBusId() : bus.getId()))); } } return result; } - private ActionStatus checkAndLogDownload(final HttpServletRequest request, final Target target, final Long module) { - final Action action = controllerManagement - .getActionForDownloadByTargetAndSoftwareModule(target.getControllerId(), module) - .orElseThrow(() -> new SoftwareModuleNotAssignedToTargetException(module, target.getControllerId())); - final String range = request.getHeader("Range"); - - final String message; - if (range != null) { - message = RepositoryConstants.SERVER_MESSAGE_PREFIX + "Target downloads range " + range + " of: " - + request.getRequestURI(); - } else { - message = RepositoryConstants.SERVER_MESSAGE_PREFIX + "Target downloads " + request.getRequestURI(); - } - - return controllerManagement.addInformationalActionStatus( - entityFactory.actionStatus().create(action.getId()).status(Status.DOWNLOAD).message(message)); - } - - private static boolean checkModule(final String fileName, final SoftwareModule module) { - return module == null || module.getArtifactByFilename(fileName).isEmpty(); - } - @Override // Exception squid:S3655 - Optional access is checked in checkModule // subroutine @@ -301,30 +279,6 @@ public class DdiRootController implements DdiRootControllerRestApi { return ResponseEntity.notFound().build(); } - private static HandlingType calculateDownloadType(final Action action) { - if (action.isDownloadOnly() || action.isForcedOrTimeForced()) { - return HandlingType.FORCED; - } - return HandlingType.ATTEMPT; - } - - private static DdiMaintenanceWindowStatus calculateMaintenanceWindow(final Action action) { - if (action.hasMaintenanceSchedule()) { - return action.isMaintenanceWindowAvailable() ? DdiMaintenanceWindowStatus.AVAILABLE - : DdiMaintenanceWindowStatus.UNAVAILABLE; - } - return null; - } - - private static HandlingType calculateUpdateType(final Action action, final HandlingType downloadType) { - if (action.isDownloadOnly()) { - return HandlingType.SKIP; - } else if (action.hasMaintenanceSchedule()) { - return action.isMaintenanceWindowAvailable() ? downloadType : HandlingType.SKIP; - } - return downloadType; - } - @Override public ResponseEntity postDeploymentBaseActionFeedback(@Valid @RequestBody final DdiActionFeedback feedback, @PathVariable("tenant") final String tenant, @PathVariable("controllerId") final String controllerId, @@ -349,91 +303,6 @@ public class DdiRootController implements DdiRootControllerRestApi { return ResponseEntity.ok().build(); } - private ActionStatusCreate generateUpdateStatus(final DdiActionFeedback feedback, final String controllerId, - final Long actionId) { - - final ActionStatusCreate actionStatusCreate = entityFactory.actionStatus().create(actionId); - - final List messages = new ArrayList<>(); - - if (!CollectionUtils.isEmpty(feedback.getStatus().getDetails())) { - messages.addAll(feedback.getStatus().getDetails()); - } - - final Integer code = feedback.getStatus().getCode(); - if (code != null) { - actionStatusCreate.code(code); - messages.add("Device reported status code: " + code); - } - - final Status status; - switch (feedback.getStatus().getExecution()) { - case CANCELED: - log.debug("Controller confirmed cancel (actionId: {}, controllerId: {}) as we got {} report.", actionId, - controllerId, feedback.getStatus().getExecution()); - status = Status.CANCELED; - addMessageIfEmpty("Target confirmed cancellation.", messages); - break; - case REJECTED: - log.info("Controller reported internal error (actionId: {}, controllerId: {}) as we got {} report.", - actionId, controllerId, feedback.getStatus().getExecution()); - status = Status.WARNING; - addMessageIfEmpty("Target REJECTED update", messages); - break; - case CLOSED: - status = handleClosedCase(feedback, controllerId, actionId, messages); - break; - case DOWNLOAD: - log.debug("Controller confirmed status of download (actionId: {}, controllerId: {}) as we got {} report.", - actionId, controllerId, feedback.getStatus().getExecution()); - status = Status.DOWNLOAD; - addMessageIfEmpty("Target confirmed download start", messages); - break; - case DOWNLOADED: - log.debug("Controller confirmed download (actionId: {}, controllerId: {}) as we got {} report.", actionId, - controllerId, feedback.getStatus().getExecution()); - status = Status.DOWNLOADED; - addMessageIfEmpty("Target confirmed download finished", messages); - break; - default: - status = handleDefaultCase(feedback, controllerId, actionId, messages); - break; - } - - return actionStatusCreate.status(status).messages(messages); - } - - private static void addMessageIfEmpty(final String text, final List messages) { - if (messages != null && messages.isEmpty()) { - messages.add(RepositoryConstants.SERVER_MESSAGE_PREFIX + text + "."); - } - } - - private Status handleDefaultCase(final DdiActionFeedback feedback, final String controllerId, final Long actionId, - final List messages) { - final Status status; - log.debug("Controller reported intermediate status (actionId: {}, controllerId: {}) as we got {} report.", - actionId, controllerId, feedback.getStatus().getExecution()); - status = Status.RUNNING; - addMessageIfEmpty("Target reported " + feedback.getStatus().getExecution(), messages); - return status; - } - - private Status handleClosedCase(final DdiActionFeedback feedback, final String controllerId, final Long actionId, - final List messages) { - final Status status; - log.debug("Controller reported closed (actionId: {}, controllerId: {}) as we got {} report.", actionId, - controllerId, feedback.getStatus().getExecution()); - if (feedback.getStatus().getResult().getFinished() == FinalResult.FAILURE) { - status = Status.ERROR; - addMessageIfEmpty("Target reported CLOSED with ERROR!", messages); - } else { - status = Status.FINISHED; - addMessageIfEmpty("Target reported CLOSED with OK!", messages); - } - return status; - } - @Override public ResponseEntity putConfigData(@Valid @RequestBody final DdiConfigData configData, @PathVariable("tenant") final String tenant, @PathVariable("controllerId") final String controllerId) { @@ -501,6 +370,149 @@ public class DdiRootController implements DdiRootControllerRestApi { } + @Override + public ResponseEntity getConfirmationBase(final String tenant, final String controllerId) { + log.debug("getConfirmationBase is called [controllerId={}].", controllerId); + final Target target = controllerManagement.findOrRegisterTargetIfItDoesNotExist(controllerId, IpUtil + .getClientIpFromRequest(RequestResponseContextHolder.getHttpServletRequest(), securityProperties)); + final Action activeAction = controllerManagement.findActiveActionWithHighestWeight(controllerId).orElse(null); + + final DdiAutoConfirmationState autoConfirmationState = getAutoConfirmationState(controllerId); + + final DdiConfirmationBase confirmationBase = DataConversionHelper.createConfirmationBase(target, activeAction, + autoConfirmationState, tenantAware); + return new ResponseEntity<>(confirmationBase, HttpStatus.OK); + } + + @Override + public ResponseEntity getConfirmationBaseAction( + @PathVariable("tenant") final String tenant, @PathVariable("controllerId") final String controllerId, + @PathVariable("actionId") final Long actionId, + @RequestParam(value = "c", required = false, defaultValue = "-1") final int resource, + @RequestParam(value = "actionHistory", defaultValue = DdiRestConstants.NO_ACTION_HISTORY) final Integer actionHistoryMessageCount) { + log.debug("getConfirmationBaseAction({},{})", controllerId, resource); + + final Target target = findTarget(controllerId); + final Action action = findActionForTarget(actionId, target); + + checkAndCancelExpiredAction(action); + + if (!action.isCancelingOrCanceled() && action.isWaitingConfirmation()) { + + final DdiConfirmationBaseAction base = generateDdiConfirmationBase(target, action, + actionHistoryMessageCount); + + log.debug("Found an active UpdateAction for target {}. Returning confirmation: {}", controllerId, base); + + return new ResponseEntity<>(base, HttpStatus.OK); + } + + return ResponseEntity.notFound().build(); + } + + @Override + public ResponseEntity postConfirmationActionFeedback( + @Valid @RequestBody final DdiConfirmationFeedback feedback, @PathVariable("tenant") final String tenant, + @PathVariable("controllerId") final String controllerId, + @PathVariable("actionId") @NotNull final Long actionId) { + log.debug("provideConfirmationActionFeedback with feedback [controllerId={}, actionId={}]: {}", controllerId, + actionId, feedback); + + final Target target = findTarget(controllerId); + final Action action = findActionForTarget(actionId, target); + + try { + + switch (feedback.getConfirmation()) { + case CONFIRMED: + log.info("Controller confirmed the action (actionId: {}, controllerId: {}) as we got {} report.", + actionId, controllerId, feedback.getConfirmation()); + confirmationManagement.confirmAction(actionId, feedback.getCode(), feedback.getDetails()); + break; + case DENIED: + default: + log.debug("Controller denied the action (actionId: {}, controllerId: {}) as we got {} report.", + actionId, controllerId, feedback.getConfirmation()); + confirmationManagement.denyAction(actionId, feedback.getCode(), feedback.getDetails()); + break; + } + } catch (final InvalidConfirmationFeedbackException e) { + if (e.getReason() == InvalidConfirmationFeedbackException.Reason.ACTION_CLOSED) { + log.warn("Updating action {} with confirmation {} not possible since action not active anymore.", + action.getId(), feedback.getConfirmation()); + return new ResponseEntity<>(HttpStatus.GONE); + } else if (e.getReason() == InvalidConfirmationFeedbackException.Reason.NOT_AWAITING_CONFIRMATION) { + log.debug("Action is not waiting for confirmation, deny request."); + return ResponseEntity.notFound().build(); + } + } + + return ResponseEntity.ok().build(); + } + + @Override + public ResponseEntity activateAutoConfirmation(final String tenant, final String controllerId, + final DdiActivateAutoConfirmation body) { + final String initiator = body == null ? null : body.getInitiator(); + final String remark = body == null ? FALLBACK_REMARK : body.getRemark(); + log.debug("Activate auto-confirmation request for device '{}' with payload: [initiator='{}' | remark='{}'", + controllerId, initiator, remark); + confirmationManagement.activateAutoConfirmation(controllerId, initiator, remark); + return new ResponseEntity<>(HttpStatus.OK); + } + + @Override + public ResponseEntity deactivateAutoConfirmation(final String tenant, final String controllerId) { + log.debug("Deactivate auto-confirmation request for device ‘{}‘", controllerId); + confirmationManagement.deactivateAutoConfirmation(controllerId); + return new ResponseEntity<>(HttpStatus.OK); + } + + @Override + public ResponseEntity setAsssignedOfflineVersion(@Valid @RequestBody DdiAssignedVersion ddiAssignedVersion, + @PathVariable("tenant") final String tenant, @PathVariable("controllerId") final String controllerId) { + boolean updated = controllerManagement.updateOfflineAssignedVersion(controllerId, + ddiAssignedVersion.getName(), ddiAssignedVersion.getVersion()); + if (updated) { + return new ResponseEntity<>(HttpStatus.CREATED); + } + return new ResponseEntity<>(HttpStatus.OK); + } + + private static boolean checkModule(final String fileName, final SoftwareModule module) { + return module == null || module.getArtifactByFilename(fileName).isEmpty(); + } + + private static HandlingType calculateDownloadType(final Action action) { + if (action.isDownloadOnly() || action.isForcedOrTimeForced()) { + return HandlingType.FORCED; + } + return HandlingType.ATTEMPT; + } + + private static DdiMaintenanceWindowStatus calculateMaintenanceWindow(final Action action) { + if (action.hasMaintenanceSchedule()) { + return action.isMaintenanceWindowAvailable() ? DdiMaintenanceWindowStatus.AVAILABLE + : DdiMaintenanceWindowStatus.UNAVAILABLE; + } + return null; + } + + private static HandlingType calculateUpdateType(final Action action, final HandlingType downloadType) { + if (action.isDownloadOnly()) { + return HandlingType.SKIP; + } else if (action.hasMaintenanceSchedule()) { + return action.isMaintenanceWindowAvailable() ? downloadType : HandlingType.SKIP; + } + return downloadType; + } + + private static void addMessageIfEmpty(final String text, final List messages) { + if (messages != null && messages.isEmpty()) { + messages.add(RepositoryConstants.SERVER_MESSAGE_PREFIX + text + "."); + } + } + private static ActionStatusCreate generateActionCancelStatus(final DdiActionFeedback feedback, final Target target, final Long actionId, final EntityFactory entityFactory) { @@ -509,21 +521,21 @@ public class DdiRootController implements DdiRootControllerRestApi { final List messages = new ArrayList<>(); final Status status; switch (feedback.getStatus().getExecution()) { - case CANCELED: - status = handleCaseCancelCanceled(feedback, target, actionId, messages); - break; - case REJECTED: - log.info("Target rejected the cancellation request (actionId: {}, controllerId: {}).", actionId, - target.getControllerId()); - status = Status.CANCEL_REJECTED; - messages.add(RepositoryConstants.SERVER_MESSAGE_PREFIX + "Target rejected the cancellation request."); - break; - case CLOSED: - status = handleCancelClosedCase(feedback, messages); - break; - default: - status = Status.RUNNING; - break; + case CANCELED: + status = handleCaseCancelCanceled(feedback, target, actionId, messages); + break; + case REJECTED: + log.info("Target rejected the cancellation request (actionId: {}, controllerId: {}).", actionId, + target.getControllerId()); + status = Status.CANCEL_REJECTED; + messages.add(RepositoryConstants.SERVER_MESSAGE_PREFIX + "Target rejected the cancellation request."); + break; + case CLOSED: + status = handleCancelClosedCase(feedback, messages); + break; + default: + status = Status.RUNNING; + break; } if (feedback.getStatus().getDetails() != null) { @@ -564,6 +576,114 @@ public class DdiRootController implements DdiRootControllerRestApi { return status; } + /** + * Retrieve the update mode from the given update message. + */ + private static UpdateMode getUpdateMode(final DdiConfigData configData) { + final DdiUpdateMode mode = configData.getMode(); + if (mode != null) { + return UpdateMode.valueOf(mode.name()); + } + return null; + } + + private ActionStatus checkAndLogDownload(final HttpServletRequest request, final Target target, final Long module) { + final Action action = controllerManagement + .getActionForDownloadByTargetAndSoftwareModule(target.getControllerId(), module) + .orElseThrow(() -> new SoftwareModuleNotAssignedToTargetException(module, target.getControllerId())); + final String range = request.getHeader("Range"); + + final String message; + if (range != null) { + message = RepositoryConstants.SERVER_MESSAGE_PREFIX + "Target downloads range " + range + " of: " + + request.getRequestURI(); + } else { + message = RepositoryConstants.SERVER_MESSAGE_PREFIX + "Target downloads " + request.getRequestURI(); + } + + return controllerManagement.addInformationalActionStatus( + entityFactory.actionStatus().create(action.getId()).status(Status.DOWNLOAD).message(message)); + } + + private ActionStatusCreate generateUpdateStatus(final DdiActionFeedback feedback, final String controllerId, + final Long actionId) { + + final ActionStatusCreate actionStatusCreate = entityFactory.actionStatus().create(actionId); + + final List messages = new ArrayList<>(); + + if (!CollectionUtils.isEmpty(feedback.getStatus().getDetails())) { + messages.addAll(feedback.getStatus().getDetails()); + } + + final Integer code = feedback.getStatus().getCode(); + if (code != null) { + actionStatusCreate.code(code); + messages.add("Device reported status code: " + code); + } + + final Status status; + switch (feedback.getStatus().getExecution()) { + case CANCELED: + log.debug("Controller confirmed cancel (actionId: {}, controllerId: {}) as we got {} report.", actionId, + controllerId, feedback.getStatus().getExecution()); + status = Status.CANCELED; + addMessageIfEmpty("Target confirmed cancellation.", messages); + break; + case REJECTED: + log.info("Controller reported internal error (actionId: {}, controllerId: {}) as we got {} report.", + actionId, controllerId, feedback.getStatus().getExecution()); + status = Status.WARNING; + addMessageIfEmpty("Target REJECTED update", messages); + break; + case CLOSED: + status = handleClosedCase(feedback, controllerId, actionId, messages); + break; + case DOWNLOAD: + log.debug("Controller confirmed status of download (actionId: {}, controllerId: {}) as we got {} report.", + actionId, controllerId, feedback.getStatus().getExecution()); + status = Status.DOWNLOAD; + addMessageIfEmpty("Target confirmed download start", messages); + break; + case DOWNLOADED: + log.debug("Controller confirmed download (actionId: {}, controllerId: {}) as we got {} report.", actionId, + controllerId, feedback.getStatus().getExecution()); + status = Status.DOWNLOADED; + addMessageIfEmpty("Target confirmed download finished", messages); + break; + default: + status = handleDefaultCase(feedback, controllerId, actionId, messages); + break; + } + + return actionStatusCreate.status(status).messages(messages); + } + + private Status handleDefaultCase(final DdiActionFeedback feedback, final String controllerId, final Long actionId, + final List messages) { + final Status status; + log.debug("Controller reported intermediate status (actionId: {}, controllerId: {}) as we got {} report.", + actionId, controllerId, feedback.getStatus().getExecution()); + status = Status.RUNNING; + addMessageIfEmpty("Target reported " + feedback.getStatus().getExecution(), messages); + return status; + } + + private Status handleClosedCase(final DdiActionFeedback feedback, final String controllerId, final Long actionId, + final List messages) { + final Status status; + log.debug("Controller reported closed (actionId: {}, controllerId: {}) as we got {} report.", actionId, + controllerId, feedback.getStatus().getExecution()); + if (feedback.getStatus().getResult().getFinished() == FinalResult.FAILURE) { + status = Status.ERROR; + addMessageIfEmpty("Target reported CLOSED with ERROR!", messages); + } else { + status = Status.FINISHED; + addMessageIfEmpty("Target reported CLOSED with OK!", messages); + } + return status; + } + private Target findTarget(final String controllerId) { return controllerManagement.getByControllerId(controllerId) .orElseThrow(() -> new EntityNotFoundException(Target.class, controllerId)); @@ -588,8 +708,7 @@ public class DdiRootController implements DdiRootControllerRestApi { * If the action has a maintenance schedule defined but is no longer valid, * cancel the action. * - * @param action - * is the {@link Action} to check. + * @param action is the {@link Action} to check. */ private void checkAndCancelExpiredAction(final Action action) { if (action != null && action.hasMaintenanceSchedule() && action.isMaintenanceScheduleLapsed()) { @@ -601,44 +720,6 @@ public class DdiRootController implements DdiRootControllerRestApi { } } - /** - * Retrieve the update mode from the given update message. - */ - private static UpdateMode getUpdateMode(final DdiConfigData configData) { - final DdiUpdateMode mode = configData.getMode(); - if (mode != null) { - return UpdateMode.valueOf(mode.name()); - } - return null; - } - - - @Override - public ResponseEntity getConfirmationBaseAction( - @PathVariable("tenant") final String tenant, @PathVariable("controllerId") final String controllerId, - @PathVariable("actionId") final Long actionId, - @RequestParam(value = "c", required = false, defaultValue = "-1") final int resource, - @RequestParam(value = "actionHistory", defaultValue = DdiRestConstants.NO_ACTION_HISTORY) final Integer actionHistoryMessageCount) { - log.debug("getConfirmationBaseAction({},{})", controllerId, resource); - - final Target target = findTarget(controllerId); - final Action action = findActionForTarget(actionId, target); - - checkAndCancelExpiredAction(action); - - if (!action.isCancelingOrCanceled() && action.isWaitingConfirmation()) { - - final DdiConfirmationBaseAction base = generateDdiConfirmationBase(target, action, - actionHistoryMessageCount); - - log.debug("Found an active UpdateAction for target {}. Returning confirmation: {}", controllerId, base); - - return new ResponseEntity<>(base, HttpStatus.OK); - } - - return ResponseEntity.notFound().build(); - } - private DdiDeploymentBase generateDdiDeploymentBase(final Target target, final Action action, final Integer actionHistoryMessageCount) { final DdiActionHistory actionHistory = generateDdiActionHistory(action, actionHistoryMessageCount).orElse(null); @@ -672,60 +753,6 @@ public class DdiRootController implements DdiRootControllerRestApi { : Optional.of(new DdiActionHistory(action.getStatus().name(), actionHistoryMsgs)); } - @Override - public ResponseEntity postConfirmationActionFeedback( - @Valid @RequestBody final DdiConfirmationFeedback feedback, @PathVariable("tenant") final String tenant, - @PathVariable("controllerId") final String controllerId, - @PathVariable("actionId") @NotNull final Long actionId) { - log.debug("provideConfirmationActionFeedback with feedback [controllerId={}, actionId={}]: {}", controllerId, - actionId, feedback); - - final Target target = findTarget(controllerId); - final Action action = findActionForTarget(actionId, target); - - try { - - switch (feedback.getConfirmation()) { - case CONFIRMED: - log.info("Controller confirmed the action (actionId: {}, controllerId: {}) as we got {} report.", - actionId, controllerId, feedback.getConfirmation()); - confirmationManagement.confirmAction(actionId, feedback.getCode(), feedback.getDetails()); - break; - case DENIED: - default: - log.debug("Controller denied the action (actionId: {}, controllerId: {}) as we got {} report.", - actionId, controllerId, feedback.getConfirmation()); - confirmationManagement.denyAction(actionId, feedback.getCode(), feedback.getDetails()); - break; - } - } catch (final InvalidConfirmationFeedbackException e) { - if (e.getReason() == InvalidConfirmationFeedbackException.Reason.ACTION_CLOSED) { - log.warn("Updating action {} with confirmation {} not possible since action not active anymore.", - action.getId(), feedback.getConfirmation()); - return new ResponseEntity<>(HttpStatus.GONE); - } else if (e.getReason() == InvalidConfirmationFeedbackException.Reason.NOT_AWAITING_CONFIRMATION) { - log.debug("Action is not waiting for confirmation, deny request."); - return ResponseEntity.notFound().build(); - } - } - - return ResponseEntity.ok().build(); - } - - @Override - public ResponseEntity getConfirmationBase(final String tenant, final String controllerId) { - log.debug("getConfirmationBase is called [controllerId={}].", controllerId); - final Target target = controllerManagement.findOrRegisterTargetIfItDoesNotExist(controllerId, IpUtil - .getClientIpFromRequest(RequestResponseContextHolder.getHttpServletRequest(), securityProperties)); - final Action activeAction = controllerManagement.findActiveActionWithHighestWeight(controllerId).orElse(null); - - final DdiAutoConfirmationState autoConfirmationState = getAutoConfirmationState(controllerId); - - final DdiConfirmationBase confirmationBase = DataConversionHelper.createConfirmationBase(target, activeAction, - autoConfirmationState, tenantAware); - return new ResponseEntity<>(confirmationBase, HttpStatus.OK); - } - private DdiAutoConfirmationState getAutoConfirmationState(final String controllerId) { return confirmationManagement.getStatus(controllerId).map(status -> { final DdiAutoConfirmationState state = DdiAutoConfirmationState.active(status.getActivatedAt()); @@ -739,33 +766,4 @@ public class DdiRootController implements DdiRootControllerRestApi { return DdiAutoConfirmationState.disabled(); }); } - - @Override - public ResponseEntity activateAutoConfirmation(final String tenant, final String controllerId, - final DdiActivateAutoConfirmation body) { - final String initiator = body == null ? null : body.getInitiator(); - final String remark = body == null ? FALLBACK_REMARK : body.getRemark(); - log.debug("Activate auto-confirmation request for device '{}' with payload: [initiator='{}' | remark='{}'", - controllerId, initiator, remark); - confirmationManagement.activateAutoConfirmation(controllerId, initiator, remark); - return new ResponseEntity<>(HttpStatus.OK); - } - - @Override - public ResponseEntity deactivateAutoConfirmation(final String tenant, final String controllerId) { - log.debug("Deactivate auto-confirmation request for device ‘{}‘", controllerId); - confirmationManagement.deactivateAutoConfirmation(controllerId); - return new ResponseEntity<>(HttpStatus.OK); - } - - @Override - public ResponseEntity setAsssignedOfflineVersion(@Valid @RequestBody DdiAssignedVersion ddiAssignedVersion, - @PathVariable("tenant") final String tenant, @PathVariable("controllerId") final String controllerId){ - boolean updated = controllerManagement.updateOfflineAssignedVersion(controllerId, - ddiAssignedVersion.getName(), ddiAssignedVersion.getVersion()); - if (updated) { - return new ResponseEntity<>(HttpStatus.CREATED); - } - return new ResponseEntity<>(HttpStatus.OK); - } } diff --git a/hawkbit-rest/hawkbit-ddi-resource/src/test/java/org/eclipse/hawkbit/ddi/rest/resource/AbstractDDiApiIntegrationTest.java b/hawkbit-rest/hawkbit-ddi-resource/src/test/java/org/eclipse/hawkbit/ddi/rest/resource/AbstractDDiApiIntegrationTest.java index babcace3c..e2f4407d1 100644 --- a/hawkbit-rest/hawkbit-ddi-resource/src/test/java/org/eclipse/hawkbit/ddi/rest/resource/AbstractDDiApiIntegrationTest.java +++ b/hawkbit-rest/hawkbit-ddi-resource/src/test/java/org/eclipse/hawkbit/ddi/rest/resource/AbstractDDiApiIntegrationTest.java @@ -24,10 +24,18 @@ import java.time.Instant; import java.util.Collections; import java.util.List; +import com.fasterxml.jackson.core.JsonFactory; +import com.fasterxml.jackson.core.JsonGenerator; +import com.fasterxml.jackson.core.JsonParser; +import com.fasterxml.jackson.core.JsonProcessingException; +import com.fasterxml.jackson.databind.ObjectMapper; +import com.fasterxml.jackson.dataformat.cbor.CBORFactory; +import com.fasterxml.jackson.dataformat.cbor.CBORGenerator; +import com.fasterxml.jackson.dataformat.cbor.CBORParser; import org.apache.commons.lang3.RandomStringUtils; import org.eclipse.hawkbit.ddi.json.model.DdiActionFeedback; -import org.eclipse.hawkbit.ddi.json.model.DdiConfirmationFeedback; import org.eclipse.hawkbit.ddi.json.model.DdiAssignedVersion; +import org.eclipse.hawkbit.ddi.json.model.DdiConfirmationFeedback; import org.eclipse.hawkbit.ddi.json.model.DdiProgress; import org.eclipse.hawkbit.ddi.json.model.DdiResult; import org.eclipse.hawkbit.ddi.json.model.DdiStatus; @@ -49,15 +57,6 @@ import org.springframework.test.web.servlet.ResultActions; import org.springframework.test.web.servlet.ResultMatcher; import org.springframework.test.web.servlet.request.MockMvcRequestBuilders; -import com.fasterxml.jackson.core.JsonFactory; -import com.fasterxml.jackson.core.JsonGenerator; -import com.fasterxml.jackson.core.JsonParser; -import com.fasterxml.jackson.core.JsonProcessingException; -import com.fasterxml.jackson.databind.ObjectMapper; -import com.fasterxml.jackson.dataformat.cbor.CBORFactory; -import com.fasterxml.jackson.dataformat.cbor.CBORGenerator; -import com.fasterxml.jackson.dataformat.cbor.CBORParser; - @ContextConfiguration(classes = { DdiApiConfiguration.class, RestConfiguration.class, RepositoryApplicationConfiguration.class, TestConfiguration.class }) @Import(TestChannelBinderConfiguration.class) @@ -65,19 +64,17 @@ import com.fasterxml.jackson.dataformat.cbor.CBORParser; public abstract class AbstractDDiApiIntegrationTest extends AbstractRestIntegrationTest { public static final int HTTP_PORT = 8080; - protected static final String HTTP_LOCALHOST = String.format("http://localhost:%s/",HTTP_PORT); + protected static final String HTTP_LOCALHOST = String.format("http://localhost:%s/", HTTP_PORT); protected static final String CONTROLLER_BASE = "/{tenant}/controller/v1/{controllerId}"; protected static final String SOFTWARE_MODULE_ARTIFACTS = CONTROLLER_BASE + "/softwaremodules/{softwareModuleId}/artifacts"; protected static final String DEPLOYMENT_BASE = CONTROLLER_BASE + "/deploymentBase/{actionId}"; + protected static final String DEPLOYMENT_FEEDBACK = DEPLOYMENT_BASE + "/feedback"; protected static final String CANCEL_ACTION = CONTROLLER_BASE + "/cancelAction/{actionId}"; + protected static final String CANCEL_FEEDBACK = CANCEL_ACTION + "/feedback"; protected static final String INSTALLED_BASE = CONTROLLER_BASE + "/installedBase/{actionId}"; protected static final String INSTALLED_BASE_ROOT = CONTROLLER_BASE + "/installedBase"; - - protected static final String DEPLOYMENT_FEEDBACK = DEPLOYMENT_BASE + "/feedback"; - protected static final String CANCEL_FEEDBACK = CANCEL_ACTION + "/feedback"; - protected static final String CONFIRMATION_BASE = CONTROLLER_BASE + "/confirmationBase"; protected static final String ACTIVATE_AUTO_CONFIRM = CONFIRMATION_BASE + "/activateAutoConfirm"; protected static final String DEACTIVATE_AUTO_CONFIRM = CONFIRMATION_BASE + "/deactivateAutoConfirm"; @@ -91,11 +88,9 @@ public abstract class AbstractDDiApiIntegrationTest extends AbstractRestIntegrat /** * Convert JSON to a CBOR equivalent. * - * @param json - * JSON object to convert + * @param json JSON object to convert * @return Equivalent CBOR data - * @throws IOException - * Invalid JSON input + * @throws IOException Invalid JSON input */ protected static byte[] jsonToCbor(final String json) throws IOException { final JsonFactory jsonFactory = new JsonFactory(); @@ -113,11 +108,9 @@ public abstract class AbstractDDiApiIntegrationTest extends AbstractRestIntegrat /** * Convert CBOR to JSON equivalent. * - * @param input - * CBOR data to convert + * @param input CBOR data to convert * @return Equivalent JSON string - * @throws IOException - * Invalid CBOR input + * @throws IOException Invalid CBOR input */ protected static String cborToJson(final byte[] input) throws IOException { final CBORFactory cborFactory = new CBORFactory(); @@ -132,6 +125,10 @@ public abstract class AbstractDDiApiIntegrationTest extends AbstractRestIntegrat return stringWriter.toString(); } + protected static ObjectMapper getMapper() { + return objectMapper; + } + protected ResultActions postDeploymentFeedback(final String controllerId, final Long actionId, final String content, final ResultMatcher statusMatcher) throws Exception { return postDeploymentFeedback(MediaType.APPLICATION_JSON_UTF8, controllerId, actionId, content.getBytes(), @@ -139,7 +136,7 @@ public abstract class AbstractDDiApiIntegrationTest extends AbstractRestIntegrat } protected ResultActions putInstalledBase(final String controllerId, final String content, - final ResultMatcher statusMatcher) throws Exception { + final ResultMatcher statusMatcher) throws Exception { return mvc.perform(put(INSTALLED_BASE_ROOT, tenantAware.getCurrentTenant(), controllerId) .content(content.getBytes()).contentType(MediaType.APPLICATION_JSON_UTF8)) .andDo(MockMvcResultPrinter.print()).andExpect(statusMatcher); @@ -170,7 +167,7 @@ public abstract class AbstractDDiApiIntegrationTest extends AbstractRestIntegrat protected ResultActions performGet(final String url, final MediaType mediaType, final ResultMatcher statusMatcher, final String... values) throws Exception { return mvc.perform(MockMvcRequestBuilders.get(url, values).accept(mediaType) - .with(new RequestOnHawkbitDefaultPortPostProcessor())) + .with(new RequestOnHawkbitDefaultPortPostProcessor())) .andDo(MockMvcResultPrinter.print()).andExpect(statusMatcher) .andExpect(content().contentTypeCompatibleWith(mediaType)); } @@ -200,61 +197,6 @@ public abstract class AbstractDDiApiIntegrationTest extends AbstractRestIntegrat getDownloadAndUploadType(actionType), getDownloadAndUploadType(actionType)); } - private ResultActions verifyBasePayload(final String prefix, final ResultActions resultActions, final String controllerId, - final DistributionSet ds, final Artifact artifact, final Artifact artifactSignature, final Long actionId, - final Long osModuleId, final String downloadType, final String updateType) throws Exception { - return resultActions.andExpect(jsonPath("$.id", equalTo(String.valueOf(actionId)))) - .andExpect(jsonPath(prefix + ".download", equalTo(downloadType))) - .andExpect(jsonPath(prefix + ".update", equalTo(updateType))) - .andExpect(jsonPath(prefix + ".chunks[?(@.part=='jvm')].name", - contains(ds.findFirstModuleByType(runtimeType).get().getName()))) - .andExpect(jsonPath(prefix + ".chunks[?(@.part=='jvm')].version", - contains(ds.findFirstModuleByType(runtimeType).get().getVersion()))) - .andExpect(jsonPath(prefix + ".chunks[?(@.part=='os')].name", - contains(ds.findFirstModuleByType(osType).get().getName()))) - .andExpect(jsonPath(prefix + ".chunks[?(@.part=='os')].version", - contains(ds.findFirstModuleByType(osType).get().getVersion()))) - .andExpect(jsonPath(prefix + ".chunks[?(@.part=='os')].artifacts[0].size", contains(ARTIFACT_SIZE))) - .andExpect(jsonPath(prefix + ".chunks[?(@.part=='os')].artifacts[0].filename", - contains(artifact.getFilename()))) - .andExpect(jsonPath(prefix + ".chunks[?(@.part=='os')].artifacts[0].hashes.md5", - contains(artifact.getMd5Hash()))) - .andExpect(jsonPath(prefix + ".chunks[?(@.part=='os')].artifacts[0].hashes.sha1", - contains(artifact.getSha1Hash()))) - .andExpect(jsonPath(prefix + ".chunks[?(@.part=='os')].artifacts[0].hashes.sha256", - contains(artifact.getSha256Hash()))) - .andExpect(jsonPath(prefix + ".chunks[?(@.part=='os')].artifacts[0]._links.download-http.href", - contains(HTTP_LOCALHOST + tenantAware.getCurrentTenant() + "/controller/v1/" + controllerId - + "/softwaremodules/" + osModuleId + "/artifacts/" + artifact.getFilename() - + "/download"))) - .andExpect(jsonPath(prefix + ".chunks[?(@.part=='os')].artifacts[0]._links.md5sum-http.href", - contains(HTTP_LOCALHOST + tenantAware.getCurrentTenant() + "/controller/v1/" + controllerId - + "/softwaremodules/" + osModuleId + "/artifacts/" + artifact.getFilename() - + "/download.MD5SUM"))) - .andExpect(jsonPath(prefix + ".chunks[?(@.part=='os')].artifacts[1].size", contains(ARTIFACT_SIZE))) - .andExpect(jsonPath(prefix + ".chunks[?(@.part=='os')].artifacts[1].filename", - contains(artifactSignature.getFilename()))) - .andExpect(jsonPath(prefix + ".chunks[?(@.part=='os')].artifacts[1].hashes.md5", - contains(artifactSignature.getMd5Hash()))) - .andExpect(jsonPath(prefix + ".chunks[?(@.part=='os')].artifacts[1].hashes.sha1", - contains(artifactSignature.getSha1Hash()))) - .andExpect(jsonPath(prefix + ".chunks[?(@.part=='os')].artifacts[1].hashes.sha256", - contains(artifactSignature.getSha256Hash()))) - .andExpect(jsonPath(prefix + ".chunks[?(@.part=='os')].artifacts[1]._links.download-http.href", - contains(HTTP_LOCALHOST + tenantAware.getCurrentTenant() + "/controller/v1/" + controllerId - + "/softwaremodules/" + osModuleId + "/artifacts/" + artifactSignature.getFilename() - + "/download"))) - .andExpect(jsonPath(prefix + ".chunks[?(@.part=='os')].artifacts[1]._links.md5sum-http.href", - contains(HTTP_LOCALHOST + tenantAware.getCurrentTenant() + "/controller/v1/" + controllerId - + "/softwaremodules/" + osModuleId + "/artifacts/" + artifactSignature.getFilename() - + "/download.MD5SUM"))) - .andExpect(jsonPath(prefix + ".chunks[?(@.part=='bApp')].version", - contains(ds.findFirstModuleByType(appType).get().getVersion()))) - .andExpect(jsonPath(prefix + ".chunks[?(@.part=='bApp')].metadata").doesNotExist()) - .andExpect(jsonPath(prefix + ".chunks[?(@.part=='bApp')].name") - .value(ds.findFirstModuleByType(appType).get().getName())); - } - protected String installedBaseLink(final String controllerId, final String actionId) { return HTTP_LOCALHOST + tenantAware.getCurrentTenant() + "/controller/v1/" + controllerId + "/installedBase/" + actionId; @@ -265,13 +207,6 @@ public abstract class AbstractDDiApiIntegrationTest extends AbstractRestIntegrat + "/deploymentBase/" + actionId; } - private static String getDownloadAndUploadType(final Action.ActionType actionType) { - if (Action.ActionType.FORCED.equals(actionType)) { - return "forced"; - } - return "attempt"; - } - protected String getJsonRejectedCancelActionFeedback() throws JsonProcessingException { return getJsonActionFeedback(DdiStatus.ExecutionStatus.REJECTED, DdiResult.FinalResult.SUCCESS, Collections.singletonList("rejected")); @@ -367,7 +302,7 @@ public abstract class AbstractDDiApiIntegrationTest extends AbstractRestIntegrat } protected String getJsonConfirmationFeedback(final DdiConfirmationFeedback.Confirmation confirmation, - final Integer code, final List messages) throws JsonProcessingException { + final Integer code, final List messages) throws JsonProcessingException { return objectMapper.writeValueAsString(new DdiConfirmationFeedback(confirmation, code, messages)); } @@ -375,10 +310,6 @@ public abstract class AbstractDDiApiIntegrationTest extends AbstractRestIntegrat return objectMapper.writeValueAsString(new DdiAssignedVersion(name, version)); } - protected static ObjectMapper getMapper(){ - return objectMapper; - } - protected ResultActions getAndVerifyConfirmationBasePayload(final String controllerId, final MediaType mediaType, final DistributionSet ds, final Artifact artifact, final Artifact artifactSignature, final Long actionId, final Long osModuleId, final String downloadType, final String updateType) throws Exception { @@ -391,4 +322,66 @@ public abstract class AbstractDDiApiIntegrationTest extends AbstractRestIntegrat static void implicitLock(final DistributionSet set) { ((JpaDistributionSet) set).setOptLockRevision(set.getOptLockRevision() + 1); } + + private static String getDownloadAndUploadType(final Action.ActionType actionType) { + if (Action.ActionType.FORCED.equals(actionType)) { + return "forced"; + } + return "attempt"; + } + + private ResultActions verifyBasePayload(final String prefix, final ResultActions resultActions, final String controllerId, + final DistributionSet ds, final Artifact artifact, final Artifact artifactSignature, final Long actionId, + final Long osModuleId, final String downloadType, final String updateType) throws Exception { + return resultActions.andExpect(jsonPath("$.id", equalTo(String.valueOf(actionId)))) + .andExpect(jsonPath(prefix + ".download", equalTo(downloadType))) + .andExpect(jsonPath(prefix + ".update", equalTo(updateType))) + .andExpect(jsonPath(prefix + ".chunks[?(@.part=='jvm')].name", + contains(ds.findFirstModuleByType(runtimeType).get().getName()))) + .andExpect(jsonPath(prefix + ".chunks[?(@.part=='jvm')].version", + contains(ds.findFirstModuleByType(runtimeType).get().getVersion()))) + .andExpect(jsonPath(prefix + ".chunks[?(@.part=='os')].name", + contains(ds.findFirstModuleByType(osType).get().getName()))) + .andExpect(jsonPath(prefix + ".chunks[?(@.part=='os')].version", + contains(ds.findFirstModuleByType(osType).get().getVersion()))) + .andExpect(jsonPath(prefix + ".chunks[?(@.part=='os')].artifacts[0].size", contains(ARTIFACT_SIZE))) + .andExpect(jsonPath(prefix + ".chunks[?(@.part=='os')].artifacts[0].filename", + contains(artifact.getFilename()))) + .andExpect(jsonPath(prefix + ".chunks[?(@.part=='os')].artifacts[0].hashes.md5", + contains(artifact.getMd5Hash()))) + .andExpect(jsonPath(prefix + ".chunks[?(@.part=='os')].artifacts[0].hashes.sha1", + contains(artifact.getSha1Hash()))) + .andExpect(jsonPath(prefix + ".chunks[?(@.part=='os')].artifacts[0].hashes.sha256", + contains(artifact.getSha256Hash()))) + .andExpect(jsonPath(prefix + ".chunks[?(@.part=='os')].artifacts[0]._links.download-http.href", + contains(HTTP_LOCALHOST + tenantAware.getCurrentTenant() + "/controller/v1/" + controllerId + + "/softwaremodules/" + osModuleId + "/artifacts/" + artifact.getFilename() + + "/download"))) + .andExpect(jsonPath(prefix + ".chunks[?(@.part=='os')].artifacts[0]._links.md5sum-http.href", + contains(HTTP_LOCALHOST + tenantAware.getCurrentTenant() + "/controller/v1/" + controllerId + + "/softwaremodules/" + osModuleId + "/artifacts/" + artifact.getFilename() + + "/download.MD5SUM"))) + .andExpect(jsonPath(prefix + ".chunks[?(@.part=='os')].artifacts[1].size", contains(ARTIFACT_SIZE))) + .andExpect(jsonPath(prefix + ".chunks[?(@.part=='os')].artifacts[1].filename", + contains(artifactSignature.getFilename()))) + .andExpect(jsonPath(prefix + ".chunks[?(@.part=='os')].artifacts[1].hashes.md5", + contains(artifactSignature.getMd5Hash()))) + .andExpect(jsonPath(prefix + ".chunks[?(@.part=='os')].artifacts[1].hashes.sha1", + contains(artifactSignature.getSha1Hash()))) + .andExpect(jsonPath(prefix + ".chunks[?(@.part=='os')].artifacts[1].hashes.sha256", + contains(artifactSignature.getSha256Hash()))) + .andExpect(jsonPath(prefix + ".chunks[?(@.part=='os')].artifacts[1]._links.download-http.href", + contains(HTTP_LOCALHOST + tenantAware.getCurrentTenant() + "/controller/v1/" + controllerId + + "/softwaremodules/" + osModuleId + "/artifacts/" + artifactSignature.getFilename() + + "/download"))) + .andExpect(jsonPath(prefix + ".chunks[?(@.part=='os')].artifacts[1]._links.md5sum-http.href", + contains(HTTP_LOCALHOST + tenantAware.getCurrentTenant() + "/controller/v1/" + controllerId + + "/softwaremodules/" + osModuleId + "/artifacts/" + artifactSignature.getFilename() + + "/download.MD5SUM"))) + .andExpect(jsonPath(prefix + ".chunks[?(@.part=='bApp')].version", + contains(ds.findFirstModuleByType(appType).get().getVersion()))) + .andExpect(jsonPath(prefix + ".chunks[?(@.part=='bApp')].metadata").doesNotExist()) + .andExpect(jsonPath(prefix + ".chunks[?(@.part=='bApp')].name") + .value(ds.findFirstModuleByType(appType).get().getName())); + } } \ No newline at end of file diff --git a/hawkbit-rest/hawkbit-ddi-resource/src/test/java/org/eclipse/hawkbit/ddi/rest/resource/DdiArtifactDownloadTest.java b/hawkbit-rest/hawkbit-ddi-resource/src/test/java/org/eclipse/hawkbit/ddi/rest/resource/DdiArtifactDownloadTest.java index a4c04f769..b04adf0ba 100644 --- a/hawkbit-rest/hawkbit-ddi-resource/src/test/java/org/eclipse/hawkbit/ddi/rest/resource/DdiArtifactDownloadTest.java +++ b/hawkbit-rest/hawkbit-ddi-resource/src/test/java/org/eclipse/hawkbit/ddi/rest/resource/DdiArtifactDownloadTest.java @@ -29,6 +29,9 @@ import java.util.List; import java.util.Locale; import java.util.TimeZone; +import io.qameta.allure.Description; +import io.qameta.allure.Feature; +import io.qameta.allure.Story; import org.apache.commons.lang3.RandomUtils; import org.eclipse.hawkbit.ddi.rest.resource.DdiArtifactDownloadTest.DownloadTestConfiguration; import org.eclipse.hawkbit.repository.event.remote.DownloadProgressEvent; @@ -48,10 +51,6 @@ import org.springframework.http.HttpHeaders; import org.springframework.http.MediaType; import org.springframework.test.web.servlet.MvcResult; -import io.qameta.allure.Description; -import io.qameta.allure.Feature; -import io.qameta.allure.Story; - /** * Test artifact downloads from the controller. */ @@ -102,55 +101,55 @@ public class DdiArtifactDownloadTest extends AbstractDDiApiIntegrationTest { // test now consistent data to test allowed methods mvc.perform(get("/{tenant}/controller/v1/{controllerId}/softwaremodules/{smId}/artifacts/{filename}", - tenantAware.getCurrentTenant(), target.getControllerId(), getOsModule(ds), artifact.getFilename()) + tenantAware.getCurrentTenant(), target.getControllerId(), getOsModule(ds), artifact.getFilename()) .header(HttpHeaders.IF_MATCH, artifact.getSha1Hash())) .andExpect(status().isOk()); mvc.perform(get("/{tenant}/controller/v1/{controllerId}/softwaremodules/{smId}/artifacts/{filename}.MD5SUM", - tenantAware.getCurrentTenant(), target.getControllerId(), getOsModule(ds), artifact.getFilename())) + tenantAware.getCurrentTenant(), target.getControllerId(), getOsModule(ds), artifact.getFilename())) .andExpect(status().isOk()); // test failed If-match mvc.perform(get("/{tenant}/controller/v1/{controllerId}/softwaremodules/{smId}/artifacts/{filename}", - tenantAware.getCurrentTenant(), target.getControllerId(), getOsModule(ds), artifact.getFilename()) + tenantAware.getCurrentTenant(), target.getControllerId(), getOsModule(ds), artifact.getFilename()) .header(HttpHeaders.IF_MATCH, "fsjkhgjfdhg")) .andExpect(status().isPreconditionFailed()); // test invalid range mvc.perform(get("/{tenant}/controller/v1/{controllerId}/softwaremodules/{smId}/artifacts/{filename}", - tenantAware.getCurrentTenant(), target.getControllerId(), getOsModule(ds), artifact.getFilename()) + tenantAware.getCurrentTenant(), target.getControllerId(), getOsModule(ds), artifact.getFilename()) .header("Range", "bytes=1-10,hdsfjksdh")) .andExpect(header().string("Content-Range", "bytes */" + 5 * 1024)) .andExpect(status().isRequestedRangeNotSatisfiable()); mvc.perform(get("/{tenant}/controller/v1/{controllerId}/softwaremodules/{smId}/artifacts/{filename}", - tenantAware.getCurrentTenant(), target.getControllerId(), getOsModule(ds), artifact.getFilename()) + tenantAware.getCurrentTenant(), target.getControllerId(), getOsModule(ds), artifact.getFilename()) .header("Range", "bytes=100-10")) .andExpect(header().string("Content-Range", "bytes */" + 5 * 1024)) .andExpect(status().isRequestedRangeNotSatisfiable()); // not allowed methods mvc.perform(put("/{tenant}/controller/v1/{controllerId}/softwaremodules/{smId}/artifacts/{filename}", - tenantAware.getCurrentTenant(), target.getControllerId(), getOsModule(ds), artifact.getFilename())) + tenantAware.getCurrentTenant(), target.getControllerId(), getOsModule(ds), artifact.getFilename())) .andExpect(status().isMethodNotAllowed()); mvc.perform(delete("/{tenant}/controller/v1/{controllerId}/softwaremodules/{smId}/artifacts/{filename}", - tenantAware.getCurrentTenant(), target.getControllerId(), getOsModule(ds), artifact.getFilename())) + tenantAware.getCurrentTenant(), target.getControllerId(), getOsModule(ds), artifact.getFilename())) .andExpect(status().isMethodNotAllowed()); mvc.perform(post("/{tenant}/controller/v1/{controllerId}/softwaremodules/{smId}/artifacts/{filename}", - tenantAware.getCurrentTenant(), target.getControllerId(), getOsModule(ds), artifact.getFilename())) + tenantAware.getCurrentTenant(), target.getControllerId(), getOsModule(ds), artifact.getFilename())) .andExpect(status().isMethodNotAllowed()); mvc.perform(put("/{tenant}/controller/v1/{controllerId}/softwaremodules/{smId}/artifacts/{filename}.MD5SUM", - tenantAware.getCurrentTenant(), target.getControllerId(), getOsModule(ds), artifact.getFilename())) + tenantAware.getCurrentTenant(), target.getControllerId(), getOsModule(ds), artifact.getFilename())) .andExpect(status().isMethodNotAllowed()); mvc.perform(delete("/{tenant}/controller/v1/{controllerId}/softwaremodules/{smId}/artifacts/{filename}.MD5SUM", - tenantAware.getCurrentTenant(), target.getControllerId(), getOsModule(ds), artifact.getFilename())) + tenantAware.getCurrentTenant(), target.getControllerId(), getOsModule(ds), artifact.getFilename())) .andExpect(status().isMethodNotAllowed()); mvc.perform(post("/{tenant}/controller/v1/{controllerId}/softwaremodules/{smId}/artifacts/{filename}.MD5SUM", - tenantAware.getCurrentTenant(), target.getControllerId(), getOsModule(ds), artifact.getFilename())) + tenantAware.getCurrentTenant(), target.getControllerId(), getOsModule(ds), artifact.getFilename())) .andExpect(status().isMethodNotAllowed()); } @@ -182,8 +181,8 @@ public class DdiArtifactDownloadTest extends AbstractDDiApiIntegrationTest { // now assign and download successful assignDistributionSet(ds, targets); final MvcResult result = mvc.perform(get( - "/{tenant}/controller/v1/{controllerId}/softwaremodules/{softwareModuleId}/artifacts/{filename}", - tenantAware.getCurrentTenant(), target.getControllerId(), getOsModule(ds), artifact.getFilename())) + "/{tenant}/controller/v1/{controllerId}/softwaremodules/{softwareModuleId}/artifacts/{filename}", + tenantAware.getCurrentTenant(), target.getControllerId(), getOsModule(ds), artifact.getFilename())) .andExpect(status().isOk()).andExpect(content().contentType(MediaType.APPLICATION_OCTET_STREAM)) .andExpect(header().string("Accept-Ranges", "bytes")) .andExpect(header().string("Last-Modified", dateFormat.format(new Date(artifact.getCreatedAt())))) @@ -191,7 +190,8 @@ public class DdiArtifactDownloadTest extends AbstractDDiApiIntegrationTest { .andReturn(); assertTrue( - Arrays.equals(result.getResponse().getContentAsByteArray(), random), "The same file that was uploaded is expected when downloaded"); + Arrays.equals(result.getResponse().getContentAsByteArray(), random), + "The same file that was uploaded is expected when downloaded"); // download complete assertThat(downLoadProgress).isEqualTo(10); @@ -217,8 +217,8 @@ public class DdiArtifactDownloadTest extends AbstractDDiApiIntegrationTest { // download final MvcResult result = mvc.perform(get( - "/{tenant}/controller/v1/{controllerId}/softwaremodules/{softwareModuleId}/artifacts/{filename}.MD5SUM", - tenantAware.getCurrentTenant(), target.getControllerId(), getOsModule(ds), artifact.getFilename())) + "/{tenant}/controller/v1/{controllerId}/softwaremodules/{softwareModuleId}/artifacts/{filename}.MD5SUM", + tenantAware.getCurrentTenant(), target.getControllerId(), getOsModule(ds), artifact.getFilename())) .andExpect(status().isOk()).andExpect(header().string("Content-Disposition", "attachment;filename=" + artifact.getFilename() + ".MD5SUM")) .andReturn(); @@ -258,8 +258,8 @@ public class DdiArtifactDownloadTest extends AbstractDDiApiIntegrationTest { final String rangeString = "" + i * range + "-" + ((i + 1) * range - 1); final MvcResult result = mvc.perform(get( - "/{tenant}/controller/v1/{controllerId}/softwaremodules/{softwareModuleId}/artifacts/{filename}", - tenantAware.getCurrentTenant(), target.getControllerId(), getOsModule(ds), "file1").header("Range", + "/{tenant}/controller/v1/{controllerId}/softwaremodules/{softwareModuleId}/artifacts/{filename}", + tenantAware.getCurrentTenant(), target.getControllerId(), getOsModule(ds), "file1").header("Range", "bytes=" + rangeString)) .andExpect(status().isPartialContent()).andExpect(header().string("ETag", artifact.getSha1Hash())) .andExpect(content().contentType(MediaType.APPLICATION_OCTET_STREAM)) @@ -276,8 +276,8 @@ public class DdiArtifactDownloadTest extends AbstractDDiApiIntegrationTest { // return last 1000 Bytes MvcResult result = mvc.perform( - get("/{tenant}/controller/v1/{controllerId}/softwaremodules/{softwareModuleId}/artifacts/{filename}", - tenantAware.getCurrentTenant(), target.getControllerId(), getOsModule(ds), "file1") + get("/{tenant}/controller/v1/{controllerId}/softwaremodules/{softwareModuleId}/artifacts/{filename}", + tenantAware.getCurrentTenant(), target.getControllerId(), getOsModule(ds), "file1") .header("Range", "bytes=-1000")) .andExpect(status().isPartialContent()).andExpect(header().string("ETag", artifact.getSha1Hash())) .andExpect(content().contentType(MediaType.APPLICATION_OCTET_STREAM)) @@ -293,8 +293,8 @@ public class DdiArtifactDownloadTest extends AbstractDDiApiIntegrationTest { // skip first 1000 Bytes and return the rest result = mvc.perform( - get("/{tenant}/controller/v1/{controllerId}/softwaremodules/{softwareModuleId}/artifacts/{filename}", - tenantAware.getCurrentTenant(), target.getControllerId(), getOsModule(ds), "file1") + get("/{tenant}/controller/v1/{controllerId}/softwaremodules/{softwareModuleId}/artifacts/{filename}", + tenantAware.getCurrentTenant(), target.getControllerId(), getOsModule(ds), "file1") .header("Range", "bytes=1000-")) .andExpect(status().isPartialContent()).andExpect(header().string("ETag", artifact.getSha1Hash())) .andExpect(content().contentType(MediaType.APPLICATION_OCTET_STREAM)) @@ -310,8 +310,8 @@ public class DdiArtifactDownloadTest extends AbstractDDiApiIntegrationTest { // Start download from file end fails mvc.perform( - get("/{tenant}/controller/v1/{controllerId}/softwaremodules/{softwareModuleId}/artifacts/{filename}", - tenantAware.getCurrentTenant(), target.getControllerId(), getOsModule(ds), "file1") + get("/{tenant}/controller/v1/{controllerId}/softwaremodules/{softwareModuleId}/artifacts/{filename}", + tenantAware.getCurrentTenant(), target.getControllerId(), getOsModule(ds), "file1") .header("Range", "bytes=" + random.length + "-")) .andExpect(status().isRequestedRangeNotSatisfiable()) .andExpect(content().contentType(MediaType.APPLICATION_OCTET_STREAM)) @@ -322,8 +322,8 @@ public class DdiArtifactDownloadTest extends AbstractDDiApiIntegrationTest { // multipart download - first 20 bytes in 2 parts result = mvc.perform( - get("/{tenant}/controller/v1/{controllerId}/softwaremodules/{softwareModuleId}/artifacts/{filename}", - tenantAware.getCurrentTenant(), target.getControllerId(), getOsModule(ds), "file1") + get("/{tenant}/controller/v1/{controllerId}/softwaremodules/{softwareModuleId}/artifacts/{filename}", + tenantAware.getCurrentTenant(), target.getControllerId(), getOsModule(ds), "file1") .header("Range", "bytes=0-9,10-19")) .andExpect(status().isPartialContent()).andExpect(header().string("ETag", artifact.getSha1Hash())) .andExpect(content().contentType("multipart/byteranges; boundary=THIS_STRING_SEPARATES_MULTIPART")) diff --git a/hawkbit-rest/hawkbit-ddi-resource/src/test/java/org/eclipse/hawkbit/ddi/rest/resource/DdiCancelActionTest.java b/hawkbit-rest/hawkbit-ddi-resource/src/test/java/org/eclipse/hawkbit/ddi/rest/resource/DdiCancelActionTest.java index 8def19903..26cdbc543 100644 --- a/hawkbit-rest/hawkbit-ddi-resource/src/test/java/org/eclipse/hawkbit/ddi/rest/resource/DdiCancelActionTest.java +++ b/hawkbit-rest/hawkbit-ddi-resource/src/test/java/org/eclipse/hawkbit/ddi/rest/resource/DdiCancelActionTest.java @@ -25,6 +25,9 @@ import java.util.ArrayList; import java.util.Collections; import java.util.List; +import io.qameta.allure.Description; +import io.qameta.allure.Feature; +import io.qameta.allure.Story; import org.eclipse.hawkbit.ddi.json.model.DdiResult; import org.eclipse.hawkbit.ddi.json.model.DdiStatus; import org.eclipse.hawkbit.ddi.rest.api.DdiRestConstants; @@ -41,10 +44,6 @@ import org.springframework.hateoas.MediaTypes; import org.springframework.http.MediaType; import org.springframework.integration.json.JsonPathUtils; -import io.qameta.allure.Description; -import io.qameta.allure.Feature; -import io.qameta.allure.Story; - /** * Test cancel action from the controller. */ @@ -52,6 +51,9 @@ import io.qameta.allure.Story; @Story("Cancel Action Resource") class DdiCancelActionTest extends AbstractDDiApiIntegrationTest { + @Autowired + ActionStatusRepository actionStatusRepository; + @Test @Description("Tests that the cancel action resource can be used with CBOR.") void cancelActionCbor() throws Exception { @@ -65,7 +67,7 @@ class DdiCancelActionTest extends AbstractDDiApiIntegrationTest { final byte[] result = mvc .perform(get("/{tenant}/controller/v1/" + TestdataFactory.DEFAULT_CONTROLLER_ID + "/cancelAction/" + cancelAction.getId(), tenantAware.getCurrentTenant()) - .accept(DdiRestConstants.MEDIA_TYPE_CBOR)) + .accept(DdiRestConstants.MEDIA_TYPE_CBOR)) .andDo(MockMvcResultPrinter.print()).andExpect(status().isOk()) .andExpect(content().contentType(DdiRestConstants.MEDIA_TYPE_CBOR)).andReturn().getResponse() .getContentAsByteArray(); @@ -76,7 +78,7 @@ class DdiCancelActionTest extends AbstractDDiApiIntegrationTest { // and submit feedback as CBOR mvc.perform(post("/{tenant}/controller/v1/" + TestdataFactory.DEFAULT_CONTROLLER_ID + "/cancelAction/" - + cancelAction.getId() + "/feedback", tenantAware.getCurrentTenant()) + + cancelAction.getId() + "/feedback", tenantAware.getCurrentTenant()) .content(jsonToCbor(getJsonProceedingCancelActionFeedback())) .contentType(DdiRestConstants.MEDIA_TYPE_CBOR).accept(DdiRestConstants.MEDIA_TYPE_CBOR)) .andDo(MockMvcResultPrinter.print()).andExpect(status().isOk()); @@ -97,7 +99,7 @@ class DdiCancelActionTest extends AbstractDDiApiIntegrationTest { // controller rejects cancellation mvc.perform(post("/{tenant}/controller/v1/" + TestdataFactory.DEFAULT_CONTROLLER_ID + "/cancelAction/" - + cancelAction.getId() + "/feedback", tenantAware.getCurrentTenant()) + + cancelAction.getId() + "/feedback", tenantAware.getCurrentTenant()) .content(getJsonRejectedCancelActionFeedback()).contentType(MediaType.APPLICATION_JSON) .accept(MediaType.APPLICATION_JSON)) .andDo(MockMvcResultPrinter.print()).andExpect(status().isOk()); @@ -106,8 +108,8 @@ class DdiCancelActionTest extends AbstractDDiApiIntegrationTest { // get update action anyway mvc.perform( - get("/{tenant}/controller/v1/" + TestdataFactory.DEFAULT_CONTROLLER_ID + "/deploymentBase/" + actionId, - tenantAware.getCurrentTenant()).accept(MediaType.APPLICATION_JSON)) + get("/{tenant}/controller/v1/" + TestdataFactory.DEFAULT_CONTROLLER_ID + "/deploymentBase/" + actionId, + tenantAware.getCurrentTenant()).accept(MediaType.APPLICATION_JSON)) .andDo(MockMvcResultPrinter.print()).andExpect(status().isOk()) .andExpect(content().contentType(MediaType.APPLICATION_JSON)) .andExpect(jsonPath("$.id", equalTo(String.valueOf(actionId)))) @@ -122,9 +124,9 @@ class DdiCancelActionTest extends AbstractDDiApiIntegrationTest { // and finish it mvc.perform(post("/{tenant}/controller/v1/" + TestdataFactory.DEFAULT_CONTROLLER_ID + "/deploymentBase/" - + actionId + "/feedback", tenantAware.getCurrentTenant()).content( - getJsonActionFeedback(DdiStatus.ExecutionStatus.CLOSED, DdiResult.FinalResult.NONE, - Collections.singletonList("message"))) + + actionId + "/feedback", tenantAware.getCurrentTenant()).content( + getJsonActionFeedback(DdiStatus.ExecutionStatus.CLOSED, DdiResult.FinalResult.NONE, + Collections.singletonList("message"))) .contentType(MediaType.APPLICATION_JSON).accept(MediaType.APPLICATION_JSON)) .andDo(MockMvcResultPrinter.print()).andExpect(status().isOk()); @@ -135,7 +137,7 @@ class DdiCancelActionTest extends AbstractDDiApiIntegrationTest { .isEqualTo(ds); assertThat( targetManagement.getByControllerID(TestdataFactory.DEFAULT_CONTROLLER_ID).get().getInstallationDate()) - .isGreaterThanOrEqualTo(current); + .isGreaterThanOrEqualTo(current); } @@ -150,7 +152,7 @@ class DdiCancelActionTest extends AbstractDDiApiIntegrationTest { final long timeBeforeFirstPoll = System.currentTimeMillis(); mvc.perform(get("/{tenant}/controller/v1/{controller}", tenantAware.getCurrentTenant(), - TestdataFactory.DEFAULT_CONTROLLER_ID).accept(MediaTypes.HAL_JSON)).andDo(MockMvcResultPrinter.print()) + TestdataFactory.DEFAULT_CONTROLLER_ID).accept(MediaTypes.HAL_JSON)).andDo(MockMvcResultPrinter.print()) .andExpect(status().isOk()).andExpect(content().contentType(MediaTypes.HAL_JSON)) .andExpect(jsonPath("$.config.polling.sleep", equalTo("00:01:00"))) .andExpect(jsonPath("$._links.deploymentBase.href", @@ -179,7 +181,7 @@ class DdiCancelActionTest extends AbstractDDiApiIntegrationTest { final long timeBefore2ndPoll = System.currentTimeMillis(); mvc.perform(get("/{tenant}/controller/v1/{controller}", tenantAware.getCurrentTenant(), - TestdataFactory.DEFAULT_CONTROLLER_ID)).andDo(MockMvcResultPrinter.print()).andExpect(status().isOk()) + TestdataFactory.DEFAULT_CONTROLLER_ID)).andDo(MockMvcResultPrinter.print()).andExpect(status().isOk()) .andExpect(content().contentType(MediaTypes.HAL_JSON)) .andExpect(jsonPath("$.config.polling.sleep", equalTo("00:01:00"))) .andExpect(jsonPath("$._links.cancelAction.href", @@ -190,7 +192,7 @@ class DdiCancelActionTest extends AbstractDDiApiIntegrationTest { .isBetween(timeBefore2ndPoll, timeAfter2ndPoll); mvc.perform(get("/{tenant}/controller/v1/" + TestdataFactory.DEFAULT_CONTROLLER_ID + "/cancelAction/" - + cancelAction.getId(), tenantAware.getCurrentTenant()).accept(MediaType.APPLICATION_JSON)) + + cancelAction.getId(), tenantAware.getCurrentTenant()).accept(MediaType.APPLICATION_JSON)) .andDo(MockMvcResultPrinter.print()).andExpect(status().isOk()) .andExpect(content().contentType(MediaType.APPLICATION_JSON)) .andExpect(jsonPath("$.id", equalTo(String.valueOf(cancelAction.getId())))) @@ -200,7 +202,7 @@ class DdiCancelActionTest extends AbstractDDiApiIntegrationTest { // controller confirmed cancelled action, should not be active anymore mvc.perform(post("/{tenant}/controller/v1/" + TestdataFactory.DEFAULT_CONTROLLER_ID + "/cancelAction/" - + cancelAction.getId() + "/feedback", tenantAware.getCurrentTenant()).accept(MediaType.APPLICATION_JSON) + + cancelAction.getId() + "/feedback", tenantAware.getCurrentTenant()).accept(MediaType.APPLICATION_JSON) .content(getJsonClosedCancelActionFeedback()).contentType(MediaType.APPLICATION_JSON) .accept(MediaType.APPLICATION_JSON)) .andDo(MockMvcResultPrinter.print()).andExpect(status().isOk()); @@ -220,41 +222,31 @@ class DdiCancelActionTest extends AbstractDDiApiIntegrationTest { // not allowed methods mvc.perform(post("/{tenant}/controller/v1/" + TestdataFactory.DEFAULT_CONTROLLER_ID + "/cancelAction/1", - tenantAware.getCurrentTenant())).andDo(MockMvcResultPrinter.print()) + tenantAware.getCurrentTenant())).andDo(MockMvcResultPrinter.print()) .andExpect(status().isMethodNotAllowed()); mvc.perform(put("/{tenant}/controller/v1/" + TestdataFactory.DEFAULT_CONTROLLER_ID + "/cancelAction/1", - tenantAware.getCurrentTenant())).andDo(MockMvcResultPrinter.print()) + tenantAware.getCurrentTenant())).andDo(MockMvcResultPrinter.print()) .andExpect(status().isMethodNotAllowed()); mvc.perform(delete("/{tenant}/controller/v1/" + TestdataFactory.DEFAULT_CONTROLLER_ID + "/cancelAction/1", - tenantAware.getCurrentTenant())).andDo(MockMvcResultPrinter.print()) + tenantAware.getCurrentTenant())).andDo(MockMvcResultPrinter.print()) .andExpect(status().isMethodNotAllowed()); // non existing target mvc.perform(get("/{tenant}/controller/v1/34534543/cancelAction/1", tenantAware.getCurrentTenant()) - .accept(MediaType.APPLICATION_JSON)).andDo(MockMvcResultPrinter.print()) + .accept(MediaType.APPLICATION_JSON)).andDo(MockMvcResultPrinter.print()) .andExpect(status().isNotFound()); createCancelAction("34534543"); // wrong media type mvc.perform(get("/{tenant}/controller/v1/34534543/cancelAction/1", tenantAware.getCurrentTenant()) - .accept(MediaType.APPLICATION_ATOM_XML)).andDo(MockMvcResultPrinter.print()) + .accept(MediaType.APPLICATION_ATOM_XML)).andDo(MockMvcResultPrinter.print()) .andExpect(status().isNotAcceptable()); } - private Action createCancelAction(final String targetid) { - final DistributionSet ds = testdataFactory.createDistributionSet(targetid); - final Target savedTarget = testdataFactory.createTarget(targetid); - final List toAssign = new ArrayList<>(); - toAssign.add(savedTarget); - final Long actionId = getFirstAssignedActionId(assignDistributionSet(ds, toAssign)); - - return deploymentManagement.cancelAction(actionId); - } - @Test @Description("Tests the feedback channel of the cancel operation.") void rootRsCancelActionFeedback() throws Exception { @@ -272,7 +264,7 @@ class DdiCancelActionTest extends AbstractDDiApiIntegrationTest { assertThat(deploymentManagement.findActiveActionsByTarget(PAGE, savedTarget.getControllerId())).hasSize(1); mvc.perform(post("/{tenant}/controller/v1/" + TestdataFactory.DEFAULT_CONTROLLER_ID + "/cancelAction/" - + cancelAction.getId() + "/feedback", tenantAware.getCurrentTenant()) + + cancelAction.getId() + "/feedback", tenantAware.getCurrentTenant()) .content(getJsonProceedingCancelActionFeedback()).contentType(MediaType.APPLICATION_JSON) .accept(MediaType.APPLICATION_JSON)) .andDo(MockMvcResultPrinter.print()).andExpect(status().isOk()); @@ -281,7 +273,7 @@ class DdiCancelActionTest extends AbstractDDiApiIntegrationTest { assertThat(countActionStatusAll()).isEqualTo(3); mvc.perform(post("/{tenant}/controller/v1/" + TestdataFactory.DEFAULT_CONTROLLER_ID + "/cancelAction/" - + cancelAction.getId() + "/feedback", tenantAware.getCurrentTenant()) + + cancelAction.getId() + "/feedback", tenantAware.getCurrentTenant()) .content(getJsonResumedCancelActionFeedback()).contentType(MediaType.APPLICATION_JSON) .accept(MediaType.APPLICATION_JSON)) .andDo(MockMvcResultPrinter.print()).andExpect(status().isOk()); @@ -289,7 +281,7 @@ class DdiCancelActionTest extends AbstractDDiApiIntegrationTest { assertThat(countActionStatusAll()).isEqualTo(4); mvc.perform(post("/{tenant}/controller/v1/" + TestdataFactory.DEFAULT_CONTROLLER_ID + "/cancelAction/" - + cancelAction.getId() + "/feedback", tenantAware.getCurrentTenant()) + + cancelAction.getId() + "/feedback", tenantAware.getCurrentTenant()) .content(getJsonScheduledCancelActionFeedback()).contentType(MediaType.APPLICATION_JSON) .accept(MediaType.APPLICATION_JSON)) .andDo(MockMvcResultPrinter.print()).andExpect(status().isOk()); @@ -299,7 +291,7 @@ class DdiCancelActionTest extends AbstractDDiApiIntegrationTest { // cancellation canceled -> should remove the action from active assertThat(deploymentManagement.findActiveActionsByTarget(PAGE, savedTarget.getControllerId())).hasSize(1); mvc.perform(post("/{tenant}/controller/v1/" + TestdataFactory.DEFAULT_CONTROLLER_ID + "/cancelAction/" - + cancelAction.getId() + "/feedback", tenantAware.getCurrentTenant()) + + cancelAction.getId() + "/feedback", tenantAware.getCurrentTenant()) .content(getJsonCanceledCancelActionFeedback()).contentType(MediaType.APPLICATION_JSON) .accept(MediaType.APPLICATION_JSON)) .andDo(MockMvcResultPrinter.print()).andExpect(status().isOk()); @@ -312,7 +304,7 @@ class DdiCancelActionTest extends AbstractDDiApiIntegrationTest { // error assertThat(deploymentManagement.findActiveActionsByTarget(PAGE, savedTarget.getControllerId())).hasSize(1); mvc.perform(post("/{tenant}/controller/v1/" + TestdataFactory.DEFAULT_CONTROLLER_ID + "/cancelAction/" - + cancelAction.getId() + "/feedback", tenantAware.getCurrentTenant()) + + cancelAction.getId() + "/feedback", tenantAware.getCurrentTenant()) .content(getJsonRejectedCancelActionFeedback()).contentType(MediaType.APPLICATION_JSON) .accept(MediaType.APPLICATION_JSON)) .andDo(MockMvcResultPrinter.print()).andExpect(status().isOk()); @@ -321,7 +313,7 @@ class DdiCancelActionTest extends AbstractDDiApiIntegrationTest { // update closed -> should remove the action from active mvc.perform(post("/{tenant}/controller/v1/" + TestdataFactory.DEFAULT_CONTROLLER_ID + "/deploymentBase/" - + cancelAction.getId() + "/feedback", tenantAware.getCurrentTenant()) + + cancelAction.getId() + "/feedback", tenantAware.getCurrentTenant()) .content(getJsonClosedCancelActionFeedback()).contentType(MediaType.APPLICATION_JSON) .accept(MediaType.APPLICATION_JSON)) .andDo(MockMvcResultPrinter.print()).andExpect(status().isOk()); @@ -359,7 +351,7 @@ class DdiCancelActionTest extends AbstractDDiApiIntegrationTest { assertThat(deploymentManagement.findActiveActionsByTarget(PAGE, savedTarget.getControllerId())).hasSize(3); assertThat(deploymentManagement.countActionsByTarget(savedTarget.getControllerId())).isEqualTo(3); mvc.perform(get("/{tenant}/controller/v1/" + TestdataFactory.DEFAULT_CONTROLLER_ID + "/cancelAction/" - + cancelAction.getId(), tenantAware.getCurrentTenant()).accept(MediaType.APPLICATION_JSON)) + + cancelAction.getId(), tenantAware.getCurrentTenant()).accept(MediaType.APPLICATION_JSON)) .andDo(MockMvcResultPrinter.print()).andExpect(status().isOk()) .andExpect(content().contentType(MediaType.APPLICATION_JSON_VALUE)) .andExpect(jsonPath("$.id", equalTo(String.valueOf(cancelAction.getId())))) @@ -367,7 +359,7 @@ class DdiCancelActionTest extends AbstractDDiApiIntegrationTest { assertThat(countActionStatusAll()).isEqualTo(6); mvc.perform(get("/{tenant}/controller/v1/{controllerId}", tenantAware.getCurrentTenant(), - TestdataFactory.DEFAULT_CONTROLLER_ID)).andDo(MockMvcResultPrinter.print()).andExpect(status().isOk()) + TestdataFactory.DEFAULT_CONTROLLER_ID)).andDo(MockMvcResultPrinter.print()).andExpect(status().isOk()) .andExpect(content().contentType(MediaTypes.HAL_JSON)) .andExpect(jsonPath("$.config.polling.sleep", equalTo("00:01:00"))) .andExpect(jsonPath("$._links.cancelAction.href", @@ -376,7 +368,7 @@ class DdiCancelActionTest extends AbstractDDiApiIntegrationTest { // now lets return feedback for the first cancelation mvc.perform(post("/{tenant}/controller/v1/" + TestdataFactory.DEFAULT_CONTROLLER_ID + "/cancelAction/" - + cancelAction.getId() + "/feedback", tenantAware.getCurrentTenant()) + + cancelAction.getId() + "/feedback", tenantAware.getCurrentTenant()) .content(getJsonClosedCancelActionFeedback()).contentType(MediaType.APPLICATION_JSON) .accept(MediaType.APPLICATION_JSON)) .andDo(MockMvcResultPrinter.print()).andExpect(status().isOk()); @@ -386,7 +378,7 @@ class DdiCancelActionTest extends AbstractDDiApiIntegrationTest { assertThat(deploymentManagement.findActiveActionsByTarget(PAGE, savedTarget.getControllerId())).hasSize(2); assertThat(deploymentManagement.countActionsByTarget(savedTarget.getControllerId())).isEqualTo(3); mvc.perform(get("/{tenant}/controller/v1/" + TestdataFactory.DEFAULT_CONTROLLER_ID + "/cancelAction/" - + cancelAction2.getId(), tenantAware.getCurrentTenant()).accept(MediaType.APPLICATION_JSON)) + + cancelAction2.getId(), tenantAware.getCurrentTenant()).accept(MediaType.APPLICATION_JSON)) .andDo(MockMvcResultPrinter.print()).andExpect(status().isOk()) .andExpect(content().contentType(MediaType.APPLICATION_JSON)) .andExpect(jsonPath("$.id", equalTo(String.valueOf(cancelAction2.getId())))) @@ -394,7 +386,7 @@ class DdiCancelActionTest extends AbstractDDiApiIntegrationTest { assertThat(countActionStatusAll()).isEqualTo(8); mvc.perform(get("/{tenant}/controller/v1/{controller}", tenantAware.getCurrentTenant(), - TestdataFactory.DEFAULT_CONTROLLER_ID)).andDo(MockMvcResultPrinter.print()).andExpect(status().isOk()) + TestdataFactory.DEFAULT_CONTROLLER_ID)).andDo(MockMvcResultPrinter.print()).andExpect(status().isOk()) .andExpect(content().contentType(MediaTypes.HAL_JSON)) .andExpect(jsonPath("$.config.polling.sleep", equalTo("00:01:00"))) .andExpect(jsonPath("$._links.cancelAction.href", @@ -403,7 +395,7 @@ class DdiCancelActionTest extends AbstractDDiApiIntegrationTest { // now lets return feedback for the second cancelation mvc.perform(post("/{tenant}/controller/v1/" + TestdataFactory.DEFAULT_CONTROLLER_ID + "/cancelAction/" - + cancelAction2.getId() + "/feedback", tenantAware.getCurrentTenant()) + + cancelAction2.getId() + "/feedback", tenantAware.getCurrentTenant()) .content(getJsonClosedCancelActionFeedback()).contentType(MediaType.APPLICATION_JSON) .accept(MediaType.APPLICATION_JSON)) .andDo(MockMvcResultPrinter.print()).andExpect(status().isOk()); @@ -412,8 +404,8 @@ class DdiCancelActionTest extends AbstractDDiApiIntegrationTest { assertThat(deploymentManagement.getAssignedDistributionSet(TestdataFactory.DEFAULT_CONTROLLER_ID).get()) .isEqualTo(ds3); mvc.perform( - get("/{tenant}/controller/v1/" + TestdataFactory.DEFAULT_CONTROLLER_ID + "/deploymentBase/" + actionId3, - tenantAware.getCurrentTenant())) + get("/{tenant}/controller/v1/" + TestdataFactory.DEFAULT_CONTROLLER_ID + "/deploymentBase/" + actionId3, + tenantAware.getCurrentTenant())) .andDo(MockMvcResultPrinter.print()).andExpect(status().isOk()); assertThat(countActionStatusAll()).isEqualTo(10); @@ -429,7 +421,7 @@ class DdiCancelActionTest extends AbstractDDiApiIntegrationTest { .isEqualTo(ds3); mvc.perform(get("/{tenant}/controller/v1/" + TestdataFactory.DEFAULT_CONTROLLER_ID + "/cancelAction/" - + cancelAction3.getId(), tenantAware.getCurrentTenant()).accept(MediaType.APPLICATION_JSON)) + + cancelAction3.getId(), tenantAware.getCurrentTenant()).accept(MediaType.APPLICATION_JSON)) .andDo(MockMvcResultPrinter.print()).andExpect(status().isOk()) .andExpect(content().contentType(MediaType.APPLICATION_JSON)) .andExpect(jsonPath("$.id", equalTo(String.valueOf(cancelAction3.getId())))) @@ -438,7 +430,7 @@ class DdiCancelActionTest extends AbstractDDiApiIntegrationTest { // now lets return feedback for the third cancelation mvc.perform(post("/{tenant}/controller/v1/" + TestdataFactory.DEFAULT_CONTROLLER_ID + "/cancelAction/" - + cancelAction3.getId() + "/feedback", tenantAware.getCurrentTenant()) + + cancelAction3.getId() + "/feedback", tenantAware.getCurrentTenant()) .content(getJsonClosedCancelActionFeedback()).contentType(MediaType.APPLICATION_JSON) .accept(MediaType.APPLICATION_JSON)) .andDo(MockMvcResultPrinter.print()).andExpect(status().isOk()); @@ -466,13 +458,13 @@ class DdiCancelActionTest extends AbstractDDiApiIntegrationTest { // only 97 action status left for (int i = 0; i < 98; i++) { mvc.perform(post("/{tenant}/controller/v1/" + TestdataFactory.DEFAULT_CONTROLLER_ID + "/cancelAction/" - + cancelAction.getId() + "/feedback", tenantAware.getCurrentTenant()).content(feedback) + + cancelAction.getId() + "/feedback", tenantAware.getCurrentTenant()).content(feedback) .contentType(MediaType.APPLICATION_JSON).accept(MediaType.APPLICATION_JSON)) .andExpect(status().isOk()); } mvc.perform(post("/{tenant}/controller/v1/" + TestdataFactory.DEFAULT_CONTROLLER_ID + "/cancelAction/" - + cancelAction.getId() + "/feedback", tenantAware.getCurrentTenant()).content(feedback) + + cancelAction.getId() + "/feedback", tenantAware.getCurrentTenant()).content(feedback) .contentType(MediaType.APPLICATION_JSON).accept(MediaType.APPLICATION_JSON)) .andExpect(status().isForbidden()); } @@ -485,22 +477,22 @@ class DdiCancelActionTest extends AbstractDDiApiIntegrationTest { // not allowed methods mvc.perform(put("/{tenant}/controller/v1/" + TestdataFactory.DEFAULT_CONTROLLER_ID + "/cancelAction/" - + cancelAction.getId() + "/feedback", tenantAware.getCurrentTenant()) + + cancelAction.getId() + "/feedback", tenantAware.getCurrentTenant()) .content(getJsonClosedCancelActionFeedback()).contentType(MediaType.APPLICATION_JSON) .accept(MediaType.APPLICATION_JSON)) .andDo(MockMvcResultPrinter.print()).andExpect(status().isMethodNotAllowed()); mvc.perform(delete("/{tenant}/controller/v1/" + TestdataFactory.DEFAULT_CONTROLLER_ID + "/cancelAction/" - + cancelAction.getId() + "/feedback", tenantAware.getCurrentTenant())) + + cancelAction.getId() + "/feedback", tenantAware.getCurrentTenant())) .andDo(MockMvcResultPrinter.print()).andExpect(status().isMethodNotAllowed()); mvc.perform(get("/{tenant}/controller/v1/" + TestdataFactory.DEFAULT_CONTROLLER_ID + "/cancelAction/" - + cancelAction.getId() + "/feedback", tenantAware.getCurrentTenant())) + + cancelAction.getId() + "/feedback", tenantAware.getCurrentTenant())) .andDo(MockMvcResultPrinter.print()).andExpect(status().isMethodNotAllowed()); // bad content type mvc.perform(post("/{tenant}/controller/v1/" + TestdataFactory.DEFAULT_CONTROLLER_ID + "/cancelAction/" - + cancelAction.getId() + "/feedback", tenantAware.getCurrentTenant()) + + cancelAction.getId() + "/feedback", tenantAware.getCurrentTenant()) .content(getJsonClosedCancelActionFeedback()).contentType(MediaType.APPLICATION_ATOM_XML) .accept(MediaType.APPLICATION_JSON)) .andDo(MockMvcResultPrinter.print()).andExpect(status().isUnsupportedMediaType()); @@ -508,33 +500,41 @@ class DdiCancelActionTest extends AbstractDDiApiIntegrationTest { // bad body String invalidFeedback = "{\"status\":{\"execution\":\"546456456\",\"result\":{\"finished\":\"none\",\"progress\":{\"cnt\":2,\"of\":5}},\"details\":\"none\"]}}"; mvc.perform(post("/{tenant}/controller/v1/" + TestdataFactory.DEFAULT_CONTROLLER_ID + "/cancelAction/" - + cancelAction.getId() + "/feedback", tenantAware.getCurrentTenant()).content(invalidFeedback) + + cancelAction.getId() + "/feedback", tenantAware.getCurrentTenant()).content(invalidFeedback) .contentType(MediaType.APPLICATION_JSON).accept(MediaType.APPLICATION_JSON)) .andDo(MockMvcResultPrinter.print()).andExpect(status().isBadRequest()); // non existing target mvc.perform(post("/{tenant}/controller/v1/12345/cancelAction/" + cancelAction.getId() + "/feedback", - tenantAware.getCurrentTenant()).content(getJsonClosedCancelActionFeedback()) + tenantAware.getCurrentTenant()).content(getJsonClosedCancelActionFeedback()) .contentType(MediaType.APPLICATION_JSON).accept(MediaType.APPLICATION_JSON)) .andDo(MockMvcResultPrinter.print()).andExpect(status().isNotFound()); // invalid action invalidFeedback = "{\"id\":\"sdfsdfsdfs\",\"status\":{\"execution\":\"closed\",\"result\":{\"finished\":\"none\",\"progress\":{\"cnt\":2,\"of\":5}},\"details\":\"details\"]}}"; mvc.perform(post("/{tenant}/controller/v1/" + TestdataFactory.DEFAULT_CONTROLLER_ID + "/cancelAction/" - + cancelAction.getId() + "/feedback", tenantAware.getCurrentTenant()).content(invalidFeedback) + + cancelAction.getId() + "/feedback", tenantAware.getCurrentTenant()).content(invalidFeedback) .contentType(MediaType.APPLICATION_JSON).accept(MediaType.APPLICATION_JSON)) .andDo(MockMvcResultPrinter.print()).andExpect(status().isBadRequest()); // finaly, get it right :) mvc.perform(post("/{tenant}/controller/v1/" + TestdataFactory.DEFAULT_CONTROLLER_ID + "/cancelAction/" - + cancelAction.getId() + "/feedback", tenantAware.getCurrentTenant()) + + cancelAction.getId() + "/feedback", tenantAware.getCurrentTenant()) .content(getJsonClosedCancelActionFeedback()).contentType(MediaType.APPLICATION_JSON) .accept(MediaType.APPLICATION_JSON)) .andDo(MockMvcResultPrinter.print()).andExpect(status().isOk()); } - @Autowired - ActionStatusRepository actionStatusRepository; + private Action createCancelAction(final String targetid) { + final DistributionSet ds = testdataFactory.createDistributionSet(targetid); + final Target savedTarget = testdataFactory.createTarget(targetid); + final List toAssign = new ArrayList<>(); + toAssign.add(savedTarget); + final Long actionId = getFirstAssignedActionId(assignDistributionSet(ds, toAssign)); + + return deploymentManagement.cancelAction(actionId); + } + private long countActionStatusAll() { return actionStatusRepository.count(); } diff --git a/hawkbit-rest/hawkbit-ddi-resource/src/test/java/org/eclipse/hawkbit/ddi/rest/resource/DdiConfigDataTest.java b/hawkbit-rest/hawkbit-ddi-resource/src/test/java/org/eclipse/hawkbit/ddi/rest/resource/DdiConfigDataTest.java index da9784b17..0ce10389e 100644 --- a/hawkbit-rest/hawkbit-ddi-resource/src/test/java/org/eclipse/hawkbit/ddi/rest/resource/DdiConfigDataTest.java +++ b/hawkbit-rest/hawkbit-ddi-resource/src/test/java/org/eclipse/hawkbit/ddi/rest/resource/DdiConfigDataTest.java @@ -28,6 +28,10 @@ import java.util.HashMap; import java.util.Map; import java.util.NoSuchElementException; +import io.qameta.allure.Description; +import io.qameta.allure.Feature; +import io.qameta.allure.Step; +import io.qameta.allure.Story; import org.eclipse.hawkbit.ddi.rest.api.DdiRestConstants; import org.eclipse.hawkbit.exception.SpServerError; import org.eclipse.hawkbit.repository.exception.AssignmentQuotaExceededException; @@ -40,10 +44,6 @@ import org.springframework.hateoas.MediaTypes; import org.springframework.http.MediaType; import org.springframework.test.context.ActiveProfiles; -import io.qameta.allure.Description; -import io.qameta.allure.Feature; -import io.qameta.allure.Step; -import io.qameta.allure.Story; /** * Test config data from the controller. */ @@ -66,8 +66,8 @@ class DdiConfigDataTest extends AbstractDDiApiIntegrationTest { attributes.put(ATTRIBUTE_KEY_VALID, ATTRIBUTE_VALUE_VALID); mvc.perform(put(TARGET1_CONFIGDATA_PATH, tenantAware.getCurrentTenant()) - .content(jsonToCbor(JsonBuilder.configData(attributes).toString())) - .contentType(DdiRestConstants.MEDIA_TYPE_CBOR)).andDo(MockMvcResultPrinter.print()) + .content(jsonToCbor(JsonBuilder.configData(attributes).toString())) + .contentType(DdiRestConstants.MEDIA_TYPE_CBOR)).andDo(MockMvcResultPrinter.print()) .andExpect(status().isOk()); assertThat(targetManagement.getControllerAttributes(TARGET1_ID)).isEqualTo(attributes); } @@ -81,15 +81,15 @@ class DdiConfigDataTest extends AbstractDDiApiIntegrationTest { final long current = System.currentTimeMillis(); mvc.perform( - get("/{tenant}/controller/v1/4712", tenantAware.getCurrentTenant()).accept(MediaTypes.HAL_JSON)) + get("/{tenant}/controller/v1/4712", tenantAware.getCurrentTenant()).accept(MediaTypes.HAL_JSON)) .andDo(MockMvcResultPrinter.print()).andExpect(status().isOk()) .andExpect(content().contentType(MediaTypes.HAL_JSON)) .andExpect(jsonPath("$.config.polling.sleep", equalTo("00:01:00"))) .andExpect(jsonPath("$._links.configData.href", equalTo( "http://localhost/" + tenantAware.getCurrentTenant() + "/controller/v1/4712/configData"))); Thread.sleep(1); // is required: otherwise processing the next line is - // often too fast and - // the following assert will fail + // often too fast and + // the following assert will fail assertThat(targetManagement.getByControllerID("4712").orElseThrow(NoSuchElementException::new) .getLastTargetQuery()) .isLessThanOrEqualTo(System.currentTimeMillis()); @@ -107,7 +107,7 @@ class DdiConfigDataTest extends AbstractDDiApiIntegrationTest { assertThat(updateControllerAttributes.isRequestControllerAttributes()).isFalse(); mvc.perform( - get("/{tenant}/controller/v1/4712", tenantAware.getCurrentTenant()).accept(MediaTypes.HAL_JSON)) + get("/{tenant}/controller/v1/4712", tenantAware.getCurrentTenant()).accept(MediaTypes.HAL_JSON)) .andDo(MockMvcResultPrinter.print()).andExpect(status().isOk()) .andExpect(content().contentType(MediaTypes.HAL_JSON)) .andExpect(jsonPath("$.config.polling.sleep", equalTo("00:01:00"))) @@ -125,14 +125,14 @@ class DdiConfigDataTest extends AbstractDDiApiIntegrationTest { attributes.put(ATTRIBUTE_KEY_VALID, ATTRIBUTE_VALUE_VALID); mvc.perform(put(TARGET1_CONFIGDATA_PATH, tenantAware.getCurrentTenant()) - .content(JsonBuilder.configData(attributes).toString()).contentType(MediaType.APPLICATION_JSON)) + .content(JsonBuilder.configData(attributes).toString()).contentType(MediaType.APPLICATION_JSON)) .andDo(MockMvcResultPrinter.print()).andExpect(status().isOk()); assertThat(targetManagement.getControllerAttributes(TARGET1_ID)).isEqualTo(attributes); // update attributes.put("sdsds", "123412"); mvc.perform(put(TARGET1_CONFIGDATA_PATH, tenantAware.getCurrentTenant()) - .content(JsonBuilder.configData(attributes).toString()).contentType(MediaType.APPLICATION_JSON)) + .content(JsonBuilder.configData(attributes).toString()).contentType(MediaType.APPLICATION_JSON)) .andDo(MockMvcResultPrinter.print()).andExpect(status().isOk()); assertThat(targetManagement.getControllerAttributes(TARGET1_ID)).isEqualTo(attributes); } @@ -149,13 +149,13 @@ class DdiConfigDataTest extends AbstractDDiApiIntegrationTest { attributes.put("dsafsdf" + i, "sdsds" + i); } mvc.perform(put(TARGET1_CONFIGDATA_PATH, tenantAware.getCurrentTenant()) - .content(JsonBuilder.configData(attributes).toString()).contentType(MediaType.APPLICATION_JSON)) + .content(JsonBuilder.configData(attributes).toString()).contentType(MediaType.APPLICATION_JSON)) .andExpect(status().isOk()); attributes = new HashMap<>(); attributes.put("on too many", "sdsds"); mvc.perform(put(TARGET1_CONFIGDATA_PATH, tenantAware.getCurrentTenant()) - .content(JsonBuilder.configData(attributes).toString()).contentType(MediaType.APPLICATION_JSON)) + .content(JsonBuilder.configData(attributes).toString()).contentType(MediaType.APPLICATION_JSON)) .andExpect(status().isForbidden()) .andExpect(jsonPath("$.exceptionClass", equalTo(AssignmentQuotaExceededException.class.getName()))) .andExpect(jsonPath("$.errorCode", equalTo(SpServerError.SP_QUOTA_EXCEEDED.getKey()))); @@ -183,17 +183,17 @@ class DdiConfigDataTest extends AbstractDDiApiIntegrationTest { final Map attributes = new HashMap<>(); attributes.put("dsafsdf", "sdsds"); mvc.perform(put("/{tenant}/controller/v1/4712/configData", tenantAware.getCurrentTenant()) - .content(JsonBuilder.configData(attributes).toString()).contentType(MediaTypes.HAL_JSON)) + .content(JsonBuilder.configData(attributes).toString()).contentType(MediaTypes.HAL_JSON)) .andDo(MockMvcResultPrinter.print()).andExpect(status().isUnsupportedMediaType()); // non existing target mvc.perform(put("/{tenant}/controller/v1/456456/configData", tenantAware.getCurrentTenant()) - .content(JsonBuilder.configData(attributes).toString()).contentType(MediaType.APPLICATION_JSON)) + .content(JsonBuilder.configData(attributes).toString()).contentType(MediaType.APPLICATION_JSON)) .andDo(MockMvcResultPrinter.print()).andExpect(status().isNotFound()); // bad body mvc.perform(put("/{tenant}/controller/v1/4712/configData", tenantAware.getCurrentTenant()) - .content("{\"id\": \"51659181\"}").contentType(MediaType.APPLICATION_JSON)) + .content("{\"id\": \"51659181\"}").contentType(MediaType.APPLICATION_JSON)) .andDo(MockMvcResultPrinter.print()).andExpect(status().isBadRequest()); } @@ -206,26 +206,6 @@ class DdiConfigDataTest extends AbstractDDiApiIntegrationTest { putAndVerifyConfigDataWithValueTooLong(); } - @Step - private void putAndVerifyConfigDataWithKeyTooLong() throws Exception { - final Map attributes = Collections.singletonMap(ATTRIBUTE_KEY_TOO_LONG, ATTRIBUTE_VALUE_VALID); - mvc.perform(put(DdiConfigDataTest.TARGET2_CONFIGDATA_PATH, tenantAware.getCurrentTenant()) - .content(JsonBuilder.configData(attributes).toString()).contentType(MediaType.APPLICATION_JSON)) - .andExpect(status().isBadRequest()) - .andExpect(jsonPath("$.exceptionClass", equalTo(InvalidTargetAttributeException.class.getName()))) - .andExpect(jsonPath("$.errorCode", equalTo(SpServerError.SP_TARGET_ATTRIBUTES_INVALID.getKey()))); - } - - @Step - private void putAndVerifyConfigDataWithValueTooLong() throws Exception { - final Map attributes = Collections.singletonMap(ATTRIBUTE_KEY_VALID, ATTRIBUTE_VALUE_TOO_LONG); - mvc.perform(put(DdiConfigDataTest.TARGET2_CONFIGDATA_PATH, tenantAware.getCurrentTenant()) - .content(JsonBuilder.configData(attributes).toString()).contentType(MediaType.APPLICATION_JSON)) - .andExpect(status().isBadRequest()) - .andExpect(jsonPath("$.exceptionClass", equalTo(InvalidTargetAttributeException.class.getName()))) - .andExpect(jsonPath("$.errorCode", equalTo(SpServerError.SP_TARGET_ATTRIBUTES_INVALID.getKey()))); - } - @Test @Description("Verify that config data (device attributes) can be updated by the controller using different update modes (merge, replace, remove).") void putConfigDataWithDifferentUpdateModes() throws Exception { @@ -249,6 +229,26 @@ class DdiConfigDataTest extends AbstractDDiApiIntegrationTest { putConfigDataWithInvalidUpdateMode(); } + @Step + private void putAndVerifyConfigDataWithKeyTooLong() throws Exception { + final Map attributes = Collections.singletonMap(ATTRIBUTE_KEY_TOO_LONG, ATTRIBUTE_VALUE_VALID); + mvc.perform(put(DdiConfigDataTest.TARGET2_CONFIGDATA_PATH, tenantAware.getCurrentTenant()) + .content(JsonBuilder.configData(attributes).toString()).contentType(MediaType.APPLICATION_JSON)) + .andExpect(status().isBadRequest()) + .andExpect(jsonPath("$.exceptionClass", equalTo(InvalidTargetAttributeException.class.getName()))) + .andExpect(jsonPath("$.errorCode", equalTo(SpServerError.SP_TARGET_ATTRIBUTES_INVALID.getKey()))); + } + + @Step + private void putAndVerifyConfigDataWithValueTooLong() throws Exception { + final Map attributes = Collections.singletonMap(ATTRIBUTE_KEY_VALID, ATTRIBUTE_VALUE_TOO_LONG); + mvc.perform(put(DdiConfigDataTest.TARGET2_CONFIGDATA_PATH, tenantAware.getCurrentTenant()) + .content(JsonBuilder.configData(attributes).toString()).contentType(MediaType.APPLICATION_JSON)) + .andExpect(status().isBadRequest()) + .andExpect(jsonPath("$.exceptionClass", equalTo(InvalidTargetAttributeException.class.getName()))) + .andExpect(jsonPath("$.errorCode", equalTo(SpServerError.SP_TARGET_ATTRIBUTES_INVALID.getKey()))); + } + @Step private void putConfigDataWithInvalidUpdateMode() throws Exception { // create some attriutes @@ -258,8 +258,8 @@ class DdiConfigDataTest extends AbstractDDiApiIntegrationTest { // use an invalid update mode mvc.perform(put(DdiConfigDataTest.TARGET1_CONFIGDATA_PATH, tenantAware.getCurrentTenant()) - .content(JsonBuilder.configData(attributes, "KJHGKJHGKJHG").toString()) - .contentType(MediaType.APPLICATION_JSON)).andDo(MockMvcResultPrinter.print()) + .content(JsonBuilder.configData(attributes, "KJHGKJHGKJHG").toString()) + .contentType(MediaType.APPLICATION_JSON)).andDo(MockMvcResultPrinter.print()) .andExpect(status().isBadRequest()); } @@ -274,8 +274,8 @@ class DdiConfigDataTest extends AbstractDDiApiIntegrationTest { removeAttributes.put("k3", "bar"); mvc.perform(put(DdiConfigDataTest.TARGET1_CONFIGDATA_PATH, tenantAware.getCurrentTenant()) - .content(JsonBuilder.configData(removeAttributes, "remove").toString()) - .contentType(MediaType.APPLICATION_JSON)).andDo(MockMvcResultPrinter.print()) + .content(JsonBuilder.configData(removeAttributes, "remove").toString()) + .contentType(MediaType.APPLICATION_JSON)).andDo(MockMvcResultPrinter.print()) .andExpect(status().isOk()); // verify attribute removal @@ -298,8 +298,8 @@ class DdiConfigDataTest extends AbstractDDiApiIntegrationTest { mergeAttributes.put("k1", "v1_modified_again"); mergeAttributes.put("k4", "v4"); mvc.perform(put(DdiConfigDataTest.TARGET1_CONFIGDATA_PATH, tenantAware.getCurrentTenant()) - .content(JsonBuilder.configData(mergeAttributes, "merge").toString()) - .contentType(MediaType.APPLICATION_JSON)).andDo(MockMvcResultPrinter.print()) + .content(JsonBuilder.configData(mergeAttributes, "merge").toString()) + .contentType(MediaType.APPLICATION_JSON)).andDo(MockMvcResultPrinter.print()) .andExpect(status().isOk()); // verify attribute merge @@ -325,8 +325,8 @@ class DdiConfigDataTest extends AbstractDDiApiIntegrationTest { replacementAttributes.put("k2", "v2"); replacementAttributes.put("k3", "v3"); mvc.perform(put(DdiConfigDataTest.TARGET1_CONFIGDATA_PATH, tenantAware.getCurrentTenant()) - .content(JsonBuilder.configData(replacementAttributes, "replace").toString()) - .contentType(MediaType.APPLICATION_JSON)).andDo(MockMvcResultPrinter.print()) + .content(JsonBuilder.configData(replacementAttributes, "replace").toString()) + .contentType(MediaType.APPLICATION_JSON)).andDo(MockMvcResultPrinter.print()) .andExpect(status().isOk()); // verify attribute replacement @@ -349,7 +349,7 @@ class DdiConfigDataTest extends AbstractDDiApiIntegrationTest { // set the initial attributes mvc.perform(put(DdiConfigDataTest.TARGET1_CONFIGDATA_PATH, tenantAware.getCurrentTenant()) - .content(JsonBuilder.configData(attributes).toString()).contentType(MediaType.APPLICATION_JSON)) + .content(JsonBuilder.configData(attributes).toString()).contentType(MediaType.APPLICATION_JSON)) .andDo(MockMvcResultPrinter.print()).andExpect(status().isOk()); // verify the initial parameters diff --git a/hawkbit-rest/hawkbit-ddi-resource/src/test/java/org/eclipse/hawkbit/ddi/rest/resource/DdiConfirmationBaseTest.java b/hawkbit-rest/hawkbit-ddi-resource/src/test/java/org/eclipse/hawkbit/ddi/rest/resource/DdiConfirmationBaseTest.java index 721f994df..bc410d438 100644 --- a/hawkbit-rest/hawkbit-ddi-resource/src/test/java/org/eclipse/hawkbit/ddi/rest/resource/DdiConfirmationBaseTest.java +++ b/hawkbit-rest/hawkbit-ddi-resource/src/test/java/org/eclipse/hawkbit/ddi/rest/resource/DdiConfirmationBaseTest.java @@ -9,6 +9,21 @@ */ package org.eclipse.hawkbit.ddi.rest.resource; +import static org.assertj.core.api.Assertions.assertThat; +import static org.eclipse.hawkbit.repository.jpa.management.JpaConfirmationManagement.CONFIRMATION_CODE_MSG_PREFIX; +import static org.hamcrest.CoreMatchers.equalTo; +import static org.hamcrest.Matchers.containsString; +import static org.hamcrest.Matchers.hasItem; +import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get; +import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.post; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.jsonPath; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; + +import java.util.Collections; +import java.util.List; +import java.util.stream.Collectors; +import java.util.stream.Stream; + import io.qameta.allure.Description; import io.qameta.allure.Feature; import io.qameta.allure.Step; @@ -49,21 +64,6 @@ import org.springframework.hateoas.MediaTypes; import org.springframework.http.MediaType; import org.springframework.test.web.servlet.ResultActions; -import java.util.Collections; -import java.util.List; -import java.util.stream.Collectors; -import java.util.stream.Stream; - -import static org.assertj.core.api.Assertions.assertThat; -import static org.eclipse.hawkbit.repository.jpa.management.JpaConfirmationManagement.CONFIRMATION_CODE_MSG_PREFIX; -import static org.hamcrest.CoreMatchers.equalTo; -import static org.hamcrest.Matchers.containsString; -import static org.hamcrest.Matchers.hasItem; -import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get; -import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.post; -import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.jsonPath; -import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; - /** * Test confirmation base from the controller. */ @@ -91,7 +91,7 @@ public class DdiConfirmationBaseTest extends AbstractDDiApiIntegrationTest { final List targetsAssignedToDs = assignDistributionSet(ds.getId(), savedTarget.getControllerId(), Action.ActionType.FORCED).getAssignedEntity().stream().map(Action::getTarget) - .collect(Collectors.toList()); + .collect(Collectors.toList()); implicitLock(ds); assertThat(deploymentManagement.findActiveActionsByTarget(PAGE, savedTarget.getControllerId())).hasSize(1); @@ -116,10 +116,10 @@ public class DdiConfirmationBaseTest extends AbstractDDiApiIntegrationTest { performGet(CONTROLLER_BASE, MediaTypes.HAL_JSON, status().isOk(), tenantAware.getCurrentTenant(), DEFAULT_CONTROLLER_ID) - .andExpect(jsonPath("$.config.polling.sleep", equalTo("00:01:00"))) - .andExpect(jsonPath("$._links.confirmationBase.href", - containsString(expectedConfirmationBaseLink))) - .andExpect(jsonPath("$._links.deploymentBase.href").doesNotExist()); + .andExpect(jsonPath("$.config.polling.sleep", equalTo("00:01:00"))) + .andExpect(jsonPath("$._links.confirmationBase.href", + containsString(expectedConfirmationBaseLink))) + .andExpect(jsonPath("$._links.deploymentBase.href").doesNotExist()); assertThat(targetManagement.getByControllerID(DEFAULT_CONTROLLER_ID).get().getLastTargetQuery()) .isGreaterThanOrEqualTo(current); @@ -179,12 +179,12 @@ public class DdiConfirmationBaseTest extends AbstractDDiApiIntegrationTest { .get(0); mvc.perform( - get(CONTROLLER_BASE, tenantAware.getCurrentTenant(), controllerId).accept(MediaType.APPLICATION_JSON)) + get(CONTROLLER_BASE, tenantAware.getCurrentTenant(), controllerId).accept(MediaType.APPLICATION_JSON)) .andDo(MockMvcResultPrinter.print()).andExpect(status().isOk()) .andExpect(jsonPath("$._links.confirmationBase.href").doesNotExist()); mvc.perform(get(CONFIRMATION_BASE_ACTION, tenantAware.getCurrentTenant(), controllerId, savedAction.getId()) - .accept(MediaType.APPLICATION_JSON)).andDo(MockMvcResultPrinter.print()) + .accept(MediaType.APPLICATION_JSON)).andDo(MockMvcResultPrinter.print()) .andExpect(status().isNotFound()); } @@ -203,7 +203,7 @@ public class DdiConfirmationBaseTest extends AbstractDDiApiIntegrationTest { .get(0); mvc.perform( - get(CONTROLLER_BASE, tenantAware.getCurrentTenant(), controllerId).accept(MediaType.APPLICATION_JSON)) + get(CONTROLLER_BASE, tenantAware.getCurrentTenant(), controllerId).accept(MediaType.APPLICATION_JSON)) .andDo(MockMvcResultPrinter.print()).andExpect(status().isOk()) .andExpect(jsonPath("$._links.confirmationBase.href").exists()) .andExpect(jsonPath("$._links.deploymentBase.href").doesNotExist()); @@ -212,7 +212,7 @@ public class DdiConfirmationBaseTest extends AbstractDDiApiIntegrationTest { .accept(MediaType.APPLICATION_JSON)).andDo(MockMvcResultPrinter.print()).andExpect(status().isOk()); mvc.perform(get(DEPLOYMENT_BASE, tenantAware.getCurrentTenant(), controllerId, savedAction.getId()) - .accept(MediaType.APPLICATION_JSON)).andDo(MockMvcResultPrinter.print()) + .accept(MediaType.APPLICATION_JSON)).andDo(MockMvcResultPrinter.print()) .andExpect(status().isNotFound()); } @@ -238,14 +238,14 @@ public class DdiConfirmationBaseTest extends AbstractDDiApiIntegrationTest { // verify confirmation endpoint is still accessible sendConfirmationFeedback(savedTarget, savedAction, DdiConfirmationFeedback.Confirmation.DENIED, 20, - "Action denied message.").andExpect(status().isOk()); + "Action denied message.").andExpect(status().isOk()); // confirmation base should still be exposed verifyActionInConfirmationBaseState(savedTarget.getControllerId(), savedAction.getId()); // verify confirmation endpoint is still accessible sendConfirmationFeedback(savedTarget, savedAction, DdiConfirmationFeedback.Confirmation.CONFIRMED, 10, - "Action confirmed message.").andExpect(status().isOk()); + "Action confirmed message.").andExpect(status().isOk()); // assert deployment link is exposed to the target verifyActionInDeploymentBaseState(controllerId, savedAction.getId()); @@ -282,38 +282,6 @@ public class DdiConfirmationBaseTest extends AbstractDDiApiIntegrationTest { verifyActionInDeploymentBaseState(controllerId, savedAction.getId()); } - @Step - private void verifyActionInDeploymentBaseState(final String controllerId, final long actionId) throws Exception { - final String expectedDeploymentBaseLink = String.format("/%s/controller/v1/%s/deploymentBase/%d", - tenantAware.getCurrentTenant(), controllerId, actionId); - - mvc.perform( - get(CONTROLLER_BASE, tenantAware.getCurrentTenant(), controllerId).accept(MediaType.APPLICATION_JSON)) - .andDo(MockMvcResultPrinter.print()).andExpect(status().isOk()) - .andExpect(jsonPath("$._links.deploymentBase.href", containsString(expectedDeploymentBaseLink))) - .andExpect(jsonPath("$._links.confirmationBase.href").doesNotExist()); - - // assert that deployment endpoint is working - mvc.perform(get(expectedDeploymentBaseLink).accept(MediaType.APPLICATION_JSON)) - .andDo(MockMvcResultPrinter.print()).andExpect(status().isOk()); - } - - @Step - private void verifyActionInConfirmationBaseState(final String controllerId, final long actionId) throws Exception { - mvc.perform( - get(CONTROLLER_BASE, tenantAware.getCurrentTenant(), controllerId).accept(MediaType.APPLICATION_JSON)) - .andDo(MockMvcResultPrinter.print()).andExpect(status().isOk()) - .andExpect(jsonPath("$._links.confirmationBase.href").exists()) - .andExpect(jsonPath("$._links.deploymentBase.href").doesNotExist()); - - mvc.perform(get(CONFIRMATION_BASE_ACTION, tenantAware.getCurrentTenant(), controllerId, actionId) - .accept(MediaType.APPLICATION_JSON)).andDo(MockMvcResultPrinter.print()).andExpect(status().isOk()); - - mvc.perform(get(DEPLOYMENT_BASE, tenantAware.getCurrentTenant(), controllerId, actionId) - .accept(MediaType.APPLICATION_JSON)).andDo(MockMvcResultPrinter.print()) - .andExpect(status().isNotFound()); - } - @Test @Description("Confirmation base provides right values if auto-confirm not active.") void getConfirmationBaseProvidesAutoConfirmStatusNotActive() throws Exception { @@ -331,7 +299,7 @@ public class DdiConfirmationBaseTest extends AbstractDDiApiIntegrationTest { tenantAware.getCurrentTenant(), controllerId); mvc.perform( - get(CONFIRMATION_BASE, tenantAware.getCurrentTenant(), controllerId).accept(MediaType.APPLICATION_JSON)) + get(CONFIRMATION_BASE, tenantAware.getCurrentTenant(), controllerId).accept(MediaType.APPLICATION_JSON)) .andDo(MockMvcResultPrinter.print()).andExpect(status().isOk()) .andExpect(jsonPath("autoConfirm.active", equalTo(Boolean.FALSE))) .andExpect(jsonPath("$._links.confirmationBase.href", containsString(confirmationBaseActionLink))) @@ -353,7 +321,7 @@ public class DdiConfirmationBaseTest extends AbstractDDiApiIntegrationTest { controllerId); mvc.perform( - get(CONFIRMATION_BASE, tenantAware.getCurrentTenant(), controllerId).accept(MediaType.APPLICATION_JSON)) + get(CONFIRMATION_BASE, tenantAware.getCurrentTenant(), controllerId).accept(MediaType.APPLICATION_JSON)) .andDo(MockMvcResultPrinter.print()).andExpect(status().isOk()) .andExpect(jsonPath("autoConfirm.active", equalTo(Boolean.TRUE))) .andExpect(initiator == null ? jsonPath("autoConfirm.initiator").doesNotExist() @@ -364,11 +332,6 @@ public class DdiConfirmationBaseTest extends AbstractDDiApiIntegrationTest { .andExpect(jsonPath("$._links.activateAutoConfirm").doesNotExist()); } - private static Stream possibleActiveStates() { - return Stream.of(Arguments.of("someInitiator", "someRemark"), Arguments.of(null, "someRemark"), - Arguments.of("someInitiator", null), Arguments.of(null, null)); - } - @ParameterizedTest @MethodSource("possibleActiveStates") @Description("Verify auto-confirm activation is handled correctly.") @@ -378,7 +341,7 @@ public class DdiConfirmationBaseTest extends AbstractDDiApiIntegrationTest { final DdiActivateAutoConfirmation body = new DdiActivateAutoConfirmation(initiator, remark); mvc.perform(post(ACTIVATE_AUTO_CONFIRM, tenantAware.getCurrentTenant(), controllerId) - .content(getMapper().writeValueAsString(body)).contentType(MediaType.APPLICATION_JSON_UTF8)) + .content(getMapper().writeValueAsString(body)).contentType(MediaType.APPLICATION_JSON_UTF8)) .andDo(MockMvcResultPrinter.print()).andExpect(status().isOk()); assertThat(confirmationManagement.getStatus(controllerId)).hasValueSatisfying(status -> { @@ -433,29 +396,16 @@ public class DdiConfirmationBaseTest extends AbstractDDiApiIntegrationTest { tenantAware.getCurrentTenant(), controllerId, savedAction.getId()); mvc.perform( - get(CONTROLLER_BASE, tenantAware.getCurrentTenant(), controllerId).accept(MediaType.APPLICATION_JSON)) + get(CONTROLLER_BASE, tenantAware.getCurrentTenant(), controllerId).accept(MediaType.APPLICATION_JSON)) .andDo(MockMvcResultPrinter.print()).andExpect(status().isOk()) .andExpect(jsonPath("$._links.deploymentBase.href").doesNotExist()) .andExpect(jsonPath("$._links.confirmationBase.href", containsString(expectedConfirmationBaseLink))); mvc.perform(get(DEPLOYMENT_BASE, tenantAware.getCurrentTenant(), controllerId, savedAction.getId()) - .accept(MediaType.APPLICATION_JSON)).andDo(MockMvcResultPrinter.print()) + .accept(MediaType.APPLICATION_JSON)).andDo(MockMvcResultPrinter.print()) .andExpect(status().isNotFound()); } - private ResultActions sendConfirmationFeedback(final Target target, final Action action, - final DdiConfirmationFeedback.Confirmation confirmation, Integer code, String message) throws Exception { - - if (message == null) { - message = RandomStringUtils.randomAlphanumeric(1000); - } - - final String feedback = getJsonConfirmationFeedback(confirmation, code, Collections.singletonList(message)); - return mvc.perform( - post(CONFIRMATION_FEEDBACK, tenantAware.getCurrentTenant(), target.getControllerId(), action.getId()) - .content(feedback).contentType(MediaType.APPLICATION_JSON)); - } - @Test @Description("Test to verify that only a specific count of messages are returned based on the input actionHistory for getControllerDeploymentActionFeedback endpoint.") @ExpectEvents({ @Expect(type = TargetCreatedEvent.class, count = 1), @@ -485,18 +435,68 @@ public class DdiConfirmationBaseTest extends AbstractDDiApiIntegrationTest { // confirmationBase not available in RUNNING state anymore mvc.perform(get(CONFIRMATION_BASE_ACTION, tenantAware.getCurrentTenant(), savedTarget.getControllerId(), - savedAction.getId()).contentType(MediaType.APPLICATION_JSON).accept(MediaType.APPLICATION_JSON)) + savedAction.getId()).contentType(MediaType.APPLICATION_JSON).accept(MediaType.APPLICATION_JSON)) .andDo(MockMvcResultPrinter.print()).andExpect(status().isNotFound()); // assert confirmed message against deploymentBase endpoint // this call will update the action due to retrieved action status update mvc.perform( - get(DEPLOYMENT_BASE + "?actionHistory=2", tenantAware.getCurrentTenant(), savedTarget.getControllerId(), - savedAction.getId()).contentType(MediaType.APPLICATION_JSON).accept(MediaType.APPLICATION_JSON)) + get(DEPLOYMENT_BASE + "?actionHistory=2", tenantAware.getCurrentTenant(), savedTarget.getControllerId(), + savedAction.getId()).contentType(MediaType.APPLICATION_JSON).accept(MediaType.APPLICATION_JSON)) .andDo(MockMvcResultPrinter.print()).andExpect(status().isOk()) .andExpect(jsonPath("$.actionHistory.messages", hasItem(containsString(CONFIRMED_MESSAGE)))) .andExpect(jsonPath("$.actionHistory.messages", hasItem(containsString(String.format(CONFIRMATION_CODE_MSG_PREFIX, CONFIRMED_CODE))))); } + private static Stream possibleActiveStates() { + return Stream.of(Arguments.of("someInitiator", "someRemark"), Arguments.of(null, "someRemark"), + Arguments.of("someInitiator", null), Arguments.of(null, null)); + } + + @Step + private void verifyActionInDeploymentBaseState(final String controllerId, final long actionId) throws Exception { + final String expectedDeploymentBaseLink = String.format("/%s/controller/v1/%s/deploymentBase/%d", + tenantAware.getCurrentTenant(), controllerId, actionId); + + mvc.perform( + get(CONTROLLER_BASE, tenantAware.getCurrentTenant(), controllerId).accept(MediaType.APPLICATION_JSON)) + .andDo(MockMvcResultPrinter.print()).andExpect(status().isOk()) + .andExpect(jsonPath("$._links.deploymentBase.href", containsString(expectedDeploymentBaseLink))) + .andExpect(jsonPath("$._links.confirmationBase.href").doesNotExist()); + + // assert that deployment endpoint is working + mvc.perform(get(expectedDeploymentBaseLink).accept(MediaType.APPLICATION_JSON)) + .andDo(MockMvcResultPrinter.print()).andExpect(status().isOk()); + } + + @Step + private void verifyActionInConfirmationBaseState(final String controllerId, final long actionId) throws Exception { + mvc.perform( + get(CONTROLLER_BASE, tenantAware.getCurrentTenant(), controllerId).accept(MediaType.APPLICATION_JSON)) + .andDo(MockMvcResultPrinter.print()).andExpect(status().isOk()) + .andExpect(jsonPath("$._links.confirmationBase.href").exists()) + .andExpect(jsonPath("$._links.deploymentBase.href").doesNotExist()); + + mvc.perform(get(CONFIRMATION_BASE_ACTION, tenantAware.getCurrentTenant(), controllerId, actionId) + .accept(MediaType.APPLICATION_JSON)).andDo(MockMvcResultPrinter.print()).andExpect(status().isOk()); + + mvc.perform(get(DEPLOYMENT_BASE, tenantAware.getCurrentTenant(), controllerId, actionId) + .accept(MediaType.APPLICATION_JSON)).andDo(MockMvcResultPrinter.print()) + .andExpect(status().isNotFound()); + } + + private ResultActions sendConfirmationFeedback(final Target target, final Action action, + final DdiConfirmationFeedback.Confirmation confirmation, Integer code, String message) throws Exception { + + if (message == null) { + message = RandomStringUtils.randomAlphanumeric(1000); + } + + final String feedback = getJsonConfirmationFeedback(confirmation, code, Collections.singletonList(message)); + return mvc.perform( + post(CONFIRMATION_FEEDBACK, tenantAware.getCurrentTenant(), target.getControllerId(), action.getId()) + .content(feedback).contentType(MediaType.APPLICATION_JSON)); + } + } diff --git a/hawkbit-rest/hawkbit-ddi-resource/src/test/java/org/eclipse/hawkbit/ddi/rest/resource/DdiDeploymentBaseTest.java b/hawkbit-rest/hawkbit-ddi-resource/src/test/java/org/eclipse/hawkbit/ddi/rest/resource/DdiDeploymentBaseTest.java index 092fa2ce8..f35040ecb 100644 --- a/hawkbit-rest/hawkbit-ddi-resource/src/test/java/org/eclipse/hawkbit/ddi/rest/resource/DdiDeploymentBaseTest.java +++ b/hawkbit-rest/hawkbit-ddi-resource/src/test/java/org/eclipse/hawkbit/ddi/rest/resource/DdiDeploymentBaseTest.java @@ -26,6 +26,10 @@ import java.util.Optional; import java.util.concurrent.TimeUnit; import java.util.stream.Collectors; +import com.jayway.jsonpath.JsonPath; +import io.qameta.allure.Description; +import io.qameta.allure.Feature; +import io.qameta.allure.Story; import org.apache.commons.lang3.RandomUtils; import org.assertj.core.api.Condition; import org.eclipse.hawkbit.ddi.json.model.DdiResult; @@ -66,12 +70,6 @@ import org.springframework.http.MediaType; import org.springframework.test.web.servlet.MvcResult; import org.springframework.test.web.servlet.request.MockMvcRequestBuilders; -import com.jayway.jsonpath.JsonPath; - -import io.qameta.allure.Description; -import io.qameta.allure.Feature; -import io.qameta.allure.Story; - /** * Test deployment base from the controller. */ @@ -79,6 +77,10 @@ import io.qameta.allure.Story; @Story("Deployment Action Resource") public class DdiDeploymentBaseTest extends AbstractDDiApiIntegrationTest { + @Autowired + ActionRepository actionRepository; + @Autowired + ActionStatusRepository actionStatusRepository; private static final String DEFAULT_CONTROLLER_ID = "4712"; @Test @@ -130,10 +132,10 @@ public class DdiDeploymentBaseTest extends AbstractDDiApiIntegrationTest { performGet(SOFTWARE_MODULE_ARTIFACTS, MediaType.APPLICATION_JSON, status().isOk(), tenantAware.getCurrentTenant(), target.getControllerId(), softwareModuleId.toString()) - .andExpect(jsonPath("$", hasSize(3))) - .andExpect(jsonPath("$.[?(@.filename=='filename0')]", hasSize(1))) - .andExpect(jsonPath("$.[?(@.filename=='filename1')]", hasSize(1))) - .andExpect(jsonPath("$.[?(@.filename=='filename2')]", hasSize(1))); + .andExpect(jsonPath("$", hasSize(3))) + .andExpect(jsonPath("$.[?(@.filename=='filename0')]", hasSize(1))) + .andExpect(jsonPath("$.[?(@.filename=='filename1')]", hasSize(1))) + .andExpect(jsonPath("$.[?(@.filename=='filename2')]", hasSize(1))); } @Test @@ -171,8 +173,8 @@ public class DdiDeploymentBaseTest extends AbstractDDiApiIntegrationTest { final long current = System.currentTimeMillis(); performGet(CONTROLLER_BASE, MediaTypes.HAL_JSON, status().isOk(), tenantAware.getCurrentTenant(), DEFAULT_CONTROLLER_ID).andExpect(jsonPath("$.config.polling.sleep", equalTo("00:01:00"))) - .andExpect(jsonPath("$._links.deploymentBase.href", - startsWith(deploymentBaseLink(DEFAULT_CONTROLLER_ID, uaction.getId().toString())))); + .andExpect(jsonPath("$._links.deploymentBase.href", + startsWith(deploymentBaseLink(DEFAULT_CONTROLLER_ID, uaction.getId().toString())))); assertThat(targetManagement.getByControllerID(DEFAULT_CONTROLLER_ID).get().getLastTargetQuery()) .isGreaterThanOrEqualTo(current); assertThat(targetManagement.getByControllerID(DEFAULT_CONTROLLER_ID).get().getLastTargetQuery()) @@ -214,7 +216,7 @@ public class DdiDeploymentBaseTest extends AbstractDDiApiIntegrationTest { DEFAULT_CONTROLLER_ID).andReturn(); assertThat(JsonPath.compile("_links.deploymentBase.href").read(mvcResult.getResponse().getContentAsString()) .toString()).isEqualTo(urlBeforeSwitch) - .startsWith(deploymentBaseLink(DEFAULT_CONTROLLER_ID, actionId.toString())); + .startsWith(deploymentBaseLink(DEFAULT_CONTROLLER_ID, actionId.toString())); // After the time is over we should see a new etag TimeUnit.MILLISECONDS.sleep(2_000); @@ -269,8 +271,8 @@ public class DdiDeploymentBaseTest extends AbstractDDiApiIntegrationTest { final long current = System.currentTimeMillis(); performGet(CONTROLLER_BASE, MediaTypes.HAL_JSON, status().isOk(), tenantAware.getCurrentTenant(), DEFAULT_CONTROLLER_ID).andExpect(jsonPath("$.config.polling.sleep", equalTo("00:01:00"))) - .andExpect(jsonPath("$._links.deploymentBase.href", - startsWith(deploymentBaseLink(DEFAULT_CONTROLLER_ID, uaction.getId().toString())))); + .andExpect(jsonPath("$._links.deploymentBase.href", + startsWith(deploymentBaseLink(DEFAULT_CONTROLLER_ID, uaction.getId().toString())))); assertThat(targetManagement.getByControllerID(DEFAULT_CONTROLLER_ID).get().getLastTargetQuery()) .isGreaterThanOrEqualTo(current); assertThat(targetManagement.getByControllerID(DEFAULT_CONTROLLER_ID).get().getLastTargetQuery()) @@ -327,8 +329,8 @@ public class DdiDeploymentBaseTest extends AbstractDDiApiIntegrationTest { final long current = System.currentTimeMillis(); performGet(CONTROLLER_BASE, MediaTypes.HAL_JSON, status().isOk(), tenantAware.getCurrentTenant(), DEFAULT_CONTROLLER_ID).andExpect(jsonPath("$.config.polling.sleep", equalTo("00:01:00"))) - .andExpect(jsonPath("$._links.deploymentBase.href", - startsWith(deploymentBaseLink(DEFAULT_CONTROLLER_ID, uaction.getId().toString())))); + .andExpect(jsonPath("$._links.deploymentBase.href", + startsWith(deploymentBaseLink(DEFAULT_CONTROLLER_ID, uaction.getId().toString())))); assertThat(targetManagement.getByControllerID(DEFAULT_CONTROLLER_ID).get().getLastTargetQuery()) .isGreaterThanOrEqualTo(current); assertThat(targetManagement.getByControllerID(DEFAULT_CONTROLLER_ID).get().getLastTargetQuery()) @@ -373,7 +375,7 @@ public class DdiDeploymentBaseTest extends AbstractDDiApiIntegrationTest { final List saved = assignDistributionSet(ds.getId(), savedTarget.getControllerId(), ActionType.DOWNLOAD_ONLY).getAssignedEntity().stream().map(Action::getTarget) - .collect(Collectors.toList()); + .collect(Collectors.toList()); implicitLock(ds); assertThat(deploymentManagement.findActiveActionsByTarget(PAGE, savedTarget.getControllerId())).hasSize(1); @@ -394,8 +396,8 @@ public class DdiDeploymentBaseTest extends AbstractDDiApiIntegrationTest { final long current = System.currentTimeMillis(); performGet(CONTROLLER_BASE, MediaTypes.HAL_JSON, status().isOk(), tenantAware.getCurrentTenant(), DEFAULT_CONTROLLER_ID).andExpect(jsonPath("$.config.polling.sleep", equalTo("00:01:00"))) - .andExpect(jsonPath("$._links.deploymentBase.href", - startsWith(deploymentBaseLink(DEFAULT_CONTROLLER_ID, uaction.getId().toString())))); + .andExpect(jsonPath("$._links.deploymentBase.href", + startsWith(deploymentBaseLink(DEFAULT_CONTROLLER_ID, uaction.getId().toString())))); assertThat(targetManagement.getByControllerID(DEFAULT_CONTROLLER_ID).get().getLastTargetQuery()) .isGreaterThanOrEqualTo(current); assertThat(targetManagement.getByControllerID(DEFAULT_CONTROLLER_ID).get().getLastTargetQuery()) @@ -417,17 +419,6 @@ public class DdiDeploymentBaseTest extends AbstractDDiApiIntegrationTest { } - private void getAndVerifyDeploymentBasePayload(final String controllerId, final MediaType mediaType, - final DistributionSet ds, final String visibleMetadataOsKey, final String visibleMetadataOsValue, - final Artifact artifact, final Artifact artifactSignature, final Long actionId, final String downloadType, - final String updateType, final Long osModuleId) throws Exception { - getAndVerifyDeploymentBasePayload(controllerId, mediaType, ds, artifact, artifactSignature, actionId, - osModuleId, downloadType, updateType).andExpect( - jsonPath("$.deployment.chunks[?(@.part=='os')].metadata[0].key").value(visibleMetadataOsKey)) - .andExpect(jsonPath("$.deployment.chunks[?(@.part=='os')].metadata[0].value") - .value(visibleMetadataOsValue)); - } - @Test @Description("Test various invalid access attempts to the deployment resource und the expected behaviour of the server.") public void badDeploymentAction() throws Exception { @@ -449,7 +440,7 @@ public class DdiDeploymentBaseTest extends AbstractDDiApiIntegrationTest { // no deployment mvc.perform( - MockMvcRequestBuilders.get(DEPLOYMENT_BASE, tenantAware.getCurrentTenant(), DEFAULT_CONTROLLER_ID, "1")) + MockMvcRequestBuilders.get(DEPLOYMENT_BASE, tenantAware.getCurrentTenant(), DEFAULT_CONTROLLER_ID, "1")) .andDo(MockMvcResultPrinter.print()).andExpect(status().isNotFound()); // wrong media type @@ -460,8 +451,8 @@ public class DdiDeploymentBaseTest extends AbstractDDiApiIntegrationTest { mvc.perform(MockMvcRequestBuilders.get(DEPLOYMENT_BASE, tenantAware.getCurrentTenant(), DEFAULT_CONTROLLER_ID, actionId)).andDo(MockMvcResultPrinter.print()).andExpect(status().isOk()); mvc.perform(MockMvcRequestBuilders - .get(DEPLOYMENT_BASE, tenantAware.getCurrentTenant(), DEFAULT_CONTROLLER_ID, actionId) - .accept(MediaType.APPLICATION_ATOM_XML)).andDo(MockMvcResultPrinter.print()) + .get(DEPLOYMENT_BASE, tenantAware.getCurrentTenant(), DEFAULT_CONTROLLER_ID, actionId) + .accept(MediaType.APPLICATION_ATOM_XML)).andDo(MockMvcResultPrinter.print()) .andExpect(status().isNotAcceptable()); } @@ -552,8 +543,6 @@ public class DdiDeploymentBaseTest extends AbstractDDiApiIntegrationTest { assertStatusMessagesCount(6); } - @Autowired - ActionRepository actionRepository; @Test @Description("Verifies that an update action is correctly set to error if the controller provides error feedback.") public void rootRsSingleDeploymentActionWithErrorFeedback() throws Exception { @@ -669,7 +658,7 @@ public class DdiDeploymentBaseTest extends AbstractDDiApiIntegrationTest { // not allowed methods mvc.perform(MockMvcRequestBuilders.get(DEPLOYMENT_FEEDBACK, tenantAware.getCurrentTenant(), - DEFAULT_CONTROLLER_ID, "2")).andDo(MockMvcResultPrinter.print()) + DEFAULT_CONTROLLER_ID, "2")).andDo(MockMvcResultPrinter.print()) .andExpect(status().isMethodNotAllowed()); mvc.perform(put(DEPLOYMENT_FEEDBACK, tenantAware.getCurrentTenant(), DEFAULT_CONTROLLER_ID, "2")) @@ -750,6 +739,21 @@ public class DdiDeploymentBaseTest extends AbstractDDiApiIntegrationTest { .andExpect(jsonPath("$.exceptionClass", equalTo(MessageNotReadableException.class.getCanonicalName()))); } + public long countActionStatusAll() { + return actionStatusRepository.count(); + } + + private void getAndVerifyDeploymentBasePayload(final String controllerId, final MediaType mediaType, + final DistributionSet ds, final String visibleMetadataOsKey, final String visibleMetadataOsValue, + final Artifact artifact, final Artifact artifactSignature, final Long actionId, final String downloadType, + final String updateType, final Long osModuleId) throws Exception { + getAndVerifyDeploymentBasePayload(controllerId, mediaType, ds, artifact, artifactSignature, actionId, + osModuleId, downloadType, updateType).andExpect( + jsonPath("$.deployment.chunks[?(@.part=='os')].metadata[0].key").value(visibleMetadataOsKey)) + .andExpect(jsonPath("$.deployment.chunks[?(@.part=='os')].metadata[0].value") + .value(visibleMetadataOsValue)); + } + private void assertActionStatusCount(final int actionStatusCount, final int minActionStatusCountInPage) { final Target target = targetManagement.getByControllerID(DdiDeploymentBaseTest.DEFAULT_CONTROLLER_ID).get(); assertThat(target.getUpdateStatus()).isEqualTo(TargetUpdateStatus.PENDING); @@ -762,19 +766,6 @@ public class DdiDeploymentBaseTest extends AbstractDDiApiIntegrationTest { new ActionStatusCondition(Status.RUNNING)); } - private static class ActionStatusCondition extends Condition { - private final Action.Status status; - - public ActionStatusCondition(final Action.Status status) { - this.status = status; - } - - @Override - public boolean matches(final ActionStatus value) { - return value.getStatus() == status; - } - } - private Target createTargetAndAssertNoActiveActions() { final Target savedTarget = testdataFactory.createTarget(DdiDeploymentBaseTest.DEFAULT_CONTROLLER_ID); assertThat(deploymentManagement.findActiveActionsByTarget(PAGE, savedTarget.getControllerId())).isEmpty(); @@ -829,12 +820,21 @@ public class DdiDeploymentBaseTest extends AbstractDDiApiIntegrationTest { .hasSize(activeActions); } - @Autowired - ActionStatusRepository actionStatusRepository; private Page findActionStatusAll(final Pageable pageable) { return JpaManagementHelper.findAllWithCountBySpec(actionStatusRepository, pageable, null); } - public long countActionStatusAll() { - return actionStatusRepository.count(); + + private static class ActionStatusCondition extends Condition { + + private final Action.Status status; + + public ActionStatusCondition(final Action.Status status) { + this.status = status; + } + + @Override + public boolean matches(final ActionStatus value) { + return value.getStatus() == status; + } } } diff --git a/hawkbit-rest/hawkbit-ddi-resource/src/test/java/org/eclipse/hawkbit/ddi/rest/resource/DdiInstalledBaseTest.java b/hawkbit-rest/hawkbit-ddi-resource/src/test/java/org/eclipse/hawkbit/ddi/rest/resource/DdiInstalledBaseTest.java index a971aa13b..2d0f0c129 100644 --- a/hawkbit-rest/hawkbit-ddi-resource/src/test/java/org/eclipse/hawkbit/ddi/rest/resource/DdiInstalledBaseTest.java +++ b/hawkbit-rest/hawkbit-ddi-resource/src/test/java/org/eclipse/hawkbit/ddi/rest/resource/DdiInstalledBaseTest.java @@ -27,6 +27,9 @@ import java.util.Collections; import java.util.List; import java.util.stream.Stream; +import io.qameta.allure.Description; +import io.qameta.allure.Feature; +import io.qameta.allure.Story; import org.apache.commons.lang3.RandomUtils; import org.eclipse.hawkbit.ddi.json.model.DdiResult; import org.eclipse.hawkbit.ddi.json.model.DdiStatus; @@ -62,10 +65,6 @@ import org.springframework.http.MediaType; import org.springframework.test.web.servlet.ResultActions; import org.springframework.test.web.servlet.request.MockMvcRequestBuilders; -import io.qameta.allure.Description; -import io.qameta.allure.Feature; -import io.qameta.allure.Story; - /** * Test installed base from the controller. */ @@ -73,6 +72,8 @@ import io.qameta.allure.Story; @Story("Installed Base Resource") public class DdiInstalledBaseTest extends AbstractDDiApiIntegrationTest { + @Autowired + ActionStatusRepository actionStatusRepository; private static final int ARTIFACT_SIZE = 5 * 1024; private static final String CONTROLLER_ID = "4715"; @@ -105,17 +106,17 @@ public class DdiInstalledBaseTest extends AbstractDDiApiIntegrationTest { final Target target = createTargetAndAssertNoActiveActions(); final DistributionSet ds = testdataFactory.createDistributionSet(""); - // update assigned version - putInstalledBase(target.getControllerId(),getJsonInstalledBase(ds.getName(),ds.getVersion()),status() + putInstalledBase(target.getControllerId(), getJsonInstalledBase(ds.getName(), ds.getVersion()), status() .isCreated()); assertThat(deploymentManagement.getAssignedDistributionSet(target.getControllerId()).get().getId()) .isEqualTo(ds.getId()); // update assigned version while version already assigned - putInstalledBase(target.getControllerId(),getJsonInstalledBase(ds.getName(),ds.getVersion()),status().isOk()); + putInstalledBase(target.getControllerId(), getJsonInstalledBase(ds.getName(), ds.getVersion()), status().isOk()); } + @Test @Description("Ensure that installedVersion is version self assigned") public void installedVersionNotExist() throws Exception { @@ -123,9 +124,8 @@ public class DdiInstalledBaseTest extends AbstractDDiApiIntegrationTest { final String dsName = "unknown"; final String dsVersion = "1.0.0"; - // get installed base - putInstalledBase(target.getControllerId(),getJsonInstalledBase(dsName,dsVersion),status().isNotFound()); + putInstalledBase(target.getControllerId(), getJsonInstalledBase(dsName, dsVersion), status().isNotFound()); assertThat(deploymentManagement.getAssignedDistributionSet(target.getControllerId()).isEmpty()).isTrue(); } @@ -401,14 +401,10 @@ public class DdiInstalledBaseTest extends AbstractDDiApiIntegrationTest { performGet(SOFTWARE_MODULE_ARTIFACTS, MediaType.APPLICATION_JSON, status().isOk(), tenantAware.getCurrentTenant(), target.getControllerId(), softwareModuleId.toString()) - .andExpect(jsonPath("$", hasSize(3))) - .andExpect(jsonPath("$.[?(@.filename=='filename0')]", hasSize(1))) - .andExpect(jsonPath("$.[?(@.filename=='filename1')]", hasSize(1))) - .andExpect(jsonPath("$.[?(@.filename=='filename2')]", hasSize(1))); - } - - private static Stream actionTypeForDeployment() { - return Stream.of(Action.ActionType.SOFT, Action.ActionType.FORCED); + .andExpect(jsonPath("$", hasSize(3))) + .andExpect(jsonPath("$.[?(@.filename=='filename0')]", hasSize(1))) + .andExpect(jsonPath("$.[?(@.filename=='filename1')]", hasSize(1))) + .andExpect(jsonPath("$.[?(@.filename=='filename2')]", hasSize(1))); } @ParameterizedTest @@ -484,8 +480,8 @@ public class DdiInstalledBaseTest extends AbstractDDiApiIntegrationTest { performGet(CONTROLLER_BASE, MediaType.APPLICATION_JSON, status().isOk(), tenantAware.getCurrentTenant(), target.getControllerId()).andDo(MockMvcResultPrinter.print()).andExpect(status().isOk()) - .andExpect(jsonPath("$._links.deploymentBase.href").exists()) - .andExpect(jsonPath("$._links.installedBase.href").doesNotExist()); + .andExpect(jsonPath("$._links.deploymentBase.href").exists()) + .andExpect(jsonPath("$._links.installedBase.href").doesNotExist()); postDeploymentFeedback(target.getControllerId(), actionId, getJsonDownloadDeploymentActionFeedback(), status().isOk()); @@ -495,8 +491,8 @@ public class DdiInstalledBaseTest extends AbstractDDiApiIntegrationTest { // Test performGet(CONTROLLER_BASE, MediaType.APPLICATION_JSON, status().isOk(), tenantAware.getCurrentTenant(), target.getControllerId()).andDo(MockMvcResultPrinter.print()).andExpect(status().isOk()) - .andExpect(jsonPath("$._links.deploymentBase.href").doesNotExist()) - .andExpect(jsonPath("$._links.installedBase.href").doesNotExist()); + .andExpect(jsonPath("$._links.deploymentBase.href").doesNotExist()) + .andExpect(jsonPath("$._links.installedBase.href").doesNotExist()); } @ParameterizedTest @@ -511,8 +507,8 @@ public class DdiInstalledBaseTest extends AbstractDDiApiIntegrationTest { performGet(CONTROLLER_BASE, MediaType.APPLICATION_JSON, status().isOk(), tenantAware.getCurrentTenant(), target.getControllerId()).andDo(MockMvcResultPrinter.print()).andExpect(status().isOk()) - .andExpect(jsonPath("$._links.deploymentBase.href").exists()) - .andExpect(jsonPath("$._links.installedBase.href").doesNotExist()); + .andExpect(jsonPath("$._links.deploymentBase.href").exists()) + .andExpect(jsonPath("$._links.installedBase.href").doesNotExist()); postDeploymentFeedback(target.getControllerId(), actionId, getJsonActionFeedback(DdiStatus.ExecutionStatus.PROCEEDING, DdiResult.FinalResult.NONE), @@ -525,8 +521,8 @@ public class DdiInstalledBaseTest extends AbstractDDiApiIntegrationTest { // Test performGet(CONTROLLER_BASE, MediaType.APPLICATION_JSON, status().isOk(), tenantAware.getCurrentTenant(), target.getControllerId()).andDo(MockMvcResultPrinter.print()).andExpect(status().isOk()) - .andExpect(jsonPath("$._links.deploymentBase.href").doesNotExist()) - .andExpect(jsonPath("$._links.installedBase.href").doesNotExist()); + .andExpect(jsonPath("$._links.deploymentBase.href").doesNotExist()) + .andExpect(jsonPath("$._links.installedBase.href").doesNotExist()); } @Test @@ -556,14 +552,14 @@ public class DdiInstalledBaseTest extends AbstractDDiApiIntegrationTest { // Test // for zero input no action history is returned mvc.perform(get(INSTALLED_BASE + "?actionHistory", tenantAware.getCurrentTenant(), 911, savedAction.getId()) - .contentType(MediaType.APPLICATION_JSON).accept(MediaType.APPLICATION_JSON)) + .contentType(MediaType.APPLICATION_JSON).accept(MediaType.APPLICATION_JSON)) .andDo(MockMvcResultPrinter.print()).andExpect(status().isOk()) .andExpect(jsonPath("$.actionHistory.messages").doesNotExist()); // depending on given query parameter value, only the latest messages are // returned mvc.perform(get(INSTALLED_BASE + "?actionHistory=2", tenantAware.getCurrentTenant(), 911, savedAction.getId()) - .contentType(MediaType.APPLICATION_JSON).accept(MediaType.APPLICATION_JSON)) + .contentType(MediaType.APPLICATION_JSON).accept(MediaType.APPLICATION_JSON)) .andDo(MockMvcResultPrinter.print()).andExpect(status().isOk()) .andExpect(jsonPath("$.actionHistory.messages", hasItem(containsString("Installation completed")))) .andExpect(jsonPath("$.actionHistory.messages", hasItem(containsString("Installation proceeding")))) @@ -572,7 +568,7 @@ public class DdiInstalledBaseTest extends AbstractDDiApiIntegrationTest { // for negative input the entire action history is returned mvc.perform(get(INSTALLED_BASE + "?actionHistory=-3", tenantAware.getCurrentTenant(), 911, savedAction.getId()) - .contentType(MediaType.APPLICATION_JSON).accept(MediaType.APPLICATION_JSON)) + .contentType(MediaType.APPLICATION_JSON).accept(MediaType.APPLICATION_JSON)) .andDo(MockMvcResultPrinter.print()).andExpect(status().isOk()) .andExpect(jsonPath("$.actionHistory.messages", hasItem(containsString("Installation completed")))) .andExpect(jsonPath("$.actionHistory.messages", hasItem(containsString("Installation proceeding")))) @@ -611,12 +607,14 @@ public class DdiInstalledBaseTest extends AbstractDDiApiIntegrationTest { mvc.perform(MockMvcRequestBuilders.get(INSTALLED_BASE, tenantAware.getCurrentTenant(), CONTROLLER_ID, actionId)) .andDo(MockMvcResultPrinter.print()).andExpect(status().isOk()); mvc.perform(MockMvcRequestBuilders.get(INSTALLED_BASE, tenantAware.getCurrentTenant(), CONTROLLER_ID, actionId) - .accept(MediaType.APPLICATION_ATOM_XML)).andDo(MockMvcResultPrinter.print()) + .accept(MediaType.APPLICATION_ATOM_XML)).andDo(MockMvcResultPrinter.print()) .andExpect(status().isNotAcceptable()); } - @Autowired - ActionStatusRepository actionStatusRepository; + private static Stream actionTypeForDeployment() { + return Stream.of(Action.ActionType.SOFT, Action.ActionType.FORCED); + } + private Target createTargetAndAssertNoActiveActions() { final Target savedTarget = testdataFactory.createTarget(CONTROLLER_ID); assertThat(deploymentManagement.findActiveActionsByTarget(PAGE, savedTarget.getControllerId())).isEmpty(); diff --git a/hawkbit-rest/hawkbit-ddi-resource/src/test/java/org/eclipse/hawkbit/ddi/rest/resource/DdiRootControllerTest.java b/hawkbit-rest/hawkbit-ddi-resource/src/test/java/org/eclipse/hawkbit/ddi/rest/resource/DdiRootControllerTest.java index 94b510662..fddb6ca24 100644 --- a/hawkbit-rest/hawkbit-ddi-resource/src/test/java/org/eclipse/hawkbit/ddi/rest/resource/DdiRootControllerTest.java +++ b/hawkbit-rest/hawkbit-ddi-resource/src/test/java/org/eclipse/hawkbit/ddi/rest/resource/DdiRootControllerTest.java @@ -33,6 +33,10 @@ import java.util.Collections; import java.util.Map; import java.util.UUID; +import io.qameta.allure.Description; +import io.qameta.allure.Feature; +import io.qameta.allure.Step; +import io.qameta.allure.Story; import org.apache.commons.lang3.RandomStringUtils; import org.eclipse.hawkbit.ddi.json.model.DdiResult; import org.eclipse.hawkbit.ddi.json.model.DdiStatus; @@ -72,11 +76,6 @@ import org.springframework.http.MediaType; import org.springframework.test.web.servlet.MvcResult; import org.springframework.test.web.servlet.ResultActions; -import io.qameta.allure.Description; -import io.qameta.allure.Feature; -import io.qameta.allure.Step; -import io.qameta.allure.Story; - /** * Test the root controller resources. */ @@ -270,7 +269,7 @@ class DdiRootControllerTest extends AbstractDDiApiIntegrationTest { assertThat(etagWithFirstUpdate).isNotNull(); mvc.perform(get(CONTROLLER_BASE, tenantAware.getCurrentTenant(), controllerId).header("If-None-Match", - etagWithFirstUpdate).with(new RequestOnHawkbitDefaultPortPostProcessor())) + etagWithFirstUpdate).with(new RequestOnHawkbitDefaultPortPostProcessor())) .andDo(MockMvcResultPrinter.print()).andExpect(status().isNotModified()); // now lets finish the update @@ -300,8 +299,8 @@ class DdiRootControllerTest extends AbstractDDiApiIntegrationTest { .getContent().get(0); mvc.perform(get(CONTROLLER_BASE, tenantAware.getCurrentTenant(), controllerId) - .header("If-None-Match", etagAfterInstallation).accept(MediaType.APPLICATION_JSON) - .with(new RequestOnHawkbitDefaultPortPostProcessor())) + .header("If-None-Match", etagAfterInstallation).accept(MediaType.APPLICATION_JSON) + .with(new RequestOnHawkbitDefaultPortPostProcessor())) .andDo(MockMvcResultPrinter.print()).andExpect(status().isOk()) .andExpect(content().contentType(MediaType.APPLICATION_JSON)) .andExpect(jsonPath("$.config.polling.sleep", equalTo("00:01:00"))) @@ -430,7 +429,7 @@ class DdiRootControllerTest extends AbstractDDiApiIntegrationTest { assertThatAttributesUpdateIsRequested(savedTarget.getControllerId()); mvc.perform(put(CONTROLLER_BASE + "/configData", tenantAware.getCurrentTenant(), savedTarget.getControllerId()) - .content(JsonBuilder.configData(attributes).toString()).contentType(MediaType.APPLICATION_JSON)) + .content(JsonBuilder.configData(attributes).toString()).contentType(MediaType.APPLICATION_JSON)) .andExpect(status().isOk()); assertThatAttributesUpdateIsNotRequested(savedTarget.getControllerId()); @@ -439,59 +438,6 @@ class DdiRootControllerTest extends AbstractDDiApiIntegrationTest { assertAttributesUpdateRequestedAfterSuccessfulDeployment(savedTarget, ds); } - @Step - private void assertAttributesUpdateNotRequestedAfterFailedDeployment(Target target, final DistributionSet ds) - throws Exception { - target = getFirstAssignedTarget(assignDistributionSet(ds.getId(), target.getControllerId())); - final Action action = deploymentManagement.findActiveActionsByTarget(PAGE, target.getControllerId()) - .getContent().get(0); - sendDeploymentActionFeedback(target, action, "closed", "failure").andExpect(status().isOk()); - assertThatAttributesUpdateIsNotRequested(target.getControllerId()); - } - - @Step - private void assertAttributesUpdateRequestedAfterSuccessfulDeployment(Target target, final DistributionSet ds) - throws Exception { - target = getFirstAssignedTarget(assignDistributionSet(ds.getId(), target.getControllerId())); - final Action action = deploymentManagement.findActiveActionsByTarget(PAGE, target.getControllerId()) - .getContent().get(0); - sendDeploymentActionFeedback(target, action, "closed", null).andExpect(status().isOk()); - assertThatAttributesUpdateIsRequested(target.getControllerId()); - } - - private void assertThatAttributesUpdateIsRequested(final String targetControllerId) throws Exception { - mvc.perform(get(CONTROLLER_BASE, tenantAware.getCurrentTenant(), targetControllerId) - .accept(MediaType.APPLICATION_JSON)).andExpect(status().isOk()) - .andExpect(jsonPath("$._links.configData.href").isNotEmpty()); - } - - private void assertThatAttributesUpdateIsNotRequested(final String targetControllerId) throws Exception { - mvc.perform(get(CONTROLLER_BASE, tenantAware.getCurrentTenant(), targetControllerId) - .accept(MediaType.APPLICATION_JSON)).andExpect(status().isOk()) - .andExpect(jsonPath("$._links.configData").doesNotExist()); - } - - private ResultActions sendDeploymentActionFeedback(final Target target, final Action action, final String execution, - String finished, String message) throws Exception { - if (finished == null) { - finished = "none"; - } - if (message == null) { - message = RandomStringUtils.randomAlphanumeric(1000); - } - - final String feedback = getJsonActionFeedback(DdiStatus.ExecutionStatus.valueOf(execution.toUpperCase()), - DdiResult.FinalResult.valueOf(finished.toUpperCase()), Collections.singletonList(message)); - return mvc.perform( - post(DEPLOYMENT_FEEDBACK, tenantAware.getCurrentTenant(), target.getControllerId(), action.getId()) - .content(feedback).contentType(MediaType.APPLICATION_JSON)); - } - - private ResultActions sendDeploymentActionFeedback(final Target target, final Action action, final String execution, - final String finished) throws Exception { - return sendDeploymentActionFeedback(target, action, execution, finished, null); - } - @Test @Description("Test to verify that only a specific count of messages are returned based on the input actionHistory for getControllerDeploymentActionFeedback endpoint.") @ExpectEvents({ @Expect(type = TargetCreatedEvent.class, count = 1), @@ -520,7 +466,7 @@ class DdiRootControllerTest extends AbstractDDiApiIntegrationTest { .andDo(MockMvcResultPrinter.print()).andExpect(status().isOk()); mvc.perform(get(DEPLOYMENT_BASE + "?actionHistory=2", tenantAware.getCurrentTenant(), 911, savedAction.getId()) - .contentType(MediaType.APPLICATION_JSON).accept(MediaType.APPLICATION_JSON)) + .contentType(MediaType.APPLICATION_JSON).accept(MediaType.APPLICATION_JSON)) .andDo(MockMvcResultPrinter.print()).andExpect(status().isOk()) .andExpect(jsonPath("$.actionHistory.messages", hasItem(containsString(TARGET_COMPLETED_INSTALLATION_MSG)))) @@ -558,12 +504,12 @@ class DdiRootControllerTest extends AbstractDDiApiIntegrationTest { .andDo(MockMvcResultPrinter.print()).andExpect(status().isOk()); mvc.perform(get(DEPLOYMENT_BASE + "?actionHistory=0", tenantAware.getCurrentTenant(), 911, savedAction.getId()) - .contentType(MediaType.APPLICATION_JSON).accept(MediaType.APPLICATION_JSON)) + .contentType(MediaType.APPLICATION_JSON).accept(MediaType.APPLICATION_JSON)) .andDo(MockMvcResultPrinter.print()).andExpect(status().isOk()) .andExpect(jsonPath("$.actionHistory.messages").doesNotExist()); mvc.perform(get(DEPLOYMENT_BASE + "?actionHistory", tenantAware.getCurrentTenant(), 911, savedAction.getId()) - .contentType(MediaType.APPLICATION_JSON).accept(MediaType.APPLICATION_JSON)) + .contentType(MediaType.APPLICATION_JSON).accept(MediaType.APPLICATION_JSON)) .andDo(MockMvcResultPrinter.print()).andExpect(status().isOk()) .andExpect(jsonPath("$.actionHistory.messages").doesNotExist()); } @@ -596,7 +542,7 @@ class DdiRootControllerTest extends AbstractDDiApiIntegrationTest { .andDo(MockMvcResultPrinter.print()).andExpect(status().isOk()); mvc.perform(get(DEPLOYMENT_BASE + "?actionHistory=-1", tenantAware.getCurrentTenant(), 911, savedAction.getId()) - .contentType(MediaType.APPLICATION_JSON).accept(MediaType.APPLICATION_JSON)) + .contentType(MediaType.APPLICATION_JSON).accept(MediaType.APPLICATION_JSON)) .andDo(MockMvcResultPrinter.print()).andExpect(status().isOk()) .andExpect(jsonPath("$.actionHistory.messages", hasItem(containsString(TARGET_SCHEDULED_INSTALLATION_MSG)))) @@ -668,7 +614,7 @@ class DdiRootControllerTest extends AbstractDDiApiIntegrationTest { .getContent().get(0); mvc.perform(get(DEPLOYMENT_BASE, tenantAware.getCurrentTenant(), "1911", action.getId()) - .accept(MediaType.APPLICATION_JSON)).andDo(MockMvcResultPrinter.print()).andExpect(status().isOk()) + .accept(MediaType.APPLICATION_JSON)).andDo(MockMvcResultPrinter.print()).andExpect(status().isOk()) .andExpect(jsonPath("$.deployment.download", equalTo("forced"))) .andExpect(jsonPath("$.deployment.update", equalTo("skip"))) .andExpect(jsonPath("$.deployment.maintenanceWindow", equalTo("unavailable"))); @@ -688,7 +634,7 @@ class DdiRootControllerTest extends AbstractDDiApiIntegrationTest { .getContent().get(0); mvc.perform(get(DEPLOYMENT_BASE, tenantAware.getCurrentTenant(), "1911", action.getId()) - .accept(MediaType.APPLICATION_JSON)).andDo(MockMvcResultPrinter.print()).andExpect(status().isOk()) + .accept(MediaType.APPLICATION_JSON)).andDo(MockMvcResultPrinter.print()).andExpect(status().isOk()) .andExpect(jsonPath("$.deployment.download", equalTo("forced"))) .andExpect(jsonPath("$.deployment.update", equalTo("forced"))) .andExpect(jsonPath("$.deployment.maintenanceWindow", equalTo("available"))); @@ -719,12 +665,65 @@ class DdiRootControllerTest extends AbstractDDiApiIntegrationTest { .andExpect(status().isBadRequest()); } + @Step + private void assertAttributesUpdateNotRequestedAfterFailedDeployment(Target target, final DistributionSet ds) + throws Exception { + target = getFirstAssignedTarget(assignDistributionSet(ds.getId(), target.getControllerId())); + final Action action = deploymentManagement.findActiveActionsByTarget(PAGE, target.getControllerId()) + .getContent().get(0); + sendDeploymentActionFeedback(target, action, "closed", "failure").andExpect(status().isOk()); + assertThatAttributesUpdateIsNotRequested(target.getControllerId()); + } + + @Step + private void assertAttributesUpdateRequestedAfterSuccessfulDeployment(Target target, final DistributionSet ds) + throws Exception { + target = getFirstAssignedTarget(assignDistributionSet(ds.getId(), target.getControllerId())); + final Action action = deploymentManagement.findActiveActionsByTarget(PAGE, target.getControllerId()) + .getContent().get(0); + sendDeploymentActionFeedback(target, action, "closed", null).andExpect(status().isOk()); + assertThatAttributesUpdateIsRequested(target.getControllerId()); + } + + private void assertThatAttributesUpdateIsRequested(final String targetControllerId) throws Exception { + mvc.perform(get(CONTROLLER_BASE, tenantAware.getCurrentTenant(), targetControllerId) + .accept(MediaType.APPLICATION_JSON)).andExpect(status().isOk()) + .andExpect(jsonPath("$._links.configData.href").isNotEmpty()); + } + + private void assertThatAttributesUpdateIsNotRequested(final String targetControllerId) throws Exception { + mvc.perform(get(CONTROLLER_BASE, tenantAware.getCurrentTenant(), targetControllerId) + .accept(MediaType.APPLICATION_JSON)).andExpect(status().isOk()) + .andExpect(jsonPath("$._links.configData").doesNotExist()); + } + + private ResultActions sendDeploymentActionFeedback(final Target target, final Action action, final String execution, + String finished, String message) throws Exception { + if (finished == null) { + finished = "none"; + } + if (message == null) { + message = RandomStringUtils.randomAlphanumeric(1000); + } + + final String feedback = getJsonActionFeedback(DdiStatus.ExecutionStatus.valueOf(execution.toUpperCase()), + DdiResult.FinalResult.valueOf(finished.toUpperCase()), Collections.singletonList(message)); + return mvc.perform( + post(DEPLOYMENT_FEEDBACK, tenantAware.getCurrentTenant(), target.getControllerId(), action.getId()) + .content(feedback).contentType(MediaType.APPLICATION_JSON)); + } + + private ResultActions sendDeploymentActionFeedback(final Target target, final Action action, final String execution, + final String finished) throws Exception { + return sendDeploymentActionFeedback(target, action, execution, finished, null); + } + private void assertDeploymentActionIsExposedToTarget(final String controllerId, final long expectedActionId) throws Exception { final String expectedDeploymentBaseLink = String.format("/%s/controller/v1/%s/deploymentBase/%d", tenantAware.getCurrentTenant(), controllerId, expectedActionId); mvc.perform( - get(CONTROLLER_BASE, tenantAware.getCurrentTenant(), controllerId).accept(MediaType.APPLICATION_JSON)) + get(CONTROLLER_BASE, tenantAware.getCurrentTenant(), controllerId).accept(MediaType.APPLICATION_JSON)) .andDo(MockMvcResultPrinter.print()).andExpect(status().isOk()) .andExpect(jsonPath("$._links.deploymentBase.href", containsString(expectedDeploymentBaseLink))); diff --git a/hawkbit-rest/hawkbit-ddi-resource/src/test/java/org/eclipse/hawkbit/ddi/rest/resource/DosFilterTest.java b/hawkbit-rest/hawkbit-ddi-resource/src/test/java/org/eclipse/hawkbit/ddi/rest/resource/DosFilterTest.java index b453bd9ec..de3d89f26 100644 --- a/hawkbit-rest/hawkbit-ddi-resource/src/test/java/org/eclipse/hawkbit/ddi/rest/resource/DosFilterTest.java +++ b/hawkbit-rest/hawkbit-ddi-resource/src/test/java/org/eclipse/hawkbit/ddi/rest/resource/DosFilterTest.java @@ -17,6 +17,9 @@ import static org.springframework.test.web.servlet.result.MockMvcResultMatchers. import java.util.Collections; import java.util.List; +import io.qameta.allure.Description; +import io.qameta.allure.Feature; +import io.qameta.allure.Story; import org.eclipse.hawkbit.repository.model.Action; import org.eclipse.hawkbit.repository.model.DistributionSet; import org.eclipse.hawkbit.repository.model.Target; @@ -30,13 +33,8 @@ import org.springframework.test.web.servlet.MvcResult; import org.springframework.test.web.servlet.setup.DefaultMockMvcBuilder; import org.springframework.web.context.WebApplicationContext; -import io.qameta.allure.Description; -import io.qameta.allure.Feature; -import io.qameta.allure.Story; - /** * Test potential DOS attack scenarios and check if the filter prevents them. - * */ @ActiveProfiles({ "test" }) @Feature("Component Tests - REST Security") @@ -100,7 +98,8 @@ class DosFilterTest extends AbstractDDiApiIntegrationTest { @Test @Description("Ensures that a relatively high number of READ requests is allowed if it is below the DoS detection threshold") - @SuppressWarnings("squid:S2925") // No idea how to get rid of the Thread.sleep here + @SuppressWarnings("squid:S2925") + // No idea how to get rid of the Thread.sleep here void acceptableGetLoad() throws Exception { for (int x = 0; x < 3; x++) { @@ -123,7 +122,7 @@ class DosFilterTest extends AbstractDDiApiIntegrationTest { int requests = 0; do { result = mvc.perform(post("/{tenant}/controller/v1/4711/deploymentBase/" + actionId + "/feedback", - tenantAware.getCurrentTenant()).header(X_FORWARDED_FOR, "10.0.0.1").content(feedback) + tenantAware.getCurrentTenant()).header(X_FORWARDED_FOR, "10.0.0.1").content(feedback) .contentType(MediaType.APPLICATION_JSON).accept(MediaType.APPLICATION_JSON)) .andReturn(); requests++; @@ -139,7 +138,8 @@ class DosFilterTest extends AbstractDDiApiIntegrationTest { @Test @Description("Ensures that a relatively high number of WRITE requests is allowed if it is below the DoS detection threshold") - @SuppressWarnings("squid:S2925") // No idea how to get rid of the Thread.sleep here + @SuppressWarnings("squid:S2925") + // No idea how to get rid of the Thread.sleep here void acceptablePutPostLoad() throws Exception { final Long actionId = prepareDeploymentBase(); final String feedback = getJsonProceedingDeploymentActionFeedback(); @@ -150,7 +150,7 @@ class DosFilterTest extends AbstractDDiApiIntegrationTest { for (int i = 0; i < 9; i++) { mvc.perform(post("/{tenant}/controller/v1/4711/deploymentBase/" + actionId + "/feedback", - tenantAware.getCurrentTenant()).header(X_FORWARDED_FOR, "10.0.0.1") + tenantAware.getCurrentTenant()).header(X_FORWARDED_FOR, "10.0.0.1") .content(feedback).contentType(MediaType.APPLICATION_JSON) .accept(MediaType.APPLICATION_JSON)) .andExpect(status().isOk());