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:
Kai Zimmermann
2018-03-08 10:42:25 +01:00
committed by GitHub
parent f4278c45ef
commit b4414438b0
32 changed files with 411 additions and 364 deletions

View File

@@ -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.

View File

@@ -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;

View File

@@ -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());

View File

@@ -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() {

View File

@@ -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);
}

View File

@@ -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) {

View File

@@ -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);
}

View File

@@ -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();

View File

@@ -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));
}
}

View File

@@ -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()));
}
}

View File

@@ -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) {

View File

@@ -32,6 +32,6 @@ public enum EventTopic {
/**
* Topic when sending a download only task, skipping the install.
*/
DOWNLOAD_AND_SKIP;
DOWNLOAD;
}

View File

@@ -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());

View File

@@ -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;
}

View File

@@ -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;
}

View File

@@ -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;
}
}

View File

@@ -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);
/**

View File

@@ -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));

View File

@@ -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

View File

@@ -50,7 +50,6 @@
<dependency>
<groupId>com.cronutils</groupId>
<artifactId>cron-utils</artifactId>
<version>5.0.5</version>
</dependency>
<!-- TEST -->

View File

@@ -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

View File

@@ -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();
}
}

View File

@@ -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;
}

View File

@@ -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() {

View File

@@ -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) {

View File

@@ -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)

View File

@@ -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();

View File

@@ -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;
}

View File

@@ -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);

View File

@@ -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();
}

View File

@@ -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());

View File

@@ -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>