Fix sonar issues and add DMF tests for maintenance window feature (#655)
* Fix sonar issues. Signed-off-by: kaizimmerm <kai.zimmermann@bosch-si.com> * POM cleanup and more sonar issues fixed. Signed-off-by: kaizimmerm <kai.zimmermann@bosch-si.com> * Remove unneeded JavaDocs. Signed-off-by: kaizimmerm <kai.zimmermann@bosch-si.com> * More sonar issues. Signed-off-by: kaizimmerm <kai.zimmermann@bosch-si.com> * More issues. Signed-off-by: kaizimmerm <kai.zimmermann@bosch-si.com> * Adapt maintenance window to naming. Signed-off-by: kaizimmerm <kai.zimmermann@bosch-si.com> * Add DMF tests. Signed-off-by: kaizimmerm <kai.zimmermann@bosch-si.com> * Readibility. Signed-off-by: kaizimmerm <kai.zimmermann@bosch-si.com> * Typos fixed. Signed-off-by: kaizimmerm <kai.zimmermann@bosch-si.com>
This commit is contained in:
@@ -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<DdiChunk> chunks;
|
||||
|
||||
private MaintenanceWindowStatus maintenanceWindow = null;
|
||||
private MaintenanceWindowStatus maintenanceWindow;
|
||||
|
||||
/**
|
||||
* Constructor.
|
||||
|
||||
@@ -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<Void> 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;
|
||||
|
||||
@@ -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());
|
||||
|
||||
|
||||
@@ -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<String, Object> getTTLMaxArgsAuthenticationQueue() {
|
||||
|
||||
@@ -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> 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<SoftwareModule, List<SoftwareModuleMetadata>> modules) {
|
||||
final Map<SoftwareModule, List<SoftwareModuleMetadata>> 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);
|
||||
}
|
||||
|
||||
|
||||
@@ -102,8 +102,9 @@ public class AmqpMessageHandlerService extends BaseAmqpService {
|
||||
* @return a message if <null> 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) {
|
||||
|
||||
@@ -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);
|
||||
|
||||
}
|
||||
|
||||
|
||||
@@ -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<Long> 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();
|
||||
|
||||
@@ -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<Boolean> callable) {
|
||||
createConditionFactory().until(() -> {
|
||||
return securityRule.runAsPrivileged(callable);
|
||||
});
|
||||
createConditionFactory().until(() -> securityRule.runAsPrivileged(callable));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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()));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -161,8 +161,9 @@ public abstract class AmqpServiceIntegrationTest extends AbstractAmqpIntegration
|
||||
|
||||
}
|
||||
|
||||
protected void assertDownloadAndInstallMessage(final Set<SoftwareModule> dsModules, final String controllerId) {
|
||||
final Message replyMessage = assertReplyMessageHeader(EventTopic.DOWNLOAD_AND_INSTALL, controllerId);
|
||||
private void assertAssignmentMessage(final Set<SoftwareModule> 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<SoftwareModule> dsModules, final String controllerId) {
|
||||
assertAssignmentMessage(dsModules, controllerId, EventTopic.DOWNLOAD_AND_INSTALL);
|
||||
}
|
||||
|
||||
protected void assertDownloadMessage(final Set<SoftwareModule> 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) {
|
||||
|
||||
|
||||
@@ -32,6 +32,6 @@ public enum EventTopic {
|
||||
/**
|
||||
* Topic when sending a download only task, skipping the install.
|
||||
*/
|
||||
DOWNLOAD_AND_SKIP;
|
||||
DOWNLOAD;
|
||||
|
||||
}
|
||||
|
||||
@@ -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());
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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<MgmtTargetAssignmentResponseBody> postAssignedDistributionSet(
|
||||
@PathVariable("controllerId") String controllerId, MgmtDistributionSetAssigment dsId,
|
||||
@PathVariable("controllerId") String controllerId, MgmtDistributionSetAssignment dsId,
|
||||
@RequestParam(value = "offline", required = false) boolean offline);
|
||||
|
||||
/**
|
||||
|
||||
@@ -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));
|
||||
|
||||
|
||||
@@ -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<MgmtTargetAssignmentResponseBody> 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
|
||||
|
||||
@@ -50,7 +50,6 @@
|
||||
<dependency>
|
||||
<groupId>com.cronutils</groupId>
|
||||
<artifactId>cron-utils</artifactId>
|
||||
<version>5.0.5</version>
|
||||
</dependency>
|
||||
|
||||
<!-- TEST -->
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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<ZonedDateTime>} of the next available window. In
|
||||
* case there is none, returns empty value.
|
||||
*/
|
||||
public Optional<ZonedDateTime> nextExecution(ZonedDateTime after) {
|
||||
// Exception squid:S1166 - lib throws exception as well if no value found
|
||||
@SuppressWarnings("squid:S1166")
|
||||
public Optional<ZonedDateTime> 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();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -26,6 +26,8 @@ public class TargetAssignDistributionSetEvent extends RemoteTenantAwareEvent {
|
||||
|
||||
private long distributionSetId;
|
||||
|
||||
private boolean maintenanceWindowAvailable;
|
||||
|
||||
private final Map<String, Long> 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<Action> 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<String, Long> getActions() {
|
||||
return actions;
|
||||
}
|
||||
|
||||
@@ -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() {
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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<String> 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)
|
||||
|
||||
@@ -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<Action> 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();
|
||||
|
||||
@@ -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<ZonedDateTime>}.
|
||||
*/
|
||||
public Optional<ZonedDateTime> 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<ZonedDateTime>}.
|
||||
*/
|
||||
public Optional<ZonedDateTime> getMaintenanceWindowEndTime() {
|
||||
if (getMaintenanceWindowStartTime().isPresent()) {
|
||||
return Optional.of(getMaintenanceWindowStartTime().get().plus(getMaintenanceWindowDuration()));
|
||||
}
|
||||
return Optional.empty();
|
||||
private Optional<ZonedDateTime> 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<ZonedDateTime> start = getMaintenanceWindowStartTime();
|
||||
final Optional<ZonedDateTime> end = getMaintenanceWindowEndTime();
|
||||
|
||||
if (start.isPresent() && end.isPresent()) {
|
||||
return now.isAfter(start.get()) && now.isBefore(end.get());
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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<String> getAllTimeZones() {
|
||||
List<String> lst = ZoneId.getAvailableZoneIds().stream()
|
||||
final List<String> 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();
|
||||
}
|
||||
|
||||
|
||||
@@ -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<Long, List<TargetIdName>> saveAssignedList = Maps.newHashMapWithExpectedSize(itemIds.size());
|
||||
|
||||
8
pom.xml
8
pom.xml
@@ -120,7 +120,7 @@
|
||||
<!-- Repeatable migrations, Disabling clean etc. -->
|
||||
<flyway.version>4.0.3</flyway.version>
|
||||
<!-- Improved integration test capabilities -->
|
||||
<spring-amqp.version>1.7.4.RELEASE</spring-amqp.version>
|
||||
<spring-amqp.version>1.7.6.RELEASE</spring-amqp.version>
|
||||
<!-- Older versions needed than defined in Boot -->
|
||||
<!-- Incompatible changes in 2.2 -->
|
||||
<json-path.version>2.0.0</json-path.version>
|
||||
@@ -149,6 +149,7 @@
|
||||
<maven.scm.plugin.version>1.9.4</maven.scm.plugin.version>
|
||||
|
||||
<!-- Misc libraries versions - START -->
|
||||
<cron-utils.version>5.0.5</cron-utils.version>
|
||||
<jsoup.version>1.8.3</jsoup.version>
|
||||
<validation-api.version>2.0.1.Final</validation-api.version>
|
||||
<allure.version>1.5.4</allure.version>
|
||||
@@ -568,6 +569,11 @@
|
||||
</dependency>
|
||||
|
||||
<!-- Misc -->
|
||||
<dependency>
|
||||
<groupId>com.cronutils</groupId>
|
||||
<artifactId>cron-utils</artifactId>
|
||||
<version>${cron-utils.version}</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.jsoup</groupId>
|
||||
<artifactId>jsoup</artifactId>
|
||||
|
||||
Reference in New Issue
Block a user