From b4414438b084d42d737eb4d6ed4a49c2cb7fa985 Mon Sep 17 00:00:00 2001 From: Kai Zimmermann Date: Thu, 8 Mar 2018 10:42:25 +0100 Subject: [PATCH] Fix sonar issues and add DMF tests for maintenance window feature (#655) * Fix sonar issues. Signed-off-by: kaizimmerm * POM cleanup and more sonar issues fixed. Signed-off-by: kaizimmerm * Remove unneeded JavaDocs. Signed-off-by: kaizimmerm * More sonar issues. Signed-off-by: kaizimmerm * More issues. Signed-off-by: kaizimmerm * Adapt maintenance window to naming. Signed-off-by: kaizimmerm * Add DMF tests. Signed-off-by: kaizimmerm * Readibility. Signed-off-by: kaizimmerm * Typos fixed. Signed-off-by: kaizimmerm --- .../hawkbit/ddi/json/model/DdiDeployment.java | 3 +- .../ddi/rest/resource/DdiRootController.java | 36 +++++--- .../rest/resource/DdiRootControllerTest.java | 44 ++++------ .../hawkbit/amqp/AmqpConfiguration.java | 7 +- .../amqp/AmqpMessageDispatcherService.java | 35 +++----- .../amqp/AmqpMessageHandlerService.java | 18 ++-- .../AmqpMessageDispatcherServiceTest.java | 4 +- .../amqp/AmqpMessageHandlerServiceTest.java | 3 +- ...ssageDispatcherServiceIntegrationTest.java | 53 +++++++++-- ...pMessageHandlerServiceIntegrationTest.java | 83 ++++++++++++++++-- .../AmqpServiceIntegrationTest.java | 17 +++- .../hawkbit/dmf/amqp/api/EventTopic.java | 2 +- .../rabbitmq/test/AmqpTestConfiguration.java | 8 +- ...Window.java => MgmtMaintenanceWindow.java} | 13 ++- .../MgmtTargetAssignmentRequestBody.java | 42 ++------- ...ava => MgmtDistributionSetAssignment.java} | 34 +++----- .../mgmt/rest/api/MgmtTargetRestApi.java | 4 +- .../resource/MgmtDistributionSetResource.java | 22 ++--- .../rest/resource/MgmtTargetResource.java | 35 ++++---- .../hawkbit-repository-api/pom.xml | 1 - .../repository/ControllerManagement.java | 6 +- .../repository/MaintenanceScheduleHelper.java | 87 +++++++++---------- .../TargetAssignDistributionSetEvent.java | 14 ++- .../model/TargetWithActionType.java | 31 +++---- .../jpa/AbstractDsAssignmentStrategy.java | 5 +- .../jpa/JpaControllerManagement.java | 47 ++++------ .../jpa/JpaDeploymentManagement.java | 4 +- .../repository/jpa/model/JpaAction.java | 68 +++++---------- .../remote/RemoteTenantAwareEventTest.java | 3 +- .../footer/MaintenanceWindowLayout.java | 32 +++---- .../ManangementConfirmationWindowLayout.java | 6 +- pom.xml | 8 +- 32 files changed, 411 insertions(+), 364 deletions(-) rename hawkbit-mgmt-api/src/main/java/org/eclipse/hawkbit/mgmt/json/model/{MaintenanceWindow.java => MgmtMaintenanceWindow.java} (83%) rename hawkbit-mgmt-api/src/main/java/org/eclipse/hawkbit/mgmt/json/model/target/{MgmtDistributionSetAssigment.java => MgmtDistributionSetAssignment.java} (52%) diff --git a/hawkbit-ddi-api/src/main/java/org/eclipse/hawkbit/ddi/json/model/DdiDeployment.java b/hawkbit-ddi-api/src/main/java/org/eclipse/hawkbit/ddi/json/model/DdiDeployment.java index 1b4ee6a10..42fc9f3d8 100644 --- a/hawkbit-ddi-api/src/main/java/org/eclipse/hawkbit/ddi/json/model/DdiDeployment.java +++ b/hawkbit-ddi-api/src/main/java/org/eclipse/hawkbit/ddi/json/model/DdiDeployment.java @@ -12,6 +12,7 @@ import java.util.Collections; import java.util.List; import javax.validation.constraints.NotNull; + import com.fasterxml.jackson.annotation.JsonInclude; import com.fasterxml.jackson.annotation.JsonProperty; import com.fasterxml.jackson.annotation.JsonValue; @@ -30,7 +31,7 @@ public class DdiDeployment { @NotNull private List chunks; - private MaintenanceWindowStatus maintenanceWindow = null; + private MaintenanceWindowStatus maintenanceWindow; /** * Constructor. diff --git a/hawkbit-ddi-resource/src/main/java/org/eclipse/hawkbit/ddi/rest/resource/DdiRootController.java b/hawkbit-ddi-resource/src/main/java/org/eclipse/hawkbit/ddi/rest/resource/DdiRootController.java index 9c85728d5..fb3ad1a83 100644 --- a/hawkbit-ddi-resource/src/main/java/org/eclipse/hawkbit/ddi/rest/resource/DdiRootController.java +++ b/hawkbit-ddi-resource/src/main/java/org/eclipse/hawkbit/ddi/rest/resource/DdiRootController.java @@ -146,8 +146,12 @@ public class DdiRootController implements DdiRootControllerRestApi { checkAndCancelExpiredAction(action); - return new ResponseEntity<>(DataConversionHelper.fromTarget(target, action, - controllerManagement.getPollingTimeForAction(action), tenantAware), HttpStatus.OK); + return new ResponseEntity<>( + DataConversionHelper.fromTarget(target, action, + action == null ? controllerManagement.getPollingTime() + : controllerManagement.getPollingTimeForAction(action.getId()), + tenantAware), + HttpStatus.OK); } @Override @@ -288,13 +292,9 @@ public class DdiRootController implements DdiRootControllerRestApi { : new DdiActionHistory(action.getStatus().name(), actionHistoryMsgs); final HandlingType downloadType = action.isForce() ? HandlingType.FORCED : HandlingType.ATTEMPT; - final HandlingType updateType = action.hasMaintenanceSchedule() - ? (action.isMaintenanceWindowAvailable() ? downloadType : HandlingType.SKIP) : downloadType; + final HandlingType updateType = calculateUpdateType(action, downloadType); - MaintenanceWindowStatus maintenanceWindow = action.hasMaintenanceSchedule() - ? (action.isMaintenanceWindowAvailable() ? MaintenanceWindowStatus.AVAILABLE - : MaintenanceWindowStatus.UNAVAILABLE) - : null; + final MaintenanceWindowStatus maintenanceWindow = calculateMaintenanceWindow(action); final DdiDeploymentBase base = new DdiDeploymentBase(Long.toString(action.getId()), new DdiDeployment(downloadType, updateType, chunks, maintenanceWindow), actionHistory); @@ -310,6 +310,21 @@ public class DdiRootController implements DdiRootControllerRestApi { return ResponseEntity.notFound().build(); } + private static MaintenanceWindowStatus calculateMaintenanceWindow(final Action action) { + if (action.hasMaintenanceSchedule()) { + return action.isMaintenanceWindowAvailable() ? MaintenanceWindowStatus.AVAILABLE + : MaintenanceWindowStatus.UNAVAILABLE; + } + return null; + } + + private static HandlingType calculateUpdateType(final Action action, final HandlingType downloadType) { + if (action.hasMaintenanceSchedule()) { + return action.isMaintenanceWindowAvailable() ? downloadType : HandlingType.SKIP; + } + return downloadType; + } + @Override public ResponseEntity postBasedeploymentActionFeedback(@Valid @RequestBody final DdiActionFeedback feedback, @PathVariable("tenant") final String tenant, @PathVariable("controllerId") final String controllerId, @@ -366,9 +381,8 @@ public class DdiRootController implements DdiRootControllerRestApi { status = handleClosedCase(feedback, controllerId, actionid, messages); break; case DOWNLOADED: - LOG.debug( - "Controller confirmed download of distribution set (actionId: {}, controllerId: {}) as we got {} report.", - actionid, controllerId, feedback.getStatus().getExecution()); + LOG.debug("Controller confirmed download (actionId: {}, controllerId: {}) as we got {} report.", actionid, + controllerId, feedback.getStatus().getExecution()); status = Status.DOWNLOADED; messages.add(RepositoryConstants.SERVER_MESSAGE_PREFIX + "Target confirmed download of distribution set."); break; diff --git a/hawkbit-ddi-resource/src/test/java/org/eclipse/hawkbit/ddi/rest/resource/DdiRootControllerTest.java b/hawkbit-ddi-resource/src/test/java/org/eclipse/hawkbit/ddi/rest/resource/DdiRootControllerTest.java index 35dd38719..ca1c7d896 100644 --- a/hawkbit-ddi-resource/src/test/java/org/eclipse/hawkbit/ddi/rest/resource/DdiRootControllerTest.java +++ b/hawkbit-ddi-resource/src/test/java/org/eclipse/hawkbit/ddi/rest/resource/DdiRootControllerTest.java @@ -15,9 +15,8 @@ import static org.eclipse.hawkbit.im.authentication.SpPermission.SpringEvalExpre import static org.eclipse.hawkbit.im.authentication.SpPermission.SpringEvalExpressions.SYSTEM_ROLE; import static org.hamcrest.CoreMatchers.equalTo; import static org.hamcrest.Matchers.containsString; -import static org.hamcrest.Matchers.hasItem; -import static org.hamcrest.Matchers.greaterThan; import static org.hamcrest.Matchers.greaterThanOrEqualTo; +import static org.hamcrest.Matchers.hasItem; import static org.hamcrest.Matchers.lessThan; import static org.hamcrest.Matchers.startsWith; import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.delete; @@ -45,7 +44,6 @@ import org.eclipse.hawkbit.repository.model.Target; import org.eclipse.hawkbit.repository.model.TargetUpdateStatus; import org.eclipse.hawkbit.repository.test.matcher.Expect; import org.eclipse.hawkbit.repository.test.matcher.ExpectEvents; -import org.eclipse.hawkbit.repository.test.util.AbstractIntegrationTest; import org.eclipse.hawkbit.repository.test.util.WithSpringAuthorityRule; import org.eclipse.hawkbit.repository.test.util.WithUser; import org.eclipse.hawkbit.rest.util.JsonBuilder; @@ -505,7 +503,7 @@ public class DdiRootControllerTest extends AbstractDDiApiIntegrationTest { @Test @Description("Test the polling time based on different maintenance window start and end time.") - public void testSleepTimeResponseForDifferentMaintenanceWindowParameters() throws Exception { + public void sleepTimeResponseForDifferentMaintenanceWindowParameters() throws Exception { final DistributionSet ds = testdataFactory.createDistributionSet(""); securityRule.runAs(WithSpringAuthorityRule.withUser("tenantadmin", HAS_AUTH_TENANT_CONFIGURATION), () -> { @@ -516,38 +514,34 @@ public class DdiRootControllerTest extends AbstractDDiApiIntegrationTest { return null; }); - Target savedTarget = testdataFactory.createTarget("1911"); - assignDistributionSetWithMaintenanceWindow(ds.getId(), savedTarget.getControllerId(), - AbstractIntegrationTest.getTestSchedule(16), AbstractIntegrationTest.getTestDuration(10), - AbstractIntegrationTest.getTestTimeZone()).getAssignedEntity().iterator().next(); + final Target savedTarget = testdataFactory.createTarget("1911"); + assignDistributionSetWithMaintenanceWindow(ds.getId(), savedTarget.getControllerId(), getTestSchedule(16), + getTestDuration(10), getTestTimeZone()).getAssignedEntity().iterator().next(); mvc.perform(get("/default-tenant/controller/v1/1911/")).andExpect(status().isOk()) .andExpect(jsonPath("$.config.polling.sleep", greaterThanOrEqualTo("00:05:00"))); - Target savedTarget1 = testdataFactory.createTarget("2911"); + final Target savedTarget1 = testdataFactory.createTarget("2911"); final DistributionSet ds1 = testdataFactory.createDistributionSet("1"); - assignDistributionSetWithMaintenanceWindow(ds1.getId(), savedTarget1.getControllerId(), - AbstractIntegrationTest.getTestSchedule(10), AbstractIntegrationTest.getTestDuration(10), - AbstractIntegrationTest.getTestTimeZone()).getAssignedEntity().iterator().next(); + assignDistributionSetWithMaintenanceWindow(ds1.getId(), savedTarget1.getControllerId(), getTestSchedule(10), + getTestDuration(10), getTestTimeZone()).getAssignedEntity().iterator().next(); mvc.perform(get("/default-tenant/controller/v1/2911/")).andExpect(status().isOk()) .andExpect(jsonPath("$.config.polling.sleep", lessThan("00:05:00"))) .andExpect(jsonPath("$.config.polling.sleep", greaterThanOrEqualTo("00:03:00"))); - Target savedTarget2 = testdataFactory.createTarget("3911"); + final Target savedTarget2 = testdataFactory.createTarget("3911"); final DistributionSet ds2 = testdataFactory.createDistributionSet("2"); - assignDistributionSetWithMaintenanceWindow(ds2.getId(), savedTarget2.getControllerId(), - AbstractIntegrationTest.getTestSchedule(5), AbstractIntegrationTest.getTestDuration(5), - AbstractIntegrationTest.getTestTimeZone()).getAssignedEntity().iterator().next(); + assignDistributionSetWithMaintenanceWindow(ds2.getId(), savedTarget2.getControllerId(), getTestSchedule(5), + getTestDuration(5), getTestTimeZone()).getAssignedEntity().iterator().next(); mvc.perform(get("/default-tenant/controller/v1/3911/")).andExpect(status().isOk()) .andExpect(jsonPath("$.config.polling.sleep", lessThan("00:02:00"))); - Target savedTarget3 = testdataFactory.createTarget("4911"); + final Target savedTarget3 = testdataFactory.createTarget("4911"); final DistributionSet ds3 = testdataFactory.createDistributionSet("3"); - assignDistributionSetWithMaintenanceWindow(ds3.getId(), savedTarget3.getControllerId(), - AbstractIntegrationTest.getTestSchedule(-5), AbstractIntegrationTest.getTestDuration(15), - AbstractIntegrationTest.getTestTimeZone()).getAssignedEntity().iterator().next(); + assignDistributionSetWithMaintenanceWindow(ds3.getId(), savedTarget3.getControllerId(), getTestSchedule(-5), + getTestDuration(15), getTestTimeZone()).getAssignedEntity().iterator().next(); mvc.perform(get("/default-tenant/controller/v1/4911/")).andExpect(status().isOk()) .andExpect(jsonPath("$.config.polling.sleep", equalTo("00:05:00"))); @@ -556,12 +550,11 @@ public class DdiRootControllerTest extends AbstractDDiApiIntegrationTest { @Test @Description("Test download and update values before maintenance window start time.") - public void testDownloadAndUpdateStatusBeforeMaintenaceWindowStartTime() throws Exception { + public void downloadAndUpdateStatusBeforeMaintenanceWindowStartTime() throws Exception { Target savedTarget = testdataFactory.createTarget("1911"); final DistributionSet ds = testdataFactory.createDistributionSet(""); savedTarget = assignDistributionSetWithMaintenanceWindow(ds.getId(), savedTarget.getControllerId(), - AbstractIntegrationTest.getTestSchedule(2), AbstractIntegrationTest.getTestDuration(1), - AbstractIntegrationTest.getTestTimeZone()).getAssignedEntity().iterator().next(); + getTestSchedule(2), getTestDuration(1), getTestTimeZone()).getAssignedEntity().iterator().next(); mvc.perform(get("/default-tenant/controller/v1/1911/")).andExpect(status().isOk()); @@ -576,12 +569,11 @@ public class DdiRootControllerTest extends AbstractDDiApiIntegrationTest { @Test @Description("Test download and update values after maintenance window start time.") - public void testDownloadAndUpdateStatusDuringMaintenaceWindow() throws Exception { + public void downloadAndUpdateStatusDuringMaintenanceWindow() throws Exception { Target savedTarget = testdataFactory.createTarget("1911"); final DistributionSet ds = testdataFactory.createDistributionSet(""); savedTarget = assignDistributionSetWithMaintenanceWindow(ds.getId(), savedTarget.getControllerId(), - AbstractIntegrationTest.getTestSchedule(-5), AbstractIntegrationTest.getTestDuration(10), - AbstractIntegrationTest.getTestTimeZone()).getAssignedEntity().iterator().next(); + getTestSchedule(-5), getTestDuration(10), getTestTimeZone()).getAssignedEntity().iterator().next(); mvc.perform(get("/default-tenant/controller/v1/1911/")).andExpect(status().isOk()); diff --git a/hawkbit-dmf/hawkbit-dmf-amqp/src/main/java/org/eclipse/hawkbit/amqp/AmqpConfiguration.java b/hawkbit-dmf/hawkbit-dmf-amqp/src/main/java/org/eclipse/hawkbit/amqp/AmqpConfiguration.java index 24931ad1b..7b0674c88 100644 --- a/hawkbit-dmf/hawkbit-dmf-amqp/src/main/java/org/eclipse/hawkbit/amqp/AmqpConfiguration.java +++ b/hawkbit-dmf/hawkbit-dmf-amqp/src/main/java/org/eclipse/hawkbit/amqp/AmqpConfiguration.java @@ -312,12 +312,11 @@ public class AmqpConfiguration { AmqpMessageDispatcherService amqpMessageDispatcherService(final RabbitTemplate rabbitTemplate, final AmqpMessageSenderService amqpSenderService, final ArtifactUrlHandler artifactUrlHandler, final SystemSecurityContext systemSecurityContext, final SystemManagement systemManagement, - final ControllerManagement controllerManagement, final TargetManagement targetManagement, - final DistributionSetManagement distributionSetManagement, + final TargetManagement targetManagement, final DistributionSetManagement distributionSetManagement, final SoftwareModuleManagement softwareModuleManagement) { return new AmqpMessageDispatcherService(rabbitTemplate, amqpSenderService, artifactUrlHandler, - systemSecurityContext, systemManagement, controllerManagement, targetManagement, serviceMatcher, - distributionSetManagement, softwareModuleManagement); + systemSecurityContext, systemManagement, targetManagement, serviceMatcher, distributionSetManagement, + softwareModuleManagement); } private static Map getTTLMaxArgsAuthenticationQueue() { diff --git a/hawkbit-dmf/hawkbit-dmf-amqp/src/main/java/org/eclipse/hawkbit/amqp/AmqpMessageDispatcherService.java b/hawkbit-dmf/hawkbit-dmf-amqp/src/main/java/org/eclipse/hawkbit/amqp/AmqpMessageDispatcherService.java index ac8288b2c..1f41b4bbe 100644 --- a/hawkbit-dmf/hawkbit-dmf-amqp/src/main/java/org/eclipse/hawkbit/amqp/AmqpMessageDispatcherService.java +++ b/hawkbit-dmf/hawkbit-dmf-amqp/src/main/java/org/eclipse/hawkbit/amqp/AmqpMessageDispatcherService.java @@ -13,7 +13,6 @@ import java.util.Collections; import java.util.List; import java.util.Map; import java.util.Map.Entry; -import java.util.Optional; import java.util.stream.Collectors; import org.eclipse.hawkbit.api.ApiType; @@ -30,7 +29,6 @@ import org.eclipse.hawkbit.dmf.json.model.DmfArtifactHash; import org.eclipse.hawkbit.dmf.json.model.DmfDownloadAndUpdateRequest; import org.eclipse.hawkbit.dmf.json.model.DmfMetadata; import org.eclipse.hawkbit.dmf.json.model.DmfSoftwareModule; -import org.eclipse.hawkbit.repository.ControllerManagement; import org.eclipse.hawkbit.repository.DistributionSetManagement; import org.eclipse.hawkbit.repository.RepositoryConstants; import org.eclipse.hawkbit.repository.SoftwareModuleManagement; @@ -42,7 +40,6 @@ import org.eclipse.hawkbit.repository.event.remote.entity.CancelTargetAssignment import org.eclipse.hawkbit.repository.model.Artifact; import org.eclipse.hawkbit.repository.model.SoftwareModule; import org.eclipse.hawkbit.repository.model.SoftwareModuleMetadata; -import org.eclipse.hawkbit.repository.model.Action; import org.eclipse.hawkbit.repository.model.Target; import org.eclipse.hawkbit.security.SystemSecurityContext; import org.eclipse.hawkbit.util.IpUtil; @@ -76,7 +73,6 @@ public class AmqpMessageDispatcherService extends BaseAmqpService { private final AmqpMessageSenderService amqpSenderService; private final SystemSecurityContext systemSecurityContext; private final SystemManagement systemManagement; - private final ControllerManagement controllerManagement; private final TargetManagement targetManagement; private final ServiceMatcher serviceMatcher; private final DistributionSetManagement distributionSetManagement; @@ -95,8 +91,6 @@ public class AmqpMessageDispatcherService extends BaseAmqpService { * for execution with system permissions * @param systemManagement * the systemManagement - * @param controllerManagement - * for target repository access * @param targetManagement * to access target information * @param serviceMatcher @@ -108,15 +102,14 @@ public class AmqpMessageDispatcherService extends BaseAmqpService { protected AmqpMessageDispatcherService(final RabbitTemplate rabbitTemplate, final AmqpMessageSenderService amqpSenderService, final ArtifactUrlHandler artifactUrlHandler, final SystemSecurityContext systemSecurityContext, final SystemManagement systemManagement, - final ControllerManagement controllerManagement, final TargetManagement targetManagement, - final ServiceMatcher serviceMatcher, final DistributionSetManagement distributionSetManagement, + final TargetManagement targetManagement, final ServiceMatcher serviceMatcher, + final DistributionSetManagement distributionSetManagement, final SoftwareModuleManagement softwareModuleManagement) { super(rabbitTemplate); this.artifactUrlHandler = artifactUrlHandler; this.amqpSenderService = amqpSenderService; this.systemSecurityContext = systemSecurityContext; this.systemManagement = systemManagement; - this.controllerManagement = controllerManagement; this.targetManagement = targetManagement; this.serviceMatcher = serviceMatcher; this.distributionSetManagement = distributionSetManagement; @@ -151,7 +144,8 @@ public class AmqpMessageDispatcherService extends BaseAmqpService { targetManagement.getByControllerID(assignedEvent.getActions().keySet()) .forEach(target -> sendUpdateMessageToTarget(assignedEvent.getTenant(), target, - assignedEvent.getActions().get(target.getControllerId()), modules)); + assignedEvent.getActions().get(target.getControllerId()), modules, + assignedEvent.isMaintenanceWindowAvailable())); }); } @@ -161,25 +155,15 @@ public class AmqpMessageDispatcherService extends BaseAmqpService { * valid maintenance window available or not based on defined maintenance * schedule. In case of no maintenance schedule or if there is a valid * window available, the topic {@link EventTopic#DOWNLOAD_AND_INSTALL} is - * returned else {@link EventTopic#DOWNLOAD_AND_SKIP} is returned. + * returned else {@link EventTopic#DOWNLOAD} is returned. * * @param target * for which to find the event type * * @return {@link EventTopic} to use for message. */ - EventTopic getEventTypeForTarget(Target target) { - Optional action = controllerManagement.findOldestActiveActionByTarget(target.getControllerId()); - - if (action.isPresent()) { - if (action.get().isMaintenanceWindowAvailable()) { - return EventTopic.DOWNLOAD_AND_INSTALL; - } else { - return EventTopic.DOWNLOAD_AND_SKIP; - } - } - - return EventTopic.DOWNLOAD_AND_INSTALL; + private static EventTopic getEventTypeForTarget(final boolean maintenanceWindowAvailable) { + return maintenanceWindowAvailable ? EventTopic.DOWNLOAD_AND_INSTALL : EventTopic.DOWNLOAD; } /** @@ -216,7 +200,7 @@ public class AmqpMessageDispatcherService extends BaseAmqpService { } protected void sendUpdateMessageToTarget(final String tenant, final Target target, final Long actionId, - final Map> modules) { + final Map> modules, final boolean maintenanceWindowAvailable) { final URI targetAdress = target.getAddress(); if (!IpUtil.isAmqpUri(targetAdress)) { @@ -236,7 +220,8 @@ public class AmqpMessageDispatcherService extends BaseAmqpService { }); final Message message = getMessageConverter().toMessage(downloadAndUpdateRequest, - createConnectorMessagePropertiesEvent(tenant, target.getControllerId(), getEventTypeForTarget(target))); + createConnectorMessagePropertiesEvent(tenant, target.getControllerId(), + getEventTypeForTarget(maintenanceWindowAvailable))); amqpSenderService.sendMessage(message, targetAdress); } diff --git a/hawkbit-dmf/hawkbit-dmf-amqp/src/main/java/org/eclipse/hawkbit/amqp/AmqpMessageHandlerService.java b/hawkbit-dmf/hawkbit-dmf-amqp/src/main/java/org/eclipse/hawkbit/amqp/AmqpMessageHandlerService.java index 0d44a4a51..1a35a578a 100644 --- a/hawkbit-dmf/hawkbit-dmf-amqp/src/main/java/org/eclipse/hawkbit/amqp/AmqpMessageHandlerService.java +++ b/hawkbit-dmf/hawkbit-dmf-amqp/src/main/java/org/eclipse/hawkbit/amqp/AmqpMessageHandlerService.java @@ -102,8 +102,9 @@ public class AmqpMessageHandlerService extends BaseAmqpService { * @return a message if no message is send back to sender */ @RabbitListener(queues = "${hawkbit.dmf.rabbitmq.receiverQueue:dmf_receiver}", containerFactory = "listenerContainerFactory") - public Message onMessage(final Message message, @Header(MessageHeaderKey.TYPE) final String type, - @Header(MessageHeaderKey.TENANT) final String tenant) { + public Message onMessage(final Message message, + @Header(name = MessageHeaderKey.TYPE, required = false) final String type, + @Header(name = MessageHeaderKey.TENANT, required = false) final String tenant) { return onMessage(message, type, tenant, getRabbitTemplate().getConnectionFactory().getVirtualHost()); } @@ -121,6 +122,9 @@ public class AmqpMessageHandlerService extends BaseAmqpService { * @return the rpc message back to supplier. */ public Message onMessage(final Message message, final String type, final String tenant, final String virtualHost) { + if (StringUtils.isEmpty(type) || StringUtils.isEmpty(tenant)) { + throw new AmqpRejectAndDontRequeueException("Invalid message! tenant and type header are mandatory!"); + } final SecurityContext oldContext = SecurityContextHolder.getContext(); try { @@ -215,7 +219,7 @@ public class AmqpMessageHandlerService extends BaseAmqpService { action.getDistributionSet().getModules().forEach(module -> modules.put(module, metadata.get(module.getId()))); amqpMessageDispatcherService.sendUpdateMessageToTarget(action.getTenant(), action.getTarget(), action.getId(), - modules); + modules, action.isMaintenanceWindowAvailable()); } /** @@ -282,6 +286,9 @@ public class AmqpMessageHandlerService extends BaseAmqpService { && message.getMessageProperties().getCorrelationId().length > 0; } + // Exception squid:MethodCyclomaticComplexity - false positive, is a simple + // mapping + @SuppressWarnings("squid:MethodCyclomaticComplexity") private Status mapStatus(final Message message, final DmfActionUpdateStatus actionUpdateStatus, final Action action) { Status status = null; @@ -311,7 +318,7 @@ public class AmqpMessageHandlerService extends BaseAmqpService { status = Status.DOWNLOADED; break; case CANCEL_REJECTED: - status = hanldeCancelRejectedState(message, action); + status = handleCancelRejectedState(message, action); break; default: logAndThrowMessageError(message, "Status for action does not exisit."); @@ -320,14 +327,13 @@ public class AmqpMessageHandlerService extends BaseAmqpService { return status; } - private Status hanldeCancelRejectedState(final Message message, final Action action) { + private Status handleCancelRejectedState(final Message message, final Action action) { if (action.isCancelingOrCanceled()) { return Status.CANCEL_REJECTED; } logAndThrowMessageError(message, "Cancel rejected message is not allowed, if action is on state: " + action.getStatus()); return null; - } private static String convertCorrelationId(final Message message) { diff --git a/hawkbit-dmf/hawkbit-dmf-amqp/src/test/java/org/eclipse/hawkbit/amqp/AmqpMessageDispatcherServiceTest.java b/hawkbit-dmf/hawkbit-dmf-amqp/src/test/java/org/eclipse/hawkbit/amqp/AmqpMessageDispatcherServiceTest.java index 8fbe5e4c9..2c5c41ef0 100644 --- a/hawkbit-dmf/hawkbit-dmf-amqp/src/test/java/org/eclipse/hawkbit/amqp/AmqpMessageDispatcherServiceTest.java +++ b/hawkbit-dmf/hawkbit-dmf-amqp/src/test/java/org/eclipse/hawkbit/amqp/AmqpMessageDispatcherServiceTest.java @@ -111,8 +111,8 @@ public class AmqpMessageDispatcherServiceTest extends AbstractIntegrationTest { when(systemManagement.getTenantMetadata()).thenReturn(tenantMetaData); amqpMessageDispatcherService = new AmqpMessageDispatcherService(rabbitTemplate, senderService, - artifactUrlHandlerMock, systemSecurityContext, systemManagement, controllerManagement, targetManagement, - serviceMatcher, distributionSetManagement, softwareModuleManagement); + artifactUrlHandlerMock, systemSecurityContext, systemManagement, targetManagement, serviceMatcher, + distributionSetManagement, softwareModuleManagement); } diff --git a/hawkbit-dmf/hawkbit-dmf-amqp/src/test/java/org/eclipse/hawkbit/amqp/AmqpMessageHandlerServiceTest.java b/hawkbit-dmf/hawkbit-dmf-amqp/src/test/java/org/eclipse/hawkbit/amqp/AmqpMessageHandlerServiceTest.java index 7faf7281b..db2fad339 100644 --- a/hawkbit-dmf/hawkbit-dmf-amqp/src/test/java/org/eclipse/hawkbit/amqp/AmqpMessageHandlerServiceTest.java +++ b/hawkbit-dmf/hawkbit-dmf-amqp/src/test/java/org/eclipse/hawkbit/amqp/AmqpMessageHandlerServiceTest.java @@ -11,6 +11,7 @@ package org.eclipse.hawkbit.amqp; import static org.assertj.core.api.Assertions.assertThat; import static org.junit.Assert.fail; import static org.mockito.Matchers.any; +import static org.mockito.Matchers.anyBoolean; import static org.mockito.Matchers.anyLong; import static org.mockito.Matchers.anyString; import static org.mockito.Mockito.mock; @@ -420,7 +421,7 @@ public class AmqpMessageHandlerServiceTest { final ArgumentCaptor actionIdCaptor = ArgumentCaptor.forClass(Long.class); verify(amqpMessageDispatcherServiceMock, times(1)).sendUpdateMessageToTarget(tenantCaptor.capture(), - targetCaptor.capture(), actionIdCaptor.capture(), any(Map.class)); + targetCaptor.capture(), actionIdCaptor.capture(), any(Map.class), anyBoolean()); final String tenant = tenantCaptor.getValue(); final String controllerId = targetCaptor.getValue().getControllerId(); final Long actionId = actionIdCaptor.getValue(); diff --git a/hawkbit-dmf/hawkbit-dmf-amqp/src/test/java/org/eclipse/hawkbit/integration/AmqpMessageDispatcherServiceIntegrationTest.java b/hawkbit-dmf/hawkbit-dmf-amqp/src/test/java/org/eclipse/hawkbit/integration/AmqpMessageDispatcherServiceIntegrationTest.java index fe2fdc05b..8e2c2b080 100644 --- a/hawkbit-dmf/hawkbit-dmf-amqp/src/test/java/org/eclipse/hawkbit/integration/AmqpMessageDispatcherServiceIntegrationTest.java +++ b/hawkbit-dmf/hawkbit-dmf-amqp/src/test/java/org/eclipse/hawkbit/integration/AmqpMessageDispatcherServiceIntegrationTest.java @@ -48,16 +48,59 @@ public class AmqpMessageDispatcherServiceIntegrationTest extends AmqpServiceInte @Expect(type = SoftwareModuleCreatedEvent.class, count = 3), @Expect(type = SoftwareModuleUpdatedEvent.class, count = 6), @Expect(type = DistributionSetCreatedEvent.class, count = 1), - @Expect(type = TargetUpdatedEvent.class, count = 1), @Expect(type = TargetPollEvent.class, count = 2) }) + @Expect(type = TargetUpdatedEvent.class, count = 1), @Expect(type = TargetPollEvent.class, count = 1) }) public void sendDownloadAndInstallStatus() { final String controllerId = TARGET_PREFIX + "sendDownloadAndInstallStatus"; registerTargetAndAssignDistributionSet(controllerId); - createAndSendTarget(controllerId, TENANT_EXIST); waitUntilTargetStatusIsPending(controllerId); assertDownloadAndInstallMessage(getDistributionSet().getModules(), controllerId); } + @Test + @Description("Verify that a distribution assignment sends a download message with window configured but before maintenance window start time.") + @ExpectEvents({ @Expect(type = TargetCreatedEvent.class, count = 1), + @Expect(type = TargetAssignDistributionSetEvent.class, count = 1), + @Expect(type = ActionCreatedEvent.class, count = 1), + @Expect(type = SoftwareModuleCreatedEvent.class, count = 3), + @Expect(type = SoftwareModuleUpdatedEvent.class, count = 6), + @Expect(type = DistributionSetCreatedEvent.class, count = 1), + @Expect(type = TargetUpdatedEvent.class, count = 1), @Expect(type = TargetPollEvent.class, count = 1) }) + public void sendDownloadStatusBeforeMaintenanceWindowStartTime() { + final String controllerId = TARGET_PREFIX + "sendDownloadStatusBeforeWindowStartTime"; + + registerAndAssertTargetWithExistingTenant(controllerId); + final DistributionSet distributionSet = testdataFactory.createDistributionSet(UUID.randomUUID().toString()); + testdataFactory.addSoftwareModuleMetadata(distributionSet); + assignDistributionSetWithMaintenanceWindow(distributionSet.getId(), controllerId, getTestSchedule(2), + getTestDuration(10), getTestTimeZone()); + + waitUntilTargetStatusIsPending(controllerId); + assertDownloadMessage(distributionSet.getModules(), controllerId); + } + + @Test + @Description("Verify that a distribution assignment sends a download and install message with window configured and during maintenance window start time.") + @ExpectEvents({ @Expect(type = TargetCreatedEvent.class, count = 1), + @Expect(type = TargetAssignDistributionSetEvent.class, count = 1), + @Expect(type = ActionCreatedEvent.class, count = 1), + @Expect(type = SoftwareModuleCreatedEvent.class, count = 3), + @Expect(type = SoftwareModuleUpdatedEvent.class, count = 6), + @Expect(type = DistributionSetCreatedEvent.class, count = 1), + @Expect(type = TargetUpdatedEvent.class, count = 1), @Expect(type = TargetPollEvent.class, count = 1) }) + public void sendDownloadAndInstallStatusMessageDuringMaintenanceWindow() { + final String controllerId = TARGET_PREFIX + "sendDAndIStatusMessageDuringWindow"; + + registerAndAssertTargetWithExistingTenant(controllerId); + final DistributionSet distributionSet = testdataFactory.createDistributionSet(UUID.randomUUID().toString()); + testdataFactory.addSoftwareModuleMetadata(distributionSet); + assignDistributionSetWithMaintenanceWindow(distributionSet.getId(), controllerId, getTestSchedule(-5), + getTestDuration(10), getTestTimeZone()); + + waitUntilTargetStatusIsPending(controllerId); + assertDownloadAndInstallMessage(distributionSet.getModules(), controllerId); + } + @Test @Description("Verify that a distribution assignment multiple times send cancel and assign events with right softwaremodules") @ExpectEvents({ @Expect(type = TargetCreatedEvent.class, count = 1), @@ -111,7 +154,7 @@ public class AmqpMessageDispatcherServiceIntegrationTest extends AmqpServiceInte public void sendDeleteMessage() { final String controllerId = TARGET_PREFIX + "sendDeleteMessage"; - registerAndAssertTargetWithExistingTenant(controllerId, 1); + registerAndAssertTargetWithExistingTenant(controllerId); targetManagement.deleteByControllerID(controllerId); assertDeleteMessage(controllerId); } @@ -125,8 +168,6 @@ public class AmqpMessageDispatcherServiceIntegrationTest extends AmqpServiceInte } private void waitUntil(final Callable callable) { - createConditionFactory().until(() -> { - return securityRule.runAsPrivileged(callable); - }); + createConditionFactory().until(() -> securityRule.runAsPrivileged(callable)); } } diff --git a/hawkbit-dmf/hawkbit-dmf-amqp/src/test/java/org/eclipse/hawkbit/integration/AmqpMessageHandlerServiceIntegrationTest.java b/hawkbit-dmf/hawkbit-dmf-amqp/src/test/java/org/eclipse/hawkbit/integration/AmqpMessageHandlerServiceIntegrationTest.java index 89554a4b4..489068dbf 100644 --- a/hawkbit-dmf/hawkbit-dmf-amqp/src/test/java/org/eclipse/hawkbit/integration/AmqpMessageHandlerServiceIntegrationTest.java +++ b/hawkbit-dmf/hawkbit-dmf-amqp/src/test/java/org/eclipse/hawkbit/integration/AmqpMessageHandlerServiceIntegrationTest.java @@ -386,6 +386,20 @@ public class AmqpMessageHandlerServiceIntegrationTest extends AmqpServiceIntegra registerTargetAndSendAndAssertUpdateActionStatus(DmfActionStatus.RUNNING, Status.RUNNING, controllerId); } + @Test + @Description("Register a target and send an update action status (downloaded). Verfiy if the updated action status is correct.") + @ExpectEvents({ @Expect(type = TargetCreatedEvent.class, count = 1), + @Expect(type = TargetAssignDistributionSetEvent.class, count = 1), + @Expect(type = ActionUpdatedEvent.class, count = 0), @Expect(type = ActionCreatedEvent.class, count = 1), + @Expect(type = SoftwareModuleCreatedEvent.class, count = 3), + @Expect(type = SoftwareModuleUpdatedEvent.class, count = 6), + @Expect(type = DistributionSetCreatedEvent.class, count = 1), + @Expect(type = TargetUpdatedEvent.class, count = 1), @Expect(type = TargetPollEvent.class, count = 1) }) + public void downloadedActionStatus() { + final String controllerId = TARGET_PREFIX + "downloadedActionStatus"; + registerTargetAndSendAndAssertUpdateActionStatus(DmfActionStatus.DOWNLOADED, Status.DOWNLOADED, controllerId); + } + @Test @Description("Register a target and send a update action status (download). Verfiy if the updated action status is correct.") @ExpectEvents({ @Expect(type = TargetCreatedEvent.class, count = 1), @@ -470,7 +484,7 @@ public class AmqpMessageHandlerServiceIntegrationTest extends AmqpServiceIntegra final String controllerId = TARGET_PREFIX + "receiveDownLoadAndInstallMessageAfterAssignment"; // setup - createAndSendTarget(controllerId, TENANT_EXIST); + registerAndAssertTargetWithExistingTenant(controllerId); final DistributionSet distributionSet = testdataFactory.createDistributionSet(UUID.randomUUID().toString()); testdataFactory.addSoftwareModuleMetadata(distributionSet); assignDistributionSet(distributionSet.getId(), controllerId); @@ -483,6 +497,60 @@ public class AmqpMessageHandlerServiceIntegrationTest extends AmqpServiceIntegra Mockito.verifyZeroInteractions(getDeadletterListener()); } + @Test + @Description("Verfiy receiving a download message if a deployment is done with window configured but before maintenance window start time.") + @ExpectEvents({ @Expect(type = TargetCreatedEvent.class, count = 1), + @Expect(type = TargetAssignDistributionSetEvent.class, count = 1), + @Expect(type = ActionCreatedEvent.class, count = 1), + @Expect(type = SoftwareModuleCreatedEvent.class, count = 3), + @Expect(type = SoftwareModuleUpdatedEvent.class, count = 6), + @Expect(type = DistributionSetCreatedEvent.class, count = 1), + @Expect(type = TargetUpdatedEvent.class, count = 1), @Expect(type = TargetPollEvent.class, count = 2) }) + public void receiveDownloadMessageBeforeMaintenanceWindowStartTime() { + final String controllerId = TARGET_PREFIX + "receiveDownLoadMessageBeforeMaintenanceWindowStartTime"; + + // setup + registerAndAssertTargetWithExistingTenant(controllerId); + final DistributionSet distributionSet = testdataFactory.createDistributionSet(UUID.randomUUID().toString()); + testdataFactory.addSoftwareModuleMetadata(distributionSet); + assignDistributionSetWithMaintenanceWindow(distributionSet.getId(), controllerId, getTestSchedule(2), + getTestDuration(1), getTestTimeZone()); + + // test + registerSameTargetAndAssertBasedOnVersion(controllerId, 1, TargetUpdateStatus.PENDING); + + // verify + assertDownloadMessage(distributionSet.getModules(), controllerId); + Mockito.verifyZeroInteractions(getDeadletterListener()); + } + + @Test + @Description("Verify receiving a download_and_install message if a deployment is done with window configured and during maintenance window start time.") + @ExpectEvents({ @Expect(type = TargetCreatedEvent.class, count = 1), + @Expect(type = TargetAssignDistributionSetEvent.class, count = 1), + @Expect(type = ActionCreatedEvent.class, count = 1), + @Expect(type = SoftwareModuleCreatedEvent.class, count = 3), + @Expect(type = SoftwareModuleUpdatedEvent.class, count = 6), + @Expect(type = DistributionSetCreatedEvent.class, count = 1), + @Expect(type = TargetUpdatedEvent.class, count = 1), @Expect(type = TargetPollEvent.class, count = 2) }) + public void receiveDownloadAndInstallMessageDuringMaintenanceWindow() { + final String controllerId = TARGET_PREFIX + "receiveDownLoadAndInstallMessageDuringMaintenanceWindow"; + + // setup + registerAndAssertTargetWithExistingTenant(controllerId); + final DistributionSet distributionSet = testdataFactory.createDistributionSet(UUID.randomUUID().toString()); + testdataFactory.addSoftwareModuleMetadata(distributionSet); + assignDistributionSetWithMaintenanceWindow(distributionSet.getId(), controllerId, getTestSchedule(-5), + getTestDuration(10), getTestTimeZone()); + + // test + registerSameTargetAndAssertBasedOnVersion(controllerId, 1, TargetUpdateStatus.PENDING); + + // verify + assertDownloadAndInstallMessage(distributionSet.getModules(), controllerId); + Mockito.verifyZeroInteractions(getDeadletterListener()); + } + @Test @Description("Verfiy receiving a cancel update message if a deployment is canceled before the target has polled the first time.") @ExpectEvents({ @Expect(type = TargetCreatedEvent.class, count = 1), @@ -497,7 +565,7 @@ public class AmqpMessageHandlerServiceIntegrationTest extends AmqpServiceIntegra final String controllerId = TARGET_PREFIX + "receiveCancelUpdateMessageAfterAssignmentWasCanceled"; // Setup - createAndSendTarget(controllerId, TENANT_EXIST); + registerAndAssertTargetWithExistingTenant(controllerId); final DistributionSet distributionSet = testdataFactory.createDistributionSet(UUID.randomUUID().toString()); final DistributionSetAssignmentResult distributionSetAssignmentResult = assignDistributionSet( distributionSet.getId(), controllerId); @@ -573,7 +641,7 @@ public class AmqpMessageHandlerServiceIntegrationTest extends AmqpServiceIntegra final String controllerId = TARGET_PREFIX + "updateAttributes"; // setup - registerAndAssertTargetWithExistingTenant(controllerId, 1); + registerAndAssertTargetWithExistingTenant(controllerId); final DmfAttributeUpdate controllerAttribute = new DmfAttributeUpdate(); controllerAttribute.getAttributes().put("test1", "testA"); controllerAttribute.getAttributes().put("test2", "testB"); @@ -593,7 +661,7 @@ public class AmqpMessageHandlerServiceIntegrationTest extends AmqpServiceIntegra final String controllerId = TARGET_PREFIX + "updateAttributesWithNoThingId"; // setup - registerAndAssertTargetWithExistingTenant(controllerId, 1); + registerAndAssertTargetWithExistingTenant(controllerId); final DmfAttributeUpdate controllerAttribute = new DmfAttributeUpdate(); controllerAttribute.getAttributes().put("test1", "testA"); controllerAttribute.getAttributes().put("test2", "testB"); @@ -618,7 +686,7 @@ public class AmqpMessageHandlerServiceIntegrationTest extends AmqpServiceIntegra // setup final String target = "ControllerAttributeTestTarget"; - registerAndAssertTargetWithExistingTenant(target, 1); + registerAndAssertTargetWithExistingTenant(target); final DmfAttributeUpdate controllerAttribute = new DmfAttributeUpdate(); controllerAttribute.getAttributes().put("test1", "testA"); controllerAttribute.getAttributes().put("test2", "testB"); @@ -707,8 +775,7 @@ public class AmqpMessageHandlerServiceIntegrationTest extends AmqpServiceIntegra private void verifyOneDeadLetterMessage() { assertEmptyReceiverQueueCount(); - createConditionFactory().until(() -> { - Mockito.verify(getDeadletterListener(), Mockito.times(1)).handleMessage(Mockito.any()); - }); + createConditionFactory() + .until(() -> Mockito.verify(getDeadletterListener(), Mockito.times(1)).handleMessage(Mockito.any())); } } diff --git a/hawkbit-dmf/hawkbit-dmf-amqp/src/test/java/org/eclipse/hawkbit/integration/AmqpServiceIntegrationTest.java b/hawkbit-dmf/hawkbit-dmf-amqp/src/test/java/org/eclipse/hawkbit/integration/AmqpServiceIntegrationTest.java index 0aeb247ce..1506d9fa1 100644 --- a/hawkbit-dmf/hawkbit-dmf-amqp/src/test/java/org/eclipse/hawkbit/integration/AmqpServiceIntegrationTest.java +++ b/hawkbit-dmf/hawkbit-dmf-amqp/src/test/java/org/eclipse/hawkbit/integration/AmqpServiceIntegrationTest.java @@ -161,8 +161,9 @@ public abstract class AmqpServiceIntegrationTest extends AbstractAmqpIntegration } - protected void assertDownloadAndInstallMessage(final Set dsModules, final String controllerId) { - final Message replyMessage = assertReplyMessageHeader(EventTopic.DOWNLOAD_AND_INSTALL, controllerId); + private void assertAssignmentMessage(final Set dsModules, final String controllerId, + final EventTopic topic) { + final Message replyMessage = assertReplyMessageHeader(topic, controllerId); assertAllTargetsCount(1); final DmfDownloadAndUpdateRequest downloadAndUpdateRequest = (DmfDownloadAndUpdateRequest) getDmfClient() @@ -180,6 +181,14 @@ public abstract class AmqpServiceIntegrationTest extends AbstractAmqpIntegration assertThat(updatedTarget.getSecurityToken()).isEqualTo(downloadAndUpdateRequest.getTargetSecurityToken()); } + protected void assertDownloadAndInstallMessage(final Set dsModules, final String controllerId) { + assertAssignmentMessage(dsModules, controllerId, EventTopic.DOWNLOAD_AND_INSTALL); + } + + protected void assertDownloadMessage(final Set dsModules, final String controllerId) { + assertAssignmentMessage(dsModules, controllerId, EventTopic.DOWNLOAD); + } + protected void createAndSendTarget(final String target, final String tenant) { final Message message = createTargetMessage(target, tenant); getDmfClient().send(message); @@ -224,6 +233,10 @@ public abstract class AmqpServiceIntegrationTest extends AbstractAmqpIntegration return replyMessage; } + protected void registerAndAssertTargetWithExistingTenant(final String controllerId) { + registerAndAssertTargetWithExistingTenant(controllerId, 1); + } + protected void registerAndAssertTargetWithExistingTenant(final String target, final int existingTargetsAfterCreation) { diff --git a/hawkbit-dmf/hawkbit-dmf-api/src/main/java/org/eclipse/hawkbit/dmf/amqp/api/EventTopic.java b/hawkbit-dmf/hawkbit-dmf-api/src/main/java/org/eclipse/hawkbit/dmf/amqp/api/EventTopic.java index 871daa5b6..0facaa3a1 100644 --- a/hawkbit-dmf/hawkbit-dmf-api/src/main/java/org/eclipse/hawkbit/dmf/amqp/api/EventTopic.java +++ b/hawkbit-dmf/hawkbit-dmf-api/src/main/java/org/eclipse/hawkbit/dmf/amqp/api/EventTopic.java @@ -32,6 +32,6 @@ public enum EventTopic { /** * Topic when sending a download only task, skipping the install. */ - DOWNLOAD_AND_SKIP; + DOWNLOAD; } diff --git a/hawkbit-dmf/hawkbit-dmf-rabbitmq-test/src/main/java/org/eclipse/hawkbit/rabbitmq/test/AmqpTestConfiguration.java b/hawkbit-dmf/hawkbit-dmf-rabbitmq-test/src/main/java/org/eclipse/hawkbit/rabbitmq/test/AmqpTestConfiguration.java index dd5fcf938..b557249b1 100644 --- a/hawkbit-dmf/hawkbit-dmf-rabbitmq-test/src/main/java/org/eclipse/hawkbit/rabbitmq/test/AmqpTestConfiguration.java +++ b/hawkbit-dmf/hawkbit-dmf-rabbitmq-test/src/main/java/org/eclipse/hawkbit/rabbitmq/test/AmqpTestConfiguration.java @@ -94,19 +94,19 @@ public class AmqpTestConfiguration { // test should break or not } catch (@SuppressWarnings("squid:S2221") final Exception e) { Throwables.propagateIfInstanceOf(e, AlivenessException.class); - LOG.error("Cannot create virtual host {}", e.getMessage()); + LOG.error("Cannot create virtual host.", e); } return factory; } @Bean - RabbitMqSetupService rabbitmqSetupService(RabbitProperties properties) { + RabbitMqSetupService rabbitmqSetupService(final RabbitProperties properties) { return new RabbitMqSetupService(properties); } @Bean @Primary - public RabbitTemplate rabbitTemplateForTest(ConnectionFactory connectionFactory) { + public RabbitTemplate rabbitTemplateForTest(final ConnectionFactory connectionFactory) { final RabbitTemplate rabbitTemplate = new RabbitTemplate(connectionFactory); rabbitTemplate.setMessageConverter(new Jackson2JsonMessageConverter()); rabbitTemplate.setReplyTimeout(TimeUnit.SECONDS.toMillis(3)); @@ -115,7 +115,7 @@ public class AmqpTestConfiguration { } @Bean - BrokerRunning brokerRunning(RabbitMqSetupService rabbitmqSetupService) { + BrokerRunning brokerRunning(final RabbitMqSetupService rabbitmqSetupService) { final BrokerRunning brokerRunning = BrokerRunning.isRunning(); brokerRunning.setHostName(rabbitmqSetupService.getHostname()); brokerRunning.getConnectionFactory().setUsername(rabbitmqSetupService.getUsername()); diff --git a/hawkbit-mgmt-api/src/main/java/org/eclipse/hawkbit/mgmt/json/model/MaintenanceWindow.java b/hawkbit-mgmt-api/src/main/java/org/eclipse/hawkbit/mgmt/json/model/MgmtMaintenanceWindow.java similarity index 83% rename from hawkbit-mgmt-api/src/main/java/org/eclipse/hawkbit/mgmt/json/model/MaintenanceWindow.java rename to hawkbit-mgmt-api/src/main/java/org/eclipse/hawkbit/mgmt/json/model/MgmtMaintenanceWindow.java index f48251545..878d8a412 100644 --- a/hawkbit-mgmt-api/src/main/java/org/eclipse/hawkbit/mgmt/json/model/MaintenanceWindow.java +++ b/hawkbit-mgmt-api/src/main/java/org/eclipse/hawkbit/mgmt/json/model/MgmtMaintenanceWindow.java @@ -8,6 +8,9 @@ */ package org.eclipse.hawkbit.mgmt.json.model; +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; +import com.fasterxml.jackson.annotation.JsonInclude; +import com.fasterxml.jackson.annotation.JsonInclude.Include; import com.fasterxml.jackson.annotation.JsonSetter; /** @@ -15,7 +18,9 @@ import com.fasterxml.jackson.annotation.JsonSetter; * schedule defined as cron expression, duration in HH:mm:ss format and time * zone as offset from UTC. */ -public class MaintenanceWindow { +@JsonInclude(Include.NON_NULL) +@JsonIgnoreProperties(ignoreUnknown = true) +public class MgmtMaintenanceWindow { private String maintenanceSchedule; private String maintenanceWindowDuration; @@ -31,7 +36,7 @@ public class MaintenanceWindow { * year". */ @JsonSetter("schedule") - public void setMaintenanceSchedule(String maintenanceSchedule) { + public void setMaintenanceSchedule(final String maintenanceSchedule) { this.maintenanceSchedule = maintenanceSchedule; } @@ -43,7 +48,7 @@ public class MaintenanceWindow { * window, for example 00:30:00 for 30 minutes. */ @JsonSetter("duration") - public void setMaintenanceWindowDuration(String maintenanceWindowDuration) { + public void setMaintenanceWindowDuration(final String maintenanceWindowDuration) { this.maintenanceWindowDuration = maintenanceWindowDuration; } @@ -57,7 +62,7 @@ public class MaintenanceWindow { * cron expression is relative to this time zone. */ @JsonSetter("timezone") - public void setMaintenanceWindowTimeZone(String maintenanceWindowTimeZone) { + public void setMaintenanceWindowTimeZone(final String maintenanceWindowTimeZone) { this.maintenanceWindowTimeZone = maintenanceWindowTimeZone; } diff --git a/hawkbit-mgmt-api/src/main/java/org/eclipse/hawkbit/mgmt/json/model/distributionset/MgmtTargetAssignmentRequestBody.java b/hawkbit-mgmt-api/src/main/java/org/eclipse/hawkbit/mgmt/json/model/distributionset/MgmtTargetAssignmentRequestBody.java index 5ac69767c..c93254005 100644 --- a/hawkbit-mgmt-api/src/main/java/org/eclipse/hawkbit/mgmt/json/model/distributionset/MgmtTargetAssignmentRequestBody.java +++ b/hawkbit-mgmt-api/src/main/java/org/eclipse/hawkbit/mgmt/json/model/distributionset/MgmtTargetAssignmentRequestBody.java @@ -8,7 +8,7 @@ */ package org.eclipse.hawkbit.mgmt.json.model.distributionset; -import org.eclipse.hawkbit.mgmt.json.model.MaintenanceWindow; +import org.eclipse.hawkbit.mgmt.json.model.MgmtMaintenanceWindow; import com.fasterxml.jackson.annotation.JsonIgnoreProperties; import com.fasterxml.jackson.annotation.JsonProperty; @@ -28,72 +28,40 @@ public class MgmtTargetAssignmentRequestBody { private MgmtActionType type; /** - * {@link MaintenanceWindow} object containing schedule, duration and + * {@link MgmtMaintenanceWindow} object containing schedule, duration and * timezone. */ - private MaintenanceWindow maintenanceWindow = null; + private MgmtMaintenanceWindow maintenanceWindow; - /** - * @return the id - */ public String getId() { return id; } - /** - * @param id - * the id to set - */ public void setId(final String id) { this.id = id; } - /** - * @return the type - */ public MgmtActionType getType() { return type; } - /** - * @param type - * the type to set - */ public void setType(final MgmtActionType type) { this.type = type; } - /** - * @return the forcetime - */ public long getForcetime() { return forcetime; } - /** - * @param forcetime - * the forcetime to set - */ public void setForcetime(final long forcetime) { this.forcetime = forcetime; } - /** - * Returns {@link MaintenanceWindow} for the target assignment request. - * - * @return {@link MaintenanceWindow}. - */ - public MaintenanceWindow getMaintenanceWindow() { + public MgmtMaintenanceWindow getMaintenanceWindow() { return maintenanceWindow; } - /** - * Sets {@link MaintenanceWindow} for the target assignment request. - * - * @param maintenanceWindow - * as {@link MaintenanceWindow}. - */ - public void setMaintenanceWindow(MaintenanceWindow maintenanceWindow) { + public void setMaintenanceWindow(final MgmtMaintenanceWindow maintenanceWindow) { this.maintenanceWindow = maintenanceWindow; } diff --git a/hawkbit-mgmt-api/src/main/java/org/eclipse/hawkbit/mgmt/json/model/target/MgmtDistributionSetAssigment.java b/hawkbit-mgmt-api/src/main/java/org/eclipse/hawkbit/mgmt/json/model/target/MgmtDistributionSetAssignment.java similarity index 52% rename from hawkbit-mgmt-api/src/main/java/org/eclipse/hawkbit/mgmt/json/model/target/MgmtDistributionSetAssigment.java rename to hawkbit-mgmt-api/src/main/java/org/eclipse/hawkbit/mgmt/json/model/target/MgmtDistributionSetAssignment.java index 7ee5cb408..27beacc5b 100644 --- a/hawkbit-mgmt-api/src/main/java/org/eclipse/hawkbit/mgmt/json/model/target/MgmtDistributionSetAssigment.java +++ b/hawkbit-mgmt-api/src/main/java/org/eclipse/hawkbit/mgmt/json/model/target/MgmtDistributionSetAssignment.java @@ -3,70 +3,56 @@ */ package org.eclipse.hawkbit.mgmt.json.model.target; -import org.eclipse.hawkbit.mgmt.json.model.MaintenanceWindow; import org.eclipse.hawkbit.mgmt.json.model.MgmtId; +import org.eclipse.hawkbit.mgmt.json.model.MgmtMaintenanceWindow; import org.eclipse.hawkbit.mgmt.json.model.distributionset.MgmtActionType; /** * Request Body of DistributionSet for assignment operations (ID only). * */ -public class MgmtDistributionSetAssigment extends MgmtId { +public class MgmtDistributionSetAssignment extends MgmtId { private long forcetime; private MgmtActionType type; /** - * {@link MaintenanceWindow} object defining a schedule, duration and + * {@link MgmtMaintenanceWindow} object defining a schedule, duration and * timezone. */ - private MaintenanceWindow maintenanceWindow = null; + private MgmtMaintenanceWindow maintenanceWindow; - /** - * @return the type - */ public MgmtActionType getType() { return type; } - /** - * @param type - * the type to set - */ public void setType(final MgmtActionType type) { this.type = type; } - /** - * @return the forcetime - */ public long getForcetime() { return forcetime; } - /** - * @param forcetime - * the forcetime to set - */ public void setForcetime(final long forcetime) { this.forcetime = forcetime; } /** - * Returns {@link MaintenanceWindow} for distribution set assignment. + * Returns {@link MgmtMaintenanceWindow} for distribution set assignment. * - * @return {@link MaintenanceWindow}. + * @return {@link MgmtMaintenanceWindow}. */ - public MaintenanceWindow getMaintenanceWindow() { + public MgmtMaintenanceWindow getMaintenanceWindow() { return maintenanceWindow; } /** - * Sets {@link MaintenanceWindow} for distribution set assignment. + * Sets {@link MgmtMaintenanceWindow} for distribution set assignment. * * @param maintenanceWindow - * as {@link MaintenanceWindow}. + * as {@link MgmtMaintenanceWindow}. */ - public void setMaintenanceWindow(MaintenanceWindow maintenanceWindow) { + public void setMaintenanceWindow(final MgmtMaintenanceWindow maintenanceWindow) { this.maintenanceWindow = maintenanceWindow; } } diff --git a/hawkbit-mgmt-api/src/main/java/org/eclipse/hawkbit/mgmt/rest/api/MgmtTargetRestApi.java b/hawkbit-mgmt-api/src/main/java/org/eclipse/hawkbit/mgmt/rest/api/MgmtTargetRestApi.java index 9d57afc36..922e4c87b 100644 --- a/hawkbit-mgmt-api/src/main/java/org/eclipse/hawkbit/mgmt/rest/api/MgmtTargetRestApi.java +++ b/hawkbit-mgmt-api/src/main/java/org/eclipse/hawkbit/mgmt/rest/api/MgmtTargetRestApi.java @@ -16,7 +16,7 @@ import org.eclipse.hawkbit.mgmt.json.model.action.MgmtActionRequestBodyPut; import org.eclipse.hawkbit.mgmt.json.model.action.MgmtActionStatus; import org.eclipse.hawkbit.mgmt.json.model.distributionset.MgmtDistributionSet; import org.eclipse.hawkbit.mgmt.json.model.distributionset.MgmtTargetAssignmentResponseBody; -import org.eclipse.hawkbit.mgmt.json.model.target.MgmtDistributionSetAssigment; +import org.eclipse.hawkbit.mgmt.json.model.target.MgmtDistributionSetAssignment; import org.eclipse.hawkbit.mgmt.json.model.target.MgmtTarget; import org.eclipse.hawkbit.mgmt.json.model.target.MgmtTargetAttributes; import org.eclipse.hawkbit.mgmt.json.model.target.MgmtTargetRequestBody; @@ -273,7 +273,7 @@ public interface MgmtTargetRestApi { MediaTypes.HAL_JSON_VALUE, MediaType.APPLICATION_JSON_VALUE }, produces = { MediaTypes.HAL_JSON_VALUE, MediaType.APPLICATION_JSON_VALUE }) ResponseEntity postAssignedDistributionSet( - @PathVariable("controllerId") String controllerId, MgmtDistributionSetAssigment dsId, + @PathVariable("controllerId") String controllerId, MgmtDistributionSetAssignment dsId, @RequestParam(value = "offline", required = false) boolean offline); /** diff --git a/hawkbit-mgmt-resource/src/main/java/org/eclipse/hawkbit/mgmt/rest/resource/MgmtDistributionSetResource.java b/hawkbit-mgmt-resource/src/main/java/org/eclipse/hawkbit/mgmt/rest/resource/MgmtDistributionSetResource.java index e7fc6d7fb..2d963814f 100644 --- a/hawkbit-mgmt-resource/src/main/java/org/eclipse/hawkbit/mgmt/rest/resource/MgmtDistributionSetResource.java +++ b/hawkbit-mgmt-resource/src/main/java/org/eclipse/hawkbit/mgmt/rest/resource/MgmtDistributionSetResource.java @@ -249,16 +249,18 @@ public class MgmtDistributionSetResource implements MgmtDistributionSetRestApi { .stream().map(MgmtTargetAssignmentRequestBody::getId).collect(Collectors.toList())))); } - final DistributionSetAssignmentResult assignDistributionSet = this.deployManagament.assignDistributionSet( - distributionSetId, - assignments.stream().map(t -> new TargetWithActionType(t.getId(), - MgmtRestModelMapper.convertActionType(t.getType()), t.getForcetime(), - t.getMaintenanceWindow() == null ? null : t.getMaintenanceWindow().getMaintenanceSchedule(), - t.getMaintenanceWindow() == null ? null - : t.getMaintenanceWindow().getMaintenanceWindowDuration(), - t.getMaintenanceWindow() == null ? null - : t.getMaintenanceWindow().getMaintenanceWindowTimeZone())) - .collect(Collectors.toList())); + final DistributionSetAssignmentResult assignDistributionSet = this.deployManagament + .assignDistributionSet(distributionSetId, assignments.stream().map(t -> { + if (t.getMaintenanceWindow() == null) { + return new TargetWithActionType(t.getId(), MgmtRestModelMapper.convertActionType(t.getType()), + t.getForcetime()); + } + + return new TargetWithActionType(t.getId(), MgmtRestModelMapper.convertActionType(t.getType()), + t.getForcetime(), t.getMaintenanceWindow().getMaintenanceSchedule(), + t.getMaintenanceWindow().getMaintenanceWindowDuration(), + t.getMaintenanceWindow().getMaintenanceWindowTimeZone()); + }).collect(Collectors.toList())); return ResponseEntity.ok(MgmtDistributionSetMapper.toResponse(assignDistributionSet)); diff --git a/hawkbit-mgmt-resource/src/main/java/org/eclipse/hawkbit/mgmt/rest/resource/MgmtTargetResource.java b/hawkbit-mgmt-resource/src/main/java/org/eclipse/hawkbit/mgmt/rest/resource/MgmtTargetResource.java index f72bd813c..78d70d2eb 100644 --- a/hawkbit-mgmt-resource/src/main/java/org/eclipse/hawkbit/mgmt/rest/resource/MgmtTargetResource.java +++ b/hawkbit-mgmt-resource/src/main/java/org/eclipse/hawkbit/mgmt/rest/resource/MgmtTargetResource.java @@ -12,11 +12,10 @@ import java.util.Arrays; import java.util.Collection; import java.util.List; import java.util.Map; -import java.util.stream.Collectors; import javax.validation.ValidationException; -import org.eclipse.hawkbit.mgmt.json.model.MaintenanceWindow; +import org.eclipse.hawkbit.mgmt.json.model.MgmtMaintenanceWindow; import org.eclipse.hawkbit.mgmt.json.model.PagedList; import org.eclipse.hawkbit.mgmt.json.model.action.MgmtAction; import org.eclipse.hawkbit.mgmt.json.model.action.MgmtActionRequestBodyPut; @@ -24,7 +23,7 @@ import org.eclipse.hawkbit.mgmt.json.model.action.MgmtActionStatus; import org.eclipse.hawkbit.mgmt.json.model.distributionset.MgmtActionType; import org.eclipse.hawkbit.mgmt.json.model.distributionset.MgmtDistributionSet; import org.eclipse.hawkbit.mgmt.json.model.distributionset.MgmtTargetAssignmentResponseBody; -import org.eclipse.hawkbit.mgmt.json.model.target.MgmtDistributionSetAssigment; +import org.eclipse.hawkbit.mgmt.json.model.target.MgmtDistributionSetAssignment; import org.eclipse.hawkbit.mgmt.json.model.target.MgmtTarget; import org.eclipse.hawkbit.mgmt.json.model.target.MgmtTargetAttributes; import org.eclipse.hawkbit.mgmt.json.model.target.MgmtTargetRequestBody; @@ -36,7 +35,6 @@ import org.eclipse.hawkbit.repository.OffsetBasedPageRequest; import org.eclipse.hawkbit.repository.TargetManagement; import org.eclipse.hawkbit.repository.exception.EntityNotFoundException; import org.eclipse.hawkbit.repository.model.Action; -import org.eclipse.hawkbit.repository.model.Action.ActionType; import org.eclipse.hawkbit.repository.model.ActionStatus; import org.eclipse.hawkbit.repository.model.Target; import org.eclipse.hawkbit.repository.model.TargetWithActionType; @@ -268,7 +266,7 @@ public class MgmtTargetResource implements MgmtTargetRestApi { @Override public ResponseEntity postAssignedDistributionSet( @PathVariable("controllerId") final String controllerId, - @RequestBody final MgmtDistributionSetAssigment dsId, + @RequestBody final MgmtDistributionSetAssignment dsId, @RequestParam(value = "offline", required = false) final boolean offline) { if (offline) { @@ -277,20 +275,21 @@ public class MgmtTargetResource implements MgmtTargetRestApi { } findTargetWithExceptionIfNotFound(controllerId); - final ActionType type = (dsId.getType() != null) ? MgmtRestModelMapper.convertActionType(dsId.getType()) - : ActionType.FORCED; - MaintenanceWindow maintenanceWindow = dsId.getMaintenanceWindow(); + final MgmtMaintenanceWindow maintenanceWindow = dsId.getMaintenanceWindow(); + + if (maintenanceWindow == null) { + return ResponseEntity.ok(MgmtDistributionSetMapper.toResponse(this.deploymentManagement + .assignDistributionSet(dsId.getId(), Arrays.asList(new TargetWithActionType(controllerId, + MgmtRestModelMapper.convertActionType(dsId.getType()), dsId.getForcetime()))))); + } + + return ResponseEntity.ok(MgmtDistributionSetMapper.toResponse(this.deploymentManagement.assignDistributionSet( + dsId.getId(), + Arrays.asList(new TargetWithActionType(controllerId, + MgmtRestModelMapper.convertActionType(dsId.getType()), dsId.getForcetime(), + maintenanceWindow.getMaintenanceSchedule(), maintenanceWindow.getMaintenanceWindowDuration(), + maintenanceWindow.getMaintenanceWindowTimeZone()))))); - return ResponseEntity - .ok(MgmtDistributionSetMapper.toResponse(this.deploymentManagement.assignDistributionSet(dsId.getId(), - Arrays.asList(controllerId).stream() - .map(t -> new TargetWithActionType(t, type, dsId.getForcetime(), - maintenanceWindow == null ? null : maintenanceWindow.getMaintenanceSchedule(), - maintenanceWindow == null ? null - : maintenanceWindow.getMaintenanceWindowDuration(), - maintenanceWindow == null ? null - : maintenanceWindow.getMaintenanceWindowTimeZone())) - .collect(Collectors.toList())))); } @Override diff --git a/hawkbit-repository/hawkbit-repository-api/pom.xml b/hawkbit-repository/hawkbit-repository-api/pom.xml index 778fac4d9..b0d3acc09 100644 --- a/hawkbit-repository/hawkbit-repository-api/pom.xml +++ b/hawkbit-repository/hawkbit-repository-api/pom.xml @@ -50,7 +50,6 @@ com.cronutils cron-utils - 5.0.5 diff --git a/hawkbit-repository/hawkbit-repository-api/src/main/java/org/eclipse/hawkbit/repository/ControllerManagement.java b/hawkbit-repository/hawkbit-repository-api/src/main/java/org/eclipse/hawkbit/repository/ControllerManagement.java index 975eca5c5..1754c686c 100644 --- a/hawkbit-repository/hawkbit-repository-api/src/main/java/org/eclipse/hawkbit/repository/ControllerManagement.java +++ b/hawkbit-repository/hawkbit-repository-api/src/main/java/org/eclipse/hawkbit/repository/ControllerManagement.java @@ -236,7 +236,7 @@ public interface ControllerManagement { * {@link TenantConfigurationKey#MAINTENANCE_WINDOW_POLL_COUNT}. */ @PreAuthorize(SpringEvalExpressions.IS_CONTROLLER) - public int getMaintenanceWindowPollCount(); + int getMaintenanceWindowPollCount(); /** * Returns polling time based on the maintenance window for an action. @@ -248,14 +248,14 @@ public interface ControllerManagement { * of maintenance window, it resets to default * {@link TenantConfigurationKey#POLLING_TIME_INTERVAL}. * - * @param action + * @param actionId * id the {@link Action} for which polling time is calculated * based on it having maintenance window or not * * @return current {@link TenantConfigurationKey#POLLING_TIME_INTERVAL}. */ @PreAuthorize(SpringEvalExpressions.IS_CONTROLLER) - String getPollingTimeForAction(final Action action); + String getPollingTimeForAction(long actionId); /** * Checks if a given target has currently or has even been assigned to the diff --git a/hawkbit-repository/hawkbit-repository-api/src/main/java/org/eclipse/hawkbit/repository/MaintenanceScheduleHelper.java b/hawkbit-repository/hawkbit-repository-api/src/main/java/org/eclipse/hawkbit/repository/MaintenanceScheduleHelper.java index 5645e8867..7ce2992dd 100644 --- a/hawkbit-repository/hawkbit-repository-api/src/main/java/org/eclipse/hawkbit/repository/MaintenanceScheduleHelper.java +++ b/hawkbit-repository/hawkbit-repository-api/src/main/java/org/eclipse/hawkbit/repository/MaintenanceScheduleHelper.java @@ -16,10 +16,9 @@ import java.time.ZoneOffset; import java.time.ZonedDateTime; import java.time.format.DateTimeParseException; import java.util.Optional; -import java.util.TimeZone; -import java.util.regex.Pattern; import org.eclipse.hawkbit.repository.exception.InvalidMaintenanceScheduleException; +import org.springframework.util.StringUtils; import com.cronutils.model.Cron; import com.cronutils.model.definition.CronDefinition; @@ -35,9 +34,7 @@ import com.cronutils.parser.CronParser; */ public class MaintenanceScheduleHelper { - ExecutionTime scheduleExecutor = null; - Duration duration = null; - TimeZone timeZone = null; + private final ExecutionTime scheduleExecutor; /** * Constructor that accepts a cron expression, duration and time zone and @@ -48,22 +45,11 @@ public class MaintenanceScheduleHelper { * maintenance window. Expression has 6 mandatory fields and 1 * last optional field: "second minute hour dayofmonth month * weekday year" - * @param duration - * in HH:mm:ss format specifying the duration of a maintenance - * window, for example 00:30:00 for 30 minutes - * @param timezone - * is the time zone specified as +/-hh:mm offset from UTC. For - * example +02:00 for CET summer time and +00:00 for UTC. The - * start time of a maintenance window calculated based on the - * cron expression is relative to this time zone. */ - public MaintenanceScheduleHelper(String cronSchedule, String duration, String timeZone) { - this.timeZone = TimeZone.getTimeZone(ZoneOffset.of(timeZone)); - this.duration = Duration.parse(convertToISODuration(duration)); - - CronDefinition cronDefinition = CronDefinitionBuilder.instanceDefinitionFor(QUARTZ); - CronParser parser = new CronParser(cronDefinition); - Cron quartzCron = parser.parse(cronSchedule); + public MaintenanceScheduleHelper(final String cronSchedule) { + final CronDefinition cronDefinition = CronDefinitionBuilder.instanceDefinitionFor(QUARTZ); + final CronParser parser = new CronParser(cronDefinition); + final Cron quartzCron = parser.parse(cronSchedule); this.scheduleExecutor = ExecutionTime.forCron(quartzCron); } @@ -78,11 +64,13 @@ public class MaintenanceScheduleHelper { * @return {@link Optional} of the next available window. In * case there is none, returns empty value. */ - public Optional nextExecution(ZonedDateTime after) { + // Exception squid:S1166 - lib throws exception as well if no value found + @SuppressWarnings("squid:S1166") + public Optional nextExecution(final ZonedDateTime after) { try { - ZonedDateTime next = this.scheduleExecutor.nextExecution(after); - return Optional.of(next); - } catch (IllegalArgumentException e) { + final ZonedDateTime next = this.scheduleExecutor.nextExecution(after); + return Optional.ofNullable(next); + } catch (final IllegalArgumentException ignored) { return Optional.empty(); } } @@ -98,7 +86,7 @@ public class MaintenanceScheduleHelper { * @return true if there is at least one valid schedule remaining, else * false. */ - public boolean hasValidScheduleAfter(ZonedDateTime after) { + private boolean hasValidScheduleAfter(final ZonedDateTime after) { return nextExecution(after).isPresent(); } @@ -122,34 +110,41 @@ public class MaintenanceScheduleHelper { * start time of a maintenance window calculated based on the * cron expression is relative to this time zone * - * @return true if the schedule is valid, else throw an exception - * * @throws InvalidMaintenanceScheduleException * if the defined schedule fails the validity criteria. */ - public static boolean validateMaintenanceSchedule(String cronSchedule, String duration, String timezone) { - // check if schedule, duration and timezone are all not null. - if (cronSchedule != null && duration != null && timezone != null) { - // check if schedule, duration and timezone are all not empty. - if (!(cronSchedule.isEmpty() || duration.isEmpty() || timezone.isEmpty())) { - ZonedDateTime now = ZonedDateTime.now(ZoneOffset.of(timezone)); - MaintenanceScheduleHelper scheduleHelper = new MaintenanceScheduleHelper(cronSchedule, duration, - timezone); - // check if there is a window currently active or available in - // future. - if (!scheduleHelper.hasValidScheduleAfter(now.minus(Duration.parse(convertToISODuration(duration))))) { - throw new InvalidMaintenanceScheduleException( - "No valid maintenance window available after current time"); - } - } else { - throw new InvalidMaintenanceScheduleException("Either of schedule, duration or timezone empty."); + public static void validateMaintenanceSchedule(final String cronSchedule, final String duration, + final String timezone) { + // check if schedule, duration and timezone are all not empty. + if (allNotEmpty(cronSchedule, duration, timezone)) { + final ZonedDateTime now; + try { + now = ZonedDateTime.now(ZoneOffset.of(timezone)); + Duration.parse(convertToISODuration(duration)); + } catch (final RuntimeException validationFailed) { + throw new InvalidMaintenanceScheduleException("No valid maintenance window provided", validationFailed); } - } else if (!(cronSchedule == null && duration == null && timezone == null)) { + + final MaintenanceScheduleHelper scheduleHelper = new MaintenanceScheduleHelper(cronSchedule); + // check if there is a window currently active or available in + // future. + if (!scheduleHelper.hasValidScheduleAfter(now.minus(Duration.parse(convertToISODuration(duration))))) { + throw new InvalidMaintenanceScheduleException( + "No valid maintenance window available after current time"); + } + + } else if (atLeastOneNotEmpty(cronSchedule, duration, timezone)) { throw new InvalidMaintenanceScheduleException( "All of schedule, duration and timezone should either be null or non empty."); } + } - return true; + private static boolean atLeastOneNotEmpty(final String cronSchedule, final String duration, final String timezone) { + return !(StringUtils.isEmpty(cronSchedule) && StringUtils.isEmpty(duration) && StringUtils.isEmpty(timezone)); + } + + private static boolean allNotEmpty(final String cronSchedule, final String duration, final String timezone) { + return !StringUtils.isEmpty(cronSchedule) && !StringUtils.isEmpty(duration) && !StringUtils.isEmpty(timezone); } /** @@ -166,7 +161,7 @@ public class MaintenanceScheduleHelper { * @throws DateTimeParseException * if the text cannot be converted to ISO format. */ - public static String convertToISODuration(String timeInterval) { + public static String convertToISODuration(final String timeInterval) { return Duration.between(LocalTime.MIN, LocalTime.parse(timeInterval)).toString(); } } diff --git a/hawkbit-repository/hawkbit-repository-api/src/main/java/org/eclipse/hawkbit/repository/event/remote/TargetAssignDistributionSetEvent.java b/hawkbit-repository/hawkbit-repository-api/src/main/java/org/eclipse/hawkbit/repository/event/remote/TargetAssignDistributionSetEvent.java index 8829ee9c0..d57742d48 100644 --- a/hawkbit-repository/hawkbit-repository-api/src/main/java/org/eclipse/hawkbit/repository/event/remote/TargetAssignDistributionSetEvent.java +++ b/hawkbit-repository/hawkbit-repository-api/src/main/java/org/eclipse/hawkbit/repository/event/remote/TargetAssignDistributionSetEvent.java @@ -26,6 +26,8 @@ public class TargetAssignDistributionSetEvent extends RemoteTenantAwareEvent { private long distributionSetId; + private boolean maintenanceWindowAvailable; + private final Map actions = new HashMap<>(); /** @@ -46,24 +48,32 @@ public class TargetAssignDistributionSetEvent extends RemoteTenantAwareEvent { * the actions and the targets * @param applicationId * the application id. + * @param maintenanceWindowAvailable + * see {@link Action#isMaintenanceWindowAvailable()} */ public TargetAssignDistributionSetEvent(final String tenant, final long distributionSetId, final List a, - final String applicationId) { + final String applicationId, final boolean maintenanceWindowAvailable) { super(distributionSetId, tenant, applicationId); this.distributionSetId = distributionSetId; + this.maintenanceWindowAvailable = maintenanceWindowAvailable; actions.putAll(a.stream().filter(action -> action.getDistributionSet().getId().longValue() == distributionSetId) .collect(Collectors.toMap(action -> action.getTarget().getControllerId(), Action::getId))); } public TargetAssignDistributionSetEvent(final Action action, final String applicationId) { - this(action.getTenant(), action.getDistributionSet().getId(), Arrays.asList(action), applicationId); + this(action.getTenant(), action.getDistributionSet().getId(), Arrays.asList(action), applicationId, + action.isMaintenanceWindowAvailable()); } public Long getDistributionSetId() { return distributionSetId; } + public boolean isMaintenanceWindowAvailable() { + return maintenanceWindowAvailable; + } + public Map getActions() { return actions; } diff --git a/hawkbit-repository/hawkbit-repository-api/src/main/java/org/eclipse/hawkbit/repository/model/TargetWithActionType.java b/hawkbit-repository/hawkbit-repository-api/src/main/java/org/eclipse/hawkbit/repository/model/TargetWithActionType.java index 0850f6606..9178461c7 100644 --- a/hawkbit-repository/hawkbit-repository-api/src/main/java/org/eclipse/hawkbit/repository/model/TargetWithActionType.java +++ b/hawkbit-repository/hawkbit-repository-api/src/main/java/org/eclipse/hawkbit/repository/model/TargetWithActionType.java @@ -14,8 +14,6 @@ import org.eclipse.hawkbit.repository.model.Action.ActionType; /** * A custom view on {@link Target} with {@link ActionType}. - * - * * */ public class TargetWithActionType { @@ -23,9 +21,9 @@ public class TargetWithActionType { private final String controllerId; private final ActionType actionType; private final long forceTime; - private String maintenanceSchedule = null; - private String maintenanceWindowDuration = null; - private String maintenanceWindowTimeZone = null; + private String maintenanceSchedule; + private String maintenanceWindowDuration; + private String maintenanceWindowTimeZone; public TargetWithActionType(final String controllerId) { this.controllerId = controllerId; @@ -35,7 +33,7 @@ public class TargetWithActionType { public TargetWithActionType(final String controllerId, final ActionType actionType, final long forceTime) { this.controllerId = controllerId; - this.actionType = actionType; + this.actionType = actionType != null ? actionType : ActionType.FORCED; this.forceTime = forceTime; } @@ -64,19 +62,16 @@ public class TargetWithActionType { * if the parameters do not define a valid maintenance schedule. */ public TargetWithActionType(final String controllerId, final ActionType actionType, final long forceTime, - String maintenanceSchedule, String maintenanceWindowDuration, String maintenanceWindowTimeZone) { - this.controllerId = controllerId; - this.actionType = actionType; - this.forceTime = forceTime; + final String maintenanceSchedule, final String maintenanceWindowDuration, + final String maintenanceWindowTimeZone) { + this(controllerId, actionType, forceTime); - if (MaintenanceScheduleHelper.validateMaintenanceSchedule(maintenanceSchedule, maintenanceWindowDuration, - maintenanceWindowTimeZone)) { - this.maintenanceSchedule = maintenanceSchedule; - this.maintenanceWindowDuration = maintenanceWindowDuration; - this.maintenanceWindowTimeZone = maintenanceWindowTimeZone; - } else { - throw new InvalidMaintenanceScheduleException("Invalid maintenance window definition"); - } + this.maintenanceSchedule = maintenanceSchedule; + this.maintenanceWindowDuration = maintenanceWindowDuration; + this.maintenanceWindowTimeZone = maintenanceWindowTimeZone; + + MaintenanceScheduleHelper.validateMaintenanceSchedule(maintenanceSchedule, maintenanceWindowDuration, + maintenanceWindowTimeZone); } public ActionType getActionType() { diff --git a/hawkbit-repository/hawkbit-repository-jpa/src/main/java/org/eclipse/hawkbit/repository/jpa/AbstractDsAssignmentStrategy.java b/hawkbit-repository/hawkbit-repository-jpa/src/main/java/org/eclipse/hawkbit/repository/jpa/AbstractDsAssignmentStrategy.java index ed127be92..e9c82b3d0 100644 --- a/hawkbit-repository/hawkbit-repository-jpa/src/main/java/org/eclipse/hawkbit/repository/jpa/AbstractDsAssignmentStrategy.java +++ b/hawkbit-repository/hawkbit-repository-jpa/src/main/java/org/eclipse/hawkbit/repository/jpa/AbstractDsAssignmentStrategy.java @@ -129,8 +129,9 @@ public abstract class AbstractDsAssignmentStrategy { return; } - afterCommit.afterCommit(() -> eventPublisher.publishEvent( - new TargetAssignDistributionSetEvent(tenant, distributionSetId, actions, applicationContext.getId()))); + afterCommit.afterCommit( + () -> eventPublisher.publishEvent(new TargetAssignDistributionSetEvent(tenant, distributionSetId, + actions, applicationContext.getId(), actions.get(0).isMaintenanceWindowAvailable()))); } protected void sendTargetUpdatedEvent(final JpaTarget target) { diff --git a/hawkbit-repository/hawkbit-repository-jpa/src/main/java/org/eclipse/hawkbit/repository/jpa/JpaControllerManagement.java b/hawkbit-repository/hawkbit-repository-jpa/src/main/java/org/eclipse/hawkbit/repository/jpa/JpaControllerManagement.java index dd287c1bb..ae69c0259 100644 --- a/hawkbit-repository/hawkbit-repository-jpa/src/main/java/org/eclipse/hawkbit/repository/jpa/JpaControllerManagement.java +++ b/hawkbit-repository/hawkbit-repository-jpa/src/main/java/org/eclipse/hawkbit/repository/jpa/JpaControllerManagement.java @@ -14,10 +14,6 @@ import java.time.ZonedDateTime; import java.time.temporal.ChronoUnit; import java.time.temporal.TemporalUnit; import java.util.Collection; -import java.time.Duration; -import java.time.ZonedDateTime; -import java.time.temporal.ChronoUnit; -import java.time.temporal.TemporalUnit; import java.util.Collections; import java.util.List; import java.util.Map; @@ -62,6 +58,7 @@ import org.eclipse.hawkbit.repository.jpa.specifications.ActionSpecifications; import org.eclipse.hawkbit.repository.model.Action; import org.eclipse.hawkbit.repository.model.Action.Status; import org.eclipse.hawkbit.repository.model.ActionStatus; +import org.eclipse.hawkbit.repository.model.DistributionSet; import org.eclipse.hawkbit.repository.model.SoftwareModule; import org.eclipse.hawkbit.repository.model.SoftwareModuleMetadata; import org.eclipse.hawkbit.repository.model.Target; @@ -210,31 +207,17 @@ public class JpaControllerManagement implements ControllerManagement { .getConfigurationValue(TenantConfigurationKey.MAINTENANCE_WINDOW_POLL_COUNT, Integer.class).getValue()); } - /** - * Returns polling time based on the maintenance window for an action. - * Server will reduce the polling interval as the start time for maintenance - * window approaches, so that at least these many attempts are made between - * current polling until start of maintenance window. Poll time keeps - * reducing with MinPollingTime as lower limit - * {@link TenantConfigurationKey#MIN_POLLING_TIME_INTERVAL}. After the start - * of maintenance window, it resets to default - * {@link TenantConfigurationKey#POLLING_TIME_INTERVAL}. - * - * @param action - * id the {@link Action} for which polling time is calculated - * based on it having maintenance window or not - * - * @return current {@link TenantConfigurationKey#POLLING_TIME_INTERVAL}. - */ @Override - public String getPollingTimeForAction(final Action action) { - if (action == null || !action.hasMaintenanceSchedule() || action.isMaintenanceScheduleLapsed()) { + public String getPollingTimeForAction(final long actionId) { + + final JpaAction action = getActionAndThrowExceptionIfNotFound(actionId); + + if (!action.hasMaintenanceSchedule() || action.isMaintenanceScheduleLapsed()) { return getPollingTime(); } - JpaAction jpaAction = getActionAndThrowExceptionIfNotFound(action.getId()); return (new EventTimer(getPollingTime(), getMinPollingTime(), ChronoUnit.SECONDS)) - .timeToNextEvent(getMaintenanceWindowPollCount(), jpaAction.getMaintenanceWindowStartTime().get()); + .timeToNextEvent(getMaintenanceWindowPollCount(), action.getMaintenanceWindowStartTime().orElse(null)); } /** @@ -244,7 +227,7 @@ public class JpaControllerManagement implements ControllerManagement { * polling, should happen when timer expires. Class makes use of java.time * package to manipulate and calculate timer duration. */ - private class EventTimer { + private static class EventTimer { private final String defaultEventInterval; private final Duration defaultEventIntervalDuration; @@ -266,7 +249,7 @@ public class JpaControllerManagement implements ControllerManagement { * @param timerUnit * representing the unit of time to be used for timer. */ - EventTimer(String defaultEventInterval, String minimumEventInterval, TemporalUnit timeUnit) { + EventTimer(final String defaultEventInterval, final String minimumEventInterval, final TemporalUnit timeUnit) { this.defaultEventInterval = defaultEventInterval; this.defaultEventIntervalDuration = Duration .parse(MaintenanceScheduleHelper.convertToISODuration(defaultEventInterval)); @@ -294,8 +277,8 @@ public class JpaControllerManagement implements ControllerManagement { * * @return String in HH:mm:ss format for time to next event. */ - String timeToNextEvent(int eventCount, ZonedDateTime timerResetTime) { - ZonedDateTime currentTime = ZonedDateTime.now(); + String timeToNextEvent(final int eventCount, final ZonedDateTime timerResetTime) { + final ZonedDateTime currentTime = ZonedDateTime.now(); // If there is no reset time, or if we already past the reset time, // return the default interval. @@ -304,7 +287,7 @@ public class JpaControllerManagement implements ControllerManagement { } // Calculate the interval timer based on desired event count. - Duration currentIntervalDuration = Duration.of(currentTime.until(timerResetTime, timeUnit), timeUnit) + final Duration currentIntervalDuration = Duration.of(currentTime.until(timerResetTime, timeUnit), timeUnit) .dividedBy(eventCount); // Need not return interval greater than the default. @@ -810,7 +793,8 @@ public class JpaControllerManagement implements ControllerManagement { // For negative and large value of messageCount, limit the number of // messages. final int limit = messageCount < 0 || messageCount >= RepositoryConstants.MAX_ACTION_HISTORY_MSG_COUNT - ? RepositoryConstants.MAX_ACTION_HISTORY_MSG_COUNT : messageCount; + ? RepositoryConstants.MAX_ACTION_HISTORY_MSG_COUNT + : messageCount; final PageRequest pageable = new PageRequest(0, limit, new Sort(Direction.DESC, "occurredAt")); final Page messages = actionStatusRepository.findMessagesByActionIdAndMessageNotLike(pageable, actionId, @@ -913,9 +897,10 @@ public class JpaControllerManagement implements ControllerManagement { * @throws EntityNotFoundException * if action with given actionId does not exist. */ + @Override @Modifying @Transactional(isolation = Isolation.READ_COMMITTED) - public Action cancelAction(long actionId) { + public Action cancelAction(final long actionId) { LOG.debug("cancelAction({})", actionId); final JpaAction action = actionRepository.findById(actionId) diff --git a/hawkbit-repository/hawkbit-repository-jpa/src/main/java/org/eclipse/hawkbit/repository/jpa/JpaDeploymentManagement.java b/hawkbit-repository/hawkbit-repository-jpa/src/main/java/org/eclipse/hawkbit/repository/jpa/JpaDeploymentManagement.java index 470f8a110..28029690c 100644 --- a/hawkbit-repository/hawkbit-repository-jpa/src/main/java/org/eclipse/hawkbit/repository/jpa/JpaDeploymentManagement.java +++ b/hawkbit-repository/hawkbit-repository-jpa/src/main/java/org/eclipse/hawkbit/repository/jpa/JpaDeploymentManagement.java @@ -402,6 +402,8 @@ public class JpaDeploymentManagement implements DeploymentManagement { } final String tenant = rolloutGroupActions.getContent().get(0).getTenant(); + final boolean maintenanceWindowAvailable = rolloutGroupActions.getContent().get(0) + .isMaintenanceWindowAvailable(); final List targetAssignments = rolloutGroupActions.getContent().stream() .map(action -> (JpaAction) action).map(this::closeActionIfSetWasAlreadyAssigned) @@ -410,7 +412,7 @@ public class JpaDeploymentManagement implements DeploymentManagement { if (!CollectionUtils.isEmpty(targetAssignments)) { afterCommit.afterCommit(() -> eventPublisher.publishEvent(new TargetAssignDistributionSetEvent(tenant, - distributionSetId, targetAssignments, applicationContext.getId()))); + distributionSetId, targetAssignments, applicationContext.getId(), maintenanceWindowAvailable))); } return rolloutGroupActions.getTotalElements(); diff --git a/hawkbit-repository/hawkbit-repository-jpa/src/main/java/org/eclipse/hawkbit/repository/jpa/model/JpaAction.java b/hawkbit-repository/hawkbit-repository-jpa/src/main/java/org/eclipse/hawkbit/repository/jpa/model/JpaAction.java index 5882b4030..fab0893e4 100644 --- a/hawkbit-repository/hawkbit-repository-jpa/src/main/java/org/eclipse/hawkbit/repository/jpa/model/JpaAction.java +++ b/hawkbit-repository/hawkbit-repository-jpa/src/main/java/org/eclipse/hawkbit/repository/jpa/model/JpaAction.java @@ -101,7 +101,7 @@ public class JpaAction extends AbstractJpaTenantAwareBaseEntity implements Actio @ConversionValue(objectValue = "DOWNLOAD", dataValue = "7"), @ConversionValue(objectValue = "SCHEDULED", dataValue = "8"), @ConversionValue(objectValue = "CANCEL_REJECTED", dataValue = "9"), - @ConversionValue(objectValue = "DOWNLOADED", dataValue = "10")}) + @ConversionValue(objectValue = "DOWNLOADED", dataValue = "10") }) @Convert("status") @NotNull private Status status; @@ -118,13 +118,13 @@ public class JpaAction extends AbstractJpaTenantAwareBaseEntity implements Actio @JoinColumn(name = "rollout", updatable = false, foreignKey = @ForeignKey(value = ConstraintMode.CONSTRAINT, name = "fk_action_rollout")) private JpaRollout rollout; - @Column(name = "maintenance_cron_schedule", length = Action.MAINTENANCE_SCHEDULE_CRON_LENGTH) + @Column(name = "maintenance_cron_schedule", updatable = false, length = Action.MAINTENANCE_SCHEDULE_CRON_LENGTH) private String maintenanceSchedule; - @Column(name = "maintenance_duration", length = Action.MAINTENANCE_WINDOW_DURATION_LENGTH) + @Column(name = "maintenance_duration", updatable = false, length = Action.MAINTENANCE_WINDOW_DURATION_LENGTH) private String maintenanceWindowDuration; - @Column(name = "maintenance_time_zone", length = Action.MAINTENANCE_WINDOW_TIMEZONE_LENGTH) + @Column(name = "maintenance_time_zone", updatable = false, length = Action.MAINTENANCE_WINDOW_TIMEZONE_LENGTH) private String maintenanceWindowTimeZone; /** @@ -243,7 +243,7 @@ public class JpaAction extends AbstractJpaTenantAwareBaseEntity implements Actio * @param maintenanceSchedule * is a cron expression to be used for scheduling. */ - public void setMaintenanceSchedule(String maintenanceSchedule) { + public void setMaintenanceSchedule(final String maintenanceSchedule) { this.maintenanceSchedule = maintenanceSchedule; } @@ -254,7 +254,7 @@ public class JpaAction extends AbstractJpaTenantAwareBaseEntity implements Actio * is the duration of an available maintenance schedule in * HH:mm:ss format. */ - public void setMaintenanceWindowDuration(String maintenanceWindowDuration) { + public void setMaintenanceWindowDuration(final String maintenanceWindowDuration) { this.maintenanceWindowDuration = maintenanceWindowDuration; } @@ -267,7 +267,7 @@ public class JpaAction extends AbstractJpaTenantAwareBaseEntity implements Actio * start time of a maintenance window calculated based on the * cron expression is relative to this time zone. */ - public void setMaintenanceWindowTimeZone(String maintenanceWindowTimeZone) { + public void setMaintenanceWindowTimeZone(final String maintenanceWindowTimeZone) { this.maintenanceWindowTimeZone = maintenanceWindowTimeZone; } @@ -277,10 +277,9 @@ public class JpaAction extends AbstractJpaTenantAwareBaseEntity implements Actio * * @return the {@link MaintenanceScheduleHelper} object. */ - MaintenanceScheduleHelper getScheduler() { + private MaintenanceScheduleHelper getScheduler() { if (this.scheduleHelper == null) { - this.scheduleHelper = new MaintenanceScheduleHelper(maintenanceSchedule, maintenanceWindowDuration, - maintenanceWindowTimeZone); + this.scheduleHelper = new MaintenanceScheduleHelper(maintenanceSchedule); } return this.scheduleHelper; } @@ -290,7 +289,7 @@ public class JpaAction extends AbstractJpaTenantAwareBaseEntity implements Actio * * @return the {@link Duration} of each maintenance window. */ - Duration getMaintenanceWindowDuration() { + private Duration getMaintenanceWindowDuration() { return Duration.parse(MaintenanceScheduleHelper.convertToISODuration(this.maintenanceWindowDuration)); } @@ -302,7 +301,7 @@ public class JpaAction extends AbstractJpaTenantAwareBaseEntity implements Actio * @return the start time as {@link Optional}. */ public Optional getMaintenanceWindowStartTime() { - ZonedDateTime now = ZonedDateTime.now(ZoneOffset.of(maintenanceWindowTimeZone)); + final ZonedDateTime now = ZonedDateTime.now(ZoneOffset.of(maintenanceWindowTimeZone)); return getScheduler().nextExecution(now.minus(getMaintenanceWindowDuration())); } @@ -313,50 +312,21 @@ public class JpaAction extends AbstractJpaTenantAwareBaseEntity implements Actio * * @return the end time of window as {@link Optional}. */ - public Optional getMaintenanceWindowEndTime() { - if (getMaintenanceWindowStartTime().isPresent()) { - return Optional.of(getMaintenanceWindowStartTime().get().plus(getMaintenanceWindowDuration())); - } - return Optional.empty(); + private Optional getMaintenanceWindowEndTime() { + return getMaintenanceWindowStartTime().map(start -> start.plus(getMaintenanceWindowDuration())); } - /** - * The method checks whether the action has a maintenance schedule defined - * for it. A maintenance schedule defines a set of maintenance windows - * during which actual update can be performed. A valid schedule defines at - * least one maintenance window. - * - * @return true if action has a maintenance schedule, else false. - */ @Override public boolean hasMaintenanceSchedule() { return this.maintenanceSchedule != null; } - /** - * The method checks whether the maintenance schedule has already lapsed for - * the action, i.e. there are no more windows available for maintenance. - * Controller manager uses the method to check if the maintenance schedule - * has lapsed, and automatically cancels the action if it is lapsed. - * - * @return true if maintenance schedule has lapsed, else false. - */ @Override public boolean isMaintenanceScheduleLapsed() { - ZonedDateTime now = ZonedDateTime.now(ZoneOffset.of(maintenanceWindowTimeZone)); + final ZonedDateTime now = ZonedDateTime.now(ZoneOffset.of(maintenanceWindowTimeZone)); return !getScheduler().nextExecution(now.minus(getMaintenanceWindowDuration())).isPresent(); } - /** - * The method checks whether a maintenance window is available for the - * action to proceed. If it is available, a 'true' value is returned. The - * maintenance window is considered available: 1) If there is no maintenance - * schedule at all, in which case device can start update any time after - * download is finished; or 2) the current time is within a scheduled - * maintenance window start and end time. - * - * @return true if maintenance window is available, else false. - */ @Override public boolean isMaintenanceWindowAvailable() { if (!hasMaintenanceSchedule()) { @@ -368,10 +338,12 @@ public class JpaAction extends AbstractJpaTenantAwareBaseEntity implements Actio // available. return false; } else { - ZonedDateTime now = ZonedDateTime.now(ZoneOffset.of(maintenanceWindowTimeZone)); - if (this.getMaintenanceWindowStartTime().isPresent() && this.getMaintenanceWindowEndTime().isPresent()) { - return now.isAfter(this.getMaintenanceWindowStartTime().get()) - && now.isBefore(this.getMaintenanceWindowEndTime().get()); + final ZonedDateTime now = ZonedDateTime.now(ZoneOffset.of(maintenanceWindowTimeZone)); + final Optional start = getMaintenanceWindowStartTime(); + final Optional end = getMaintenanceWindowEndTime(); + + if (start.isPresent() && end.isPresent()) { + return now.isAfter(start.get()) && now.isBefore(end.get()); } else { return false; } diff --git a/hawkbit-repository/hawkbit-repository-jpa/src/test/java/org/eclipse/hawkbit/repository/event/remote/RemoteTenantAwareEventTest.java b/hawkbit-repository/hawkbit-repository-jpa/src/test/java/org/eclipse/hawkbit/repository/event/remote/RemoteTenantAwareEventTest.java index 46a776f41..e71e1f93a 100644 --- a/hawkbit-repository/hawkbit-repository-jpa/src/test/java/org/eclipse/hawkbit/repository/event/remote/RemoteTenantAwareEventTest.java +++ b/hawkbit-repository/hawkbit-repository-jpa/src/test/java/org/eclipse/hawkbit/repository/event/remote/RemoteTenantAwareEventTest.java @@ -55,7 +55,8 @@ public class RemoteTenantAwareEventTest extends AbstractRemoteEventTest { final Action action = actionRepository.save(generateAction); final TargetAssignDistributionSetEvent assignmentEvent = new TargetAssignDistributionSetEvent( - action.getTenant(), dsA.getId(), Arrays.asList(action), serviceMatcher.getServiceId()); + action.getTenant(), dsA.getId(), Arrays.asList(action), serviceMatcher.getServiceId(), + action.isMaintenanceWindowAvailable()); TargetAssignDistributionSetEvent underTest = (TargetAssignDistributionSetEvent) createProtoStuffEvent( assignmentEvent); diff --git a/hawkbit-ui/src/main/java/org/eclipse/hawkbit/ui/management/footer/MaintenanceWindowLayout.java b/hawkbit-ui/src/main/java/org/eclipse/hawkbit/ui/management/footer/MaintenanceWindowLayout.java index 28ed12b26..79e780b6f 100644 --- a/hawkbit-ui/src/main/java/org/eclipse/hawkbit/ui/management/footer/MaintenanceWindowLayout.java +++ b/hawkbit-ui/src/main/java/org/eclipse/hawkbit/ui/management/footer/MaintenanceWindowLayout.java @@ -18,6 +18,7 @@ import java.util.stream.Collectors; import org.eclipse.hawkbit.repository.MaintenanceScheduleHelper; import org.eclipse.hawkbit.ui.utils.SPDateTimeUtil; import org.eclipse.hawkbit.ui.utils.VaadinMessageSource; +import org.springframework.util.StringUtils; import com.vaadin.data.Property.ValueChangeEvent; import com.vaadin.data.Property.ValueChangeListener; @@ -53,12 +54,12 @@ public class MaintenanceWindowLayout extends VerticalLayout { * strings. */ public MaintenanceWindowLayout(final VaadinMessageSource i18n) { - + HorizontalLayout optionContainer; HorizontalLayout controlContainer; - + this.i18n = i18n; - + optionContainer = new HorizontalLayout(); controlContainer = new HorizontalLayout(); addComponent(optionContainer); @@ -82,17 +83,18 @@ public class MaintenanceWindowLayout extends VerticalLayout { /** * Validates if the maintenance schedule is a valid cron expression. */ - class CronValidation implements Validator { + private static class CronValidation implements Validator { private static final long serialVersionUID = 1L; @Override - public void validate(Object value) throws InvalidValueException { + public void validate(final Object value) throws InvalidValueException { try { - String expr = (String) value; + final String expr = (String) value; if (!expr.isEmpty()) { - new MaintenanceScheduleHelper((String) value, "00:00:00", getClientTimeZone()); + MaintenanceScheduleHelper.validateMaintenanceSchedule((String) value, "00:00:00", + getClientTimeZone()); } - } catch (IllegalArgumentException e) { + } catch (final IllegalArgumentException e) { Notification.show(e.getMessage()); throw e; } @@ -102,17 +104,17 @@ public class MaintenanceWindowLayout extends VerticalLayout { /** * Validates if the duration is specified in expected format. */ - class DurationValidator implements Validator { + private static class DurationValidator implements Validator { private static final long serialVersionUID = 1L; @Override - public void validate(Object value) throws InvalidValueException { + public void validate(final Object value) { try { - String expr = (String) value; - if (!expr.isEmpty()) { + final String expr = (String) value; + if (!StringUtils.isEmpty(expr)) { MaintenanceScheduleHelper.convertToISODuration((String) value); } - } catch (DateTimeParseException e) { + } catch (final DateTimeParseException e) { Notification.show(e.getMessage()); throw e; } @@ -176,7 +178,7 @@ public class MaintenanceWindowLayout extends VerticalLayout { * Get list of all time zone offsets supported. */ private static List getAllTimeZones() { - List lst = ZoneId.getAvailableZoneIds().stream() + final List lst = ZoneId.getAvailableZoneIds().stream() .map(id -> ZonedDateTime.now(ZoneId.of(id)).getOffset().getId().replace("Z", "+00:00")).distinct() .collect(Collectors.toList()); lst.sort(null); @@ -212,7 +214,7 @@ public class MaintenanceWindowLayout extends VerticalLayout { * * @return boolean. */ - public boolean getMaintenanceOption() { + public boolean isMaintenanceWindowEnabled() { return maintenanceWindowSelection.getValue(); } diff --git a/hawkbit-ui/src/main/java/org/eclipse/hawkbit/ui/management/footer/ManangementConfirmationWindowLayout.java b/hawkbit-ui/src/main/java/org/eclipse/hawkbit/ui/management/footer/ManangementConfirmationWindowLayout.java index 22f48e408..8dfbbcffc 100644 --- a/hawkbit-ui/src/main/java/org/eclipse/hawkbit/ui/management/footer/ManangementConfirmationWindowLayout.java +++ b/hawkbit-ui/src/main/java/org/eclipse/hawkbit/ui/management/footer/ManangementConfirmationWindowLayout.java @@ -159,13 +159,13 @@ public class ManangementConfirmationWindowLayout extends AbstractConfirmationWin ? actionTypeOptionGroupLayout.getForcedTimeDateField().getValue().getTime() : RepositoryModelConstants.NO_FORCE_TIME; - final String maintenanceSchedule = maintenanceWindowLayout.getMaintenanceOption() + final String maintenanceSchedule = maintenanceWindowLayout.isMaintenanceWindowEnabled() ? maintenanceWindowLayout.getMaintenanceSchedule() : null; - final String maintenanceDuration = maintenanceWindowLayout.getMaintenanceOption() + final String maintenanceDuration = maintenanceWindowLayout.isMaintenanceWindowEnabled() ? maintenanceWindowLayout.getMaintenanceDuration() : null; - final String maintenanceTimeZone = maintenanceWindowLayout.getMaintenanceOption() + final String maintenanceTimeZone = maintenanceWindowLayout.isMaintenanceWindowEnabled() ? maintenanceWindowLayout.getMaintenanceTimeZone() : null; final Map> saveAssignedList = Maps.newHashMapWithExpectedSize(itemIds.size()); diff --git a/pom.xml b/pom.xml index 0fb794036..46792c8f7 100644 --- a/pom.xml +++ b/pom.xml @@ -120,7 +120,7 @@ 4.0.3 - 1.7.4.RELEASE + 1.7.6.RELEASE 2.0.0 @@ -149,6 +149,7 @@ 1.9.4 + 5.0.5 1.8.3 2.0.1.Final 1.5.4 @@ -568,6 +569,11 @@ + + com.cronutils + cron-utils + ${cron-utils.version} + org.jsoup jsoup