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 java.util.List;
|
||||||
|
|
||||||
import javax.validation.constraints.NotNull;
|
import javax.validation.constraints.NotNull;
|
||||||
|
|
||||||
import com.fasterxml.jackson.annotation.JsonInclude;
|
import com.fasterxml.jackson.annotation.JsonInclude;
|
||||||
import com.fasterxml.jackson.annotation.JsonProperty;
|
import com.fasterxml.jackson.annotation.JsonProperty;
|
||||||
import com.fasterxml.jackson.annotation.JsonValue;
|
import com.fasterxml.jackson.annotation.JsonValue;
|
||||||
@@ -30,7 +31,7 @@ public class DdiDeployment {
|
|||||||
@NotNull
|
@NotNull
|
||||||
private List<DdiChunk> chunks;
|
private List<DdiChunk> chunks;
|
||||||
|
|
||||||
private MaintenanceWindowStatus maintenanceWindow = null;
|
private MaintenanceWindowStatus maintenanceWindow;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Constructor.
|
* Constructor.
|
||||||
|
|||||||
@@ -146,8 +146,12 @@ public class DdiRootController implements DdiRootControllerRestApi {
|
|||||||
|
|
||||||
checkAndCancelExpiredAction(action);
|
checkAndCancelExpiredAction(action);
|
||||||
|
|
||||||
return new ResponseEntity<>(DataConversionHelper.fromTarget(target, action,
|
return new ResponseEntity<>(
|
||||||
controllerManagement.getPollingTimeForAction(action), tenantAware), HttpStatus.OK);
|
DataConversionHelper.fromTarget(target, action,
|
||||||
|
action == null ? controllerManagement.getPollingTime()
|
||||||
|
: controllerManagement.getPollingTimeForAction(action.getId()),
|
||||||
|
tenantAware),
|
||||||
|
HttpStatus.OK);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@@ -288,13 +292,9 @@ public class DdiRootController implements DdiRootControllerRestApi {
|
|||||||
: new DdiActionHistory(action.getStatus().name(), actionHistoryMsgs);
|
: new DdiActionHistory(action.getStatus().name(), actionHistoryMsgs);
|
||||||
|
|
||||||
final HandlingType downloadType = action.isForce() ? HandlingType.FORCED : HandlingType.ATTEMPT;
|
final HandlingType downloadType = action.isForce() ? HandlingType.FORCED : HandlingType.ATTEMPT;
|
||||||
final HandlingType updateType = action.hasMaintenanceSchedule()
|
final HandlingType updateType = calculateUpdateType(action, downloadType);
|
||||||
? (action.isMaintenanceWindowAvailable() ? downloadType : HandlingType.SKIP) : downloadType;
|
|
||||||
|
|
||||||
MaintenanceWindowStatus maintenanceWindow = action.hasMaintenanceSchedule()
|
final MaintenanceWindowStatus maintenanceWindow = calculateMaintenanceWindow(action);
|
||||||
? (action.isMaintenanceWindowAvailable() ? MaintenanceWindowStatus.AVAILABLE
|
|
||||||
: MaintenanceWindowStatus.UNAVAILABLE)
|
|
||||||
: null;
|
|
||||||
|
|
||||||
final DdiDeploymentBase base = new DdiDeploymentBase(Long.toString(action.getId()),
|
final DdiDeploymentBase base = new DdiDeploymentBase(Long.toString(action.getId()),
|
||||||
new DdiDeployment(downloadType, updateType, chunks, maintenanceWindow), actionHistory);
|
new DdiDeployment(downloadType, updateType, chunks, maintenanceWindow), actionHistory);
|
||||||
@@ -310,6 +310,21 @@ public class DdiRootController implements DdiRootControllerRestApi {
|
|||||||
return ResponseEntity.notFound().build();
|
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
|
@Override
|
||||||
public ResponseEntity<Void> postBasedeploymentActionFeedback(@Valid @RequestBody final DdiActionFeedback feedback,
|
public ResponseEntity<Void> postBasedeploymentActionFeedback(@Valid @RequestBody final DdiActionFeedback feedback,
|
||||||
@PathVariable("tenant") final String tenant, @PathVariable("controllerId") final String controllerId,
|
@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);
|
status = handleClosedCase(feedback, controllerId, actionid, messages);
|
||||||
break;
|
break;
|
||||||
case DOWNLOADED:
|
case DOWNLOADED:
|
||||||
LOG.debug(
|
LOG.debug("Controller confirmed download (actionId: {}, controllerId: {}) as we got {} report.", actionid,
|
||||||
"Controller confirmed download of distribution set (actionId: {}, controllerId: {}) as we got {} report.",
|
controllerId, feedback.getStatus().getExecution());
|
||||||
actionid, controllerId, feedback.getStatus().getExecution());
|
|
||||||
status = Status.DOWNLOADED;
|
status = Status.DOWNLOADED;
|
||||||
messages.add(RepositoryConstants.SERVER_MESSAGE_PREFIX + "Target confirmed download of distribution set.");
|
messages.add(RepositoryConstants.SERVER_MESSAGE_PREFIX + "Target confirmed download of distribution set.");
|
||||||
break;
|
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.eclipse.hawkbit.im.authentication.SpPermission.SpringEvalExpressions.SYSTEM_ROLE;
|
||||||
import static org.hamcrest.CoreMatchers.equalTo;
|
import static org.hamcrest.CoreMatchers.equalTo;
|
||||||
import static org.hamcrest.Matchers.containsString;
|
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.greaterThanOrEqualTo;
|
||||||
|
import static org.hamcrest.Matchers.hasItem;
|
||||||
import static org.hamcrest.Matchers.lessThan;
|
import static org.hamcrest.Matchers.lessThan;
|
||||||
import static org.hamcrest.Matchers.startsWith;
|
import static org.hamcrest.Matchers.startsWith;
|
||||||
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.delete;
|
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.model.TargetUpdateStatus;
|
||||||
import org.eclipse.hawkbit.repository.test.matcher.Expect;
|
import org.eclipse.hawkbit.repository.test.matcher.Expect;
|
||||||
import org.eclipse.hawkbit.repository.test.matcher.ExpectEvents;
|
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.WithSpringAuthorityRule;
|
||||||
import org.eclipse.hawkbit.repository.test.util.WithUser;
|
import org.eclipse.hawkbit.repository.test.util.WithUser;
|
||||||
import org.eclipse.hawkbit.rest.util.JsonBuilder;
|
import org.eclipse.hawkbit.rest.util.JsonBuilder;
|
||||||
@@ -505,7 +503,7 @@ public class DdiRootControllerTest extends AbstractDDiApiIntegrationTest {
|
|||||||
|
|
||||||
@Test
|
@Test
|
||||||
@Description("Test the polling time based on different maintenance window start and end time.")
|
@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("");
|
final DistributionSet ds = testdataFactory.createDistributionSet("");
|
||||||
|
|
||||||
securityRule.runAs(WithSpringAuthorityRule.withUser("tenantadmin", HAS_AUTH_TENANT_CONFIGURATION), () -> {
|
securityRule.runAs(WithSpringAuthorityRule.withUser("tenantadmin", HAS_AUTH_TENANT_CONFIGURATION), () -> {
|
||||||
@@ -516,38 +514,34 @@ public class DdiRootControllerTest extends AbstractDDiApiIntegrationTest {
|
|||||||
return null;
|
return null;
|
||||||
});
|
});
|
||||||
|
|
||||||
Target savedTarget = testdataFactory.createTarget("1911");
|
final Target savedTarget = testdataFactory.createTarget("1911");
|
||||||
assignDistributionSetWithMaintenanceWindow(ds.getId(), savedTarget.getControllerId(),
|
assignDistributionSetWithMaintenanceWindow(ds.getId(), savedTarget.getControllerId(), getTestSchedule(16),
|
||||||
AbstractIntegrationTest.getTestSchedule(16), AbstractIntegrationTest.getTestDuration(10),
|
getTestDuration(10), getTestTimeZone()).getAssignedEntity().iterator().next();
|
||||||
AbstractIntegrationTest.getTestTimeZone()).getAssignedEntity().iterator().next();
|
|
||||||
|
|
||||||
mvc.perform(get("/default-tenant/controller/v1/1911/")).andExpect(status().isOk())
|
mvc.perform(get("/default-tenant/controller/v1/1911/")).andExpect(status().isOk())
|
||||||
.andExpect(jsonPath("$.config.polling.sleep", greaterThanOrEqualTo("00:05:00")));
|
.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");
|
final DistributionSet ds1 = testdataFactory.createDistributionSet("1");
|
||||||
assignDistributionSetWithMaintenanceWindow(ds1.getId(), savedTarget1.getControllerId(),
|
assignDistributionSetWithMaintenanceWindow(ds1.getId(), savedTarget1.getControllerId(), getTestSchedule(10),
|
||||||
AbstractIntegrationTest.getTestSchedule(10), AbstractIntegrationTest.getTestDuration(10),
|
getTestDuration(10), getTestTimeZone()).getAssignedEntity().iterator().next();
|
||||||
AbstractIntegrationTest.getTestTimeZone()).getAssignedEntity().iterator().next();
|
|
||||||
|
|
||||||
mvc.perform(get("/default-tenant/controller/v1/2911/")).andExpect(status().isOk())
|
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", lessThan("00:05:00")))
|
||||||
.andExpect(jsonPath("$.config.polling.sleep", greaterThanOrEqualTo("00:03: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");
|
final DistributionSet ds2 = testdataFactory.createDistributionSet("2");
|
||||||
assignDistributionSetWithMaintenanceWindow(ds2.getId(), savedTarget2.getControllerId(),
|
assignDistributionSetWithMaintenanceWindow(ds2.getId(), savedTarget2.getControllerId(), getTestSchedule(5),
|
||||||
AbstractIntegrationTest.getTestSchedule(5), AbstractIntegrationTest.getTestDuration(5),
|
getTestDuration(5), getTestTimeZone()).getAssignedEntity().iterator().next();
|
||||||
AbstractIntegrationTest.getTestTimeZone()).getAssignedEntity().iterator().next();
|
|
||||||
|
|
||||||
mvc.perform(get("/default-tenant/controller/v1/3911/")).andExpect(status().isOk())
|
mvc.perform(get("/default-tenant/controller/v1/3911/")).andExpect(status().isOk())
|
||||||
.andExpect(jsonPath("$.config.polling.sleep", lessThan("00:02:00")));
|
.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");
|
final DistributionSet ds3 = testdataFactory.createDistributionSet("3");
|
||||||
assignDistributionSetWithMaintenanceWindow(ds3.getId(), savedTarget3.getControllerId(),
|
assignDistributionSetWithMaintenanceWindow(ds3.getId(), savedTarget3.getControllerId(), getTestSchedule(-5),
|
||||||
AbstractIntegrationTest.getTestSchedule(-5), AbstractIntegrationTest.getTestDuration(15),
|
getTestDuration(15), getTestTimeZone()).getAssignedEntity().iterator().next();
|
||||||
AbstractIntegrationTest.getTestTimeZone()).getAssignedEntity().iterator().next();
|
|
||||||
|
|
||||||
mvc.perform(get("/default-tenant/controller/v1/4911/")).andExpect(status().isOk())
|
mvc.perform(get("/default-tenant/controller/v1/4911/")).andExpect(status().isOk())
|
||||||
.andExpect(jsonPath("$.config.polling.sleep", equalTo("00:05:00")));
|
.andExpect(jsonPath("$.config.polling.sleep", equalTo("00:05:00")));
|
||||||
@@ -556,12 +550,11 @@ public class DdiRootControllerTest extends AbstractDDiApiIntegrationTest {
|
|||||||
|
|
||||||
@Test
|
@Test
|
||||||
@Description("Test download and update values before maintenance window start time.")
|
@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");
|
Target savedTarget = testdataFactory.createTarget("1911");
|
||||||
final DistributionSet ds = testdataFactory.createDistributionSet("");
|
final DistributionSet ds = testdataFactory.createDistributionSet("");
|
||||||
savedTarget = assignDistributionSetWithMaintenanceWindow(ds.getId(), savedTarget.getControllerId(),
|
savedTarget = assignDistributionSetWithMaintenanceWindow(ds.getId(), savedTarget.getControllerId(),
|
||||||
AbstractIntegrationTest.getTestSchedule(2), AbstractIntegrationTest.getTestDuration(1),
|
getTestSchedule(2), getTestDuration(1), getTestTimeZone()).getAssignedEntity().iterator().next();
|
||||||
AbstractIntegrationTest.getTestTimeZone()).getAssignedEntity().iterator().next();
|
|
||||||
|
|
||||||
mvc.perform(get("/default-tenant/controller/v1/1911/")).andExpect(status().isOk());
|
mvc.perform(get("/default-tenant/controller/v1/1911/")).andExpect(status().isOk());
|
||||||
|
|
||||||
@@ -576,12 +569,11 @@ public class DdiRootControllerTest extends AbstractDDiApiIntegrationTest {
|
|||||||
|
|
||||||
@Test
|
@Test
|
||||||
@Description("Test download and update values after maintenance window start time.")
|
@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");
|
Target savedTarget = testdataFactory.createTarget("1911");
|
||||||
final DistributionSet ds = testdataFactory.createDistributionSet("");
|
final DistributionSet ds = testdataFactory.createDistributionSet("");
|
||||||
savedTarget = assignDistributionSetWithMaintenanceWindow(ds.getId(), savedTarget.getControllerId(),
|
savedTarget = assignDistributionSetWithMaintenanceWindow(ds.getId(), savedTarget.getControllerId(),
|
||||||
AbstractIntegrationTest.getTestSchedule(-5), AbstractIntegrationTest.getTestDuration(10),
|
getTestSchedule(-5), getTestDuration(10), getTestTimeZone()).getAssignedEntity().iterator().next();
|
||||||
AbstractIntegrationTest.getTestTimeZone()).getAssignedEntity().iterator().next();
|
|
||||||
|
|
||||||
mvc.perform(get("/default-tenant/controller/v1/1911/")).andExpect(status().isOk());
|
mvc.perform(get("/default-tenant/controller/v1/1911/")).andExpect(status().isOk());
|
||||||
|
|
||||||
|
|||||||
@@ -312,12 +312,11 @@ public class AmqpConfiguration {
|
|||||||
AmqpMessageDispatcherService amqpMessageDispatcherService(final RabbitTemplate rabbitTemplate,
|
AmqpMessageDispatcherService amqpMessageDispatcherService(final RabbitTemplate rabbitTemplate,
|
||||||
final AmqpMessageSenderService amqpSenderService, final ArtifactUrlHandler artifactUrlHandler,
|
final AmqpMessageSenderService amqpSenderService, final ArtifactUrlHandler artifactUrlHandler,
|
||||||
final SystemSecurityContext systemSecurityContext, final SystemManagement systemManagement,
|
final SystemSecurityContext systemSecurityContext, final SystemManagement systemManagement,
|
||||||
final ControllerManagement controllerManagement, final TargetManagement targetManagement,
|
final TargetManagement targetManagement, final DistributionSetManagement distributionSetManagement,
|
||||||
final DistributionSetManagement distributionSetManagement,
|
|
||||||
final SoftwareModuleManagement softwareModuleManagement) {
|
final SoftwareModuleManagement softwareModuleManagement) {
|
||||||
return new AmqpMessageDispatcherService(rabbitTemplate, amqpSenderService, artifactUrlHandler,
|
return new AmqpMessageDispatcherService(rabbitTemplate, amqpSenderService, artifactUrlHandler,
|
||||||
systemSecurityContext, systemManagement, controllerManagement, targetManagement, serviceMatcher,
|
systemSecurityContext, systemManagement, targetManagement, serviceMatcher, distributionSetManagement,
|
||||||
distributionSetManagement, softwareModuleManagement);
|
softwareModuleManagement);
|
||||||
}
|
}
|
||||||
|
|
||||||
private static Map<String, Object> getTTLMaxArgsAuthenticationQueue() {
|
private static Map<String, Object> getTTLMaxArgsAuthenticationQueue() {
|
||||||
|
|||||||
@@ -13,7 +13,6 @@ import java.util.Collections;
|
|||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
import java.util.Map.Entry;
|
import java.util.Map.Entry;
|
||||||
import java.util.Optional;
|
|
||||||
import java.util.stream.Collectors;
|
import java.util.stream.Collectors;
|
||||||
|
|
||||||
import org.eclipse.hawkbit.api.ApiType;
|
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.DmfDownloadAndUpdateRequest;
|
||||||
import org.eclipse.hawkbit.dmf.json.model.DmfMetadata;
|
import org.eclipse.hawkbit.dmf.json.model.DmfMetadata;
|
||||||
import org.eclipse.hawkbit.dmf.json.model.DmfSoftwareModule;
|
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.DistributionSetManagement;
|
||||||
import org.eclipse.hawkbit.repository.RepositoryConstants;
|
import org.eclipse.hawkbit.repository.RepositoryConstants;
|
||||||
import org.eclipse.hawkbit.repository.SoftwareModuleManagement;
|
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.Artifact;
|
||||||
import org.eclipse.hawkbit.repository.model.SoftwareModule;
|
import org.eclipse.hawkbit.repository.model.SoftwareModule;
|
||||||
import org.eclipse.hawkbit.repository.model.SoftwareModuleMetadata;
|
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.repository.model.Target;
|
||||||
import org.eclipse.hawkbit.security.SystemSecurityContext;
|
import org.eclipse.hawkbit.security.SystemSecurityContext;
|
||||||
import org.eclipse.hawkbit.util.IpUtil;
|
import org.eclipse.hawkbit.util.IpUtil;
|
||||||
@@ -76,7 +73,6 @@ public class AmqpMessageDispatcherService extends BaseAmqpService {
|
|||||||
private final AmqpMessageSenderService amqpSenderService;
|
private final AmqpMessageSenderService amqpSenderService;
|
||||||
private final SystemSecurityContext systemSecurityContext;
|
private final SystemSecurityContext systemSecurityContext;
|
||||||
private final SystemManagement systemManagement;
|
private final SystemManagement systemManagement;
|
||||||
private final ControllerManagement controllerManagement;
|
|
||||||
private final TargetManagement targetManagement;
|
private final TargetManagement targetManagement;
|
||||||
private final ServiceMatcher serviceMatcher;
|
private final ServiceMatcher serviceMatcher;
|
||||||
private final DistributionSetManagement distributionSetManagement;
|
private final DistributionSetManagement distributionSetManagement;
|
||||||
@@ -95,8 +91,6 @@ public class AmqpMessageDispatcherService extends BaseAmqpService {
|
|||||||
* for execution with system permissions
|
* for execution with system permissions
|
||||||
* @param systemManagement
|
* @param systemManagement
|
||||||
* the systemManagement
|
* the systemManagement
|
||||||
* @param controllerManagement
|
|
||||||
* for target repository access
|
|
||||||
* @param targetManagement
|
* @param targetManagement
|
||||||
* to access target information
|
* to access target information
|
||||||
* @param serviceMatcher
|
* @param serviceMatcher
|
||||||
@@ -108,15 +102,14 @@ public class AmqpMessageDispatcherService extends BaseAmqpService {
|
|||||||
protected AmqpMessageDispatcherService(final RabbitTemplate rabbitTemplate,
|
protected AmqpMessageDispatcherService(final RabbitTemplate rabbitTemplate,
|
||||||
final AmqpMessageSenderService amqpSenderService, final ArtifactUrlHandler artifactUrlHandler,
|
final AmqpMessageSenderService amqpSenderService, final ArtifactUrlHandler artifactUrlHandler,
|
||||||
final SystemSecurityContext systemSecurityContext, final SystemManagement systemManagement,
|
final SystemSecurityContext systemSecurityContext, final SystemManagement systemManagement,
|
||||||
final ControllerManagement controllerManagement, final TargetManagement targetManagement,
|
final TargetManagement targetManagement, final ServiceMatcher serviceMatcher,
|
||||||
final ServiceMatcher serviceMatcher, final DistributionSetManagement distributionSetManagement,
|
final DistributionSetManagement distributionSetManagement,
|
||||||
final SoftwareModuleManagement softwareModuleManagement) {
|
final SoftwareModuleManagement softwareModuleManagement) {
|
||||||
super(rabbitTemplate);
|
super(rabbitTemplate);
|
||||||
this.artifactUrlHandler = artifactUrlHandler;
|
this.artifactUrlHandler = artifactUrlHandler;
|
||||||
this.amqpSenderService = amqpSenderService;
|
this.amqpSenderService = amqpSenderService;
|
||||||
this.systemSecurityContext = systemSecurityContext;
|
this.systemSecurityContext = systemSecurityContext;
|
||||||
this.systemManagement = systemManagement;
|
this.systemManagement = systemManagement;
|
||||||
this.controllerManagement = controllerManagement;
|
|
||||||
this.targetManagement = targetManagement;
|
this.targetManagement = targetManagement;
|
||||||
this.serviceMatcher = serviceMatcher;
|
this.serviceMatcher = serviceMatcher;
|
||||||
this.distributionSetManagement = distributionSetManagement;
|
this.distributionSetManagement = distributionSetManagement;
|
||||||
@@ -151,7 +144,8 @@ public class AmqpMessageDispatcherService extends BaseAmqpService {
|
|||||||
|
|
||||||
targetManagement.getByControllerID(assignedEvent.getActions().keySet())
|
targetManagement.getByControllerID(assignedEvent.getActions().keySet())
|
||||||
.forEach(target -> sendUpdateMessageToTarget(assignedEvent.getTenant(), target,
|
.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
|
* valid maintenance window available or not based on defined maintenance
|
||||||
* schedule. In case of no maintenance schedule or if there is a valid
|
* schedule. In case of no maintenance schedule or if there is a valid
|
||||||
* window available, the topic {@link EventTopic#DOWNLOAD_AND_INSTALL} is
|
* 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
|
* @param target
|
||||||
* for which to find the event type
|
* for which to find the event type
|
||||||
*
|
*
|
||||||
* @return {@link EventTopic} to use for message.
|
* @return {@link EventTopic} to use for message.
|
||||||
*/
|
*/
|
||||||
EventTopic getEventTypeForTarget(Target target) {
|
private static EventTopic getEventTypeForTarget(final boolean maintenanceWindowAvailable) {
|
||||||
Optional<Action> action = controllerManagement.findOldestActiveActionByTarget(target.getControllerId());
|
return maintenanceWindowAvailable ? EventTopic.DOWNLOAD_AND_INSTALL : EventTopic.DOWNLOAD;
|
||||||
|
|
||||||
if (action.isPresent()) {
|
|
||||||
if (action.get().isMaintenanceWindowAvailable()) {
|
|
||||||
return EventTopic.DOWNLOAD_AND_INSTALL;
|
|
||||||
} else {
|
|
||||||
return EventTopic.DOWNLOAD_AND_SKIP;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return EventTopic.DOWNLOAD_AND_INSTALL;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -216,7 +200,7 @@ public class AmqpMessageDispatcherService extends BaseAmqpService {
|
|||||||
}
|
}
|
||||||
|
|
||||||
protected void sendUpdateMessageToTarget(final String tenant, final Target target, final Long actionId,
|
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();
|
final URI targetAdress = target.getAddress();
|
||||||
if (!IpUtil.isAmqpUri(targetAdress)) {
|
if (!IpUtil.isAmqpUri(targetAdress)) {
|
||||||
@@ -236,7 +220,8 @@ public class AmqpMessageDispatcherService extends BaseAmqpService {
|
|||||||
});
|
});
|
||||||
|
|
||||||
final Message message = getMessageConverter().toMessage(downloadAndUpdateRequest,
|
final Message message = getMessageConverter().toMessage(downloadAndUpdateRequest,
|
||||||
createConnectorMessagePropertiesEvent(tenant, target.getControllerId(), getEventTypeForTarget(target)));
|
createConnectorMessagePropertiesEvent(tenant, target.getControllerId(),
|
||||||
|
getEventTypeForTarget(maintenanceWindowAvailable)));
|
||||||
amqpSenderService.sendMessage(message, targetAdress);
|
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
|
* @return a message if <null> no message is send back to sender
|
||||||
*/
|
*/
|
||||||
@RabbitListener(queues = "${hawkbit.dmf.rabbitmq.receiverQueue:dmf_receiver}", containerFactory = "listenerContainerFactory")
|
@RabbitListener(queues = "${hawkbit.dmf.rabbitmq.receiverQueue:dmf_receiver}", containerFactory = "listenerContainerFactory")
|
||||||
public Message onMessage(final Message message, @Header(MessageHeaderKey.TYPE) final String type,
|
public Message onMessage(final Message message,
|
||||||
@Header(MessageHeaderKey.TENANT) final String tenant) {
|
@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());
|
return onMessage(message, type, tenant, getRabbitTemplate().getConnectionFactory().getVirtualHost());
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -121,6 +122,9 @@ public class AmqpMessageHandlerService extends BaseAmqpService {
|
|||||||
* @return the rpc message back to supplier.
|
* @return the rpc message back to supplier.
|
||||||
*/
|
*/
|
||||||
public Message onMessage(final Message message, final String type, final String tenant, final String virtualHost) {
|
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();
|
final SecurityContext oldContext = SecurityContextHolder.getContext();
|
||||||
try {
|
try {
|
||||||
@@ -215,7 +219,7 @@ public class AmqpMessageHandlerService extends BaseAmqpService {
|
|||||||
action.getDistributionSet().getModules().forEach(module -> modules.put(module, metadata.get(module.getId())));
|
action.getDistributionSet().getModules().forEach(module -> modules.put(module, metadata.get(module.getId())));
|
||||||
|
|
||||||
amqpMessageDispatcherService.sendUpdateMessageToTarget(action.getTenant(), action.getTarget(), action.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;
|
&& 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,
|
private Status mapStatus(final Message message, final DmfActionUpdateStatus actionUpdateStatus,
|
||||||
final Action action) {
|
final Action action) {
|
||||||
Status status = null;
|
Status status = null;
|
||||||
@@ -311,7 +318,7 @@ public class AmqpMessageHandlerService extends BaseAmqpService {
|
|||||||
status = Status.DOWNLOADED;
|
status = Status.DOWNLOADED;
|
||||||
break;
|
break;
|
||||||
case CANCEL_REJECTED:
|
case CANCEL_REJECTED:
|
||||||
status = hanldeCancelRejectedState(message, action);
|
status = handleCancelRejectedState(message, action);
|
||||||
break;
|
break;
|
||||||
default:
|
default:
|
||||||
logAndThrowMessageError(message, "Status for action does not exisit.");
|
logAndThrowMessageError(message, "Status for action does not exisit.");
|
||||||
@@ -320,14 +327,13 @@ public class AmqpMessageHandlerService extends BaseAmqpService {
|
|||||||
return status;
|
return status;
|
||||||
}
|
}
|
||||||
|
|
||||||
private Status hanldeCancelRejectedState(final Message message, final Action action) {
|
private Status handleCancelRejectedState(final Message message, final Action action) {
|
||||||
if (action.isCancelingOrCanceled()) {
|
if (action.isCancelingOrCanceled()) {
|
||||||
return Status.CANCEL_REJECTED;
|
return Status.CANCEL_REJECTED;
|
||||||
}
|
}
|
||||||
logAndThrowMessageError(message,
|
logAndThrowMessageError(message,
|
||||||
"Cancel rejected message is not allowed, if action is on state: " + action.getStatus());
|
"Cancel rejected message is not allowed, if action is on state: " + action.getStatus());
|
||||||
return null;
|
return null;
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private static String convertCorrelationId(final Message message) {
|
private static String convertCorrelationId(final Message message) {
|
||||||
|
|||||||
@@ -111,8 +111,8 @@ public class AmqpMessageDispatcherServiceTest extends AbstractIntegrationTest {
|
|||||||
when(systemManagement.getTenantMetadata()).thenReturn(tenantMetaData);
|
when(systemManagement.getTenantMetadata()).thenReturn(tenantMetaData);
|
||||||
|
|
||||||
amqpMessageDispatcherService = new AmqpMessageDispatcherService(rabbitTemplate, senderService,
|
amqpMessageDispatcherService = new AmqpMessageDispatcherService(rabbitTemplate, senderService,
|
||||||
artifactUrlHandlerMock, systemSecurityContext, systemManagement, controllerManagement, targetManagement,
|
artifactUrlHandlerMock, systemSecurityContext, systemManagement, targetManagement, serviceMatcher,
|
||||||
serviceMatcher, distributionSetManagement, softwareModuleManagement);
|
distributionSetManagement, softwareModuleManagement);
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -11,6 +11,7 @@ package org.eclipse.hawkbit.amqp;
|
|||||||
import static org.assertj.core.api.Assertions.assertThat;
|
import static org.assertj.core.api.Assertions.assertThat;
|
||||||
import static org.junit.Assert.fail;
|
import static org.junit.Assert.fail;
|
||||||
import static org.mockito.Matchers.any;
|
import static org.mockito.Matchers.any;
|
||||||
|
import static org.mockito.Matchers.anyBoolean;
|
||||||
import static org.mockito.Matchers.anyLong;
|
import static org.mockito.Matchers.anyLong;
|
||||||
import static org.mockito.Matchers.anyString;
|
import static org.mockito.Matchers.anyString;
|
||||||
import static org.mockito.Mockito.mock;
|
import static org.mockito.Mockito.mock;
|
||||||
@@ -420,7 +421,7 @@ public class AmqpMessageHandlerServiceTest {
|
|||||||
final ArgumentCaptor<Long> actionIdCaptor = ArgumentCaptor.forClass(Long.class);
|
final ArgumentCaptor<Long> actionIdCaptor = ArgumentCaptor.forClass(Long.class);
|
||||||
|
|
||||||
verify(amqpMessageDispatcherServiceMock, times(1)).sendUpdateMessageToTarget(tenantCaptor.capture(),
|
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 tenant = tenantCaptor.getValue();
|
||||||
final String controllerId = targetCaptor.getValue().getControllerId();
|
final String controllerId = targetCaptor.getValue().getControllerId();
|
||||||
final Long actionId = actionIdCaptor.getValue();
|
final Long actionId = actionIdCaptor.getValue();
|
||||||
|
|||||||
@@ -48,16 +48,59 @@ public class AmqpMessageDispatcherServiceIntegrationTest extends AmqpServiceInte
|
|||||||
@Expect(type = SoftwareModuleCreatedEvent.class, count = 3),
|
@Expect(type = SoftwareModuleCreatedEvent.class, count = 3),
|
||||||
@Expect(type = SoftwareModuleUpdatedEvent.class, count = 6),
|
@Expect(type = SoftwareModuleUpdatedEvent.class, count = 6),
|
||||||
@Expect(type = DistributionSetCreatedEvent.class, count = 1),
|
@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() {
|
public void sendDownloadAndInstallStatus() {
|
||||||
final String controllerId = TARGET_PREFIX + "sendDownloadAndInstallStatus";
|
final String controllerId = TARGET_PREFIX + "sendDownloadAndInstallStatus";
|
||||||
registerTargetAndAssignDistributionSet(controllerId);
|
registerTargetAndAssignDistributionSet(controllerId);
|
||||||
|
|
||||||
createAndSendTarget(controllerId, TENANT_EXIST);
|
|
||||||
waitUntilTargetStatusIsPending(controllerId);
|
waitUntilTargetStatusIsPending(controllerId);
|
||||||
assertDownloadAndInstallMessage(getDistributionSet().getModules(), 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
|
@Test
|
||||||
@Description("Verify that a distribution assignment multiple times send cancel and assign events with right softwaremodules")
|
@Description("Verify that a distribution assignment multiple times send cancel and assign events with right softwaremodules")
|
||||||
@ExpectEvents({ @Expect(type = TargetCreatedEvent.class, count = 1),
|
@ExpectEvents({ @Expect(type = TargetCreatedEvent.class, count = 1),
|
||||||
@@ -111,7 +154,7 @@ public class AmqpMessageDispatcherServiceIntegrationTest extends AmqpServiceInte
|
|||||||
public void sendDeleteMessage() {
|
public void sendDeleteMessage() {
|
||||||
final String controllerId = TARGET_PREFIX + "sendDeleteMessage";
|
final String controllerId = TARGET_PREFIX + "sendDeleteMessage";
|
||||||
|
|
||||||
registerAndAssertTargetWithExistingTenant(controllerId, 1);
|
registerAndAssertTargetWithExistingTenant(controllerId);
|
||||||
targetManagement.deleteByControllerID(controllerId);
|
targetManagement.deleteByControllerID(controllerId);
|
||||||
assertDeleteMessage(controllerId);
|
assertDeleteMessage(controllerId);
|
||||||
}
|
}
|
||||||
@@ -125,8 +168,6 @@ public class AmqpMessageDispatcherServiceIntegrationTest extends AmqpServiceInte
|
|||||||
}
|
}
|
||||||
|
|
||||||
private void waitUntil(final Callable<Boolean> callable) {
|
private void waitUntil(final Callable<Boolean> callable) {
|
||||||
createConditionFactory().until(() -> {
|
createConditionFactory().until(() -> securityRule.runAsPrivileged(callable));
|
||||||
return securityRule.runAsPrivileged(callable);
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -386,6 +386,20 @@ public class AmqpMessageHandlerServiceIntegrationTest extends AmqpServiceIntegra
|
|||||||
registerTargetAndSendAndAssertUpdateActionStatus(DmfActionStatus.RUNNING, Status.RUNNING, controllerId);
|
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
|
@Test
|
||||||
@Description("Register a target and send a update action status (download). Verfiy if the updated action status is correct.")
|
@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),
|
@ExpectEvents({ @Expect(type = TargetCreatedEvent.class, count = 1),
|
||||||
@@ -470,7 +484,7 @@ public class AmqpMessageHandlerServiceIntegrationTest extends AmqpServiceIntegra
|
|||||||
final String controllerId = TARGET_PREFIX + "receiveDownLoadAndInstallMessageAfterAssignment";
|
final String controllerId = TARGET_PREFIX + "receiveDownLoadAndInstallMessageAfterAssignment";
|
||||||
|
|
||||||
// setup
|
// setup
|
||||||
createAndSendTarget(controllerId, TENANT_EXIST);
|
registerAndAssertTargetWithExistingTenant(controllerId);
|
||||||
final DistributionSet distributionSet = testdataFactory.createDistributionSet(UUID.randomUUID().toString());
|
final DistributionSet distributionSet = testdataFactory.createDistributionSet(UUID.randomUUID().toString());
|
||||||
testdataFactory.addSoftwareModuleMetadata(distributionSet);
|
testdataFactory.addSoftwareModuleMetadata(distributionSet);
|
||||||
assignDistributionSet(distributionSet.getId(), controllerId);
|
assignDistributionSet(distributionSet.getId(), controllerId);
|
||||||
@@ -483,6 +497,60 @@ public class AmqpMessageHandlerServiceIntegrationTest extends AmqpServiceIntegra
|
|||||||
Mockito.verifyZeroInteractions(getDeadletterListener());
|
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
|
@Test
|
||||||
@Description("Verfiy receiving a cancel update message if a deployment is canceled before the target has polled the first time.")
|
@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),
|
@ExpectEvents({ @Expect(type = TargetCreatedEvent.class, count = 1),
|
||||||
@@ -497,7 +565,7 @@ public class AmqpMessageHandlerServiceIntegrationTest extends AmqpServiceIntegra
|
|||||||
final String controllerId = TARGET_PREFIX + "receiveCancelUpdateMessageAfterAssignmentWasCanceled";
|
final String controllerId = TARGET_PREFIX + "receiveCancelUpdateMessageAfterAssignmentWasCanceled";
|
||||||
|
|
||||||
// Setup
|
// Setup
|
||||||
createAndSendTarget(controllerId, TENANT_EXIST);
|
registerAndAssertTargetWithExistingTenant(controllerId);
|
||||||
final DistributionSet distributionSet = testdataFactory.createDistributionSet(UUID.randomUUID().toString());
|
final DistributionSet distributionSet = testdataFactory.createDistributionSet(UUID.randomUUID().toString());
|
||||||
final DistributionSetAssignmentResult distributionSetAssignmentResult = assignDistributionSet(
|
final DistributionSetAssignmentResult distributionSetAssignmentResult = assignDistributionSet(
|
||||||
distributionSet.getId(), controllerId);
|
distributionSet.getId(), controllerId);
|
||||||
@@ -573,7 +641,7 @@ public class AmqpMessageHandlerServiceIntegrationTest extends AmqpServiceIntegra
|
|||||||
final String controllerId = TARGET_PREFIX + "updateAttributes";
|
final String controllerId = TARGET_PREFIX + "updateAttributes";
|
||||||
|
|
||||||
// setup
|
// setup
|
||||||
registerAndAssertTargetWithExistingTenant(controllerId, 1);
|
registerAndAssertTargetWithExistingTenant(controllerId);
|
||||||
final DmfAttributeUpdate controllerAttribute = new DmfAttributeUpdate();
|
final DmfAttributeUpdate controllerAttribute = new DmfAttributeUpdate();
|
||||||
controllerAttribute.getAttributes().put("test1", "testA");
|
controllerAttribute.getAttributes().put("test1", "testA");
|
||||||
controllerAttribute.getAttributes().put("test2", "testB");
|
controllerAttribute.getAttributes().put("test2", "testB");
|
||||||
@@ -593,7 +661,7 @@ public class AmqpMessageHandlerServiceIntegrationTest extends AmqpServiceIntegra
|
|||||||
final String controllerId = TARGET_PREFIX + "updateAttributesWithNoThingId";
|
final String controllerId = TARGET_PREFIX + "updateAttributesWithNoThingId";
|
||||||
|
|
||||||
// setup
|
// setup
|
||||||
registerAndAssertTargetWithExistingTenant(controllerId, 1);
|
registerAndAssertTargetWithExistingTenant(controllerId);
|
||||||
final DmfAttributeUpdate controllerAttribute = new DmfAttributeUpdate();
|
final DmfAttributeUpdate controllerAttribute = new DmfAttributeUpdate();
|
||||||
controllerAttribute.getAttributes().put("test1", "testA");
|
controllerAttribute.getAttributes().put("test1", "testA");
|
||||||
controllerAttribute.getAttributes().put("test2", "testB");
|
controllerAttribute.getAttributes().put("test2", "testB");
|
||||||
@@ -618,7 +686,7 @@ public class AmqpMessageHandlerServiceIntegrationTest extends AmqpServiceIntegra
|
|||||||
|
|
||||||
// setup
|
// setup
|
||||||
final String target = "ControllerAttributeTestTarget";
|
final String target = "ControllerAttributeTestTarget";
|
||||||
registerAndAssertTargetWithExistingTenant(target, 1);
|
registerAndAssertTargetWithExistingTenant(target);
|
||||||
final DmfAttributeUpdate controllerAttribute = new DmfAttributeUpdate();
|
final DmfAttributeUpdate controllerAttribute = new DmfAttributeUpdate();
|
||||||
controllerAttribute.getAttributes().put("test1", "testA");
|
controllerAttribute.getAttributes().put("test1", "testA");
|
||||||
controllerAttribute.getAttributes().put("test2", "testB");
|
controllerAttribute.getAttributes().put("test2", "testB");
|
||||||
@@ -707,8 +775,7 @@ public class AmqpMessageHandlerServiceIntegrationTest extends AmqpServiceIntegra
|
|||||||
|
|
||||||
private void verifyOneDeadLetterMessage() {
|
private void verifyOneDeadLetterMessage() {
|
||||||
assertEmptyReceiverQueueCount();
|
assertEmptyReceiverQueueCount();
|
||||||
createConditionFactory().until(() -> {
|
createConditionFactory()
|
||||||
Mockito.verify(getDeadletterListener(), Mockito.times(1)).handleMessage(Mockito.any());
|
.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) {
|
private void assertAssignmentMessage(final Set<SoftwareModule> dsModules, final String controllerId,
|
||||||
final Message replyMessage = assertReplyMessageHeader(EventTopic.DOWNLOAD_AND_INSTALL, controllerId);
|
final EventTopic topic) {
|
||||||
|
final Message replyMessage = assertReplyMessageHeader(topic, controllerId);
|
||||||
assertAllTargetsCount(1);
|
assertAllTargetsCount(1);
|
||||||
|
|
||||||
final DmfDownloadAndUpdateRequest downloadAndUpdateRequest = (DmfDownloadAndUpdateRequest) getDmfClient()
|
final DmfDownloadAndUpdateRequest downloadAndUpdateRequest = (DmfDownloadAndUpdateRequest) getDmfClient()
|
||||||
@@ -180,6 +181,14 @@ public abstract class AmqpServiceIntegrationTest extends AbstractAmqpIntegration
|
|||||||
assertThat(updatedTarget.getSecurityToken()).isEqualTo(downloadAndUpdateRequest.getTargetSecurityToken());
|
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) {
|
protected void createAndSendTarget(final String target, final String tenant) {
|
||||||
final Message message = createTargetMessage(target, tenant);
|
final Message message = createTargetMessage(target, tenant);
|
||||||
getDmfClient().send(message);
|
getDmfClient().send(message);
|
||||||
@@ -224,6 +233,10 @@ public abstract class AmqpServiceIntegrationTest extends AbstractAmqpIntegration
|
|||||||
return replyMessage;
|
return replyMessage;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
protected void registerAndAssertTargetWithExistingTenant(final String controllerId) {
|
||||||
|
registerAndAssertTargetWithExistingTenant(controllerId, 1);
|
||||||
|
}
|
||||||
|
|
||||||
protected void registerAndAssertTargetWithExistingTenant(final String target,
|
protected void registerAndAssertTargetWithExistingTenant(final String target,
|
||||||
final int existingTargetsAfterCreation) {
|
final int existingTargetsAfterCreation) {
|
||||||
|
|
||||||
|
|||||||
@@ -32,6 +32,6 @@ public enum EventTopic {
|
|||||||
/**
|
/**
|
||||||
* Topic when sending a download only task, skipping the install.
|
* 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
|
// test should break or not
|
||||||
} catch (@SuppressWarnings("squid:S2221") final Exception e) {
|
} catch (@SuppressWarnings("squid:S2221") final Exception e) {
|
||||||
Throwables.propagateIfInstanceOf(e, AlivenessException.class);
|
Throwables.propagateIfInstanceOf(e, AlivenessException.class);
|
||||||
LOG.error("Cannot create virtual host {}", e.getMessage());
|
LOG.error("Cannot create virtual host.", e);
|
||||||
}
|
}
|
||||||
return factory;
|
return factory;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Bean
|
@Bean
|
||||||
RabbitMqSetupService rabbitmqSetupService(RabbitProperties properties) {
|
RabbitMqSetupService rabbitmqSetupService(final RabbitProperties properties) {
|
||||||
return new RabbitMqSetupService(properties);
|
return new RabbitMqSetupService(properties);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Bean
|
@Bean
|
||||||
@Primary
|
@Primary
|
||||||
public RabbitTemplate rabbitTemplateForTest(ConnectionFactory connectionFactory) {
|
public RabbitTemplate rabbitTemplateForTest(final ConnectionFactory connectionFactory) {
|
||||||
final RabbitTemplate rabbitTemplate = new RabbitTemplate(connectionFactory);
|
final RabbitTemplate rabbitTemplate = new RabbitTemplate(connectionFactory);
|
||||||
rabbitTemplate.setMessageConverter(new Jackson2JsonMessageConverter());
|
rabbitTemplate.setMessageConverter(new Jackson2JsonMessageConverter());
|
||||||
rabbitTemplate.setReplyTimeout(TimeUnit.SECONDS.toMillis(3));
|
rabbitTemplate.setReplyTimeout(TimeUnit.SECONDS.toMillis(3));
|
||||||
@@ -115,7 +115,7 @@ public class AmqpTestConfiguration {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Bean
|
@Bean
|
||||||
BrokerRunning brokerRunning(RabbitMqSetupService rabbitmqSetupService) {
|
BrokerRunning brokerRunning(final RabbitMqSetupService rabbitmqSetupService) {
|
||||||
final BrokerRunning brokerRunning = BrokerRunning.isRunning();
|
final BrokerRunning brokerRunning = BrokerRunning.isRunning();
|
||||||
brokerRunning.setHostName(rabbitmqSetupService.getHostname());
|
brokerRunning.setHostName(rabbitmqSetupService.getHostname());
|
||||||
brokerRunning.getConnectionFactory().setUsername(rabbitmqSetupService.getUsername());
|
brokerRunning.getConnectionFactory().setUsername(rabbitmqSetupService.getUsername());
|
||||||
|
|||||||
@@ -8,6 +8,9 @@
|
|||||||
*/
|
*/
|
||||||
package org.eclipse.hawkbit.mgmt.json.model;
|
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;
|
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
|
* schedule defined as cron expression, duration in HH:mm:ss format and time
|
||||||
* zone as offset from UTC.
|
* zone as offset from UTC.
|
||||||
*/
|
*/
|
||||||
public class MaintenanceWindow {
|
@JsonInclude(Include.NON_NULL)
|
||||||
|
@JsonIgnoreProperties(ignoreUnknown = true)
|
||||||
|
public class MgmtMaintenanceWindow {
|
||||||
|
|
||||||
private String maintenanceSchedule;
|
private String maintenanceSchedule;
|
||||||
private String maintenanceWindowDuration;
|
private String maintenanceWindowDuration;
|
||||||
@@ -31,7 +36,7 @@ public class MaintenanceWindow {
|
|||||||
* year".
|
* year".
|
||||||
*/
|
*/
|
||||||
@JsonSetter("schedule")
|
@JsonSetter("schedule")
|
||||||
public void setMaintenanceSchedule(String maintenanceSchedule) {
|
public void setMaintenanceSchedule(final String maintenanceSchedule) {
|
||||||
this.maintenanceSchedule = maintenanceSchedule;
|
this.maintenanceSchedule = maintenanceSchedule;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -43,7 +48,7 @@ public class MaintenanceWindow {
|
|||||||
* window, for example 00:30:00 for 30 minutes.
|
* window, for example 00:30:00 for 30 minutes.
|
||||||
*/
|
*/
|
||||||
@JsonSetter("duration")
|
@JsonSetter("duration")
|
||||||
public void setMaintenanceWindowDuration(String maintenanceWindowDuration) {
|
public void setMaintenanceWindowDuration(final String maintenanceWindowDuration) {
|
||||||
this.maintenanceWindowDuration = maintenanceWindowDuration;
|
this.maintenanceWindowDuration = maintenanceWindowDuration;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -57,7 +62,7 @@ public class MaintenanceWindow {
|
|||||||
* cron expression is relative to this time zone.
|
* cron expression is relative to this time zone.
|
||||||
*/
|
*/
|
||||||
@JsonSetter("timezone")
|
@JsonSetter("timezone")
|
||||||
public void setMaintenanceWindowTimeZone(String maintenanceWindowTimeZone) {
|
public void setMaintenanceWindowTimeZone(final String maintenanceWindowTimeZone) {
|
||||||
this.maintenanceWindowTimeZone = maintenanceWindowTimeZone;
|
this.maintenanceWindowTimeZone = maintenanceWindowTimeZone;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -8,7 +8,7 @@
|
|||||||
*/
|
*/
|
||||||
package org.eclipse.hawkbit.mgmt.json.model.distributionset;
|
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.JsonIgnoreProperties;
|
||||||
import com.fasterxml.jackson.annotation.JsonProperty;
|
import com.fasterxml.jackson.annotation.JsonProperty;
|
||||||
@@ -28,72 +28,40 @@ public class MgmtTargetAssignmentRequestBody {
|
|||||||
private MgmtActionType type;
|
private MgmtActionType type;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* {@link MaintenanceWindow} object containing schedule, duration and
|
* {@link MgmtMaintenanceWindow} object containing schedule, duration and
|
||||||
* timezone.
|
* timezone.
|
||||||
*/
|
*/
|
||||||
private MaintenanceWindow maintenanceWindow = null;
|
private MgmtMaintenanceWindow maintenanceWindow;
|
||||||
|
|
||||||
/**
|
|
||||||
* @return the id
|
|
||||||
*/
|
|
||||||
public String getId() {
|
public String getId() {
|
||||||
return id;
|
return id;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* @param id
|
|
||||||
* the id to set
|
|
||||||
*/
|
|
||||||
public void setId(final String id) {
|
public void setId(final String id) {
|
||||||
this.id = id;
|
this.id = id;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* @return the type
|
|
||||||
*/
|
|
||||||
public MgmtActionType getType() {
|
public MgmtActionType getType() {
|
||||||
return type;
|
return type;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* @param type
|
|
||||||
* the type to set
|
|
||||||
*/
|
|
||||||
public void setType(final MgmtActionType type) {
|
public void setType(final MgmtActionType type) {
|
||||||
this.type = type;
|
this.type = type;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* @return the forcetime
|
|
||||||
*/
|
|
||||||
public long getForcetime() {
|
public long getForcetime() {
|
||||||
return forcetime;
|
return forcetime;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* @param forcetime
|
|
||||||
* the forcetime to set
|
|
||||||
*/
|
|
||||||
public void setForcetime(final long forcetime) {
|
public void setForcetime(final long forcetime) {
|
||||||
this.forcetime = forcetime;
|
this.forcetime = forcetime;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
public MgmtMaintenanceWindow getMaintenanceWindow() {
|
||||||
* Returns {@link MaintenanceWindow} for the target assignment request.
|
|
||||||
*
|
|
||||||
* @return {@link MaintenanceWindow}.
|
|
||||||
*/
|
|
||||||
public MaintenanceWindow getMaintenanceWindow() {
|
|
||||||
return maintenanceWindow;
|
return maintenanceWindow;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
public void setMaintenanceWindow(final MgmtMaintenanceWindow maintenanceWindow) {
|
||||||
* Sets {@link MaintenanceWindow} for the target assignment request.
|
|
||||||
*
|
|
||||||
* @param maintenanceWindow
|
|
||||||
* as {@link MaintenanceWindow}.
|
|
||||||
*/
|
|
||||||
public void setMaintenanceWindow(MaintenanceWindow maintenanceWindow) {
|
|
||||||
this.maintenanceWindow = maintenanceWindow;
|
this.maintenanceWindow = maintenanceWindow;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -3,70 +3,56 @@
|
|||||||
*/
|
*/
|
||||||
package org.eclipse.hawkbit.mgmt.json.model.target;
|
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.MgmtId;
|
||||||
|
import org.eclipse.hawkbit.mgmt.json.model.MgmtMaintenanceWindow;
|
||||||
import org.eclipse.hawkbit.mgmt.json.model.distributionset.MgmtActionType;
|
import org.eclipse.hawkbit.mgmt.json.model.distributionset.MgmtActionType;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Request Body of DistributionSet for assignment operations (ID only).
|
* Request Body of DistributionSet for assignment operations (ID only).
|
||||||
*
|
*
|
||||||
*/
|
*/
|
||||||
public class MgmtDistributionSetAssigment extends MgmtId {
|
public class MgmtDistributionSetAssignment extends MgmtId {
|
||||||
private long forcetime;
|
private long forcetime;
|
||||||
private MgmtActionType type;
|
private MgmtActionType type;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* {@link MaintenanceWindow} object defining a schedule, duration and
|
* {@link MgmtMaintenanceWindow} object defining a schedule, duration and
|
||||||
* timezone.
|
* timezone.
|
||||||
*/
|
*/
|
||||||
private MaintenanceWindow maintenanceWindow = null;
|
private MgmtMaintenanceWindow maintenanceWindow;
|
||||||
|
|
||||||
/**
|
|
||||||
* @return the type
|
|
||||||
*/
|
|
||||||
public MgmtActionType getType() {
|
public MgmtActionType getType() {
|
||||||
return type;
|
return type;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* @param type
|
|
||||||
* the type to set
|
|
||||||
*/
|
|
||||||
public void setType(final MgmtActionType type) {
|
public void setType(final MgmtActionType type) {
|
||||||
this.type = type;
|
this.type = type;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* @return the forcetime
|
|
||||||
*/
|
|
||||||
public long getForcetime() {
|
public long getForcetime() {
|
||||||
return forcetime;
|
return forcetime;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* @param forcetime
|
|
||||||
* the forcetime to set
|
|
||||||
*/
|
|
||||||
public void setForcetime(final long forcetime) {
|
public void setForcetime(final long forcetime) {
|
||||||
this.forcetime = 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;
|
return maintenanceWindow;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Sets {@link MaintenanceWindow} for distribution set assignment.
|
* Sets {@link MgmtMaintenanceWindow} for distribution set assignment.
|
||||||
*
|
*
|
||||||
* @param maintenanceWindow
|
* @param maintenanceWindow
|
||||||
* as {@link MaintenanceWindow}.
|
* as {@link MgmtMaintenanceWindow}.
|
||||||
*/
|
*/
|
||||||
public void setMaintenanceWindow(MaintenanceWindow maintenanceWindow) {
|
public void setMaintenanceWindow(final MgmtMaintenanceWindow maintenanceWindow) {
|
||||||
this.maintenanceWindow = 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.action.MgmtActionStatus;
|
||||||
import org.eclipse.hawkbit.mgmt.json.model.distributionset.MgmtDistributionSet;
|
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.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.MgmtTarget;
|
||||||
import org.eclipse.hawkbit.mgmt.json.model.target.MgmtTargetAttributes;
|
import org.eclipse.hawkbit.mgmt.json.model.target.MgmtTargetAttributes;
|
||||||
import org.eclipse.hawkbit.mgmt.json.model.target.MgmtTargetRequestBody;
|
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,
|
MediaTypes.HAL_JSON_VALUE, MediaType.APPLICATION_JSON_VALUE }, produces = { MediaTypes.HAL_JSON_VALUE,
|
||||||
MediaType.APPLICATION_JSON_VALUE })
|
MediaType.APPLICATION_JSON_VALUE })
|
||||||
ResponseEntity<MgmtTargetAssignmentResponseBody> postAssignedDistributionSet(
|
ResponseEntity<MgmtTargetAssignmentResponseBody> postAssignedDistributionSet(
|
||||||
@PathVariable("controllerId") String controllerId, MgmtDistributionSetAssigment dsId,
|
@PathVariable("controllerId") String controllerId, MgmtDistributionSetAssignment dsId,
|
||||||
@RequestParam(value = "offline", required = false) boolean offline);
|
@RequestParam(value = "offline", required = false) boolean offline);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
@@ -249,16 +249,18 @@ public class MgmtDistributionSetResource implements MgmtDistributionSetRestApi {
|
|||||||
.stream().map(MgmtTargetAssignmentRequestBody::getId).collect(Collectors.toList()))));
|
.stream().map(MgmtTargetAssignmentRequestBody::getId).collect(Collectors.toList()))));
|
||||||
}
|
}
|
||||||
|
|
||||||
final DistributionSetAssignmentResult assignDistributionSet = this.deployManagament.assignDistributionSet(
|
final DistributionSetAssignmentResult assignDistributionSet = this.deployManagament
|
||||||
distributionSetId,
|
.assignDistributionSet(distributionSetId, assignments.stream().map(t -> {
|
||||||
assignments.stream().map(t -> new TargetWithActionType(t.getId(),
|
if (t.getMaintenanceWindow() == null) {
|
||||||
MgmtRestModelMapper.convertActionType(t.getType()), t.getForcetime(),
|
return new TargetWithActionType(t.getId(), MgmtRestModelMapper.convertActionType(t.getType()),
|
||||||
t.getMaintenanceWindow() == null ? null : t.getMaintenanceWindow().getMaintenanceSchedule(),
|
t.getForcetime());
|
||||||
t.getMaintenanceWindow() == null ? null
|
}
|
||||||
: t.getMaintenanceWindow().getMaintenanceWindowDuration(),
|
|
||||||
t.getMaintenanceWindow() == null ? null
|
return new TargetWithActionType(t.getId(), MgmtRestModelMapper.convertActionType(t.getType()),
|
||||||
: t.getMaintenanceWindow().getMaintenanceWindowTimeZone()))
|
t.getForcetime(), t.getMaintenanceWindow().getMaintenanceSchedule(),
|
||||||
.collect(Collectors.toList()));
|
t.getMaintenanceWindow().getMaintenanceWindowDuration(),
|
||||||
|
t.getMaintenanceWindow().getMaintenanceWindowTimeZone());
|
||||||
|
}).collect(Collectors.toList()));
|
||||||
|
|
||||||
return ResponseEntity.ok(MgmtDistributionSetMapper.toResponse(assignDistributionSet));
|
return ResponseEntity.ok(MgmtDistributionSetMapper.toResponse(assignDistributionSet));
|
||||||
|
|
||||||
|
|||||||
@@ -12,11 +12,10 @@ import java.util.Arrays;
|
|||||||
import java.util.Collection;
|
import java.util.Collection;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
import java.util.stream.Collectors;
|
|
||||||
|
|
||||||
import javax.validation.ValidationException;
|
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.PagedList;
|
||||||
import org.eclipse.hawkbit.mgmt.json.model.action.MgmtAction;
|
import org.eclipse.hawkbit.mgmt.json.model.action.MgmtAction;
|
||||||
import org.eclipse.hawkbit.mgmt.json.model.action.MgmtActionRequestBodyPut;
|
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.MgmtActionType;
|
||||||
import org.eclipse.hawkbit.mgmt.json.model.distributionset.MgmtDistributionSet;
|
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.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.MgmtTarget;
|
||||||
import org.eclipse.hawkbit.mgmt.json.model.target.MgmtTargetAttributes;
|
import org.eclipse.hawkbit.mgmt.json.model.target.MgmtTargetAttributes;
|
||||||
import org.eclipse.hawkbit.mgmt.json.model.target.MgmtTargetRequestBody;
|
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.TargetManagement;
|
||||||
import org.eclipse.hawkbit.repository.exception.EntityNotFoundException;
|
import org.eclipse.hawkbit.repository.exception.EntityNotFoundException;
|
||||||
import org.eclipse.hawkbit.repository.model.Action;
|
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.ActionStatus;
|
||||||
import org.eclipse.hawkbit.repository.model.Target;
|
import org.eclipse.hawkbit.repository.model.Target;
|
||||||
import org.eclipse.hawkbit.repository.model.TargetWithActionType;
|
import org.eclipse.hawkbit.repository.model.TargetWithActionType;
|
||||||
@@ -268,7 +266,7 @@ public class MgmtTargetResource implements MgmtTargetRestApi {
|
|||||||
@Override
|
@Override
|
||||||
public ResponseEntity<MgmtTargetAssignmentResponseBody> postAssignedDistributionSet(
|
public ResponseEntity<MgmtTargetAssignmentResponseBody> postAssignedDistributionSet(
|
||||||
@PathVariable("controllerId") final String controllerId,
|
@PathVariable("controllerId") final String controllerId,
|
||||||
@RequestBody final MgmtDistributionSetAssigment dsId,
|
@RequestBody final MgmtDistributionSetAssignment dsId,
|
||||||
@RequestParam(value = "offline", required = false) final boolean offline) {
|
@RequestParam(value = "offline", required = false) final boolean offline) {
|
||||||
|
|
||||||
if (offline) {
|
if (offline) {
|
||||||
@@ -277,20 +275,21 @@ public class MgmtTargetResource implements MgmtTargetRestApi {
|
|||||||
}
|
}
|
||||||
|
|
||||||
findTargetWithExceptionIfNotFound(controllerId);
|
findTargetWithExceptionIfNotFound(controllerId);
|
||||||
final ActionType type = (dsId.getType() != null) ? MgmtRestModelMapper.convertActionType(dsId.getType())
|
final MgmtMaintenanceWindow maintenanceWindow = dsId.getMaintenanceWindow();
|
||||||
: ActionType.FORCED;
|
|
||||||
MaintenanceWindow 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
|
@Override
|
||||||
|
|||||||
@@ -50,7 +50,6 @@
|
|||||||
<dependency>
|
<dependency>
|
||||||
<groupId>com.cronutils</groupId>
|
<groupId>com.cronutils</groupId>
|
||||||
<artifactId>cron-utils</artifactId>
|
<artifactId>cron-utils</artifactId>
|
||||||
<version>5.0.5</version>
|
|
||||||
</dependency>
|
</dependency>
|
||||||
|
|
||||||
<!-- TEST -->
|
<!-- TEST -->
|
||||||
|
|||||||
@@ -236,7 +236,7 @@ public interface ControllerManagement {
|
|||||||
* {@link TenantConfigurationKey#MAINTENANCE_WINDOW_POLL_COUNT}.
|
* {@link TenantConfigurationKey#MAINTENANCE_WINDOW_POLL_COUNT}.
|
||||||
*/
|
*/
|
||||||
@PreAuthorize(SpringEvalExpressions.IS_CONTROLLER)
|
@PreAuthorize(SpringEvalExpressions.IS_CONTROLLER)
|
||||||
public int getMaintenanceWindowPollCount();
|
int getMaintenanceWindowPollCount();
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns polling time based on the maintenance window for an action.
|
* 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
|
* of maintenance window, it resets to default
|
||||||
* {@link TenantConfigurationKey#POLLING_TIME_INTERVAL}.
|
* {@link TenantConfigurationKey#POLLING_TIME_INTERVAL}.
|
||||||
*
|
*
|
||||||
* @param action
|
* @param actionId
|
||||||
* id the {@link Action} for which polling time is calculated
|
* id the {@link Action} for which polling time is calculated
|
||||||
* based on it having maintenance window or not
|
* based on it having maintenance window or not
|
||||||
*
|
*
|
||||||
* @return current {@link TenantConfigurationKey#POLLING_TIME_INTERVAL}.
|
* @return current {@link TenantConfigurationKey#POLLING_TIME_INTERVAL}.
|
||||||
*/
|
*/
|
||||||
@PreAuthorize(SpringEvalExpressions.IS_CONTROLLER)
|
@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
|
* 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.ZonedDateTime;
|
||||||
import java.time.format.DateTimeParseException;
|
import java.time.format.DateTimeParseException;
|
||||||
import java.util.Optional;
|
import java.util.Optional;
|
||||||
import java.util.TimeZone;
|
|
||||||
import java.util.regex.Pattern;
|
|
||||||
|
|
||||||
import org.eclipse.hawkbit.repository.exception.InvalidMaintenanceScheduleException;
|
import org.eclipse.hawkbit.repository.exception.InvalidMaintenanceScheduleException;
|
||||||
|
import org.springframework.util.StringUtils;
|
||||||
|
|
||||||
import com.cronutils.model.Cron;
|
import com.cronutils.model.Cron;
|
||||||
import com.cronutils.model.definition.CronDefinition;
|
import com.cronutils.model.definition.CronDefinition;
|
||||||
@@ -35,9 +34,7 @@ import com.cronutils.parser.CronParser;
|
|||||||
*/
|
*/
|
||||||
public class MaintenanceScheduleHelper {
|
public class MaintenanceScheduleHelper {
|
||||||
|
|
||||||
ExecutionTime scheduleExecutor = null;
|
private final ExecutionTime scheduleExecutor;
|
||||||
Duration duration = null;
|
|
||||||
TimeZone timeZone = null;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Constructor that accepts a cron expression, duration and time zone and
|
* 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
|
* maintenance window. Expression has 6 mandatory fields and 1
|
||||||
* last optional field: "second minute hour dayofmonth month
|
* last optional field: "second minute hour dayofmonth month
|
||||||
* weekday year"
|
* 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) {
|
public MaintenanceScheduleHelper(final String cronSchedule) {
|
||||||
this.timeZone = TimeZone.getTimeZone(ZoneOffset.of(timeZone));
|
final CronDefinition cronDefinition = CronDefinitionBuilder.instanceDefinitionFor(QUARTZ);
|
||||||
this.duration = Duration.parse(convertToISODuration(duration));
|
final CronParser parser = new CronParser(cronDefinition);
|
||||||
|
final Cron quartzCron = parser.parse(cronSchedule);
|
||||||
CronDefinition cronDefinition = CronDefinitionBuilder.instanceDefinitionFor(QUARTZ);
|
|
||||||
CronParser parser = new CronParser(cronDefinition);
|
|
||||||
Cron quartzCron = parser.parse(cronSchedule);
|
|
||||||
this.scheduleExecutor = ExecutionTime.forCron(quartzCron);
|
this.scheduleExecutor = ExecutionTime.forCron(quartzCron);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -78,11 +64,13 @@ public class MaintenanceScheduleHelper {
|
|||||||
* @return {@link Optional<ZonedDateTime>} of the next available window. In
|
* @return {@link Optional<ZonedDateTime>} of the next available window. In
|
||||||
* case there is none, returns empty value.
|
* 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 {
|
try {
|
||||||
ZonedDateTime next = this.scheduleExecutor.nextExecution(after);
|
final ZonedDateTime next = this.scheduleExecutor.nextExecution(after);
|
||||||
return Optional.of(next);
|
return Optional.ofNullable(next);
|
||||||
} catch (IllegalArgumentException e) {
|
} catch (final IllegalArgumentException ignored) {
|
||||||
return Optional.empty();
|
return Optional.empty();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -98,7 +86,7 @@ public class MaintenanceScheduleHelper {
|
|||||||
* @return true if there is at least one valid schedule remaining, else
|
* @return true if there is at least one valid schedule remaining, else
|
||||||
* false.
|
* false.
|
||||||
*/
|
*/
|
||||||
public boolean hasValidScheduleAfter(ZonedDateTime after) {
|
private boolean hasValidScheduleAfter(final ZonedDateTime after) {
|
||||||
return nextExecution(after).isPresent();
|
return nextExecution(after).isPresent();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -122,34 +110,41 @@ public class MaintenanceScheduleHelper {
|
|||||||
* start time of a maintenance window calculated based on the
|
* start time of a maintenance window calculated based on the
|
||||||
* cron expression is relative to this time zone
|
* cron expression is relative to this time zone
|
||||||
*
|
*
|
||||||
* @return true if the schedule is valid, else throw an exception
|
|
||||||
*
|
|
||||||
* @throws InvalidMaintenanceScheduleException
|
* @throws InvalidMaintenanceScheduleException
|
||||||
* if the defined schedule fails the validity criteria.
|
* if the defined schedule fails the validity criteria.
|
||||||
*/
|
*/
|
||||||
public static boolean validateMaintenanceSchedule(String cronSchedule, String duration, String timezone) {
|
public static void validateMaintenanceSchedule(final String cronSchedule, final String duration,
|
||||||
// check if schedule, duration and timezone are all not null.
|
final String timezone) {
|
||||||
if (cronSchedule != null && duration != null && timezone != null) {
|
// check if schedule, duration and timezone are all not empty.
|
||||||
// check if schedule, duration and timezone are all not empty.
|
if (allNotEmpty(cronSchedule, duration, timezone)) {
|
||||||
if (!(cronSchedule.isEmpty() || duration.isEmpty() || timezone.isEmpty())) {
|
final ZonedDateTime now;
|
||||||
ZonedDateTime now = ZonedDateTime.now(ZoneOffset.of(timezone));
|
try {
|
||||||
MaintenanceScheduleHelper scheduleHelper = new MaintenanceScheduleHelper(cronSchedule, duration,
|
now = ZonedDateTime.now(ZoneOffset.of(timezone));
|
||||||
timezone);
|
Duration.parse(convertToISODuration(duration));
|
||||||
// check if there is a window currently active or available in
|
} catch (final RuntimeException validationFailed) {
|
||||||
// future.
|
throw new InvalidMaintenanceScheduleException("No valid maintenance window provided", validationFailed);
|
||||||
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.");
|
|
||||||
}
|
}
|
||||||
} 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(
|
throw new InvalidMaintenanceScheduleException(
|
||||||
"All of schedule, duration and timezone should either be null or non empty.");
|
"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
|
* @throws DateTimeParseException
|
||||||
* if the text cannot be converted to ISO format.
|
* 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();
|
return Duration.between(LocalTime.MIN, LocalTime.parse(timeInterval)).toString();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -26,6 +26,8 @@ public class TargetAssignDistributionSetEvent extends RemoteTenantAwareEvent {
|
|||||||
|
|
||||||
private long distributionSetId;
|
private long distributionSetId;
|
||||||
|
|
||||||
|
private boolean maintenanceWindowAvailable;
|
||||||
|
|
||||||
private final Map<String, Long> actions = new HashMap<>();
|
private final Map<String, Long> actions = new HashMap<>();
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -46,24 +48,32 @@ public class TargetAssignDistributionSetEvent extends RemoteTenantAwareEvent {
|
|||||||
* the actions and the targets
|
* the actions and the targets
|
||||||
* @param applicationId
|
* @param applicationId
|
||||||
* the application id.
|
* the application id.
|
||||||
|
* @param maintenanceWindowAvailable
|
||||||
|
* see {@link Action#isMaintenanceWindowAvailable()}
|
||||||
*/
|
*/
|
||||||
public TargetAssignDistributionSetEvent(final String tenant, final long distributionSetId, final List<Action> a,
|
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);
|
super(distributionSetId, tenant, applicationId);
|
||||||
this.distributionSetId = distributionSetId;
|
this.distributionSetId = distributionSetId;
|
||||||
|
this.maintenanceWindowAvailable = maintenanceWindowAvailable;
|
||||||
actions.putAll(a.stream().filter(action -> action.getDistributionSet().getId().longValue() == distributionSetId)
|
actions.putAll(a.stream().filter(action -> action.getDistributionSet().getId().longValue() == distributionSetId)
|
||||||
.collect(Collectors.toMap(action -> action.getTarget().getControllerId(), Action::getId)));
|
.collect(Collectors.toMap(action -> action.getTarget().getControllerId(), Action::getId)));
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public TargetAssignDistributionSetEvent(final Action action, final String applicationId) {
|
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() {
|
public Long getDistributionSetId() {
|
||||||
return distributionSetId;
|
return distributionSetId;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public boolean isMaintenanceWindowAvailable() {
|
||||||
|
return maintenanceWindowAvailable;
|
||||||
|
}
|
||||||
|
|
||||||
public Map<String, Long> getActions() {
|
public Map<String, Long> getActions() {
|
||||||
return actions;
|
return actions;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -14,8 +14,6 @@ import org.eclipse.hawkbit.repository.model.Action.ActionType;
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* A custom view on {@link Target} with {@link ActionType}.
|
* A custom view on {@link Target} with {@link ActionType}.
|
||||||
*
|
|
||||||
*
|
|
||||||
*
|
*
|
||||||
*/
|
*/
|
||||||
public class TargetWithActionType {
|
public class TargetWithActionType {
|
||||||
@@ -23,9 +21,9 @@ public class TargetWithActionType {
|
|||||||
private final String controllerId;
|
private final String controllerId;
|
||||||
private final ActionType actionType;
|
private final ActionType actionType;
|
||||||
private final long forceTime;
|
private final long forceTime;
|
||||||
private String maintenanceSchedule = null;
|
private String maintenanceSchedule;
|
||||||
private String maintenanceWindowDuration = null;
|
private String maintenanceWindowDuration;
|
||||||
private String maintenanceWindowTimeZone = null;
|
private String maintenanceWindowTimeZone;
|
||||||
|
|
||||||
public TargetWithActionType(final String controllerId) {
|
public TargetWithActionType(final String controllerId) {
|
||||||
this.controllerId = controllerId;
|
this.controllerId = controllerId;
|
||||||
@@ -35,7 +33,7 @@ public class TargetWithActionType {
|
|||||||
|
|
||||||
public TargetWithActionType(final String controllerId, final ActionType actionType, final long forceTime) {
|
public TargetWithActionType(final String controllerId, final ActionType actionType, final long forceTime) {
|
||||||
this.controllerId = controllerId;
|
this.controllerId = controllerId;
|
||||||
this.actionType = actionType;
|
this.actionType = actionType != null ? actionType : ActionType.FORCED;
|
||||||
this.forceTime = forceTime;
|
this.forceTime = forceTime;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -64,19 +62,16 @@ public class TargetWithActionType {
|
|||||||
* if the parameters do not define a valid maintenance schedule.
|
* if the parameters do not define a valid maintenance schedule.
|
||||||
*/
|
*/
|
||||||
public TargetWithActionType(final String controllerId, final ActionType actionType, final long forceTime,
|
public TargetWithActionType(final String controllerId, final ActionType actionType, final long forceTime,
|
||||||
String maintenanceSchedule, String maintenanceWindowDuration, String maintenanceWindowTimeZone) {
|
final String maintenanceSchedule, final String maintenanceWindowDuration,
|
||||||
this.controllerId = controllerId;
|
final String maintenanceWindowTimeZone) {
|
||||||
this.actionType = actionType;
|
this(controllerId, actionType, forceTime);
|
||||||
this.forceTime = forceTime;
|
|
||||||
|
|
||||||
if (MaintenanceScheduleHelper.validateMaintenanceSchedule(maintenanceSchedule, maintenanceWindowDuration,
|
this.maintenanceSchedule = maintenanceSchedule;
|
||||||
maintenanceWindowTimeZone)) {
|
this.maintenanceWindowDuration = maintenanceWindowDuration;
|
||||||
this.maintenanceSchedule = maintenanceSchedule;
|
this.maintenanceWindowTimeZone = maintenanceWindowTimeZone;
|
||||||
this.maintenanceWindowDuration = maintenanceWindowDuration;
|
|
||||||
this.maintenanceWindowTimeZone = maintenanceWindowTimeZone;
|
MaintenanceScheduleHelper.validateMaintenanceSchedule(maintenanceSchedule, maintenanceWindowDuration,
|
||||||
} else {
|
maintenanceWindowTimeZone);
|
||||||
throw new InvalidMaintenanceScheduleException("Invalid maintenance window definition");
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public ActionType getActionType() {
|
public ActionType getActionType() {
|
||||||
|
|||||||
@@ -129,8 +129,9 @@ public abstract class AbstractDsAssignmentStrategy {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
afterCommit.afterCommit(() -> eventPublisher.publishEvent(
|
afterCommit.afterCommit(
|
||||||
new TargetAssignDistributionSetEvent(tenant, distributionSetId, actions, applicationContext.getId())));
|
() -> eventPublisher.publishEvent(new TargetAssignDistributionSetEvent(tenant, distributionSetId,
|
||||||
|
actions, applicationContext.getId(), actions.get(0).isMaintenanceWindowAvailable())));
|
||||||
}
|
}
|
||||||
|
|
||||||
protected void sendTargetUpdatedEvent(final JpaTarget target) {
|
protected void sendTargetUpdatedEvent(final JpaTarget target) {
|
||||||
|
|||||||
@@ -14,10 +14,6 @@ import java.time.ZonedDateTime;
|
|||||||
import java.time.temporal.ChronoUnit;
|
import java.time.temporal.ChronoUnit;
|
||||||
import java.time.temporal.TemporalUnit;
|
import java.time.temporal.TemporalUnit;
|
||||||
import java.util.Collection;
|
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.Collections;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Map;
|
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;
|
||||||
import org.eclipse.hawkbit.repository.model.Action.Status;
|
import org.eclipse.hawkbit.repository.model.Action.Status;
|
||||||
import org.eclipse.hawkbit.repository.model.ActionStatus;
|
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.SoftwareModule;
|
||||||
import org.eclipse.hawkbit.repository.model.SoftwareModuleMetadata;
|
import org.eclipse.hawkbit.repository.model.SoftwareModuleMetadata;
|
||||||
import org.eclipse.hawkbit.repository.model.Target;
|
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());
|
.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
|
@Override
|
||||||
public String getPollingTimeForAction(final Action action) {
|
public String getPollingTimeForAction(final long actionId) {
|
||||||
if (action == null || !action.hasMaintenanceSchedule() || action.isMaintenanceScheduleLapsed()) {
|
|
||||||
|
final JpaAction action = getActionAndThrowExceptionIfNotFound(actionId);
|
||||||
|
|
||||||
|
if (!action.hasMaintenanceSchedule() || action.isMaintenanceScheduleLapsed()) {
|
||||||
return getPollingTime();
|
return getPollingTime();
|
||||||
}
|
}
|
||||||
|
|
||||||
JpaAction jpaAction = getActionAndThrowExceptionIfNotFound(action.getId());
|
|
||||||
return (new EventTimer(getPollingTime(), getMinPollingTime(), ChronoUnit.SECONDS))
|
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
|
* polling, should happen when timer expires. Class makes use of java.time
|
||||||
* package to manipulate and calculate timer duration.
|
* package to manipulate and calculate timer duration.
|
||||||
*/
|
*/
|
||||||
private class EventTimer {
|
private static class EventTimer {
|
||||||
|
|
||||||
private final String defaultEventInterval;
|
private final String defaultEventInterval;
|
||||||
private final Duration defaultEventIntervalDuration;
|
private final Duration defaultEventIntervalDuration;
|
||||||
@@ -266,7 +249,7 @@ public class JpaControllerManagement implements ControllerManagement {
|
|||||||
* @param timerUnit
|
* @param timerUnit
|
||||||
* representing the unit of time to be used for timer.
|
* 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.defaultEventInterval = defaultEventInterval;
|
||||||
this.defaultEventIntervalDuration = Duration
|
this.defaultEventIntervalDuration = Duration
|
||||||
.parse(MaintenanceScheduleHelper.convertToISODuration(defaultEventInterval));
|
.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.
|
* @return String in HH:mm:ss format for time to next event.
|
||||||
*/
|
*/
|
||||||
String timeToNextEvent(int eventCount, ZonedDateTime timerResetTime) {
|
String timeToNextEvent(final int eventCount, final ZonedDateTime timerResetTime) {
|
||||||
ZonedDateTime currentTime = ZonedDateTime.now();
|
final ZonedDateTime currentTime = ZonedDateTime.now();
|
||||||
|
|
||||||
// If there is no reset time, or if we already past the reset time,
|
// If there is no reset time, or if we already past the reset time,
|
||||||
// return the default interval.
|
// return the default interval.
|
||||||
@@ -304,7 +287,7 @@ public class JpaControllerManagement implements ControllerManagement {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Calculate the interval timer based on desired event count.
|
// 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);
|
.dividedBy(eventCount);
|
||||||
|
|
||||||
// Need not return interval greater than the default.
|
// 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
|
// For negative and large value of messageCount, limit the number of
|
||||||
// messages.
|
// messages.
|
||||||
final int limit = messageCount < 0 || messageCount >= RepositoryConstants.MAX_ACTION_HISTORY_MSG_COUNT
|
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 PageRequest pageable = new PageRequest(0, limit, new Sort(Direction.DESC, "occurredAt"));
|
||||||
final Page<String> messages = actionStatusRepository.findMessagesByActionIdAndMessageNotLike(pageable, actionId,
|
final Page<String> messages = actionStatusRepository.findMessagesByActionIdAndMessageNotLike(pageable, actionId,
|
||||||
@@ -913,9 +897,10 @@ public class JpaControllerManagement implements ControllerManagement {
|
|||||||
* @throws EntityNotFoundException
|
* @throws EntityNotFoundException
|
||||||
* if action with given actionId does not exist.
|
* if action with given actionId does not exist.
|
||||||
*/
|
*/
|
||||||
|
@Override
|
||||||
@Modifying
|
@Modifying
|
||||||
@Transactional(isolation = Isolation.READ_COMMITTED)
|
@Transactional(isolation = Isolation.READ_COMMITTED)
|
||||||
public Action cancelAction(long actionId) {
|
public Action cancelAction(final long actionId) {
|
||||||
LOG.debug("cancelAction({})", actionId);
|
LOG.debug("cancelAction({})", actionId);
|
||||||
|
|
||||||
final JpaAction action = actionRepository.findById(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 String tenant = rolloutGroupActions.getContent().get(0).getTenant();
|
||||||
|
final boolean maintenanceWindowAvailable = rolloutGroupActions.getContent().get(0)
|
||||||
|
.isMaintenanceWindowAvailable();
|
||||||
|
|
||||||
final List<Action> targetAssignments = rolloutGroupActions.getContent().stream()
|
final List<Action> targetAssignments = rolloutGroupActions.getContent().stream()
|
||||||
.map(action -> (JpaAction) action).map(this::closeActionIfSetWasAlreadyAssigned)
|
.map(action -> (JpaAction) action).map(this::closeActionIfSetWasAlreadyAssigned)
|
||||||
@@ -410,7 +412,7 @@ public class JpaDeploymentManagement implements DeploymentManagement {
|
|||||||
|
|
||||||
if (!CollectionUtils.isEmpty(targetAssignments)) {
|
if (!CollectionUtils.isEmpty(targetAssignments)) {
|
||||||
afterCommit.afterCommit(() -> eventPublisher.publishEvent(new TargetAssignDistributionSetEvent(tenant,
|
afterCommit.afterCommit(() -> eventPublisher.publishEvent(new TargetAssignDistributionSetEvent(tenant,
|
||||||
distributionSetId, targetAssignments, applicationContext.getId())));
|
distributionSetId, targetAssignments, applicationContext.getId(), maintenanceWindowAvailable)));
|
||||||
}
|
}
|
||||||
|
|
||||||
return rolloutGroupActions.getTotalElements();
|
return rolloutGroupActions.getTotalElements();
|
||||||
|
|||||||
@@ -101,7 +101,7 @@ public class JpaAction extends AbstractJpaTenantAwareBaseEntity implements Actio
|
|||||||
@ConversionValue(objectValue = "DOWNLOAD", dataValue = "7"),
|
@ConversionValue(objectValue = "DOWNLOAD", dataValue = "7"),
|
||||||
@ConversionValue(objectValue = "SCHEDULED", dataValue = "8"),
|
@ConversionValue(objectValue = "SCHEDULED", dataValue = "8"),
|
||||||
@ConversionValue(objectValue = "CANCEL_REJECTED", dataValue = "9"),
|
@ConversionValue(objectValue = "CANCEL_REJECTED", dataValue = "9"),
|
||||||
@ConversionValue(objectValue = "DOWNLOADED", dataValue = "10")})
|
@ConversionValue(objectValue = "DOWNLOADED", dataValue = "10") })
|
||||||
@Convert("status")
|
@Convert("status")
|
||||||
@NotNull
|
@NotNull
|
||||||
private Status status;
|
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"))
|
@JoinColumn(name = "rollout", updatable = false, foreignKey = @ForeignKey(value = ConstraintMode.CONSTRAINT, name = "fk_action_rollout"))
|
||||||
private JpaRollout 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;
|
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;
|
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;
|
private String maintenanceWindowTimeZone;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -243,7 +243,7 @@ public class JpaAction extends AbstractJpaTenantAwareBaseEntity implements Actio
|
|||||||
* @param maintenanceSchedule
|
* @param maintenanceSchedule
|
||||||
* is a cron expression to be used for scheduling.
|
* is a cron expression to be used for scheduling.
|
||||||
*/
|
*/
|
||||||
public void setMaintenanceSchedule(String maintenanceSchedule) {
|
public void setMaintenanceSchedule(final String maintenanceSchedule) {
|
||||||
this.maintenanceSchedule = maintenanceSchedule;
|
this.maintenanceSchedule = maintenanceSchedule;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -254,7 +254,7 @@ public class JpaAction extends AbstractJpaTenantAwareBaseEntity implements Actio
|
|||||||
* is the duration of an available maintenance schedule in
|
* is the duration of an available maintenance schedule in
|
||||||
* HH:mm:ss format.
|
* HH:mm:ss format.
|
||||||
*/
|
*/
|
||||||
public void setMaintenanceWindowDuration(String maintenanceWindowDuration) {
|
public void setMaintenanceWindowDuration(final String maintenanceWindowDuration) {
|
||||||
this.maintenanceWindowDuration = 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
|
* start time of a maintenance window calculated based on the
|
||||||
* cron expression is relative to this time zone.
|
* cron expression is relative to this time zone.
|
||||||
*/
|
*/
|
||||||
public void setMaintenanceWindowTimeZone(String maintenanceWindowTimeZone) {
|
public void setMaintenanceWindowTimeZone(final String maintenanceWindowTimeZone) {
|
||||||
this.maintenanceWindowTimeZone = maintenanceWindowTimeZone;
|
this.maintenanceWindowTimeZone = maintenanceWindowTimeZone;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -277,10 +277,9 @@ public class JpaAction extends AbstractJpaTenantAwareBaseEntity implements Actio
|
|||||||
*
|
*
|
||||||
* @return the {@link MaintenanceScheduleHelper} object.
|
* @return the {@link MaintenanceScheduleHelper} object.
|
||||||
*/
|
*/
|
||||||
MaintenanceScheduleHelper getScheduler() {
|
private MaintenanceScheduleHelper getScheduler() {
|
||||||
if (this.scheduleHelper == null) {
|
if (this.scheduleHelper == null) {
|
||||||
this.scheduleHelper = new MaintenanceScheduleHelper(maintenanceSchedule, maintenanceWindowDuration,
|
this.scheduleHelper = new MaintenanceScheduleHelper(maintenanceSchedule);
|
||||||
maintenanceWindowTimeZone);
|
|
||||||
}
|
}
|
||||||
return this.scheduleHelper;
|
return this.scheduleHelper;
|
||||||
}
|
}
|
||||||
@@ -290,7 +289,7 @@ public class JpaAction extends AbstractJpaTenantAwareBaseEntity implements Actio
|
|||||||
*
|
*
|
||||||
* @return the {@link Duration} of each maintenance window.
|
* @return the {@link Duration} of each maintenance window.
|
||||||
*/
|
*/
|
||||||
Duration getMaintenanceWindowDuration() {
|
private Duration getMaintenanceWindowDuration() {
|
||||||
return Duration.parse(MaintenanceScheduleHelper.convertToISODuration(this.maintenanceWindowDuration));
|
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>}.
|
* @return the start time as {@link Optional<ZonedDateTime>}.
|
||||||
*/
|
*/
|
||||||
public Optional<ZonedDateTime> getMaintenanceWindowStartTime() {
|
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()));
|
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>}.
|
* @return the end time of window as {@link Optional<ZonedDateTime>}.
|
||||||
*/
|
*/
|
||||||
public Optional<ZonedDateTime> getMaintenanceWindowEndTime() {
|
private Optional<ZonedDateTime> getMaintenanceWindowEndTime() {
|
||||||
if (getMaintenanceWindowStartTime().isPresent()) {
|
return getMaintenanceWindowStartTime().map(start -> start.plus(getMaintenanceWindowDuration()));
|
||||||
return Optional.of(getMaintenanceWindowStartTime().get().plus(getMaintenanceWindowDuration()));
|
|
||||||
}
|
|
||||||
return Optional.empty();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* 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
|
@Override
|
||||||
public boolean hasMaintenanceSchedule() {
|
public boolean hasMaintenanceSchedule() {
|
||||||
return this.maintenanceSchedule != null;
|
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
|
@Override
|
||||||
public boolean isMaintenanceScheduleLapsed() {
|
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();
|
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
|
@Override
|
||||||
public boolean isMaintenanceWindowAvailable() {
|
public boolean isMaintenanceWindowAvailable() {
|
||||||
if (!hasMaintenanceSchedule()) {
|
if (!hasMaintenanceSchedule()) {
|
||||||
@@ -368,10 +338,12 @@ public class JpaAction extends AbstractJpaTenantAwareBaseEntity implements Actio
|
|||||||
// available.
|
// available.
|
||||||
return false;
|
return false;
|
||||||
} else {
|
} else {
|
||||||
ZonedDateTime now = ZonedDateTime.now(ZoneOffset.of(maintenanceWindowTimeZone));
|
final ZonedDateTime now = ZonedDateTime.now(ZoneOffset.of(maintenanceWindowTimeZone));
|
||||||
if (this.getMaintenanceWindowStartTime().isPresent() && this.getMaintenanceWindowEndTime().isPresent()) {
|
final Optional<ZonedDateTime> start = getMaintenanceWindowStartTime();
|
||||||
return now.isAfter(this.getMaintenanceWindowStartTime().get())
|
final Optional<ZonedDateTime> end = getMaintenanceWindowEndTime();
|
||||||
&& now.isBefore(this.getMaintenanceWindowEndTime().get());
|
|
||||||
|
if (start.isPresent() && end.isPresent()) {
|
||||||
|
return now.isAfter(start.get()) && now.isBefore(end.get());
|
||||||
} else {
|
} else {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -55,7 +55,8 @@ public class RemoteTenantAwareEventTest extends AbstractRemoteEventTest {
|
|||||||
final Action action = actionRepository.save(generateAction);
|
final Action action = actionRepository.save(generateAction);
|
||||||
|
|
||||||
final TargetAssignDistributionSetEvent assignmentEvent = new TargetAssignDistributionSetEvent(
|
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(
|
TargetAssignDistributionSetEvent underTest = (TargetAssignDistributionSetEvent) createProtoStuffEvent(
|
||||||
assignmentEvent);
|
assignmentEvent);
|
||||||
|
|||||||
@@ -18,6 +18,7 @@ import java.util.stream.Collectors;
|
|||||||
import org.eclipse.hawkbit.repository.MaintenanceScheduleHelper;
|
import org.eclipse.hawkbit.repository.MaintenanceScheduleHelper;
|
||||||
import org.eclipse.hawkbit.ui.utils.SPDateTimeUtil;
|
import org.eclipse.hawkbit.ui.utils.SPDateTimeUtil;
|
||||||
import org.eclipse.hawkbit.ui.utils.VaadinMessageSource;
|
import org.eclipse.hawkbit.ui.utils.VaadinMessageSource;
|
||||||
|
import org.springframework.util.StringUtils;
|
||||||
|
|
||||||
import com.vaadin.data.Property.ValueChangeEvent;
|
import com.vaadin.data.Property.ValueChangeEvent;
|
||||||
import com.vaadin.data.Property.ValueChangeListener;
|
import com.vaadin.data.Property.ValueChangeListener;
|
||||||
@@ -53,12 +54,12 @@ public class MaintenanceWindowLayout extends VerticalLayout {
|
|||||||
* strings.
|
* strings.
|
||||||
*/
|
*/
|
||||||
public MaintenanceWindowLayout(final VaadinMessageSource i18n) {
|
public MaintenanceWindowLayout(final VaadinMessageSource i18n) {
|
||||||
|
|
||||||
HorizontalLayout optionContainer;
|
HorizontalLayout optionContainer;
|
||||||
HorizontalLayout controlContainer;
|
HorizontalLayout controlContainer;
|
||||||
|
|
||||||
this.i18n = i18n;
|
this.i18n = i18n;
|
||||||
|
|
||||||
optionContainer = new HorizontalLayout();
|
optionContainer = new HorizontalLayout();
|
||||||
controlContainer = new HorizontalLayout();
|
controlContainer = new HorizontalLayout();
|
||||||
addComponent(optionContainer);
|
addComponent(optionContainer);
|
||||||
@@ -82,17 +83,18 @@ public class MaintenanceWindowLayout extends VerticalLayout {
|
|||||||
/**
|
/**
|
||||||
* Validates if the maintenance schedule is a valid cron expression.
|
* 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;
|
private static final long serialVersionUID = 1L;
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void validate(Object value) throws InvalidValueException {
|
public void validate(final Object value) throws InvalidValueException {
|
||||||
try {
|
try {
|
||||||
String expr = (String) value;
|
final String expr = (String) value;
|
||||||
if (!expr.isEmpty()) {
|
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());
|
Notification.show(e.getMessage());
|
||||||
throw e;
|
throw e;
|
||||||
}
|
}
|
||||||
@@ -102,17 +104,17 @@ public class MaintenanceWindowLayout extends VerticalLayout {
|
|||||||
/**
|
/**
|
||||||
* Validates if the duration is specified in expected format.
|
* 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;
|
private static final long serialVersionUID = 1L;
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void validate(Object value) throws InvalidValueException {
|
public void validate(final Object value) {
|
||||||
try {
|
try {
|
||||||
String expr = (String) value;
|
final String expr = (String) value;
|
||||||
if (!expr.isEmpty()) {
|
if (!StringUtils.isEmpty(expr)) {
|
||||||
MaintenanceScheduleHelper.convertToISODuration((String) value);
|
MaintenanceScheduleHelper.convertToISODuration((String) value);
|
||||||
}
|
}
|
||||||
} catch (DateTimeParseException e) {
|
} catch (final DateTimeParseException e) {
|
||||||
Notification.show(e.getMessage());
|
Notification.show(e.getMessage());
|
||||||
throw e;
|
throw e;
|
||||||
}
|
}
|
||||||
@@ -176,7 +178,7 @@ public class MaintenanceWindowLayout extends VerticalLayout {
|
|||||||
* Get list of all time zone offsets supported.
|
* Get list of all time zone offsets supported.
|
||||||
*/
|
*/
|
||||||
private static List<String> getAllTimeZones() {
|
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()
|
.map(id -> ZonedDateTime.now(ZoneId.of(id)).getOffset().getId().replace("Z", "+00:00")).distinct()
|
||||||
.collect(Collectors.toList());
|
.collect(Collectors.toList());
|
||||||
lst.sort(null);
|
lst.sort(null);
|
||||||
@@ -212,7 +214,7 @@ public class MaintenanceWindowLayout extends VerticalLayout {
|
|||||||
*
|
*
|
||||||
* @return boolean.
|
* @return boolean.
|
||||||
*/
|
*/
|
||||||
public boolean getMaintenanceOption() {
|
public boolean isMaintenanceWindowEnabled() {
|
||||||
return maintenanceWindowSelection.getValue();
|
return maintenanceWindowSelection.getValue();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -159,13 +159,13 @@ public class ManangementConfirmationWindowLayout extends AbstractConfirmationWin
|
|||||||
? actionTypeOptionGroupLayout.getForcedTimeDateField().getValue().getTime()
|
? actionTypeOptionGroupLayout.getForcedTimeDateField().getValue().getTime()
|
||||||
: RepositoryModelConstants.NO_FORCE_TIME;
|
: RepositoryModelConstants.NO_FORCE_TIME;
|
||||||
|
|
||||||
final String maintenanceSchedule = maintenanceWindowLayout.getMaintenanceOption()
|
final String maintenanceSchedule = maintenanceWindowLayout.isMaintenanceWindowEnabled()
|
||||||
? maintenanceWindowLayout.getMaintenanceSchedule() : null;
|
? maintenanceWindowLayout.getMaintenanceSchedule() : null;
|
||||||
|
|
||||||
final String maintenanceDuration = maintenanceWindowLayout.getMaintenanceOption()
|
final String maintenanceDuration = maintenanceWindowLayout.isMaintenanceWindowEnabled()
|
||||||
? maintenanceWindowLayout.getMaintenanceDuration() : null;
|
? maintenanceWindowLayout.getMaintenanceDuration() : null;
|
||||||
|
|
||||||
final String maintenanceTimeZone = maintenanceWindowLayout.getMaintenanceOption()
|
final String maintenanceTimeZone = maintenanceWindowLayout.isMaintenanceWindowEnabled()
|
||||||
? maintenanceWindowLayout.getMaintenanceTimeZone() : null;
|
? maintenanceWindowLayout.getMaintenanceTimeZone() : null;
|
||||||
|
|
||||||
final Map<Long, List<TargetIdName>> saveAssignedList = Maps.newHashMapWithExpectedSize(itemIds.size());
|
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. -->
|
<!-- Repeatable migrations, Disabling clean etc. -->
|
||||||
<flyway.version>4.0.3</flyway.version>
|
<flyway.version>4.0.3</flyway.version>
|
||||||
<!-- Improved integration test capabilities -->
|
<!-- 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 -->
|
<!-- Older versions needed than defined in Boot -->
|
||||||
<!-- Incompatible changes in 2.2 -->
|
<!-- Incompatible changes in 2.2 -->
|
||||||
<json-path.version>2.0.0</json-path.version>
|
<json-path.version>2.0.0</json-path.version>
|
||||||
@@ -149,6 +149,7 @@
|
|||||||
<maven.scm.plugin.version>1.9.4</maven.scm.plugin.version>
|
<maven.scm.plugin.version>1.9.4</maven.scm.plugin.version>
|
||||||
|
|
||||||
<!-- Misc libraries versions - START -->
|
<!-- Misc libraries versions - START -->
|
||||||
|
<cron-utils.version>5.0.5</cron-utils.version>
|
||||||
<jsoup.version>1.8.3</jsoup.version>
|
<jsoup.version>1.8.3</jsoup.version>
|
||||||
<validation-api.version>2.0.1.Final</validation-api.version>
|
<validation-api.version>2.0.1.Final</validation-api.version>
|
||||||
<allure.version>1.5.4</allure.version>
|
<allure.version>1.5.4</allure.version>
|
||||||
@@ -568,6 +569,11 @@
|
|||||||
</dependency>
|
</dependency>
|
||||||
|
|
||||||
<!-- Misc -->
|
<!-- Misc -->
|
||||||
|
<dependency>
|
||||||
|
<groupId>com.cronutils</groupId>
|
||||||
|
<artifactId>cron-utils</artifactId>
|
||||||
|
<version>${cron-utils.version}</version>
|
||||||
|
</dependency>
|
||||||
<dependency>
|
<dependency>
|
||||||
<groupId>org.jsoup</groupId>
|
<groupId>org.jsoup</groupId>
|
||||||
<artifactId>jsoup</artifactId>
|
<artifactId>jsoup</artifactId>
|
||||||
|
|||||||
Reference in New Issue
Block a user