From 34fc7a7012356e60448e3b5eab8ece30183a5f5b Mon Sep 17 00:00:00 2001 From: Jeroen Laverman Date: Wed, 12 Jun 2019 15:43:29 +0200 Subject: [PATCH] Add fix for Download-Only deployment type on DDI-API (#848) Signed-off-by: Jeroen Laverman --- .../hawkbit/repository/model/Action.java | 7 + .../ddi/rest/resource/DdiRootController.java | 15 +- .../rest/resource/DdiDeploymentBaseTest.java | 172 +++++++++++++++--- 3 files changed, 169 insertions(+), 25 deletions(-) diff --git a/hawkbit-repository/hawkbit-repository-api/src/main/java/org/eclipse/hawkbit/repository/model/Action.java b/hawkbit-repository/hawkbit-repository-api/src/main/java/org/eclipse/hawkbit/repository/model/Action.java index 456e719b4..1debf906e 100644 --- a/hawkbit-repository/hawkbit-repository-api/src/main/java/org/eclipse/hawkbit/repository/model/Action.java +++ b/hawkbit-repository/hawkbit-repository-api/src/main/java/org/eclipse/hawkbit/repository/model/Action.java @@ -142,6 +142,13 @@ public interface Action extends TenantAwareBaseEntity { return ActionType.FORCED == getActionType(); } + /** + * @return true when action is downloadonly, false otherwise + */ + default boolean isDownloadOnly() { + return ActionType.DOWNLOAD_ONLY == getActionType(); + } + /** * Action status as reported by the controller. * 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 15ebfeed1..c5cd3cdc9 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 @@ -294,7 +294,7 @@ public class DdiRootController implements DdiRootControllerRestApi { final DdiActionHistory actionHistory = actionHistoryMsgs.isEmpty() ? null : new DdiActionHistory(action.getStatus().name(), actionHistoryMsgs); - final HandlingType downloadType = action.isForce() ? HandlingType.FORCED : HandlingType.ATTEMPT; + final HandlingType downloadType = calculateDownloadType(action); final HandlingType updateType = calculateUpdateType(action, downloadType); final DdiMaintenanceWindowStatus maintenanceWindow = calculateMaintenanceWindow(action); @@ -302,7 +302,7 @@ public class DdiRootController implements DdiRootControllerRestApi { final DdiDeploymentBase base = new DdiDeploymentBase(Long.toString(action.getId()), new DdiDeployment(downloadType, updateType, chunks, maintenanceWindow), actionHistory); - LOG.debug("Found an active UpdateAction for target {}. returning deyploment: {}", controllerId, base); + LOG.debug("Found an active UpdateAction for target {}. returning deployment: {}", controllerId, base); controllerManagement.registerRetrieved(action.getId(), RepositoryConstants.SERVER_MESSAGE_PREFIX + "Target retrieved update action and should start now the download."); @@ -313,6 +313,13 @@ public class DdiRootController implements DdiRootControllerRestApi { return ResponseEntity.notFound().build(); } + private static HandlingType calculateDownloadType(final Action action) { + if (action.isDownloadOnly() || action.isForce()) { + return HandlingType.FORCED; + } + return HandlingType.ATTEMPT; + } + private static DdiMaintenanceWindowStatus calculateMaintenanceWindow(final Action action) { if (action.hasMaintenanceSchedule()) { return action.isMaintenanceWindowAvailable() ? DdiMaintenanceWindowStatus.AVAILABLE @@ -322,7 +329,9 @@ public class DdiRootController implements DdiRootControllerRestApi { } private static HandlingType calculateUpdateType(final Action action, final HandlingType downloadType) { - if (action.hasMaintenanceSchedule()) { + if (action.isDownloadOnly()) { + return HandlingType.SKIP; + } else if (action.hasMaintenanceSchedule()) { return action.isMaintenanceWindowAvailable() ? downloadType : HandlingType.SKIP; } return downloadType; 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 1fa4aade9..c4c46e787 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 @@ -24,6 +24,7 @@ import static org.springframework.test.web.servlet.result.MockMvcResultMatchers. import java.io.ByteArrayInputStream; import java.util.ArrayList; import java.util.Arrays; +import java.util.Collections; import java.util.List; import java.util.concurrent.TimeUnit; @@ -107,7 +108,7 @@ public class DdiDeploymentBaseTest extends AbstractDDiApiIntegrationTest { } @Test - @Description("Ensures that artifacts are not found, when softare module does not exists.") + @Description("Ensures that artifacts are not found, when software module does not exists.") public void artifactsNotFound() throws Exception { final Target target = testdataFactory.createTarget(); final Long softwareModuleIdNotExist = 1l; @@ -141,8 +142,8 @@ public class DdiDeploymentBaseTest extends AbstractDDiApiIntegrationTest { } @Test - @Description("Forced deployment to a controller. Checks if the resource reponse payload for a given deployment is as expected.") - public void deplomentForceAction() throws Exception { + @Description("Forced deployment to a controller. Checks if the resource response payload for a given deployment is as expected.") + public void deploymentForceAction() throws Exception { // Prepare test data final DistributionSet ds = testdataFactory.createDistributionSet("", true); final DistributionSet ds2 = testdataFactory.createDistributionSet("2", true); @@ -260,7 +261,7 @@ public class DdiDeploymentBaseTest extends AbstractDDiApiIntegrationTest { } @Test - @Description("Checks that the deployementBase URL changes when the action is switched from soft to forced in TIMEFORCED case.") + @Description("Checks that the deploymentBase URL changes when the action is switched from soft to forced in TIMEFORCED case.") public void changeEtagIfActionSwitchesFromSoftToForced() throws Exception { // Prepare test data final Target target = testdataFactory.createTarget("4712"); @@ -297,8 +298,8 @@ public class DdiDeploymentBaseTest extends AbstractDDiApiIntegrationTest { } @Test - @Description("Attempt/soft deployment to a controller. Checks if the resource reponse payload for a given deployment is as expected.") - public void deplomentAttemptAction() throws Exception { + @Description("Attempt/soft deployment to a controller. Checks if the resource response payload for a given deployment is as expected.") + public void deploymentAttemptAction() throws Exception { // Prepare test data final DistributionSet ds = testdataFactory.createDistributionSet("", true); final DistributionSet ds2 = testdataFactory.createDistributionSet("2", true); @@ -306,7 +307,7 @@ public class DdiDeploymentBaseTest extends AbstractDDiApiIntegrationTest { final String visibleMetadataOsValue = "withValue"; final int artifactSize = 5 * 1024; - final byte random[] = RandomUtils.nextBytes(artifactSize); + final byte[] random = RandomUtils.nextBytes(artifactSize); final Artifact artifact = artifactManagement.create( new ArtifactUpload(new ByteArrayInputStream(random), getOsModule(ds), "test1", false, artifactSize)); final Artifact artifactSignature = artifactManagement.create(new ArtifactUpload( @@ -423,14 +424,14 @@ public class DdiDeploymentBaseTest extends AbstractDDiApiIntegrationTest { } @Test - @Description("Attempt/soft deployment to a controller including automated switch to hard. Checks if the resource reponse payload for a given deployment is as expected.") - public void deplomentAutoForceAction() throws Exception { + @Description("Attempt/soft deployment to a controller including automated switch to hard. Checks if the resource response payload for a given deployment is as expected.") + public void deploymentAutoForceAction() throws Exception { // Prepare test data final DistributionSet ds = testdataFactory.createDistributionSet("", true); final DistributionSet ds2 = testdataFactory.createDistributionSet("2", true); final int artifactSize = 5 * 1024; - final byte random[] = RandomUtils.nextBytes(artifactSize); + final byte[] random = RandomUtils.nextBytes(artifactSize); final Artifact artifact = artifactManagement.create( new ArtifactUpload(new ByteArrayInputStream(random), getOsModule(ds), "test1", false, artifactSize)); final Artifact artifactSignature = artifactManagement.create(new ArtifactUpload( @@ -539,6 +540,133 @@ public class DdiDeploymentBaseTest extends AbstractDDiApiIntegrationTest { assertThat(actionStatusMessage.getStatus()).isEqualTo(Status.RETRIEVED); } + @Test + @Description("Test download-only (forced + skip) deployment to a controller. Checks if the resource response payload for a given deployment is as expected.") + public void deploymentDownloadOnlyAction () throws Exception { + // Prepare test data + final DistributionSet ds = testdataFactory.createDistributionSet("", true); + final DistributionSet ds2 = testdataFactory.createDistributionSet("2", true); + final String visibleMetadataOsKey = "metaDataVisible"; + final String visibleMetadataOsValue = "withValue"; + + final int artifactSize = 5 * 1024; + final byte[] random = RandomUtils.nextBytes(artifactSize); + final Artifact artifact = artifactManagement.create( + new ArtifactUpload(new ByteArrayInputStream(random), getOsModule(ds), "test1", false, artifactSize)); + final Artifact artifactSignature = artifactManagement.create(new ArtifactUpload( + new ByteArrayInputStream(random), getOsModule(ds), "test1.signature", false, artifactSize)); + + softwareModuleManagement.createMetaData(entityFactory.softwareModuleMetadata().create(getOsModule(ds)) + .key(visibleMetadataOsKey).value(visibleMetadataOsValue).targetVisible(true)); + softwareModuleManagement.createMetaData(entityFactory.softwareModuleMetadata().create(getOsModule(ds)) + .key("metaDataNotVisible").value("withValue").targetVisible(false)); + + final Target savedTarget = testdataFactory.createTarget("4712"); + + assertThat(deploymentManagement.findActiveActionsByTarget(PAGE, savedTarget.getControllerId())).isEmpty(); + assertThat(deploymentManagement.countActionsAll()).isEqualTo(0); + assertThat(deploymentManagement.countActionStatusAll()).isEqualTo(0); + + List saved = deploymentManagement.assignDistributionSet(ds.getId(), ActionType.DOWNLOAD_ONLY, + RepositoryModelConstants.NO_FORCE_TIME, Collections.singletonList(savedTarget.getControllerId())) + .getAssignedEntity(); + assertThat(deploymentManagement.findActiveActionsByTarget(PAGE, savedTarget.getControllerId())).hasSize(1); + + final Action action = deploymentManagement.findActiveActionsByTarget(PAGE, savedTarget.getControllerId()) + .getContent().get(0); + assertThat(deploymentManagement.countActionsAll()).isEqualTo(1); + assignDistributionSet(ds2, saved).getAssignedEntity(); + assertThat(deploymentManagement.findActiveActionsByTarget(PAGE, savedTarget.getControllerId())).hasSize(2); + assertThat(deploymentManagement.countActionsAll()).isEqualTo(2); + + final Action uaction = deploymentManagement.findActiveActionsByTarget(PAGE, savedTarget.getControllerId()) + .getContent().get(0); + assertThat(uaction.getDistributionSet()).isEqualTo(ds); + assertThat(deploymentManagement.findActiveActionsByTarget(PAGE, savedTarget.getControllerId())).hasSize(2); + + // Run test + + final long current = System.currentTimeMillis(); + mvc.perform(get("/{tenant}/controller/v1/4712", tenantAware.getCurrentTenant())) + .andDo(MockMvcResultPrinter.print()).andExpect(status().isOk()) + .andExpect(content().contentType(MediaTypes.HAL_JSON_UTF8)) + .andExpect(jsonPath("$.config.polling.sleep", equalTo("00:01:00"))) + .andExpect(jsonPath("$._links.deploymentBase.href", startsWith("http://localhost/" + + tenantAware.getCurrentTenant() + "/controller/v1/4712/deploymentBase/" + uaction.getId()))); + assertThat(targetManagement.getByControllerID("4712").get().getLastTargetQuery()) + .isGreaterThanOrEqualTo(current); + assertThat(targetManagement.getByControllerID("4712").get().getLastTargetQuery()) + .isLessThanOrEqualTo(System.currentTimeMillis()); + assertThat(deploymentManagement.countActionStatusAll()).isEqualTo(2); + + final DistributionSet findDistributionSetByAction = distributionSetManagement.getByAction(action.getId()).get(); + + mvc.perform( + get("/{tenant}/controller/v1/4712/deploymentBase/" + uaction.getId(), tenantAware.getCurrentTenant()) + .accept(MediaType.APPLICATION_JSON)) + .andDo(MockMvcResultPrinter.print()).andExpect(status().isOk()) + .andExpect(content().contentType(MediaType.APPLICATION_JSON_UTF8)) + .andExpect(jsonPath("$.id", equalTo(String.valueOf(action.getId())))) + .andExpect(jsonPath("$.deployment.download", equalTo("forced"))) + .andExpect(jsonPath("$.deployment.update", equalTo("skip"))) + .andExpect(jsonPath("$.deployment.chunks[?(@.part=='jvm')].name", + contains(ds.findFirstModuleByType(runtimeType).get().getName()))) + .andExpect(jsonPath("$.deployment.chunks[?(@.part=='jvm')].version", + contains(ds.findFirstModuleByType(runtimeType).get().getVersion()))) + .andExpect(jsonPath("$.deployment.chunks[?(@.part=='os')].name", + contains(ds.findFirstModuleByType(osType).get().getName()))) + .andExpect(jsonPath("$.deployment.chunks[?(@.part=='os')].version", + contains(ds.findFirstModuleByType(osType).get().getVersion()))) + .andExpect(jsonPath("$.deployment.chunks[?(@.part=='os')].metadata[0].key").value(visibleMetadataOsKey)) + .andExpect(jsonPath("$.deployment.chunks[?(@.part=='os')].metadata[0].value") + .value(visibleMetadataOsValue)) + .andExpect(jsonPath("$.deployment.chunks[?(@.part=='os')].artifacts[0].size", contains(5 * 1024))) + .andExpect(jsonPath("$.deployment.chunks[?(@.part=='os')].artifacts[0].filename", contains("test1"))) + .andExpect(jsonPath("$.deployment.chunks[?(@.part=='os')].artifacts[0].hashes.md5", + contains(artifact.getMd5Hash()))) + .andExpect(jsonPath("$.deployment.chunks[?(@.part=='os')].artifacts[0].hashes.sha1", + contains(artifact.getSha1Hash()))) + .andExpect( + jsonPath("$.deployment.chunks[?(@.part=='os')].artifacts[0]._links.download-http.href", + contains(HTTP_LOCALHOST + tenantAware.getCurrentTenant() + + "/controller/v1/4712/softwaremodules/" + + getOsModule(findDistributionSetByAction) + "/artifacts/test1"))) + .andExpect( + jsonPath("$.deployment.chunks[?(@.part=='os')].artifacts[0]._links.md5sum-http.href", + contains(HTTP_LOCALHOST + tenantAware.getCurrentTenant() + + "/controller/v1/4712/softwaremodules/" + + getOsModule(findDistributionSetByAction) + "/artifacts/test1.MD5SUM"))) + .andExpect(jsonPath("$.deployment.chunks[?(@.part=='os')].artifacts[1].size", contains(5 * 1024))) + .andExpect(jsonPath("$.deployment.chunks[?(@.part=='os')].artifacts[1].filename", + contains("test1.signature"))) + .andExpect(jsonPath("$.deployment.chunks[?(@.part=='os')].artifacts[1].hashes.md5", + contains(artifactSignature.getMd5Hash()))) + .andExpect(jsonPath("$.deployment.chunks[?(@.part=='os')].artifacts[1].hashes.sha1", + contains(artifactSignature.getSha1Hash()))) + .andExpect( + jsonPath("$.deployment.chunks[?(@.part=='os')].artifacts[1]._links.download-http.href", + contains(HTTP_LOCALHOST + tenantAware.getCurrentTenant() + + "/controller/v1/4712/softwaremodules/" + + getOsModule(findDistributionSetByAction) + "/artifacts/test1.signature"))) + .andExpect(jsonPath("$.deployment.chunks[?(@.part=='os')].artifacts[1]._links.md5sum-http.href", + contains(HTTP_LOCALHOST + tenantAware.getCurrentTenant() + + "/controller/v1/4712/softwaremodules/" + getOsModule(findDistributionSetByAction) + + "/artifacts/test1.signature.MD5SUM"))) + .andExpect(jsonPath("$.deployment.chunks[?(@.part=='bApp')].version", + contains(ds.findFirstModuleByType(appType).get().getVersion()))) + .andExpect(jsonPath("$.deployment.chunks[?(@.part=='bApp')].metadata").doesNotExist()) + .andExpect(jsonPath("$.deployment.chunks[?(@.part=='bApp')].name") + .value(ds.findFirstModuleByType(appType).get().getName())); + + // Retrieved is reported + final List actionStatusMessages = deploymentManagement + .findActionStatusByAction(PageRequest.of(0, 100, Direction.DESC, "id"), uaction.getId()).getContent(); + assertThat(actionStatusMessages).hasSize(2); + final ActionStatus actionStatusMessage = actionStatusMessages.iterator().next(); + assertThat(actionStatusMessage.getStatus()).isEqualTo(Status.RETRIEVED); + + } + @Test @Description("Test various invalid access attempts to the deployment resource und the expected behaviour of the server.") public void badDeploymentAction() throws Exception { @@ -563,7 +691,7 @@ public class DdiDeploymentBaseTest extends AbstractDDiApiIntegrationTest { .andDo(MockMvcResultPrinter.print()).andExpect(status().isNotFound()); // wrong media type - final List toAssign = Arrays.asList(target); + final List toAssign = Collections.singletonList(target); final DistributionSet savedSet = testdataFactory.createDistributionSet(""); final Long actionId = assignDistributionSet(savedSet, toAssign).getActionIds().get(0); @@ -575,9 +703,9 @@ public class DdiDeploymentBaseTest extends AbstractDDiApiIntegrationTest { } @Test - @Description("The server protects itself against to many feedback upload attempts. The test verfies that " - + "it is not possible to exceed the configured maximum number of feedback uplods.") - public void tooMuchDeplomentActionFeedback() throws Exception { + @Description("The server protects itself against to many feedback upload attempts. The test verifies that " + + "it is not possible to exceed the configured maximum number of feedback uploads.") + public void tooMuchDeploymentActionFeedback() throws Exception { final Target target = testdataFactory.createTarget("4712"); final DistributionSet ds = testdataFactory.createDistributionSet(""); @@ -627,7 +755,7 @@ public class DdiDeploymentBaseTest extends AbstractDDiApiIntegrationTest { @Test @Description("Multiple uploads of deployment status feedback to the server.") - public void multipleDeplomentActionFeedback() throws Exception { + public void multipleDeploymentActionFeedback() throws Exception { final Target savedTarget1 = testdataFactory.createTarget("4712"); testdataFactory.createTarget("4713"); testdataFactory.createTarget("4714"); @@ -704,8 +832,8 @@ public class DdiDeploymentBaseTest extends AbstractDDiApiIntegrationTest { } @Test - @Description("Verfies that an update action is correctly set to error if the controller provides error feedback.") - public void rootRsSingleDeplomentActionWithErrorFeedback() throws Exception { + @Description("Verifies that an update action is correctly set to error if the controller provides error feedback.") + public void rootRsSingleDeploymentActionWithErrorFeedback() throws Exception { DistributionSet ds = testdataFactory.createDistributionSet(""); final Target savedTarget = testdataFactory.createTarget("4712"); @@ -764,13 +892,13 @@ public class DdiDeploymentBaseTest extends AbstractDDiApiIntegrationTest { } @Test - @Description("Verfies that the controller can provided as much feedback entries as necessry as long as it is in the configured limites.") - public void rootRsSingleDeplomentActionFeedback() throws Exception { + @Description("Verifies that the controller can provided as much feedback entries as necessary as long as it is in the configured limits.") + public void rootRsSingleDeploymentActionFeedback() throws Exception { final DistributionSet ds = testdataFactory.createDistributionSet(""); final Target savedTarget = testdataFactory.createTarget("4712"); - final List toAssign = Arrays.asList(savedTarget); + final List toAssign = Collections.singletonList(savedTarget); Target myT = targetManagement.getByControllerID("4712").get(); assertThat(myT.getUpdateStatus()).isEqualTo(TargetUpdateStatus.UNKNOWN); @@ -891,8 +1019,8 @@ public class DdiDeploymentBaseTest extends AbstractDDiApiIntegrationTest { } @Test - @Description("Various forbidden request appempts on the feedback resource. Ensures correct answering behaviour as expected to these kind of errors.") - public void badDeplomentActionFeedback() throws Exception { + @Description("Various forbidden request attempts on the feedback resource. Ensures correct answering behaviour as expected to these kind of errors.") + public void badDeploymentActionFeedback() throws Exception { final DistributionSet savedSet = testdataFactory.createDistributionSet(""); final DistributionSet savedSet2 = testdataFactory.createDistributionSet("1");