Add support for pollingTime overrides (#2533)
* Add support for pollingTime overrides
* the current format HH:mm:ss is still supported
* add option for deviation percent (HH:mm:ss~\d{1,2}%) which allows the system to do some randomizing of the poll interval
* add support for overriding default polling time interval for devices matching some RSQL filters (in order), e.g. 01:00:00~10%, group == 'eu' -> 00:02:00~15%, status != in_sync -> 00:05:00
* IMPORTANT: overdue time is calculated according to the default polling time. So, the overdue status might be incorrect for targets with overridden poll interval
Signed-off-by: Avgustin Marinov <Avgustin.Marinov@bosch.com>
* Remove min polling time from the tenant config - it is a system configuration
Signed-off-by: Avgustin Marinov <Avgustin.Marinov@bosch.com>
* Add support for bigger poll intervals and overdue + duration format config support
Signed-off-by: Avgustin Marinov <Avgustin.Marinov@bosch.com>
---------
Signed-off-by: Avgustin Marinov <Avgustin.Marinov@bosch.com>
This commit is contained in:
@@ -26,7 +26,7 @@ import lombok.Data;
|
||||
@Schema(description = "Suggested sleep time between polls")
|
||||
public class DdiPolling {
|
||||
|
||||
@Schema(description = "Sleep time in HH:MM:SS notation", pattern = "HH:MM:SS", example = "12:00:00")
|
||||
@Schema(description = "Sleep time in HH:mm:ss notation", pattern = "HH:mm:ss", example = "12:00:00")
|
||||
private final String sleep;
|
||||
|
||||
/**
|
||||
|
||||
@@ -155,10 +155,12 @@ public class DdiRootController implements DdiRootControllerRestApi {
|
||||
checkAndCancelExpiredAction(activeAction);
|
||||
|
||||
// activeAction
|
||||
return new ResponseEntity<>(DataConversionHelper.fromTarget(target, installedAction, activeAction,
|
||||
return new ResponseEntity<>(DataConversionHelper.fromTarget(
|
||||
target,
|
||||
installedAction, activeAction,
|
||||
activeAction == null
|
||||
? controllerManagement.getPollingTime()
|
||||
: controllerManagement.getPollingTimeForAction(activeAction), tenantAware),
|
||||
? controllerManagement.getPollingTime(target)
|
||||
: controllerManagement.getPollingTimeForAction(target, activeAction), tenantAware),
|
||||
HttpStatus.OK);
|
||||
}
|
||||
|
||||
|
||||
@@ -30,6 +30,7 @@ import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.
|
||||
import java.util.Collections;
|
||||
import java.util.Map;
|
||||
import java.util.UUID;
|
||||
import java.util.concurrent.Callable;
|
||||
|
||||
import org.eclipse.hawkbit.ddi.json.model.DdiResult;
|
||||
import org.eclipse.hawkbit.ddi.json.model.DdiStatus;
|
||||
@@ -38,6 +39,7 @@ import org.eclipse.hawkbit.im.authentication.SpPermission;
|
||||
import org.eclipse.hawkbit.repository.event.remote.TargetAssignDistributionSetEvent;
|
||||
import org.eclipse.hawkbit.repository.event.remote.TargetAttributesRequestedEvent;
|
||||
import org.eclipse.hawkbit.repository.event.remote.TargetPollEvent;
|
||||
import org.eclipse.hawkbit.repository.event.remote.TenantConfigurationDeletedEvent;
|
||||
import org.eclipse.hawkbit.repository.event.remote.entity.ActionCreatedEvent;
|
||||
import org.eclipse.hawkbit.repository.event.remote.entity.ActionUpdatedEvent;
|
||||
import org.eclipse.hawkbit.repository.event.remote.entity.DistributionSetCreatedEvent;
|
||||
@@ -103,7 +105,6 @@ class DdiRootControllerTest extends AbstractDDiApiIntegrationTest {
|
||||
.andExpect(status().isOk())
|
||||
.andExpect(content().contentType(MediaTypes.HAL_JSON))
|
||||
.andReturn();
|
||||
|
||||
// verify that we did not specify a content-type in the request, in case there are any default values
|
||||
assertThat(result.getRequest().getHeader("Accept")).isNull();
|
||||
}
|
||||
@@ -124,7 +125,6 @@ class DdiRootControllerTest extends AbstractDDiApiIntegrationTest {
|
||||
testdataFactory.createTarget(knownTargetControllerId);
|
||||
final Target findTargetByControllerID = targetManagement.getByControllerID(knownTargetControllerId).get();
|
||||
assertThat(findTargetByControllerID.getCreatedBy()).isEqualTo(knownCreatedBy);
|
||||
|
||||
// make a poll, audit information should not be changed, run as controller principal!
|
||||
SecurityContextSwitch.runAs(SecurityContextSwitch.withController("controller", CONTROLLER_ROLE_ANONYMOUS),
|
||||
() -> {
|
||||
@@ -133,7 +133,6 @@ class DdiRootControllerTest extends AbstractDDiApiIntegrationTest {
|
||||
.andExpect(status().isOk());
|
||||
return null;
|
||||
});
|
||||
|
||||
// verify that audit information has not changed
|
||||
final Target targetVerify = targetManagement.getByControllerID(knownTargetControllerId).get();
|
||||
assertThat(targetVerify.getCreatedBy()).isEqualTo(findTargetByControllerID.getCreatedBy());
|
||||
@@ -161,7 +160,6 @@ class DdiRootControllerTest extends AbstractDDiApiIntegrationTest {
|
||||
@Expect(type = TargetCreatedEvent.class, count = 1),
|
||||
@Expect(type = TargetPollEvent.class, count = 1) })
|
||||
void rootRsPlugAndPlay() throws Exception {
|
||||
|
||||
final long current = System.currentTimeMillis();
|
||||
final String controllerId = "4711";
|
||||
|
||||
@@ -172,7 +170,6 @@ class DdiRootControllerTest extends AbstractDDiApiIntegrationTest {
|
||||
.andExpect(jsonPath("$.config.polling.sleep", equalTo("00:01:00")));
|
||||
assertThat(targetManagement.getByControllerID(controllerId).get().getLastTargetQuery())
|
||||
.isGreaterThanOrEqualTo(current);
|
||||
|
||||
assertThat(targetManagement.getByControllerID(controllerId).get().getUpdateStatus())
|
||||
.isEqualTo(TargetUpdateStatus.REGISTERED);
|
||||
|
||||
@@ -194,26 +191,46 @@ class DdiRootControllerTest extends AbstractDDiApiIntegrationTest {
|
||||
* Ensures that tenant specific polling time, which is saved in the db, is delivered to the controller.
|
||||
*/
|
||||
@Test
|
||||
@WithUser(principal = "knownpricipal", allSpPermissions = false)
|
||||
@WithUser(principal = "knownpricipal")
|
||||
@ExpectEvents({
|
||||
@Expect(type = TargetCreatedEvent.class, count = 1),
|
||||
@Expect(type = TargetPollEvent.class, count = 1),
|
||||
@Expect(type = TenantConfigurationCreatedEvent.class, count = 1) })
|
||||
@Expect(type = TenantConfigurationCreatedEvent.class, count = 1),
|
||||
@Expect(type = TenantConfigurationDeletedEvent.class, count = 1) })
|
||||
void pollWithModifiedGlobalPollingTime() throws Exception {
|
||||
SecurityContextSwitch.runAs(SecurityContextSwitch.withUser("tenantadmin", TENANT_CONFIGURATION),
|
||||
withPollingTime("00:02:00", () -> SecurityContextSwitch.runAs(
|
||||
SecurityContextSwitch.withUser("controller", CONTROLLER_ROLE_ANONYMOUS),
|
||||
() -> {
|
||||
tenantConfigurationManagement.addOrUpdateConfiguration(TenantConfigurationKey.POLLING_TIME_INTERVAL, "00:02:00");
|
||||
mvc.perform(get(CONTROLLER_BASE, tenantAware.getCurrentTenant(), 4711))
|
||||
.andDo(MockMvcResultPrinter.print())
|
||||
.andExpect(status().isOk())
|
||||
.andExpect(content().contentType(MediaTypes.HAL_JSON))
|
||||
.andExpect(jsonPath("$.config.polling.sleep", equalTo("00:02:00")));
|
||||
return null;
|
||||
});
|
||||
}));
|
||||
}
|
||||
|
||||
SecurityContextSwitch.runAs(SecurityContextSwitch.withUser("controller", CONTROLLER_ROLE_ANONYMOUS), () -> {
|
||||
mvc.perform(get(CONTROLLER_BASE, tenantAware.getCurrentTenant(), 4711))
|
||||
.andDo(MockMvcResultPrinter.print())
|
||||
.andExpect(status().isOk())
|
||||
.andExpect(content().contentType(MediaTypes.HAL_JSON))
|
||||
.andExpect(jsonPath("$.config.polling.sleep", equalTo("00:02:00")));
|
||||
return null;
|
||||
});
|
||||
/**
|
||||
* Ensures that tenant specific polling time, which is saved in the db, is delivered to the controller.
|
||||
*/
|
||||
@Test
|
||||
@WithUser(principal = "knownpricipal")
|
||||
@ExpectEvents({
|
||||
@Expect(type = TargetCreatedEvent.class, count = 1),
|
||||
@Expect(type = TargetPollEvent.class, count = 1),
|
||||
@Expect(type = TenantConfigurationCreatedEvent.class, count = 1),
|
||||
@Expect(type = TenantConfigurationDeletedEvent.class, count = 1) })
|
||||
void pollWithModifiedWithOverridesGlobalPollingTime() throws Exception {
|
||||
withPollingTime("00:02:00, controllerid == 4711 -> 00:01:00", () -> SecurityContextSwitch.runAs(
|
||||
SecurityContextSwitch.withUser("controller", CONTROLLER_ROLE_ANONYMOUS),
|
||||
() -> {
|
||||
mvc.perform(get(CONTROLLER_BASE, tenantAware.getCurrentTenant(), 4711))
|
||||
.andDo(MockMvcResultPrinter.print())
|
||||
.andExpect(status().isOk())
|
||||
.andExpect(content().contentType(MediaTypes.HAL_JSON))
|
||||
.andExpect(jsonPath("$.config.polling.sleep", equalTo("00:01:00")));
|
||||
return null;
|
||||
}));
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -242,21 +259,20 @@ class DdiRootControllerTest extends AbstractDDiApiIntegrationTest {
|
||||
.andExpect(jsonPath("$.config.polling.sleep", equalTo("00:01:00")))
|
||||
.andReturn().getResponse()
|
||||
.getHeader("ETag");
|
||||
|
||||
mvc.perform(get(CONTROLLER_BASE, tenantAware.getCurrentTenant(), controllerId).header("If-None-Match", etag))
|
||||
.andDo(MockMvcResultPrinter.print())
|
||||
.andExpect(status().isNotModified());
|
||||
|
||||
final Target target = targetManagement.getByControllerID(controllerId).get();
|
||||
final DistributionSet ds = testdataFactory.createDistributionSet("");
|
||||
|
||||
assignDistributionSet(ds.getId(), controllerId);
|
||||
|
||||
final Action updateAction = deploymentManagement.findActiveActionsByTarget(target.getControllerId(), PAGE)
|
||||
.getContent().get(0);
|
||||
final Action updateAction = deploymentManagement.findActiveActionsByTarget(target.getControllerId(), PAGE).getContent().get(0);
|
||||
final String etagWithFirstUpdate = mvc
|
||||
.perform(get(CONTROLLER_BASE, tenantAware.getCurrentTenant(), controllerId)
|
||||
.header("If-None-Match", etag).accept(MediaType.APPLICATION_JSON).with(new RequestOnHawkbitDefaultPortPostProcessor()))
|
||||
.header("If-None-Match", etag)
|
||||
.accept(MediaType.APPLICATION_JSON)
|
||||
.with(new RequestOnHawkbitDefaultPortPostProcessor()))
|
||||
.andDo(MockMvcResultPrinter.print())
|
||||
.andExpect(status().isOk())
|
||||
.andExpect(content().contentType(MediaType.APPLICATION_JSON))
|
||||
@@ -265,9 +281,7 @@ class DdiRootControllerTest extends AbstractDDiApiIntegrationTest {
|
||||
.andExpect(jsonPath("$._links.deploymentBase.href",
|
||||
startsWith(deploymentBaseLink("4711", updateAction.getId().toString()))))
|
||||
.andReturn().getResponse().getHeader("ETag");
|
||||
|
||||
assertThat(etagWithFirstUpdate).isNotNull();
|
||||
|
||||
mvc.perform(get(CONTROLLER_BASE, tenantAware.getCurrentTenant(), controllerId).header("If-None-Match",
|
||||
etagWithFirstUpdate).with(new RequestOnHawkbitDefaultPortPostProcessor()))
|
||||
.andDo(MockMvcResultPrinter.print())
|
||||
@@ -277,7 +291,6 @@ class DdiRootControllerTest extends AbstractDDiApiIntegrationTest {
|
||||
sendDeploymentActionFeedback(target, updateAction, "closed", null)
|
||||
.andDo(MockMvcResultPrinter.print())
|
||||
.andExpect(status().isOk());
|
||||
|
||||
// as the update was installed, and we always receive the installed action, the
|
||||
// original state cannot be restored
|
||||
final String etagAfterInstallation = mvc
|
||||
@@ -295,11 +308,8 @@ class DdiRootControllerTest extends AbstractDDiApiIntegrationTest {
|
||||
|
||||
// Now another deployment
|
||||
final DistributionSet ds2 = testdataFactory.createDistributionSet("2");
|
||||
|
||||
assignDistributionSet(ds2.getId(), controllerId);
|
||||
|
||||
final Action updateAction2 = deploymentManagement.findActiveActionsByTarget(target.getControllerId(), PAGE).getContent().get(0);
|
||||
|
||||
mvc.perform(get(CONTROLLER_BASE, tenantAware.getCurrentTenant(), controllerId)
|
||||
.header("If-None-Match", etagAfterInstallation).accept(MediaType.APPLICATION_JSON)
|
||||
.with(new RequestOnHawkbitDefaultPortPostProcessor()))
|
||||
@@ -312,7 +322,6 @@ class DdiRootControllerTest extends AbstractDDiApiIntegrationTest {
|
||||
.andExpect(jsonPath("$._links.deploymentBase.href",
|
||||
startsWith(deploymentBaseLink("4711", updateAction2.getId().toString()))))
|
||||
.andReturn().getResponse().getHeader("ETag");
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -322,26 +331,22 @@ class DdiRootControllerTest extends AbstractDDiApiIntegrationTest {
|
||||
@Test
|
||||
@ExpectEvents({
|
||||
@Expect(type = TargetCreatedEvent.class, count = 1),
|
||||
@Expect(type = TargetUpdatedEvent.class, count = 1),
|
||||
@Expect(type = TargetUpdatedEvent.class, count = 1),
|
||||
@Expect(type = TargetPollEvent.class, count = 1) })
|
||||
void rootRsPreCommissioned() throws Exception {
|
||||
final String controllerId = "4711";
|
||||
testdataFactory.createTarget(controllerId);
|
||||
|
||||
assertThat(targetManagement.getByControllerID(controllerId).get().getUpdateStatus()).isEqualTo(TargetUpdateStatus.UNKNOWN);
|
||||
|
||||
final long current = System.currentTimeMillis();
|
||||
mvc.perform(get(CONTROLLER_BASE, tenantAware.getCurrentTenant(), controllerId))
|
||||
.andDo(MockMvcResultPrinter.print())
|
||||
.andExpect(status().isOk())
|
||||
.andExpect(content().contentType(MediaTypes.HAL_JSON))
|
||||
.andExpect(jsonPath("$.config.polling.sleep", equalTo("00:01:00")));
|
||||
|
||||
assertThat(targetManagement.getByControllerID(controllerId).get().getLastTargetQuery())
|
||||
.isLessThanOrEqualTo(System.currentTimeMillis());
|
||||
assertThat(targetManagement.getByControllerID(controllerId).get().getLastTargetQuery())
|
||||
.isGreaterThanOrEqualTo(current);
|
||||
|
||||
assertThat(targetManagement.getByControllerID(controllerId).get().getUpdateStatus())
|
||||
.isEqualTo(TargetUpdateStatus.REGISTERED);
|
||||
}
|
||||
@@ -357,7 +362,6 @@ class DdiRootControllerTest extends AbstractDDiApiIntegrationTest {
|
||||
// test
|
||||
final String knownControllerId1 = "0815";
|
||||
final long create = System.currentTimeMillis();
|
||||
|
||||
// make a poll, audit information should be set on plug and play
|
||||
SecurityContextSwitch.runAs(SecurityContextSwitch.withController("controller", CONTROLLER_ROLE_ANONYMOUS),
|
||||
() -> {
|
||||
@@ -366,7 +370,6 @@ class DdiRootControllerTest extends AbstractDDiApiIntegrationTest {
|
||||
.andExpect(status().isOk());
|
||||
return null;
|
||||
});
|
||||
|
||||
// verify
|
||||
final Target target = targetManagement.getByControllerID(knownControllerId1).get();
|
||||
assertThat(target.getAddress()).isEqualTo(IpUtil.createHttpUri("127.0.0.1"));
|
||||
@@ -385,17 +388,14 @@ class DdiRootControllerTest extends AbstractDDiApiIntegrationTest {
|
||||
@Expect(type = TargetPollEvent.class, count = 1) })
|
||||
void rootRsIpAddressNotStoredIfDisabled() throws Exception {
|
||||
securityProperties.getClients().setTrackRemoteIp(false);
|
||||
|
||||
// test
|
||||
final String knownControllerId1 = "0815";
|
||||
mvc.perform(get(CONTROLLER_BASE, tenantAware.getCurrentTenant(), knownControllerId1))
|
||||
.andDo(MockMvcResultPrinter.print())
|
||||
.andExpect(status().isOk());
|
||||
|
||||
// verify
|
||||
final Target target = targetManagement.getByControllerID(knownControllerId1).get();
|
||||
assertThat(target.getAddress()).isEqualTo(IpUtil.createHttpUri("***"));
|
||||
|
||||
securityProperties.getClients().setTrackRemoteIp(true);
|
||||
}
|
||||
|
||||
@@ -417,16 +417,13 @@ class DdiRootControllerTest extends AbstractDDiApiIntegrationTest {
|
||||
final DistributionSet ds = testdataFactory.createDistributionSet("");
|
||||
Target savedTarget = testdataFactory.createTarget("911");
|
||||
savedTarget = getFirstAssignedTarget(assignDistributionSet(ds.getId(), savedTarget.getControllerId()));
|
||||
final Action savedAction = deploymentManagement.findActiveActionsByTarget(savedTarget.getControllerId(), PAGE)
|
||||
.getContent().get(0);
|
||||
final Action savedAction = deploymentManagement.findActiveActionsByTarget(savedTarget.getControllerId(), PAGE).getContent().get(0);
|
||||
sendDeploymentActionFeedback(savedTarget, savedAction, "proceeding", null)
|
||||
.andDo(MockMvcResultPrinter.print())
|
||||
.andExpect(status().isOk());
|
||||
|
||||
sendDeploymentActionFeedback(savedTarget, savedAction, "closed", "failure")
|
||||
.andDo(MockMvcResultPrinter.print())
|
||||
.andExpect(status().isOk());
|
||||
|
||||
sendDeploymentActionFeedback(savedTarget, savedAction, "closed", "success")
|
||||
.andDo(MockMvcResultPrinter.print())
|
||||
.andExpect(status().isGone());
|
||||
@@ -453,14 +450,11 @@ class DdiRootControllerTest extends AbstractDDiApiIntegrationTest {
|
||||
final Target savedTarget = testdataFactory.createTarget("922");
|
||||
final Map<String, String> attributes = Collections.singletonMap("AttributeKey", "AttributeValue");
|
||||
assertThatAttributesUpdateIsRequested(savedTarget.getControllerId());
|
||||
|
||||
mvc.perform(put(CONTROLLER_BASE + "/configData", tenantAware.getCurrentTenant(), savedTarget.getControllerId())
|
||||
.content(JsonBuilder.configData(attributes).toString()).contentType(MediaType.APPLICATION_JSON))
|
||||
.andExpect(status().isOk());
|
||||
assertThatAttributesUpdateIsNotRequested(savedTarget.getControllerId());
|
||||
|
||||
assertAttributesUpdateNotRequestedAfterFailedDeployment(savedTarget, ds);
|
||||
|
||||
assertAttributesUpdateRequestedAfterSuccessfulDeployment(savedTarget, ds);
|
||||
}
|
||||
|
||||
@@ -483,31 +477,23 @@ class DdiRootControllerTest extends AbstractDDiApiIntegrationTest {
|
||||
final DistributionSet ds = testdataFactory.createDistributionSet("");
|
||||
Target savedTarget = testdataFactory.createTarget("911");
|
||||
savedTarget = getFirstAssignedTarget(assignDistributionSet(ds.getId(), savedTarget.getControllerId()));
|
||||
final Action savedAction = deploymentManagement.findActiveActionsByTarget(savedTarget.getControllerId(), PAGE)
|
||||
.getContent().get(0);
|
||||
|
||||
final Action savedAction = deploymentManagement.findActiveActionsByTarget(savedTarget.getControllerId(), PAGE).getContent().get(0);
|
||||
sendDeploymentActionFeedback(savedTarget, savedAction, "scheduled", null, TARGET_SCHEDULED_INSTALLATION_MSG)
|
||||
.andDo(MockMvcResultPrinter.print())
|
||||
.andExpect(status().isOk());
|
||||
|
||||
sendDeploymentActionFeedback(savedTarget, savedAction, "proceeding", null, TARGET_PROCEEDING_INSTALLATION_MSG)
|
||||
.andDo(MockMvcResultPrinter.print())
|
||||
.andExpect(status().isOk());
|
||||
|
||||
sendDeploymentActionFeedback(savedTarget, savedAction, "closed", "success", TARGET_COMPLETED_INSTALLATION_MSG)
|
||||
.andDo(MockMvcResultPrinter.print())
|
||||
.andExpect(status().isOk());
|
||||
|
||||
mvc.perform(get(DEPLOYMENT_BASE + "?actionHistory=2", tenantAware.getCurrentTenant(), 911, savedAction.getId())
|
||||
.contentType(MediaType.APPLICATION_JSON).accept(MediaType.APPLICATION_JSON))
|
||||
.andDo(MockMvcResultPrinter.print())
|
||||
.andExpect(status().isOk())
|
||||
.andExpect(jsonPath("$.actionHistory.messages",
|
||||
hasItem(containsString(TARGET_COMPLETED_INSTALLATION_MSG))))
|
||||
.andExpect(jsonPath("$.actionHistory.messages",
|
||||
hasItem(containsString(TARGET_PROCEEDING_INSTALLATION_MSG))))
|
||||
.andExpect(jsonPath("$.actionHistory.messages",
|
||||
not(hasItem(containsString(TARGET_SCHEDULED_INSTALLATION_MSG)))));
|
||||
.andExpect(jsonPath("$.actionHistory.messages", hasItem(containsString(TARGET_COMPLETED_INSTALLATION_MSG))))
|
||||
.andExpect(jsonPath("$.actionHistory.messages", hasItem(containsString(TARGET_PROCEEDING_INSTALLATION_MSG))))
|
||||
.andExpect(jsonPath("$.actionHistory.messages", not(hasItem(containsString(TARGET_SCHEDULED_INSTALLATION_MSG)))));
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -530,25 +516,20 @@ class DdiRootControllerTest extends AbstractDDiApiIntegrationTest {
|
||||
Target savedTarget = testdataFactory.createTarget("911");
|
||||
savedTarget = getFirstAssignedTarget(assignDistributionSet(ds.getId(), savedTarget.getControllerId()));
|
||||
final Action savedAction = deploymentManagement.findActiveActionsByTarget(savedTarget.getControllerId(), PAGE).getContent().get(0);
|
||||
|
||||
sendDeploymentActionFeedback(savedTarget, savedAction, "scheduled", null, TARGET_SCHEDULED_INSTALLATION_MSG)
|
||||
.andDo(MockMvcResultPrinter.print())
|
||||
.andExpect(status().isOk());
|
||||
|
||||
sendDeploymentActionFeedback(savedTarget, savedAction, "proceeding", null, TARGET_PROCEEDING_INSTALLATION_MSG)
|
||||
.andDo(MockMvcResultPrinter.print())
|
||||
.andExpect(status().isOk());
|
||||
|
||||
sendDeploymentActionFeedback(savedTarget, savedAction, "closed", "success", TARGET_COMPLETED_INSTALLATION_MSG)
|
||||
.andDo(MockMvcResultPrinter.print())
|
||||
.andExpect(status().isOk());
|
||||
|
||||
mvc.perform(get(DEPLOYMENT_BASE + "?actionHistory=0", tenantAware.getCurrentTenant(), 911, savedAction.getId())
|
||||
.contentType(MediaType.APPLICATION_JSON).accept(MediaType.APPLICATION_JSON))
|
||||
.andDo(MockMvcResultPrinter.print())
|
||||
.andExpect(status().isOk())
|
||||
.andExpect(jsonPath("$.actionHistory.messages").doesNotExist());
|
||||
|
||||
mvc.perform(get(DEPLOYMENT_BASE + "?actionHistory", tenantAware.getCurrentTenant(), 911, savedAction.getId())
|
||||
.contentType(MediaType.APPLICATION_JSON).accept(MediaType.APPLICATION_JSON))
|
||||
.andDo(MockMvcResultPrinter.print())
|
||||
@@ -567,7 +548,7 @@ class DdiRootControllerTest extends AbstractDDiApiIntegrationTest {
|
||||
@Expect(type = DistributionSetUpdatedEvent.class, count = 1), // implicit lock
|
||||
@Expect(type = SoftwareModuleUpdatedEvent.class, count = 3), // implicit lock
|
||||
@Expect(type = TargetAssignDistributionSetEvent.class, count = 1),
|
||||
@Expect(type = ActionCreatedEvent.class, count = 1),
|
||||
@Expect(type = ActionCreatedEvent.class, count = 1),
|
||||
@Expect(type = ActionUpdatedEvent.class, count = 2),
|
||||
@Expect(type = TargetUpdatedEvent.class, count = 2),
|
||||
@Expect(type = TargetAttributesRequestedEvent.class, count = 1) })
|
||||
@@ -575,31 +556,23 @@ class DdiRootControllerTest extends AbstractDDiApiIntegrationTest {
|
||||
final DistributionSet ds = testdataFactory.createDistributionSet("");
|
||||
Target savedTarget = testdataFactory.createTarget("911");
|
||||
savedTarget = getFirstAssignedTarget(assignDistributionSet(ds.getId(), savedTarget.getControllerId()));
|
||||
final Action savedAction = deploymentManagement.findActiveActionsByTarget(savedTarget.getControllerId(), PAGE)
|
||||
.getContent().get(0);
|
||||
|
||||
final Action savedAction = deploymentManagement.findActiveActionsByTarget(savedTarget.getControllerId(), PAGE).getContent().get(0);
|
||||
sendDeploymentActionFeedback(savedTarget, savedAction, "scheduled", null, TARGET_SCHEDULED_INSTALLATION_MSG)
|
||||
.andDo(MockMvcResultPrinter.print())
|
||||
.andExpect(status().isOk());
|
||||
|
||||
sendDeploymentActionFeedback(savedTarget, savedAction, "proceeding", null, TARGET_PROCEEDING_INSTALLATION_MSG)
|
||||
.andDo(MockMvcResultPrinter.print())
|
||||
.andExpect(status().isOk());
|
||||
|
||||
sendDeploymentActionFeedback(savedTarget, savedAction, "closed", "success", TARGET_COMPLETED_INSTALLATION_MSG)
|
||||
.andDo(MockMvcResultPrinter.print())
|
||||
.andExpect(status().isOk());
|
||||
|
||||
mvc.perform(get(DEPLOYMENT_BASE + "?actionHistory=-1", tenantAware.getCurrentTenant(), 911, savedAction.getId())
|
||||
.contentType(MediaType.APPLICATION_JSON).accept(MediaType.APPLICATION_JSON))
|
||||
.andDo(MockMvcResultPrinter.print())
|
||||
.andExpect(status().isOk())
|
||||
.andExpect(jsonPath("$.actionHistory.messages",
|
||||
hasItem(containsString(TARGET_SCHEDULED_INSTALLATION_MSG))))
|
||||
.andExpect(jsonPath("$.actionHistory.messages",
|
||||
hasItem(containsString(TARGET_PROCEEDING_INSTALLATION_MSG))))
|
||||
.andExpect(jsonPath("$.actionHistory.messages",
|
||||
hasItem(containsString(TARGET_COMPLETED_INSTALLATION_MSG))));
|
||||
.andExpect(jsonPath("$.actionHistory.messages", hasItem(containsString(TARGET_SCHEDULED_INSTALLATION_MSG))))
|
||||
.andExpect(jsonPath("$.actionHistory.messages", hasItem(containsString(TARGET_PROCEEDING_INSTALLATION_MSG))))
|
||||
.andExpect(jsonPath("$.actionHistory.messages", hasItem(containsString(TARGET_COMPLETED_INSTALLATION_MSG))));
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -611,14 +584,13 @@ class DdiRootControllerTest extends AbstractDDiApiIntegrationTest {
|
||||
|
||||
SecurityContextSwitch.runAs(SecurityContextSwitch.withUser("tenantadmin", TENANT_CONFIGURATION),
|
||||
() -> {
|
||||
tenantConfigurationManagement.addOrUpdateConfiguration(TenantConfigurationKey.POLLING_TIME_INTERVAL,
|
||||
"00:05:00");
|
||||
tenantConfigurationManagement.addOrUpdateConfiguration(TenantConfigurationKey.MIN_POLLING_TIME_INTERVAL, "00:01:00");
|
||||
tenantConfigurationManagement.addOrUpdateConfiguration(TenantConfigurationKey.POLLING_TIME, "00:05:00");
|
||||
return null;
|
||||
});
|
||||
|
||||
final Target savedTarget = testdataFactory.createTarget("1911");
|
||||
assignDistributionSetWithMaintenanceWindow(ds.getId(), savedTarget.getControllerId(), getTestSchedule(16),
|
||||
assignDistributionSetWithMaintenanceWindow(
|
||||
ds.getId(), savedTarget.getControllerId(), getTestSchedule(16),
|
||||
getTestDuration(10), getTestTimeZone()).getAssignedEntity().iterator().next();
|
||||
|
||||
mvc.perform(get(CONTROLLER_BASE, "default-tenant", "1911"))
|
||||
@@ -627,9 +599,9 @@ class DdiRootControllerTest extends AbstractDDiApiIntegrationTest {
|
||||
|
||||
final Target savedTarget1 = testdataFactory.createTarget("2911");
|
||||
final DistributionSet ds1 = testdataFactory.createDistributionSet("1");
|
||||
assignDistributionSetWithMaintenanceWindow(ds1.getId(), savedTarget1.getControllerId(), getTestSchedule(10),
|
||||
assignDistributionSetWithMaintenanceWindow(
|
||||
ds1.getId(), savedTarget1.getControllerId(), getTestSchedule(10),
|
||||
getTestDuration(10), getTestTimeZone()).getAssignedEntity().iterator().next();
|
||||
|
||||
mvc.perform(get(CONTROLLER_BASE, "default-tenant", "2911"))
|
||||
.andExpect(status().isOk())
|
||||
.andExpect(jsonPath("$.config.polling.sleep", lessThan("00:05:00")))
|
||||
@@ -637,7 +609,8 @@ class DdiRootControllerTest extends AbstractDDiApiIntegrationTest {
|
||||
|
||||
final Target savedTarget2 = testdataFactory.createTarget("3911");
|
||||
final DistributionSet ds2 = testdataFactory.createDistributionSet("2");
|
||||
assignDistributionSetWithMaintenanceWindow(ds2.getId(), savedTarget2.getControllerId(), getTestSchedule(5),
|
||||
assignDistributionSetWithMaintenanceWindow(
|
||||
ds2.getId(), savedTarget2.getControllerId(), getTestSchedule(5),
|
||||
getTestDuration(5), getTestTimeZone()).getAssignedEntity().iterator().next();
|
||||
|
||||
mvc.perform(get(CONTROLLER_BASE, "default-tenant", "3911"))
|
||||
@@ -646,9 +619,9 @@ class DdiRootControllerTest extends AbstractDDiApiIntegrationTest {
|
||||
|
||||
final Target savedTarget3 = testdataFactory.createTarget("4911");
|
||||
final DistributionSet ds3 = testdataFactory.createDistributionSet("3");
|
||||
assignDistributionSetWithMaintenanceWindow(ds3.getId(), savedTarget3.getControllerId(), getTestSchedule(-5),
|
||||
assignDistributionSetWithMaintenanceWindow(
|
||||
ds3.getId(), savedTarget3.getControllerId(), getTestSchedule(-5),
|
||||
getTestDuration(15), getTestTimeZone()).getAssignedEntity().iterator().next();
|
||||
|
||||
mvc.perform(get(CONTROLLER_BASE, "default-tenant", "4911"))
|
||||
.andExpect(status().isOk())
|
||||
.andExpect(jsonPath("$.config.polling.sleep", equalTo("00:05:00")));
|
||||
@@ -661,16 +634,12 @@ class DdiRootControllerTest extends AbstractDDiApiIntegrationTest {
|
||||
void downloadAndUpdateStatusBeforeMaintenanceWindowStartTime() throws Exception {
|
||||
Target savedTarget = testdataFactory.createTarget("1911");
|
||||
final DistributionSet ds = testdataFactory.createDistributionSet("");
|
||||
savedTarget = getFirstAssignedTarget(assignDistributionSetWithMaintenanceWindow(ds.getId(),
|
||||
savedTarget.getControllerId(), getTestSchedule(2), getTestDuration(1), getTestTimeZone()));
|
||||
|
||||
mvc.perform(get(CONTROLLER_BASE, "default-tenant", "1911"))
|
||||
.andExpect(status().isOk());
|
||||
savedTarget = getFirstAssignedTarget(assignDistributionSetWithMaintenanceWindow(
|
||||
ds.getId(), savedTarget.getControllerId(), getTestSchedule(2), getTestDuration(1), getTestTimeZone()));
|
||||
mvc.perform(get(CONTROLLER_BASE, "default-tenant", "1911")).andExpect(status().isOk());
|
||||
|
||||
final Action action = deploymentManagement.findActiveActionsByTarget(savedTarget.getControllerId(), PAGE).getContent().get(0);
|
||||
|
||||
mvc.perform(get(DEPLOYMENT_BASE, tenantAware.getCurrentTenant(), "1911", action.getId())
|
||||
.accept(MediaType.APPLICATION_JSON))
|
||||
mvc.perform(get(DEPLOYMENT_BASE, tenantAware.getCurrentTenant(), "1911", action.getId()).accept(MediaType.APPLICATION_JSON))
|
||||
.andDo(MockMvcResultPrinter.print())
|
||||
.andExpect(status().isOk())
|
||||
.andExpect(jsonPath("$.deployment.download", equalTo("forced")))
|
||||
@@ -685,16 +654,13 @@ class DdiRootControllerTest extends AbstractDDiApiIntegrationTest {
|
||||
void downloadAndUpdateStatusDuringMaintenanceWindow() throws Exception {
|
||||
Target savedTarget = testdataFactory.createTarget("1911");
|
||||
final DistributionSet ds = testdataFactory.createDistributionSet("");
|
||||
savedTarget = getFirstAssignedTarget(assignDistributionSetWithMaintenanceWindow(ds.getId(),
|
||||
savedTarget.getControllerId(), getTestSchedule(-5), getTestDuration(10), getTestTimeZone()));
|
||||
savedTarget = getFirstAssignedTarget(assignDistributionSetWithMaintenanceWindow(
|
||||
ds.getId(), savedTarget.getControllerId(), getTestSchedule(-5), getTestDuration(10), getTestTimeZone()));
|
||||
|
||||
mvc.perform(get(CONTROLLER_BASE, "default-tenant", "1911"))
|
||||
.andExpect(status().isOk());
|
||||
mvc.perform(get(CONTROLLER_BASE, "default-tenant", "1911")).andExpect(status().isOk());
|
||||
|
||||
final Action action = deploymentManagement.findActiveActionsByTarget(savedTarget.getControllerId(), PAGE).getContent().get(0);
|
||||
|
||||
mvc.perform(get(DEPLOYMENT_BASE, tenantAware.getCurrentTenant(), "1911", action.getId())
|
||||
.accept(MediaType.APPLICATION_JSON))
|
||||
mvc.perform(get(DEPLOYMENT_BASE, tenantAware.getCurrentTenant(), "1911", action.getId()).accept(MediaType.APPLICATION_JSON))
|
||||
.andDo(MockMvcResultPrinter.print())
|
||||
.andExpect(status().isOk())
|
||||
.andExpect(jsonPath("$.deployment.download", equalTo("forced")))
|
||||
@@ -725,42 +691,37 @@ class DdiRootControllerTest extends AbstractDDiApiIntegrationTest {
|
||||
@Test
|
||||
void rootRsWithInvalidControllerId() throws Exception {
|
||||
final String invalidControllerId = randomString(Target.CONTROLLER_ID_MAX_SIZE + 1);
|
||||
mvc.perform(get(CONTROLLER_BASE, tenantAware.getCurrentTenant(), invalidControllerId))
|
||||
.andExpect(status().isBadRequest());
|
||||
mvc.perform(get(CONTROLLER_BASE, tenantAware.getCurrentTenant(), invalidControllerId)).andExpect(status().isBadRequest());
|
||||
}
|
||||
|
||||
private void assertAttributesUpdateNotRequestedAfterFailedDeployment(Target target, final DistributionSet ds) throws Exception {
|
||||
target = getFirstAssignedTarget(assignDistributionSet(ds.getId(), target.getControllerId()));
|
||||
final Action action = deploymentManagement.findActiveActionsByTarget(target.getControllerId(), PAGE).getContent().get(0);
|
||||
sendDeploymentActionFeedback(target, action, "closed", "failure")
|
||||
.andExpect(status().isOk());
|
||||
sendDeploymentActionFeedback(target, action, "closed", "failure").andExpect(status().isOk());
|
||||
assertThatAttributesUpdateIsNotRequested(target.getControllerId());
|
||||
}
|
||||
|
||||
private void assertAttributesUpdateRequestedAfterSuccessfulDeployment(Target target, final DistributionSet ds) throws Exception {
|
||||
target = getFirstAssignedTarget(assignDistributionSet(ds.getId(), target.getControllerId()));
|
||||
final Action action = deploymentManagement.findActiveActionsByTarget(target.getControllerId(), PAGE).getContent().get(0);
|
||||
sendDeploymentActionFeedback(target, action, "closed", null)
|
||||
.andExpect(status().isOk());
|
||||
sendDeploymentActionFeedback(target, action, "closed", null).andExpect(status().isOk());
|
||||
assertThatAttributesUpdateIsRequested(target.getControllerId());
|
||||
}
|
||||
|
||||
private void assertThatAttributesUpdateIsRequested(final String targetControllerId) throws Exception {
|
||||
mvc.perform(get(CONTROLLER_BASE, tenantAware.getCurrentTenant(), targetControllerId)
|
||||
.accept(MediaType.APPLICATION_JSON))
|
||||
mvc.perform(get(CONTROLLER_BASE, tenantAware.getCurrentTenant(), targetControllerId).accept(MediaType.APPLICATION_JSON))
|
||||
.andExpect(status().isOk())
|
||||
.andExpect(jsonPath("$._links.configData.href").isNotEmpty());
|
||||
}
|
||||
|
||||
private void assertThatAttributesUpdateIsNotRequested(final String targetControllerId) throws Exception {
|
||||
mvc.perform(get(CONTROLLER_BASE, tenantAware.getCurrentTenant(), targetControllerId)
|
||||
.accept(MediaType.APPLICATION_JSON))
|
||||
mvc.perform(get(CONTROLLER_BASE, tenantAware.getCurrentTenant(), targetControllerId).accept(MediaType.APPLICATION_JSON))
|
||||
.andExpect(status().isOk())
|
||||
.andExpect(jsonPath("$._links.configData").doesNotExist());
|
||||
}
|
||||
|
||||
private ResultActions sendDeploymentActionFeedback(final Target target, final Action action, final String execution, String finished,
|
||||
String message) throws Exception {
|
||||
private ResultActions sendDeploymentActionFeedback(
|
||||
final Target target, final Action action, final String execution, String finished, String message) throws Exception {
|
||||
if (finished == null) {
|
||||
finished = "none";
|
||||
}
|
||||
@@ -770,9 +731,8 @@ class DdiRootControllerTest extends AbstractDDiApiIntegrationTest {
|
||||
|
||||
final String feedback = getJsonActionFeedback(DdiStatus.ExecutionStatus.valueOf(execution.toUpperCase()),
|
||||
DdiResult.FinalResult.valueOf(finished.toUpperCase()), Collections.singletonList(message));
|
||||
return mvc.perform(
|
||||
post(DEPLOYMENT_FEEDBACK, tenantAware.getCurrentTenant(), target.getControllerId(), action.getId())
|
||||
.content(feedback).contentType(MediaType.APPLICATION_JSON));
|
||||
return mvc.perform(post(DEPLOYMENT_FEEDBACK, tenantAware.getCurrentTenant(), target.getControllerId(), action.getId())
|
||||
.content(feedback).contentType(MediaType.APPLICATION_JSON));
|
||||
}
|
||||
|
||||
private ResultActions sendDeploymentActionFeedback(final Target target, final Action action, final String execution, final String finished)
|
||||
@@ -781,12 +741,29 @@ class DdiRootControllerTest extends AbstractDDiApiIntegrationTest {
|
||||
}
|
||||
|
||||
private void assertDeploymentActionIsExposedToTarget(final String controllerId, final long expectedActionId) throws Exception {
|
||||
final String expectedDeploymentBaseLink = String.format("/%s/controller/v1/%s/deploymentBase/%d",
|
||||
final String expectedDeploymentBaseLink = String.format(
|
||||
"/%s/controller/v1/%s/deploymentBase/%d",
|
||||
tenantAware.getCurrentTenant(), controllerId, expectedActionId);
|
||||
mvc.perform(
|
||||
get(CONTROLLER_BASE, tenantAware.getCurrentTenant(), controllerId).accept(MediaType.APPLICATION_JSON))
|
||||
mvc.perform(get(CONTROLLER_BASE, tenantAware.getCurrentTenant(), controllerId).accept(MediaType.APPLICATION_JSON))
|
||||
.andDo(MockMvcResultPrinter.print())
|
||||
.andExpect(status().isOk())
|
||||
.andExpect(jsonPath("$._links.deploymentBase.href", containsString(expectedDeploymentBaseLink)));
|
||||
}
|
||||
|
||||
private void withPollingTime(final String pollingTime, final Callable<Void> runnable) throws Exception {
|
||||
SecurityContextSwitch.runAs(SecurityContextSwitch.withUser("tenantadmin", TENANT_CONFIGURATION),
|
||||
() -> {
|
||||
tenantConfigurationManagement.addOrUpdateConfiguration(TenantConfigurationKey.POLLING_TIME, pollingTime);
|
||||
return null;
|
||||
});
|
||||
try {
|
||||
runnable.call();
|
||||
} finally {
|
||||
SecurityContextSwitch.runAs(SecurityContextSwitch.withUser("tenantadmin", TENANT_CONFIGURATION),
|
||||
() -> {
|
||||
tenantConfigurationManagement.deleteConfiguration(TenantConfigurationKey.POLLING_TIME);
|
||||
return null;
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -38,11 +38,11 @@ public class GatewayTokenAuthenticator extends Authenticator.AbstractAuthenticat
|
||||
super(tenantConfigurationManagement, tenantAware, systemSecurityContext);
|
||||
gatewaySecurityTokenKeyConfigRunner = () -> {
|
||||
log.trace("retrieving configuration value for configuration key {}",
|
||||
TenantConfigurationKey.AUTHENTICATION_MODE_GATEWAY_SECURITY_TOKEN_KEY);
|
||||
TenantConfigurationKey.AUTHENTICATION_GATEWAY_SECURITY_TOKEN_KEY);
|
||||
|
||||
return systemSecurityContext
|
||||
.runAsSystem(() -> tenantConfigurationManagement
|
||||
.getConfigurationValue(TenantConfigurationKey.AUTHENTICATION_MODE_GATEWAY_SECURITY_TOKEN_KEY, String.class)
|
||||
.getConfigurationValue(TenantConfigurationKey.AUTHENTICATION_GATEWAY_SECURITY_TOKEN_KEY, String.class)
|
||||
.getValue());
|
||||
};
|
||||
}
|
||||
@@ -78,6 +78,6 @@ public class GatewayTokenAuthenticator extends Authenticator.AbstractAuthenticat
|
||||
|
||||
@Override
|
||||
protected String getTenantConfigurationKey() {
|
||||
return TenantConfigurationKey.AUTHENTICATION_MODE_GATEWAY_SECURITY_TOKEN_ENABLED;
|
||||
return TenantConfigurationKey.AUTHENTICATION_GATEWAY_SECURITY_TOKEN_ENABLED;
|
||||
}
|
||||
}
|
||||
@@ -58,7 +58,7 @@ public class SecurityHeaderAuthenticator extends Authenticator.AbstractAuthentic
|
||||
this.sslIssuerHashBasicHeader = caAuthorityNameHeader;
|
||||
sslIssuerNameConfigTenantRunner = () -> systemSecurityContext.runAsSystem(
|
||||
() -> tenantConfigurationManagement.getConfigurationValue(
|
||||
TenantConfigurationKey.AUTHENTICATION_MODE_HEADER_AUTHORITY_NAME, String.class).getValue());
|
||||
TenantConfigurationKey.AUTHENTICATION_HEADER_AUTHORITY_NAME, String.class).getValue());
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -95,7 +95,7 @@ public class SecurityHeaderAuthenticator extends Authenticator.AbstractAuthentic
|
||||
|
||||
@Override
|
||||
protected String getTenantConfigurationKey() {
|
||||
return TenantConfigurationKey.AUTHENTICATION_MODE_HEADER_ENABLED;
|
||||
return TenantConfigurationKey.AUTHENTICATION_HEADER_ENABLED;
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
@@ -77,6 +77,6 @@ public class SecurityTokenAuthenticator extends Authenticator.AbstractAuthentica
|
||||
|
||||
@Override
|
||||
protected String getTenantConfigurationKey() {
|
||||
return TenantConfigurationKey.AUTHENTICATION_MODE_TARGET_SECURITY_TOKEN_ENABLED;
|
||||
return TenantConfigurationKey.AUTHENTICATION_TARGET_SECURITY_TOKEN_ENABLED;
|
||||
}
|
||||
}
|
||||
@@ -67,10 +67,10 @@ class GatewayTokenAuthenticatorTest {
|
||||
void testWithGwToken() {
|
||||
final ControllerSecurityToken securityToken = prepareSecurityToken(GATEWAY_TOKEN);
|
||||
when(tenantConfigurationManagementMock.getConfigurationValue(
|
||||
TenantConfigurationKey.AUTHENTICATION_MODE_GATEWAY_SECURITY_TOKEN_KEY, String.class))
|
||||
TenantConfigurationKey.AUTHENTICATION_GATEWAY_SECURITY_TOKEN_KEY, String.class))
|
||||
.thenReturn(CONFIG_VALUE_GW_TOKEN);
|
||||
when(tenantConfigurationManagementMock.getConfigurationValue(
|
||||
TenantConfigurationKey.AUTHENTICATION_MODE_GATEWAY_SECURITY_TOKEN_ENABLED, Boolean.class))
|
||||
TenantConfigurationKey.AUTHENTICATION_GATEWAY_SECURITY_TOKEN_ENABLED, Boolean.class))
|
||||
.thenReturn(CONFIG_VALUE_ENABLED);
|
||||
|
||||
assertThat(authenticator.authenticate(securityToken))
|
||||
@@ -85,10 +85,10 @@ class GatewayTokenAuthenticatorTest {
|
||||
void testWithBadGwToken() {
|
||||
final ControllerSecurityToken securityToken = prepareSecurityToken(UNKNOWN_TOKEN);
|
||||
when(tenantConfigurationManagementMock.getConfigurationValue(
|
||||
TenantConfigurationKey.AUTHENTICATION_MODE_GATEWAY_SECURITY_TOKEN_KEY, String.class))
|
||||
TenantConfigurationKey.AUTHENTICATION_GATEWAY_SECURITY_TOKEN_KEY, String.class))
|
||||
.thenReturn(CONFIG_VALUE_GW_TOKEN);
|
||||
when(tenantConfigurationManagementMock.getConfigurationValue(
|
||||
TenantConfigurationKey.AUTHENTICATION_MODE_GATEWAY_SECURITY_TOKEN_ENABLED, Boolean.class))
|
||||
TenantConfigurationKey.AUTHENTICATION_GATEWAY_SECURITY_TOKEN_ENABLED, Boolean.class))
|
||||
.thenReturn(CONFIG_VALUE_ENABLED);
|
||||
|
||||
assertThat(authenticator.authenticate(securityToken)).isNull();
|
||||
@@ -109,7 +109,7 @@ class GatewayTokenAuthenticatorTest {
|
||||
void testWithGwTokenButDisabled() {
|
||||
final ControllerSecurityToken securityToken = prepareSecurityToken(GATEWAY_TOKEN);
|
||||
when(tenantConfigurationManagementMock.getConfigurationValue(
|
||||
TenantConfigurationKey.AUTHENTICATION_MODE_GATEWAY_SECURITY_TOKEN_ENABLED, Boolean.class))
|
||||
TenantConfigurationKey.AUTHENTICATION_GATEWAY_SECURITY_TOKEN_ENABLED, Boolean.class))
|
||||
.thenReturn(CONFIG_VALUE_DISABLED);
|
||||
|
||||
assertThat(authenticator.authenticate(securityToken)).isNull();
|
||||
|
||||
@@ -78,10 +78,10 @@ class SecurityHeaderAuthenticatorTest {
|
||||
void testWithSingleKnownHash() {
|
||||
final ControllerSecurityToken securityToken = prepareSecurityToken(SINGLE_HASH);
|
||||
when(tenantConfigurationManagementMock.getConfigurationValue(
|
||||
TenantConfigurationKey.AUTHENTICATION_MODE_HEADER_AUTHORITY_NAME, String.class))
|
||||
TenantConfigurationKey.AUTHENTICATION_HEADER_AUTHORITY_NAME, String.class))
|
||||
.thenReturn(CONFIG_VALUE_SINGLE_HASH);
|
||||
when(tenantConfigurationManagementMock.getConfigurationValue(
|
||||
TenantConfigurationKey.AUTHENTICATION_MODE_HEADER_ENABLED, Boolean.class))
|
||||
TenantConfigurationKey.AUTHENTICATION_HEADER_ENABLED, Boolean.class))
|
||||
.thenReturn(CONFIG_VALUE_ENABLED);
|
||||
|
||||
assertThat(authenticator.authenticate(securityToken))
|
||||
@@ -95,10 +95,10 @@ class SecurityHeaderAuthenticatorTest {
|
||||
@Test
|
||||
void testWithMultipleKnownHashes() {
|
||||
when(tenantConfigurationManagementMock.getConfigurationValue(
|
||||
TenantConfigurationKey.AUTHENTICATION_MODE_HEADER_AUTHORITY_NAME, String.class))
|
||||
TenantConfigurationKey.AUTHENTICATION_HEADER_AUTHORITY_NAME, String.class))
|
||||
.thenReturn(CONFIG_VALUE_MULTI_HASH);
|
||||
when(tenantConfigurationManagementMock.getConfigurationValue(
|
||||
TenantConfigurationKey.AUTHENTICATION_MODE_HEADER_ENABLED, Boolean.class))
|
||||
TenantConfigurationKey.AUTHENTICATION_HEADER_ENABLED, Boolean.class))
|
||||
.thenReturn(CONFIG_VALUE_ENABLED);
|
||||
|
||||
assertThat(authenticator.authenticate(prepareSecurityToken(SINGLE_HASH)))
|
||||
@@ -119,10 +119,10 @@ class SecurityHeaderAuthenticatorTest {
|
||||
void testWithUnknownHash() {
|
||||
final ControllerSecurityToken securityToken = prepareSecurityToken(UNKNOWN_HASH);
|
||||
when(tenantConfigurationManagementMock.getConfigurationValue(
|
||||
TenantConfigurationKey.AUTHENTICATION_MODE_HEADER_AUTHORITY_NAME, String.class))
|
||||
TenantConfigurationKey.AUTHENTICATION_HEADER_AUTHORITY_NAME, String.class))
|
||||
.thenReturn(CONFIG_VALUE_MULTI_HASH);
|
||||
when(tenantConfigurationManagementMock.getConfigurationValue(
|
||||
TenantConfigurationKey.AUTHENTICATION_MODE_HEADER_ENABLED, Boolean.class))
|
||||
TenantConfigurationKey.AUTHENTICATION_HEADER_ENABLED, Boolean.class))
|
||||
.thenReturn(CONFIG_VALUE_ENABLED);
|
||||
|
||||
assertThat(authenticator.authenticate(securityToken)).isNull();
|
||||
@@ -155,7 +155,7 @@ class SecurityHeaderAuthenticatorTest {
|
||||
void testWithSingleKnownHashButDisabled() {
|
||||
final ControllerSecurityToken securityToken = prepareSecurityToken(SINGLE_HASH);
|
||||
when(tenantConfigurationManagementMock.getConfigurationValue(
|
||||
TenantConfigurationKey.AUTHENTICATION_MODE_HEADER_ENABLED, Boolean.class))
|
||||
TenantConfigurationKey.AUTHENTICATION_HEADER_ENABLED, Boolean.class))
|
||||
.thenReturn(CONFIG_VALUE_DISABLED);
|
||||
|
||||
assertThat(authenticator.authenticate(securityToken)).isNull();
|
||||
|
||||
@@ -72,7 +72,7 @@ class SecurityTokenAuthenticatorTest {
|
||||
void testWithSecToken() {
|
||||
final ControllerSecurityToken securityToken = prepareSecurityToken(SECURITY_TOKEN);
|
||||
when(tenantConfigurationManagementMock.getConfigurationValue(
|
||||
TenantConfigurationKey.AUTHENTICATION_MODE_TARGET_SECURITY_TOKEN_ENABLED, Boolean.class))
|
||||
TenantConfigurationKey.AUTHENTICATION_TARGET_SECURITY_TOKEN_ENABLED, Boolean.class))
|
||||
.thenReturn(CONFIG_VALUE_ENABLED);
|
||||
|
||||
final Target target = Mockito.mock(Target.class);
|
||||
@@ -92,7 +92,7 @@ class SecurityTokenAuthenticatorTest {
|
||||
void testWithBadSecToken() {
|
||||
final ControllerSecurityToken securityToken = prepareSecurityToken(UNKNOWN_TOKEN);
|
||||
when(tenantConfigurationManagementMock.getConfigurationValue(
|
||||
TenantConfigurationKey.AUTHENTICATION_MODE_TARGET_SECURITY_TOKEN_ENABLED, Boolean.class))
|
||||
TenantConfigurationKey.AUTHENTICATION_TARGET_SECURITY_TOKEN_ENABLED, Boolean.class))
|
||||
.thenReturn(CONFIG_VALUE_ENABLED);
|
||||
|
||||
assertThat(authenticator.authenticate(securityToken)).isNull();
|
||||
@@ -113,7 +113,7 @@ class SecurityTokenAuthenticatorTest {
|
||||
void testWithSecTokenButDisabled() {
|
||||
final ControllerSecurityToken securityToken = prepareSecurityToken(SECURITY_TOKEN);
|
||||
when(tenantConfigurationManagementMock.getConfigurationValue(
|
||||
TenantConfigurationKey.AUTHENTICATION_MODE_TARGET_SECURITY_TOKEN_ENABLED, Boolean.class))
|
||||
TenantConfigurationKey.AUTHENTICATION_TARGET_SECURITY_TOKEN_ENABLED, Boolean.class))
|
||||
.thenReturn(CONFIG_VALUE_DISABLED);
|
||||
|
||||
assertThat(authenticator.authenticate(securityToken)).isNull();
|
||||
|
||||
@@ -32,8 +32,7 @@ import org.springframework.hateoas.RepresentationModel;
|
||||
@JsonIgnoreProperties(ignoreUnknown = true)
|
||||
@Schema(description = """
|
||||
**properties**:
|
||||
* **rollout.approval.enabled** - Boolean, The configuration key 'rollout.approval.enabled' defines if
|
||||
approval mode for Rollout Management is enabled.
|
||||
* **rollout.approval.enabled** - Boolean, The configuration key 'rollout.approval.enabled' defines if approval mode for Rollout Management is enabled.
|
||||
* **repository.actions.autoclose.enabled** - Boolean, The configuration key 'repository.actions.autoclose.enabled' defines if autoclose running actions with new Distribution Set assignment is enabled.
|
||||
* **user.confirmation.flow.enabled** - Boolean, The configuration key 'user.confirmation.flow.enabled' defines if confirmation is required when distribution set is assigned to target.
|
||||
* **authentication.gatewaytoken.enabled** - Boolean, The configuration key 'authentication.gatewaytoken.enabled' defines if the authentication mode 'gateway security token' is enabled.
|
||||
@@ -42,13 +41,12 @@ import org.springframework.hateoas.RepresentationModel;
|
||||
* **authentication.header.enabled** - Boolean, The configuration key 'authentication.header.enabled' defines if the authentication mode 'authority header' is enabled.
|
||||
* **maintenanceWindowPollCount** - Integer, The configuration key 'maintenanceWindowPollCount' defines the polling interval so that controller tries to poll at least these many times between the last polling and before start of maintenance window. The polling interval is bounded by configured pollingTime and minPollingTime. The polling interval is modified as per following scheme: pollingTime(@time=t) = (maintenanceWindowStartTime - t)/maintenanceWindowPollCount.
|
||||
* **authentication.targettoken.enabled** - Boolean, The configuration key 'authentication.targettoken.enabled' defines if the authentication mode 'target security token' is enabled.
|
||||
* **pollingTime** - String, The configuration key 'pollingTime' defines the time interval between two poll requests of a target.
|
||||
* **anonymous.download.enabled** - Boolean, The configuration key 'anonymous.download.enabled' defines if the anonymous download mode is enabled.
|
||||
* **authentication.header.authority** - String, The configuration key 'authentication.header.authority' defines the name of the 'authority header'.
|
||||
* **minPollingTime** - String, The configuration key 'minPollingTime' defines the smallest time interval permitted between two poll requests of a target.
|
||||
* **pollingTime** - String, The configuration key 'pollingTime' defines the time interval between two poll requests of a target.
|
||||
* **pollingOverdueTime** - String, The configuration key 'pollingOverdueTime' defines the period of time after the SP server will recognize a target, which is not performing pull requests anymore.
|
||||
* **authentication.header.authority** - String, The configuration key 'authentication.header.authority' defines the name of the 'authority header'.
|
||||
* **authentication.gatewaytoken.key** - String, The configuration key 'authentication.gatewaytoken.key' defines the key of the gateway security token.
|
||||
* **action.cleanup.actionStatus** - String, The configuration key 'action.cleanup.actionStatus' defines the list of action status that should be taken into account for the cleanup.
|
||||
* **pollingOverdueTime** - String, The configuration key 'pollingOverdueTime' defines the period of time after the SP server will recognize a target, which is not performing pull requests anymore.
|
||||
* **multi.assignments.enabled** - Boolean, The configuration key 'multi.assignments.enabled' defines if multiple distribution sets can be assigned to the same targets.
|
||||
* **batch.assignments.enabled** - Boolean, The configuration key 'batch.assignments.enabled' defines if distribution set can be assigned to multiple targets in a single batch message.
|
||||
* **implicit.lock.enabled** - Boolean (true by default), The configuration key 'implicit.lock.enabled' defines if distribution set and their software modules shall be implicitly locked when assigned to target, rollout or target filter.
|
||||
|
||||
@@ -69,7 +69,7 @@ public class MgmtTenantManagementResourceTest extends AbstractManagementApiInteg
|
||||
void getTenantConfiguration() throws Exception {
|
||||
//Test TenantConfiguration property
|
||||
mvc.perform(get(MgmtRestConstants.SYSTEM_V1_REQUEST_MAPPING + "/configs/{keyName}",
|
||||
TenantConfigurationProperties.TenantConfigurationKey.AUTHENTICATION_MODE_GATEWAY_SECURITY_TOKEN_KEY))
|
||||
TenantConfigurationProperties.TenantConfigurationKey.AUTHENTICATION_GATEWAY_SECURITY_TOKEN_KEY))
|
||||
.andDo(MockMvcResultPrinter.print())
|
||||
.andExpect(status().isOk());
|
||||
}
|
||||
@@ -98,7 +98,7 @@ public class MgmtTenantManagementResourceTest extends AbstractManagementApiInteg
|
||||
final String json = mapper.writeValueAsString(bodyPut);
|
||||
|
||||
mvc.perform(put(MgmtRestConstants.SYSTEM_V1_REQUEST_MAPPING + "/configs/{keyName}",
|
||||
TenantConfigurationProperties.TenantConfigurationKey.AUTHENTICATION_MODE_GATEWAY_SECURITY_TOKEN_KEY).content(json)
|
||||
TenantConfigurationProperties.TenantConfigurationKey.AUTHENTICATION_GATEWAY_SECURITY_TOKEN_KEY).content(json)
|
||||
.contentType(MediaType.APPLICATION_JSON))
|
||||
.andDo(MockMvcResultPrinter.print())
|
||||
.andExpect(status().isOk());
|
||||
@@ -282,7 +282,7 @@ public class MgmtTenantManagementResourceTest extends AbstractManagementApiInteg
|
||||
@Test
|
||||
void deleteTenantConfiguration() throws Exception {
|
||||
mvc.perform(delete(MgmtRestConstants.SYSTEM_V1_REQUEST_MAPPING + "/configs/{keyName}",
|
||||
TenantConfigurationProperties.TenantConfigurationKey.AUTHENTICATION_MODE_GATEWAY_SECURITY_TOKEN_KEY))
|
||||
TenantConfigurationProperties.TenantConfigurationKey.AUTHENTICATION_GATEWAY_SECURITY_TOKEN_KEY))
|
||||
.andDo(MockMvcResultPrinter.print())
|
||||
.andExpect(status().isOk());
|
||||
}
|
||||
@@ -305,7 +305,7 @@ public class MgmtTenantManagementResourceTest extends AbstractManagementApiInteg
|
||||
void getTenantConfigurationReadGWToken() throws Exception {
|
||||
SecurityContextSwitch.runAs(SecurityContextSwitch.withUser("tenant_admin", SpPermission.TENANT_CONFIGURATION), () -> {
|
||||
tenantConfigurationManagement.addOrUpdateConfiguration(
|
||||
TenantConfigurationProperties.TenantConfigurationKey.AUTHENTICATION_MODE_GATEWAY_SECURITY_TOKEN_KEY,
|
||||
TenantConfigurationProperties.TenantConfigurationKey.AUTHENTICATION_GATEWAY_SECURITY_TOKEN_KEY,
|
||||
"123");
|
||||
return null;
|
||||
});
|
||||
|
||||
@@ -35,6 +35,7 @@ import org.eclipse.hawkbit.repository.model.SoftwareModule;
|
||||
import org.eclipse.hawkbit.repository.model.SoftwareModuleMetadata;
|
||||
import org.eclipse.hawkbit.repository.model.Target;
|
||||
import org.eclipse.hawkbit.repository.model.TargetUpdateStatus;
|
||||
import org.eclipse.hawkbit.tenancy.configuration.ControllerPollProperties;
|
||||
import org.eclipse.hawkbit.tenancy.configuration.TenantConfigurationProperties.TenantConfigurationKey;
|
||||
import org.springframework.data.domain.Page;
|
||||
import org.springframework.data.domain.Pageable;
|
||||
@@ -188,38 +189,24 @@ public interface ControllerManagement {
|
||||
/**
|
||||
* Returns configured polling interval at which the controller polls hawkBit server.
|
||||
*
|
||||
* @return current {@link TenantConfigurationKey#POLLING_TIME_INTERVAL}.
|
||||
* @param target {@link Target} for which polling time is calculated (it could be overridden for a specific targets).
|
||||
* @return current {@link TenantConfigurationKey#POLLING_TIME}.
|
||||
*/
|
||||
@PreAuthorize(SpringEvalExpressions.IS_CONTROLLER)
|
||||
String getPollingTime();
|
||||
|
||||
/**
|
||||
* Returns the configured minimum polling interval.
|
||||
*
|
||||
* @return current {@link TenantConfigurationKey#MIN_POLLING_TIME_INTERVAL}.
|
||||
*/
|
||||
@PreAuthorize(SpringEvalExpressions.IS_CONTROLLER)
|
||||
String getMinPollingTime();
|
||||
|
||||
/**
|
||||
* Returns the count to be used for reducing polling interval while calling {@link ControllerManagement#getPollingTimeForAction(Action)}.
|
||||
*
|
||||
* @return configured value of {@link TenantConfigurationKey#MAINTENANCE_WINDOW_POLL_COUNT}.
|
||||
*/
|
||||
@PreAuthorize(SpringEvalExpressions.IS_CONTROLLER)
|
||||
int getMaintenanceWindowPollCount();
|
||||
String getPollingTime(Target target);
|
||||
|
||||
/**
|
||||
* 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}.
|
||||
* Poll time keeps reducing with MinPollingTime as lower limit {@link ControllerPollProperties#getMinPollingTime()}. After the start
|
||||
* of maintenance window, it resets to default {@link TenantConfigurationKey#POLLING_TIME}.
|
||||
*
|
||||
* @param target {@link Target} for which polling time is calculated
|
||||
* @param action {@link Action} for which polling time is calculated based on it having maintenance window or not
|
||||
* @return current {@link TenantConfigurationKey#POLLING_TIME_INTERVAL}.
|
||||
* @return current {@link TenantConfigurationKey#POLLING_TIME}.
|
||||
*/
|
||||
@PreAuthorize(SpringEvalExpressions.IS_CONTROLLER)
|
||||
String getPollingTimeForAction(Action action);
|
||||
String getPollingTimeForAction(Target target, Action action);
|
||||
|
||||
/**
|
||||
* Checks if a given target has currently or has even been assigned to the given artifact through the action history list. This can e.g.
|
||||
|
||||
@@ -31,11 +31,10 @@ public interface TenantConfigurationManagement {
|
||||
* Adds or updates a specific configuration for a specific tenant.
|
||||
*
|
||||
* @param configurationKeyName the key of the configuration
|
||||
* @param value the configuration value which will be written into the
|
||||
* database.
|
||||
* @param value the configuration value which will be written into the database.
|
||||
* @return the configuration value which was just written into the database.
|
||||
* @throws TenantConfigurationValidatorException if the {@code propertyType} and the value in general does not
|
||||
* match the expected type and format defined by the Key
|
||||
* @throws TenantConfigurationValidatorException if the {@code propertyType} and the value in general does not match the expected type and
|
||||
* format defined by the Key
|
||||
* @throws ConversionFailedException if the property cannot be converted to the given
|
||||
*/
|
||||
@PreAuthorize(value = SpringEvalExpressions.HAS_AUTH_TENANT_CONFIGURATION)
|
||||
|
||||
@@ -16,11 +16,9 @@ import lombok.Data;
|
||||
import org.springframework.boot.context.properties.ConfigurationProperties;
|
||||
|
||||
/**
|
||||
* Defines global configuration for the controllers/clients on the provisioning
|
||||
* targets/devices.
|
||||
*
|
||||
* Note: many of the controller related properties can be overridden on tenant
|
||||
* level.
|
||||
* Defines global configuration for the controllers/clients on the provisioning targets/devices.
|
||||
* <p/>
|
||||
* Note: many of the controller related properties can be overridden on tenant level.
|
||||
*/
|
||||
@Data
|
||||
@ConfigurationProperties(prefix = "hawkbit.controller")
|
||||
@@ -30,22 +28,24 @@ public class ControllerPollProperties implements Serializable {
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
/**
|
||||
* Maximum polling time that can be configured system-wide and by tenant in HH:MM:SS notation.
|
||||
* Maximum polling time that can be configured system-wide and by tenant in HH:mm:ss notation.
|
||||
*/
|
||||
private String maxPollingTime = "23:59:59";
|
||||
|
||||
/**
|
||||
* Minimum polling time that can be configured by a tenant in HH:MM:SS notation.
|
||||
* Minimum polling time that can be configured by a tenant in HH:mm:ss notation.
|
||||
*/
|
||||
private String minPollingTime = "00:00:30";
|
||||
|
||||
/**
|
||||
* Controller polling time that can be configured system-wide and by tenant in HH:MM:SS notation.
|
||||
* Controller polling time that can be configured system-wide and by tenant in HH:mm:ss(~\d{1,2}%)? notation, plus
|
||||
* followed (optionally and ordered) by a comma separated @lt;QL filter@gt; -@gt; polling time that overrides the
|
||||
* default polling time for the targets that match the filter.
|
||||
*/
|
||||
private String pollingTime = "00:05:00";
|
||||
|
||||
/**
|
||||
* Controller polling overdue time that can be configured system-wide and by tenant in HH:MM:SS notation.
|
||||
* Controller polling overdue time that can be configured system-wide and by tenant in HH:mm:ss notation.
|
||||
*/
|
||||
private String pollingOverdueTime = "00:05:00";
|
||||
|
||||
|
||||
@@ -13,101 +13,78 @@ import java.time.Duration;
|
||||
import java.time.LocalTime;
|
||||
import java.time.format.DateTimeFormatter;
|
||||
import java.time.format.DateTimeParseException;
|
||||
import java.time.temporal.TemporalAccessor;
|
||||
|
||||
import lombok.NoArgsConstructor;
|
||||
|
||||
/**
|
||||
* This class is a helper for converting a duration into a string and for the
|
||||
* other way. The string is in the format expected in configuration and database
|
||||
* {@link #DURATION_FORMAT}.
|
||||
* This class is a helper for converting a duration into a string and for the other way. The string is in the format expected
|
||||
* in configuration and database - in {@link Duration} default format or in custom format like "01:00:00" or "01:01:50:50"
|
||||
* (starting with seconds, minutes, hours, days from the end).
|
||||
*/
|
||||
@NoArgsConstructor(access = lombok.AccessLevel.PRIVATE)
|
||||
public final class DurationHelper {
|
||||
|
||||
/**
|
||||
* Format of the String expected in configuration file and in the database.
|
||||
*/
|
||||
public static final String DURATION_FORMAT = "HH:mm:ss";
|
||||
|
||||
private DurationHelper() {
|
||||
// utility class
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a {@link DurationRangeValidator}.
|
||||
*
|
||||
* @param min imum of range.
|
||||
* @param max imum of range.
|
||||
* @return {@link DurationRangeValidator} range.
|
||||
*/
|
||||
public static DurationRangeValidator durationRangeValidator(final Duration min, final Duration max) {
|
||||
return new DurationRangeValidator(min, max);
|
||||
}
|
||||
private static final DateTimeFormatter DURATION_FORMATER = DateTimeFormatter.ofPattern("HH:mm:ss");
|
||||
private static final long SECONDS_PER_DAY = 24 * 60 * 60L; // 24 hours * 60 minutes * 60 seconds
|
||||
private static final Duration DAY = Duration.ofDays(1);
|
||||
|
||||
/**
|
||||
* Converts a Duration into a formatted String
|
||||
*
|
||||
* @param duration duration, which will be converted into a formatted String
|
||||
* @return String in the duration format, specified at
|
||||
* {@link #DURATION_FORMAT}
|
||||
* @return String in the duration format, specified as HH:mm:ss or d+:HH:mm:ss
|
||||
*/
|
||||
public static String durationToFormattedString(final Duration duration) {
|
||||
public static String toString(final Duration duration) {
|
||||
if (duration == null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return LocalTime.ofNanoOfDay(duration.toNanos()).format(DateTimeFormatter.ofPattern(DURATION_FORMAT));
|
||||
if (duration.compareTo(DAY) < 0) { // backward compatible HH:mm:ss
|
||||
return LocalTime.ofSecondOfDay(duration.toSeconds()).format(DURATION_FORMATER);
|
||||
} else { // custom format d+:HH:mm:ss
|
||||
return duration.toDays() + ":" + LocalTime.ofSecondOfDay(duration.toSeconds() % SECONDS_PER_DAY).format(DURATION_FORMATER);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Converts a formatted String into a Duration object.
|
||||
*
|
||||
* @param formattedDuration String in {@link #DURATION_FORMAT}
|
||||
* @return duration
|
||||
* @param durationStr String in {@link Duration} default format or in custom format like "01:00:00" or "01:01:50:50"
|
||||
* (starting with seconds, minutes, hours, days from the end)
|
||||
* @return duration as a {@link Duration} object
|
||||
* @throws DateTimeParseException when String is in wrong format
|
||||
*/
|
||||
public static Duration formattedStringToDuration(final String formattedDuration) {
|
||||
if (formattedDuration == null) {
|
||||
public static Duration fromString(final String durationStr) {
|
||||
if (durationStr == null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
final TemporalAccessor ta = DateTimeFormatter.ofPattern(DURATION_FORMAT).parse(formattedDuration.trim());
|
||||
return Duration.between(LocalTime.MIDNIGHT, LocalTime.from(ta));
|
||||
}
|
||||
|
||||
/**
|
||||
* converts values of time constants to a Duration object..
|
||||
*
|
||||
* @param hours count of hours
|
||||
* @param minutes count of minutes
|
||||
* @param seconds count of seconds
|
||||
* @return duration
|
||||
*/
|
||||
public static Duration getDurationByTimeValues(final long hours, final long minutes, final long seconds) {
|
||||
return Duration.ofHours(hours).plusMinutes(minutes).plusSeconds(seconds);
|
||||
}
|
||||
|
||||
/**
|
||||
* Duration validation utility class. Checks if the requested duration is in
|
||||
* the defined min/max range.
|
||||
*/
|
||||
public static final class DurationRangeValidator {
|
||||
|
||||
private final Duration min;
|
||||
private final Duration max;
|
||||
|
||||
private DurationRangeValidator(final Duration min, final Duration max) {
|
||||
this.min = min;
|
||||
this.max = max;
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if the requested duration is in the defined min/max range.
|
||||
*
|
||||
* @param duration to checked
|
||||
* @return <code>true</code> if in time range
|
||||
*/
|
||||
public boolean isWithinRange(final Duration duration) {
|
||||
return duration.compareTo(min) >= 0 && duration.compareTo(max) <= 0;
|
||||
if (durationStr.charAt(0) == 'P') {
|
||||
// Handle ISO-8601 format, e.g., "PT1H30M"
|
||||
return Duration.parse(durationStr);
|
||||
} else {
|
||||
// Handle custom format, e.g., "01:00:00" or "01:01:50:50"
|
||||
final String[] split = durationStr.split(":");
|
||||
if (split.length == 1) { // ss
|
||||
return Duration.ofSeconds(Long.parseLong(split[0]));
|
||||
} else if (split.length == 2) { // mm:ss
|
||||
return Duration
|
||||
.ofMinutes(Long.parseLong(split[0]))
|
||||
.plusSeconds(Long.parseLong(split[1]));
|
||||
} else if (split.length == 3) { // HH:mm:ss
|
||||
return Duration
|
||||
.ofHours(Long.parseLong(split[0]))
|
||||
.plusMinutes(Long.parseLong(split[1]))
|
||||
.plusSeconds(Long.parseLong(split[2]));
|
||||
} else if (split.length == 4) { // d:HH:mm:ss
|
||||
return Duration
|
||||
.ofDays(Long.parseLong(split[0]))
|
||||
.plusHours(Long.parseLong(split[1]))
|
||||
.plusMinutes(Long.parseLong(split[2]))
|
||||
.plusSeconds(Long.parseLong(split[3]));
|
||||
} else {
|
||||
throw new IllegalArgumentException("No more then 4 chunks (split by ':') are allowed in duration");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,107 @@
|
||||
/**
|
||||
* Copyright (c) 2025 Contributors to the Eclipse Foundation
|
||||
*
|
||||
* This program and the accompanying materials are made
|
||||
* available under the terms of the Eclipse Public License 2.0
|
||||
* which is available at https://www.eclipse.org/legal/epl-2.0/
|
||||
*
|
||||
* SPDX-License-Identifier: EPL-2.0
|
||||
*/
|
||||
package org.eclipse.hawkbit.tenancy.configuration;
|
||||
|
||||
import java.time.Duration;
|
||||
import java.time.format.DateTimeParseException;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
import java.util.Optional;
|
||||
import java.util.Random;
|
||||
import java.util.regex.Matcher;
|
||||
import java.util.regex.Pattern;
|
||||
|
||||
import lombok.Value;
|
||||
import org.eclipse.hawkbit.repository.exception.TenantConfigurationValidatorException;
|
||||
|
||||
@Value
|
||||
public class PollingTime {
|
||||
|
||||
private static final Pattern OVERRIDE_PATTERN = Pattern.compile(
|
||||
"\\s{0,5},\\s{0,5}(?<qlStr>[^,]*)\\s{0,5}->\\s{0,5}(?<pollInterval>" + PollingInterval.POLLING_INTERVALE_REGEX + ")\\s{0,5}");
|
||||
|
||||
PollingInterval pollingInterval;
|
||||
List<Override> overrides;
|
||||
|
||||
public PollingTime(final String pollingTime) {
|
||||
final int indexOfComma = pollingTime.indexOf(',');
|
||||
if (indexOfComma == -1) { // no overrides
|
||||
pollingInterval = new PollingInterval(pollingTime);
|
||||
overrides = Collections.emptyList();
|
||||
} else {
|
||||
// Extract the main polling interval and overrides
|
||||
final String pollingIntervalStr = pollingTime.substring(0, indexOfComma);
|
||||
pollingInterval = new PollingInterval(pollingIntervalStr);
|
||||
overrides = new ArrayList<>();
|
||||
final String overridesStr = pollingTime.substring(indexOfComma).trim(); // with initial comma
|
||||
final Matcher overridesMatcher = OVERRIDE_PATTERN.matcher(overridesStr);
|
||||
for (int start = 0; start < overridesStr.length(); start = overridesMatcher.end()) {
|
||||
if (overridesMatcher.find(start)) {
|
||||
overrides.add(new Override(
|
||||
overridesMatcher.group("qlStr").trim(),
|
||||
new PollingInterval(overridesMatcher.group("pollInterval").trim())));
|
||||
} else {
|
||||
throw new TenantConfigurationValidatorException("Invalid pollingTime overrides: " + overridesStr);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Value
|
||||
public static class PollingInterval {
|
||||
|
||||
private static final Random RANDOM = new Random();
|
||||
|
||||
public static final String POLLING_INTERVALE_REGEX = "\\s{0,5}(?<pollingInterval>\\d{2}:[0-5]\\d:[0-5]\\d)\\s{0,5}(~(?<deviationPercent>\\d{1,2})%)?\\s{0,5}";
|
||||
private static final Pattern POLLING_INTERVAL_PATTERN = Pattern.compile(POLLING_INTERVALE_REGEX);
|
||||
|
||||
Duration interval;
|
||||
int deviationPercent;
|
||||
|
||||
public PollingInterval(final String pollingInterval) {
|
||||
final Matcher matcher = POLLING_INTERVAL_PATTERN.matcher(pollingInterval);
|
||||
if (matcher.matches()) {
|
||||
try {
|
||||
this.interval = DurationHelper.fromString(matcher.group("pollingInterval"));
|
||||
} catch (final DateTimeParseException ex) {
|
||||
throw new TenantConfigurationValidatorException(
|
||||
"The given configuration value is expected as a string in the format HH:mm:ss(~\\d{1,2})?.");
|
||||
}
|
||||
this.deviationPercent = Optional.ofNullable(matcher.group("deviationPercent")).map(Integer::parseInt).orElse(0);
|
||||
} else {
|
||||
throw new TenantConfigurationValidatorException("Invalid pollingInterval: " + pollingInterval);
|
||||
}
|
||||
}
|
||||
|
||||
public String getFormattedIntervalWithDeviation(final Duration minPollingTime, final Duration maxPollingTime) {
|
||||
if (deviationPercent > 0) {
|
||||
final long millis = interval.toMillis();
|
||||
final long maxDeviationMillis = (millis * deviationPercent) / 100;
|
||||
final long deviation = RANDOM.nextLong(-maxDeviationMillis, maxDeviationMillis + 1);
|
||||
if (deviation != 0) {
|
||||
final Duration intervalWithDeviation = Duration.ofMillis(millis + deviation);
|
||||
if (minPollingTime != null && intervalWithDeviation.compareTo(minPollingTime) < 0) {
|
||||
return DurationHelper.toString(minPollingTime);
|
||||
} else if (maxPollingTime != null && intervalWithDeviation.compareTo(maxPollingTime) > 0) {
|
||||
return DurationHelper.toString(maxPollingTime);
|
||||
} else {
|
||||
return DurationHelper.toString(intervalWithDeviation);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return DurationHelper.toString(interval);
|
||||
}
|
||||
}
|
||||
|
||||
// This record holds the override information for a specific QL string and its associated polling interval.
|
||||
public record Override(String qlStr, PollingInterval pollingInterval) {}
|
||||
}
|
||||
@@ -67,50 +67,41 @@ public class TenantConfigurationProperties {
|
||||
/**
|
||||
* Header based authentication enabled.
|
||||
*/
|
||||
public static final String AUTHENTICATION_MODE_HEADER_ENABLED = "authentication.header.enabled";
|
||||
public static final String AUTHENTICATION_HEADER_ENABLED = "authentication.header.enabled";
|
||||
/**
|
||||
* Header based authentication authority name.
|
||||
*/
|
||||
public static final String AUTHENTICATION_MODE_HEADER_AUTHORITY_NAME = "authentication.header.authority";
|
||||
public static final String AUTHENTICATION_HEADER_AUTHORITY_NAME = "authentication.header.authority";
|
||||
/**
|
||||
* Target token based authentication enabled.
|
||||
*/
|
||||
public static final String AUTHENTICATION_MODE_TARGET_SECURITY_TOKEN_ENABLED = "authentication.targettoken.enabled";
|
||||
public static final String AUTHENTICATION_TARGET_SECURITY_TOKEN_ENABLED = "authentication.targettoken.enabled";
|
||||
/**
|
||||
* Gateway token based authentication enabled.
|
||||
*/
|
||||
public static final String AUTHENTICATION_MODE_GATEWAY_SECURITY_TOKEN_ENABLED = "authentication.gatewaytoken.enabled";
|
||||
public static final String AUTHENTICATION_GATEWAY_SECURITY_TOKEN_ENABLED = "authentication.gatewaytoken.enabled";
|
||||
/**
|
||||
* Gateway token value.
|
||||
*/
|
||||
public static final String AUTHENTICATION_MODE_GATEWAY_SECURITY_TOKEN_KEY = "authentication.gatewaytoken.key";
|
||||
public static final String AUTHENTICATION_GATEWAY_SECURITY_TOKEN_KEY = "authentication.gatewaytoken.key";
|
||||
/**
|
||||
* See system default in
|
||||
* {@link ControllerPollProperties#getPollingTime()}.
|
||||
* See system default in {@link ControllerPollProperties#getPollingTime()}.
|
||||
*/
|
||||
public static final String POLLING_TIME_INTERVAL = "pollingTime";
|
||||
public static final String POLLING_TIME = "pollingTime";
|
||||
/**
|
||||
* See system default in
|
||||
* {@link ControllerPollProperties#getMinPollingTime()}.
|
||||
* See system default in {@link ControllerPollProperties#getPollingOverdueTime()}.
|
||||
*/
|
||||
public static final String MIN_POLLING_TIME_INTERVAL = "minPollingTime";
|
||||
public static final String POLLING_OVERDUE_TIME = "pollingOverdueTime";
|
||||
/**
|
||||
* See system default in
|
||||
* {@link ControllerPollProperties#getMaintenanceWindowPollCount()}.
|
||||
* See system default in {@link ControllerPollProperties#getMaintenanceWindowPollCount()}.
|
||||
*/
|
||||
public static final String MAINTENANCE_WINDOW_POLL_COUNT = "maintenanceWindowPollCount";
|
||||
/**
|
||||
* See system default in
|
||||
* {@link ControllerPollProperties#getPollingOverdueTime()}.
|
||||
*/
|
||||
public static final String POLLING_OVERDUE_TIME_INTERVAL = "pollingOverdueTime";
|
||||
/**
|
||||
* Represents setting if approval for a rollout is needed.
|
||||
*/
|
||||
public static final String ROLLOUT_APPROVAL_ENABLED = "rollout.approval.enabled";
|
||||
/**
|
||||
* Repository on autoclose mode instead of canceling in case of new DS
|
||||
* assignment over active actions.
|
||||
* Repository on autoclose mode instead of canceling in case of new Distribution Set assignment over active actions.
|
||||
*/
|
||||
public static final String REPOSITORY_ACTIONS_AUTOCLOSE_ENABLED = "repository.actions.autoclose.enabled";
|
||||
/**
|
||||
@@ -118,7 +109,7 @@ public class TenantConfigurationProperties {
|
||||
*/
|
||||
public static final String ACTION_CLEANUP_ENABLED = "action.cleanup.enabled";
|
||||
/**
|
||||
* Specifies the action expiry in milli-seconds.
|
||||
* Specifies the action expiry in milliseconds.
|
||||
*/
|
||||
public static final String ACTION_CLEANUP_ACTION_EXPIRY = "action.cleanup.actionExpiry";
|
||||
/**
|
||||
@@ -156,11 +147,11 @@ public class TenantConfigurationProperties {
|
||||
/**
|
||||
* Validates if an object matches the allowed data format of the corresponding key
|
||||
*
|
||||
* @param context application context
|
||||
* @param value which will be validated
|
||||
* @param context application context
|
||||
* @throws TenantConfigurationValidatorException is thrown, when object is invalid
|
||||
*/
|
||||
public void validate(final ApplicationContext context, final Object value) {
|
||||
public void validate(final Object value, final ApplicationContext context) {
|
||||
if (validator == null) {
|
||||
Objects.requireNonNull(DEFAULT_TYPE_VALIDATORS.get(dataType), "No validator defined for " + keyName).validate(value);
|
||||
} else {
|
||||
|
||||
@@ -0,0 +1,35 @@
|
||||
/**
|
||||
* Copyright (c) 2015 Bosch Software Innovations GmbH and others
|
||||
*
|
||||
* This program and the accompanying materials are made
|
||||
* available under the terms of the Eclipse Public License 2.0
|
||||
* which is available at https://www.eclipse.org/legal/epl-2.0/
|
||||
*
|
||||
* SPDX-License-Identifier: EPL-2.0
|
||||
*/
|
||||
package org.eclipse.hawkbit.tenancy.configuration.validator;
|
||||
|
||||
import static org.eclipse.hawkbit.tenancy.configuration.DurationHelper.fromString;
|
||||
|
||||
import java.time.Duration;
|
||||
|
||||
import org.eclipse.hawkbit.repository.exception.TenantConfigurationValidatorException;
|
||||
|
||||
/**
|
||||
* This class is used to validate, that the property is a String and that it is in the correct duration format.
|
||||
*/
|
||||
public class TenantConfigurationDurationValidator extends TenantConfigurationStringValidator {
|
||||
|
||||
// Exception squid:S1166 - Hide origin exception
|
||||
@SuppressWarnings({ "squid:S1166" })
|
||||
@Override
|
||||
public void validate(final Object tenantConfigurationObject) {
|
||||
super.validate(tenantConfigurationObject);
|
||||
|
||||
final String tenantConfigurationString = (String) tenantConfigurationObject;
|
||||
final Duration duration = fromString(tenantConfigurationString);
|
||||
if (duration.isNegative()) {
|
||||
throw new TenantConfigurationValidatorException("The given configuration value is not in the allowed to be negative.");
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,67 +0,0 @@
|
||||
/**
|
||||
* Copyright (c) 2015 Bosch Software Innovations GmbH and others
|
||||
*
|
||||
* This program and the accompanying materials are made
|
||||
* available under the terms of the Eclipse Public License 2.0
|
||||
* which is available at https://www.eclipse.org/legal/epl-2.0/
|
||||
*
|
||||
* SPDX-License-Identifier: EPL-2.0
|
||||
*/
|
||||
package org.eclipse.hawkbit.tenancy.configuration.validator;
|
||||
|
||||
import java.time.Duration;
|
||||
import java.time.format.DateTimeParseException;
|
||||
|
||||
import org.eclipse.hawkbit.repository.exception.TenantConfigurationValidatorException;
|
||||
import org.eclipse.hawkbit.tenancy.configuration.ControllerPollProperties;
|
||||
import org.eclipse.hawkbit.tenancy.configuration.DurationHelper;
|
||||
|
||||
/**
|
||||
* This class is used to validate, that the property is a String and that it is in the correct duration format.
|
||||
*/
|
||||
public class TenantConfigurationPollingDurationValidator implements TenantConfigurationValidator {
|
||||
|
||||
private final Duration minDuration;
|
||||
private final Duration maxDuration;
|
||||
|
||||
/**
|
||||
* This constructor is called by {@link org.eclipse.hawkbit.tenancy.configuration.TenantConfigurationProperties} using
|
||||
* ApplicationContext.getAutowireCapableBeanFactory().createBean(Class) to validate the polling duration configuration.
|
||||
* This insures the wiring of the properties is done correctly.
|
||||
*
|
||||
* @param properties property accessor for poll configuration
|
||||
*/
|
||||
public TenantConfigurationPollingDurationValidator(final ControllerPollProperties properties) {
|
||||
minDuration = DurationHelper.formattedStringToDuration(properties.getMinPollingTime());
|
||||
maxDuration = DurationHelper.formattedStringToDuration(properties.getMaxPollingTime());
|
||||
}
|
||||
|
||||
@Override
|
||||
// Exception squid:S1166 - Hide origin exception
|
||||
@SuppressWarnings({ "squid:S1166" })
|
||||
public void validate(final Object tenantConfigurationObject) {
|
||||
TenantConfigurationValidator.super.validate(tenantConfigurationObject);
|
||||
final String tenantConfigurationString = (String) tenantConfigurationObject;
|
||||
|
||||
final Duration tenantConfigurationValue;
|
||||
try {
|
||||
tenantConfigurationValue = DurationHelper.formattedStringToDuration(tenantConfigurationString);
|
||||
} catch (final DateTimeParseException ex) {
|
||||
throw new TenantConfigurationValidatorException(
|
||||
String.format("The given configuration value is expected as a string in the format %s.",
|
||||
DurationHelper.DURATION_FORMAT));
|
||||
}
|
||||
|
||||
if (!DurationHelper.durationRangeValidator(minDuration, maxDuration).isWithinRange(tenantConfigurationValue)) {
|
||||
throw new TenantConfigurationValidatorException(
|
||||
String.format("The given configuration value is not in the allowed range from %s to %s.",
|
||||
DurationHelper.durationToFormattedString(minDuration),
|
||||
DurationHelper.durationToFormattedString(maxDuration)));
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public Class<?> validateToClass() {
|
||||
return String.class;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,67 @@
|
||||
/**
|
||||
* Copyright (c) 2015 Bosch Software Innovations GmbH and others
|
||||
*
|
||||
* This program and the accompanying materials are made
|
||||
* available under the terms of the Eclipse Public License 2.0
|
||||
* which is available at https://www.eclipse.org/legal/epl-2.0/
|
||||
*
|
||||
* SPDX-License-Identifier: EPL-2.0
|
||||
*/
|
||||
package org.eclipse.hawkbit.tenancy.configuration.validator;
|
||||
|
||||
import static org.eclipse.hawkbit.tenancy.configuration.DurationHelper.fromString;
|
||||
|
||||
import java.time.Duration;
|
||||
|
||||
import org.eclipse.hawkbit.repository.exception.TenantConfigurationValidatorException;
|
||||
import org.eclipse.hawkbit.tenancy.configuration.ControllerPollProperties;
|
||||
import org.eclipse.hawkbit.tenancy.configuration.DurationHelper;
|
||||
import org.eclipse.hawkbit.tenancy.configuration.PollingTime;
|
||||
|
||||
/**
|
||||
* This class is used to validate, that the property is a String and that it is in the correct polling time format.
|
||||
*/
|
||||
public class TenantConfigurationPollingTimeValidator extends TenantConfigurationStringValidator {
|
||||
|
||||
private final Duration minPollingInterval;
|
||||
private final Duration maxPollingInterval;
|
||||
|
||||
/**
|
||||
* This constructor is called by {@link org.eclipse.hawkbit.tenancy.configuration.TenantConfigurationProperties} using
|
||||
* ApplicationContext.getAutowireCapableBeanFactory().createBean(Class) to validate the polling duration configuration.
|
||||
* This insures the wiring of the properties is done correctly.
|
||||
*
|
||||
* @param properties property accessor for poll configuration
|
||||
*/
|
||||
public TenantConfigurationPollingTimeValidator(final ControllerPollProperties properties) {
|
||||
this.minPollingInterval = fromString(properties.getMinPollingTime());
|
||||
this.maxPollingInterval = fromString(properties.getMaxPollingTime());
|
||||
}
|
||||
|
||||
@Override
|
||||
public void validate(final Object tenantConfigurationObject) {
|
||||
super.validate(tenantConfigurationObject);
|
||||
final String tenantConfigurationString = (String) tenantConfigurationObject;
|
||||
|
||||
// validate parsable
|
||||
final PollingTime pollingTime = new PollingTime(tenantConfigurationString);
|
||||
// validate polling interval in range
|
||||
validateInRange(pollingTime.getPollingInterval().getInterval());
|
||||
for (final PollingTime.Override override : pollingTime.getOverrides()) {
|
||||
validateInRange(override.pollingInterval().getInterval());
|
||||
}
|
||||
}
|
||||
|
||||
private void validateInRange(final Duration pollingInterval) {
|
||||
if (pollingInterval.compareTo(minPollingInterval) < 0) {
|
||||
throw new TenantConfigurationValidatorException(String.format(
|
||||
"The polling interval is smaller then minimum polling interval. The allowed range is [%s, %s].",
|
||||
DurationHelper.toString(minPollingInterval), DurationHelper.toString(maxPollingInterval)));
|
||||
}
|
||||
if (pollingInterval.compareTo(maxPollingInterval) > 0) {
|
||||
throw new TenantConfigurationValidatorException(String.format(
|
||||
"The polling interval is bigger then minimum polling interval. The allowed range is [%s, %s].",
|
||||
DurationHelper.toString(minPollingInterval), DurationHelper.toString(maxPollingInterval)));
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -12,12 +12,12 @@ package org.eclipse.hawkbit.tenancy.configuration.validator;
|
||||
import org.eclipse.hawkbit.repository.exception.TenantConfigurationValidatorException;
|
||||
|
||||
/**
|
||||
* base interface for clases which can validate tenant configuration values.
|
||||
* base interface for classes which can validate tenant configuration values.
|
||||
*/
|
||||
public interface TenantConfigurationValidator {
|
||||
|
||||
/**
|
||||
* validates the tenant configuration value
|
||||
* Validates the tenant configuration value
|
||||
*
|
||||
* @param tenantConfigurationValue value which will be validated.
|
||||
* @throws TenantConfigurationValidatorException is thrown, when parameter is invalid.
|
||||
|
||||
@@ -0,0 +1,64 @@
|
||||
/**
|
||||
* Copyright (c) 2025 Contributors to the Eclipse Foundation
|
||||
*
|
||||
* This program and the accompanying materials are made
|
||||
* available under the terms of the Eclipse Public License 2.0
|
||||
* which is available at https://www.eclipse.org/legal/epl-2.0/
|
||||
*
|
||||
* SPDX-License-Identifier: EPL-2.0
|
||||
*/
|
||||
package org.eclipse.hawkbit.repository;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
|
||||
import java.time.Duration;
|
||||
|
||||
import org.eclipse.hawkbit.tenancy.configuration.DurationHelper;
|
||||
import org.eclipse.hawkbit.tenancy.configuration.PollingTime;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
class PollingTimeTest {
|
||||
|
||||
@Test
|
||||
void testBackwardsCompatibility() {
|
||||
final PollingTime pollingTime = new PollingTime("01:00:00");
|
||||
assertThat(pollingTime.getPollingInterval().getInterval()).hasToString("PT1H");
|
||||
assertThat(pollingTime.getPollingInterval().getDeviationPercent()).isZero();
|
||||
assertThat(pollingTime.getOverrides()).isEmpty();
|
||||
}
|
||||
|
||||
@Test
|
||||
void testDeviation() {
|
||||
final PollingTime pollingTime = new PollingTime("01:00:00~10%");
|
||||
final long maxDeviation = (Duration.ofHours(1).toMillis() / 10);
|
||||
final long deviation = Duration.ofHours(1).toMillis()
|
||||
- DurationHelper.fromString(pollingTime.getPollingInterval().getFormattedIntervalWithDeviation(null, null)).toMillis();
|
||||
assertThat(deviation)
|
||||
.isGreaterThanOrEqualTo(-maxDeviation)
|
||||
.isLessThanOrEqualTo(maxDeviation);
|
||||
}
|
||||
|
||||
@Test
|
||||
void testComplexWithOverrides() {
|
||||
assertExpectedComplexWithOverrides("01:00:00~10%, group == 'eu' -> 00:02:00~15%, status != in_sync -> 00:05:00");
|
||||
}
|
||||
|
||||
@Test
|
||||
void testComplexWithOverridesWithWhitespaces() {
|
||||
assertExpectedComplexWithOverrides("01:00:00~10%, group == 'eu' -> 00:02:00~15%, status != in_sync ->00:05:00");
|
||||
assertExpectedComplexWithOverrides(" 01:00:00~10%, group == 'eu' -> 00:02:00~15%, status != in_sync ->00:05:00 ");
|
||||
assertExpectedComplexWithOverrides(" 01:00:00~10% , group == 'eu' -> 00:02:00 ~15%, status != in_sync ->00:05:00 ");
|
||||
}
|
||||
|
||||
private static void assertExpectedComplexWithOverrides(final String pollingTimeStr) {
|
||||
final PollingTime pollingTime = new PollingTime(pollingTimeStr);
|
||||
assertThat(pollingTime.getPollingInterval().getInterval()).hasToString("PT1H");
|
||||
assertThat(pollingTime.getPollingInterval().getDeviationPercent()).isEqualTo(10);
|
||||
assertThat(pollingTime.getOverrides().get(0).qlStr()).isEqualTo("group == 'eu'");
|
||||
assertThat(pollingTime.getOverrides().get(0).pollingInterval().getInterval()).hasToString("PT2M");
|
||||
assertThat(pollingTime.getOverrides().get(0).pollingInterval().getDeviationPercent()).isEqualTo(15);
|
||||
assertThat(pollingTime.getOverrides().get(1).qlStr()).isEqualTo("status != in_sync");
|
||||
assertThat(pollingTime.getOverrides().get(1).pollingInterval().getInterval()).hasToString("PT5M");
|
||||
assertThat(pollingTime.getOverrides().get(1).pollingInterval().getDeviationPercent()).isZero();
|
||||
}
|
||||
}
|
||||
@@ -1,5 +1,5 @@
|
||||
/**
|
||||
* Copyright (c) 2015 Bosch Software Innovations GmbH and others
|
||||
* Copyright (c) 2025 Contributors to the Eclipse Foundation
|
||||
*
|
||||
* This program and the accompanying materials are made
|
||||
* available under the terms of the Eclipse Public License 2.0
|
||||
@@ -17,6 +17,8 @@ import org.eclipse.hawkbit.repository.model.helper.SystemSecurityContextHolder;
|
||||
import org.eclipse.hawkbit.repository.model.helper.TenantConfigurationManagementHolder;
|
||||
import org.eclipse.hawkbit.security.SystemSecurityContext;
|
||||
import org.eclipse.hawkbit.tenancy.configuration.DurationHelper;
|
||||
import org.eclipse.hawkbit.tenancy.configuration.PollingTime;
|
||||
import org.eclipse.hawkbit.tenancy.configuration.PollingTime.PollingInterval;
|
||||
import org.eclipse.hawkbit.tenancy.configuration.TenantConfigurationProperties.TenantConfigurationKey;
|
||||
|
||||
/**
|
||||
@@ -29,19 +31,26 @@ public final class TimestampCalculator {
|
||||
|
||||
/**
|
||||
* Calculates the overdue timestamp (<em>overdue_ts</em>) based on the current timestamp and the intervals for polling and poll-overdue:
|
||||
* <p>
|
||||
* <p/>
|
||||
* <em>overdue_ts = now_ts - pollingInterval - pollingOverdueInterval</em>;<br>
|
||||
* <em>pollingInterval</em> and <em>pollingOverdueInterval</em> are retrieved from tenant-specific system configuration.
|
||||
* <p/>
|
||||
* Note: this method checks against the default polling time interval. I.e. overrides are not considered.
|
||||
*
|
||||
* @return <em>overdue_ts</em> in milliseconds since Unix epoch as long value
|
||||
*/
|
||||
public static long calculateOverdueTimestamp() {
|
||||
return System.currentTimeMillis() - getDurationForKey(TenantConfigurationKey.POLLING_TIME_INTERVAL).toMillis()
|
||||
- getDurationForKey(TenantConfigurationKey.POLLING_OVERDUE_TIME_INTERVAL).toMillis();
|
||||
return calculateOverdueTimestamp(
|
||||
new PollingTime(getRawStringForKey(TenantConfigurationKey.POLLING_TIME)).getPollingInterval(),
|
||||
DurationHelper.fromString(getRawStringForKey(TenantConfigurationKey.POLLING_OVERDUE_TIME)));
|
||||
}
|
||||
|
||||
private static Duration getDurationForKey(final String key) {
|
||||
return DurationHelper.formattedStringToDuration(getRawStringForKey(key));
|
||||
private static long calculateOverdueTimestamp(final PollingInterval pollingInterval, final Duration pollingOverdueTime) {
|
||||
return System.currentTimeMillis()
|
||||
- (pollingInterval.getDeviationPercent() == 0
|
||||
? pollingInterval.getInterval().toMillis()
|
||||
: pollingInterval.getInterval().toMillis() * (100 + pollingInterval.getDeviationPercent()) / 100)
|
||||
- pollingOverdueTime.toMillis();
|
||||
}
|
||||
|
||||
private static String getRawStringForKey(final String key) {
|
||||
|
||||
@@ -8,7 +8,7 @@
|
||||
# SPDX-License-Identifier: EPL-2.0
|
||||
#
|
||||
|
||||
# Defines the polling time for the controllers in HH:MM:SS notation
|
||||
# Defines the polling time for the controllers in HH:mm:ss notation
|
||||
hawkbit.controller.pollingTime=00:05:00
|
||||
hawkbit.controller.pollingOverdueTime=00:05:00
|
||||
hawkbit.controller.maxPollingTime=23:59:59
|
||||
@@ -52,24 +52,16 @@ hawkbit.server.tenant.configuration.authentication-gatewaytoken-key.defaultValue
|
||||
|
||||
hawkbit.server.tenant.configuration.polling-time.keyName=pollingTime
|
||||
hawkbit.server.tenant.configuration.polling-time.defaultValue=${hawkbit.controller.pollingTime}
|
||||
hawkbit.server.tenant.configuration.polling-time.validator=org.eclipse.hawkbit.tenancy.configuration.validator.TenantConfigurationPollingDurationValidator
|
||||
|
||||
hawkbit.server.tenant.configuration.min-polling-time.keyName=minPollingTime
|
||||
hawkbit.server.tenant.configuration.min-polling-time.defaultValue=${hawkbit.controller.minPollingTime}
|
||||
hawkbit.server.tenant.configuration.min-polling-time.validator=org.eclipse.hawkbit.tenancy.configuration.validator.TenantConfigurationPollingDurationValidator
|
||||
hawkbit.server.tenant.configuration.polling-time.validator=org.eclipse.hawkbit.tenancy.configuration.validator.TenantConfigurationPollingTimeValidator
|
||||
|
||||
hawkbit.server.tenant.configuration.polling-overdue-time.keyName=pollingOverdueTime
|
||||
hawkbit.server.tenant.configuration.polling-overdue-time.defaultValue=${hawkbit.controller.pollingOverdueTime}
|
||||
hawkbit.server.tenant.configuration.polling-overdue-time.validator=org.eclipse.hawkbit.tenancy.configuration.validator.TenantConfigurationPollingDurationValidator
|
||||
hawkbit.server.tenant.configuration.polling-overdue-time.validator=org.eclipse.hawkbit.tenancy.configuration.validator.TenantConfigurationDurationValidator
|
||||
|
||||
hawkbit.server.tenant.configuration.maintenance-window-poll-count.keyName=maintenanceWindowPollCount
|
||||
hawkbit.server.tenant.configuration.maintenance-window-poll-count.defaultValue=${hawkbit.controller.maintenanceWindowPollCount}
|
||||
hawkbit.server.tenant.configuration.maintenance-window-poll-count.dataType=java.lang.Integer
|
||||
|
||||
#hawkbit.server.tenant.configuration.anonymous-download-enabled.keyName=anonymous.download.enabled
|
||||
#hawkbit.server.tenant.configuration.anonymous-download-enabled.defaultValue=${hawkbit.server.download.anonymous.enabled}
|
||||
#hawkbit.server.tenant.configuration.anonymous-download-enabled.dataType=java.lang.Boolean
|
||||
|
||||
hawkbit.server.tenant.configuration.rollout-approval-enabled.keyName=rollout.approval.enabled
|
||||
hawkbit.server.tenant.configuration.rollout-approval-enabled.defaultValue=false
|
||||
hawkbit.server.tenant.configuration.rollout-approval-enabled.dataType=java.lang.Boolean
|
||||
|
||||
@@ -161,6 +161,7 @@ import org.eclipse.hawkbit.security.SecurityTokenGenerator;
|
||||
import org.eclipse.hawkbit.security.SystemSecurityContext;
|
||||
import org.eclipse.hawkbit.tenancy.TenantAware;
|
||||
import org.eclipse.hawkbit.tenancy.UserAuthoritiesResolver;
|
||||
import org.eclipse.hawkbit.tenancy.configuration.ControllerPollProperties;
|
||||
import org.eclipse.hawkbit.tenancy.configuration.TenantConfigurationProperties;
|
||||
import org.eclipse.hawkbit.utils.TenantConfigHelper;
|
||||
import org.springframework.beans.BeansException;
|
||||
@@ -850,16 +851,15 @@ public class RepositoryApplicationConfiguration {
|
||||
final DeploymentManagement deploymentManagement, final ConfirmationManagement confirmationManagement,
|
||||
final SoftwareModuleRepository softwareModuleRepository, final SoftwareModuleMetadataRepository softwareModuleMetadataRepository,
|
||||
final DistributionSetManagement distributionSetManagement,
|
||||
final TenantConfigurationManagement tenantConfigurationManagement,
|
||||
final TenantConfigurationManagement tenantConfigurationManagement, final ControllerPollProperties controllerPollProperties,
|
||||
final PlatformTransactionManager txManager, final EntityFactory entityFactory, final EntityManager entityManager,
|
||||
final AfterTransactionCommitExecutor afterCommit,
|
||||
final SystemSecurityContext systemSecurityContext, final TenantAware tenantAware,
|
||||
final ScheduledExecutorService executorService) {
|
||||
return new JpaControllerManagement(actionRepository, actionStatusRepository, quotaManagement, repositoryProperties,
|
||||
targetRepository, targetTypeManagement, deploymentManagement, confirmationManagement, softwareModuleRepository,
|
||||
softwareModuleMetadataRepository, distributionSetManagement, tenantConfigurationManagement, txManager,
|
||||
entityFactory, entityManager, afterCommit, systemSecurityContext, tenantAware,
|
||||
executorService);
|
||||
softwareModuleMetadataRepository, distributionSetManagement, tenantConfigurationManagement, controllerPollProperties,
|
||||
txManager,entityFactory, entityManager, afterCommit, systemSecurityContext, tenantAware, executorService);
|
||||
}
|
||||
|
||||
@Bean
|
||||
|
||||
@@ -79,6 +79,7 @@ import org.eclipse.hawkbit.repository.jpa.model.JpaAction_;
|
||||
import org.eclipse.hawkbit.repository.jpa.model.JpaDistributionSet;
|
||||
import org.eclipse.hawkbit.repository.jpa.model.JpaTarget;
|
||||
import org.eclipse.hawkbit.repository.jpa.model.JpaTarget_;
|
||||
import org.eclipse.hawkbit.repository.jpa.ql.EntityMatcher;
|
||||
import org.eclipse.hawkbit.repository.jpa.repository.ActionRepository;
|
||||
import org.eclipse.hawkbit.repository.jpa.repository.ActionStatusRepository;
|
||||
import org.eclipse.hawkbit.repository.jpa.repository.SoftwareModuleMetadataRepository;
|
||||
@@ -101,6 +102,9 @@ import org.eclipse.hawkbit.repository.model.TargetType;
|
||||
import org.eclipse.hawkbit.repository.model.TargetUpdateStatus;
|
||||
import org.eclipse.hawkbit.security.SystemSecurityContext;
|
||||
import org.eclipse.hawkbit.tenancy.TenantAware;
|
||||
import org.eclipse.hawkbit.tenancy.configuration.ControllerPollProperties;
|
||||
import org.eclipse.hawkbit.tenancy.configuration.DurationHelper;
|
||||
import org.eclipse.hawkbit.tenancy.configuration.PollingTime;
|
||||
import org.eclipse.hawkbit.tenancy.configuration.TenantConfigurationProperties.TenantConfigurationKey;
|
||||
import org.springframework.dao.ConcurrencyFailureException;
|
||||
import org.springframework.data.domain.Page;
|
||||
@@ -116,6 +120,7 @@ import org.springframework.transaction.PlatformTransactionManager;
|
||||
import org.springframework.transaction.annotation.Isolation;
|
||||
import org.springframework.transaction.annotation.Transactional;
|
||||
import org.springframework.transaction.support.TransactionCallback;
|
||||
import org.springframework.util.ObjectUtils;
|
||||
import org.springframework.util.StringUtils;
|
||||
import org.springframework.validation.annotation.Validated;
|
||||
|
||||
@@ -140,6 +145,8 @@ public class JpaControllerManagement extends JpaActionManagement implements Cont
|
||||
private final SoftwareModuleMetadataRepository softwareModuleMetadataRepository;
|
||||
private final DistributionSetManagement distributionSetManagement;
|
||||
private final TenantConfigurationManagement tenantConfigurationManagement;
|
||||
private final ControllerPollProperties controllerPollProperties;
|
||||
private final Duration minPollingTime, maxPollingTime;
|
||||
private final PlatformTransactionManager txManager;
|
||||
private final EntityFactory entityFactory;
|
||||
private final EntityManager entityManager;
|
||||
@@ -155,7 +162,7 @@ public class JpaControllerManagement extends JpaActionManagement implements Cont
|
||||
final DeploymentManagement deploymentManagement, final ConfirmationManagement confirmationManagement,
|
||||
final SoftwareModuleRepository softwareModuleRepository, final SoftwareModuleMetadataRepository softwareModuleMetadataRepository,
|
||||
final DistributionSetManagement distributionSetManagement,
|
||||
final TenantConfigurationManagement tenantConfigurationManagement,
|
||||
final TenantConfigurationManagement tenantConfigurationManagement, final ControllerPollProperties controllerPollProperties,
|
||||
final PlatformTransactionManager txManager, final EntityFactory entityFactory, final EntityManager entityManager,
|
||||
final AfterTransactionCommitExecutor afterCommit,
|
||||
final SystemSecurityContext systemSecurityContext, final TenantAware tenantAware,
|
||||
@@ -170,6 +177,13 @@ public class JpaControllerManagement extends JpaActionManagement implements Cont
|
||||
this.softwareModuleMetadataRepository = softwareModuleMetadataRepository;
|
||||
this.distributionSetManagement = distributionSetManagement;
|
||||
this.tenantConfigurationManagement = tenantConfigurationManagement;
|
||||
this.controllerPollProperties = controllerPollProperties;
|
||||
minPollingTime = controllerPollProperties.getMinPollingTime() == null
|
||||
? Duration.of(0, ChronoUnit.SECONDS)
|
||||
: DurationHelper.fromString(controllerPollProperties.getMinPollingTime());
|
||||
maxPollingTime = controllerPollProperties.getMaxPollingTime() == null
|
||||
? Duration.of(100, ChronoUnit.YEARS)
|
||||
: DurationHelper.fromString(controllerPollProperties.getMaxPollingTime());
|
||||
this.txManager = txManager;
|
||||
this.entityFactory = entityFactory;
|
||||
this.entityManager = entityManager;
|
||||
@@ -345,6 +359,7 @@ public class JpaControllerManagement extends JpaActionManagement implements Cont
|
||||
public Target findOrRegisterTargetIfItDoesNotExist(final String controllerId, final URI address, final String name, final String type) {
|
||||
return findOrRegisterTargetIfItDoesNotExist0(controllerId, address, name, type);
|
||||
}
|
||||
|
||||
private Target findOrRegisterTargetIfItDoesNotExist0(final String controllerId, final URI address, final String name, final String type) {
|
||||
final Specification<JpaTarget> spec = (targetRoot, query, cb) -> cb.equal(targetRoot.get(JpaTarget_.controllerId), controllerId);
|
||||
return targetRepository.findOne(spec)
|
||||
@@ -373,42 +388,39 @@ public class JpaControllerManagement extends JpaActionManagement implements Cont
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getPollingTime() {
|
||||
return systemSecurityContext.runAsSystem(() -> tenantConfigurationManagement
|
||||
.getConfigurationValue(TenantConfigurationKey.POLLING_TIME_INTERVAL, String.class).getValue());
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the configured minimum polling interval.
|
||||
*
|
||||
* @return current {@link TenantConfigurationKey#MIN_POLLING_TIME_INTERVAL}.
|
||||
*/
|
||||
@Override
|
||||
public String getMinPollingTime() {
|
||||
return systemSecurityContext.runAsSystem(() -> tenantConfigurationManagement
|
||||
.getConfigurationValue(TenantConfigurationKey.MIN_POLLING_TIME_INTERVAL, String.class).getValue());
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the count to be used for reducing polling interval while calling {@link ControllerManagement#getPollingTimeForAction(Action)}.
|
||||
*
|
||||
* @return configured value of
|
||||
* {@link TenantConfigurationKey#MAINTENANCE_WINDOW_POLL_COUNT}.
|
||||
*/
|
||||
@Override
|
||||
public int getMaintenanceWindowPollCount() {
|
||||
return systemSecurityContext.runAsSystem(() -> tenantConfigurationManagement
|
||||
.getConfigurationValue(TenantConfigurationKey.MAINTENANCE_WINDOW_POLL_COUNT, Integer.class).getValue());
|
||||
public String getPollingTime(final Target target) {
|
||||
return systemSecurityContext.runAsSystem(() -> {
|
||||
final PollingTime pollingTime = new PollingTime(
|
||||
tenantConfigurationManagement.getConfigurationValue(TenantConfigurationKey.POLLING_TIME, String.class).getValue());
|
||||
if (!ObjectUtils.isEmpty(pollingTime.getOverrides()) && target instanceof JpaTarget jpaTarget) {
|
||||
for (final PollingTime.Override override : pollingTime.getOverrides()) {
|
||||
try {
|
||||
if (EntityMatcher.forRsql(override.qlStr()).match(jpaTarget)) {
|
||||
return override.pollingInterval().getFormattedIntervalWithDeviation(minPollingTime, maxPollingTime);
|
||||
}
|
||||
} catch (final Exception e) {
|
||||
log.warn("Error while evaluating polling override for target {}: {}", jpaTarget.getId(), e.getMessage());
|
||||
}
|
||||
}
|
||||
}
|
||||
// returns default - no overrides or not applicable for the target
|
||||
return pollingTime.getPollingInterval().getFormattedIntervalWithDeviation(minPollingTime, maxPollingTime);
|
||||
});
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getPollingTimeForAction(final Action action) {
|
||||
public String getPollingTimeForAction(final Target target, final Action action) {
|
||||
final String pollingTime = getPollingTime(target);
|
||||
if (!action.hasMaintenanceSchedule() || action.isMaintenanceScheduleLapsed()) {
|
||||
return getPollingTime();
|
||||
return pollingTime;
|
||||
} else {
|
||||
// the count to be used for reducing polling interval -> the configured value of {@link TenantConfigurationKey#MAINTENANCE_WINDOW_POLL_COUNT}
|
||||
final int maintenanceWindowPollCount = systemSecurityContext.runAsSystem(
|
||||
() -> tenantConfigurationManagement.getConfigurationValue(
|
||||
TenantConfigurationKey.MAINTENANCE_WINDOW_POLL_COUNT, Integer.class).getValue());
|
||||
return new EventTimer(pollingTime, controllerPollProperties.getMinPollingTime(), ChronoUnit.SECONDS)
|
||||
.timeToNextEvent(maintenanceWindowPollCount, action.getMaintenanceWindowStartTime().orElse(null));
|
||||
}
|
||||
|
||||
return new EventTimer(getPollingTime(), getMinPollingTime(), ChronoUnit.SECONDS)
|
||||
.timeToNextEvent(getMaintenanceWindowPollCount(), action.getMaintenanceWindowStartTime().orElse(null));
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
@@ -9,8 +9,10 @@
|
||||
*/
|
||||
package org.eclipse.hawkbit.repository.jpa.management;
|
||||
|
||||
import static org.eclipse.hawkbit.tenancy.configuration.TenantConfigurationProperties.TenantConfigurationKey.AUTHENTICATION_GATEWAY_SECURITY_TOKEN_KEY;
|
||||
import static org.eclipse.hawkbit.tenancy.configuration.TenantConfigurationProperties.TenantConfigurationKey.BATCH_ASSIGNMENTS_ENABLED;
|
||||
import static org.eclipse.hawkbit.tenancy.configuration.TenantConfigurationProperties.TenantConfigurationKey.MULTI_ASSIGNMENTS_ENABLED;
|
||||
import static org.eclipse.hawkbit.tenancy.configuration.TenantConfigurationProperties.TenantConfigurationKey.POLLING_TIME;
|
||||
import static org.eclipse.hawkbit.tenancy.configuration.TenantConfigurationProperties.TenantConfigurationKey.REPOSITORY_ACTIONS_AUTOCLOSE_ENABLED;
|
||||
|
||||
import java.io.Serializable;
|
||||
@@ -18,8 +20,8 @@ import java.time.Duration;
|
||||
import java.time.Instant;
|
||||
import java.time.LocalDateTime;
|
||||
import java.time.ZoneId;
|
||||
import java.time.temporal.ChronoUnit;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.function.Function;
|
||||
@@ -33,7 +35,9 @@ import org.eclipse.hawkbit.repository.exception.TenantConfigurationValidatorExce
|
||||
import org.eclipse.hawkbit.repository.exception.TenantConfigurationValueChangeNotAllowedException;
|
||||
import org.eclipse.hawkbit.repository.jpa.configuration.Constants;
|
||||
import org.eclipse.hawkbit.repository.jpa.executor.AfterTransactionCommitExecutor;
|
||||
import org.eclipse.hawkbit.repository.jpa.model.JpaTarget;
|
||||
import org.eclipse.hawkbit.repository.jpa.model.JpaTenantConfiguration;
|
||||
import org.eclipse.hawkbit.repository.jpa.ql.EntityMatcher;
|
||||
import org.eclipse.hawkbit.repository.jpa.repository.TenantConfigurationRepository;
|
||||
import org.eclipse.hawkbit.repository.model.PollStatus;
|
||||
import org.eclipse.hawkbit.repository.model.Target;
|
||||
@@ -42,6 +46,7 @@ import org.eclipse.hawkbit.repository.model.TenantConfigurationValue;
|
||||
import org.eclipse.hawkbit.repository.model.helper.SystemSecurityContextHolder;
|
||||
import org.eclipse.hawkbit.security.SystemSecurityContext;
|
||||
import org.eclipse.hawkbit.tenancy.configuration.DurationHelper;
|
||||
import org.eclipse.hawkbit.tenancy.configuration.PollingTime;
|
||||
import org.eclipse.hawkbit.tenancy.configuration.TenantConfigurationProperties;
|
||||
import org.eclipse.hawkbit.tenancy.configuration.TenantConfigurationProperties.TenantConfigurationKey;
|
||||
import org.springframework.cache.Cache;
|
||||
@@ -55,6 +60,7 @@ import org.springframework.dao.ConcurrencyFailureException;
|
||||
import org.springframework.retry.annotation.Backoff;
|
||||
import org.springframework.retry.annotation.Retryable;
|
||||
import org.springframework.transaction.annotation.Transactional;
|
||||
import org.springframework.util.ObjectUtils;
|
||||
import org.springframework.validation.annotation.Validated;
|
||||
|
||||
/**
|
||||
@@ -90,9 +96,8 @@ public class JpaTenantConfigurationManagement implements TenantConfigurationMana
|
||||
@Transactional
|
||||
@Retryable(retryFor = { ConcurrencyFailureException.class }, maxAttempts = Constants.TX_RT_MAX,
|
||||
backoff = @Backoff(delay = Constants.TX_RT_DELAY))
|
||||
public <T extends Serializable> TenantConfigurationValue<T> addOrUpdateConfiguration(
|
||||
final String configurationKeyName, final T value) {
|
||||
return addOrUpdateConfiguration0(Collections.singletonMap(configurationKeyName, value)).values().iterator().next();
|
||||
public <T extends Serializable> TenantConfigurationValue<T> addOrUpdateConfiguration(final String configurationKeyName, final T value) {
|
||||
return addOrUpdateConfiguration0(Map.of(configurationKeyName, value)).values().iterator().next();
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -138,11 +143,9 @@ public class JpaTenantConfigurationManagement implements TenantConfigurationMana
|
||||
checkAccess(configurationKeyName);
|
||||
|
||||
final TenantConfigurationKey configurationKey = tenantConfigurationProperties.fromKeyName(configurationKeyName);
|
||||
|
||||
validateTenantConfigurationDataType(configurationKey, propertyType);
|
||||
|
||||
final TenantConfiguration tenantConfiguration = tenantConfigurationRepository.findByKey(configurationKey.getKeyName());
|
||||
|
||||
return buildTenantConfigurationValueByKey(configurationKey, propertyType, tenantConfiguration);
|
||||
}
|
||||
|
||||
@@ -151,7 +154,6 @@ public class JpaTenantConfigurationManagement implements TenantConfigurationMana
|
||||
checkAccess(configurationKeyName);
|
||||
|
||||
final TenantConfigurationKey key = tenantConfigurationProperties.fromKeyName(configurationKeyName);
|
||||
|
||||
if (!key.getDataType().isAssignableFrom(propertyType)) {
|
||||
throw new TenantConfigurationValidatorException(String.format(
|
||||
"Cannot parse the database value of type %s into the type %s.", key.getDataType(), propertyType));
|
||||
@@ -162,47 +164,62 @@ public class JpaTenantConfigurationManagement implements TenantConfigurationMana
|
||||
|
||||
@Override
|
||||
public Function<Target, PollStatus> pollStatusResolver() {
|
||||
final Duration pollTime = DurationHelper.formattedStringToDuration(
|
||||
getConfigurationValue(TenantConfigurationKey.POLLING_TIME_INTERVAL, String.class).getValue());
|
||||
final Duration overdueTime = DurationHelper.formattedStringToDuration(
|
||||
getConfigurationValue(TenantConfigurationKey.POLLING_OVERDUE_TIME_INTERVAL, String.class)
|
||||
.getValue());
|
||||
final PollingTime pollingTime = new PollingTime(
|
||||
getConfigurationValue(TenantConfigurationKey.POLLING_TIME, String.class).getValue());
|
||||
final Duration pollingOverdueTime = DurationHelper.fromString(
|
||||
getConfigurationValue(TenantConfigurationKey.POLLING_OVERDUE_TIME, String.class).getValue());
|
||||
return target -> {
|
||||
final Long lastTargetQuery = target.getLastTargetQuery();
|
||||
if (lastTargetQuery == null) {
|
||||
return null;
|
||||
}
|
||||
final LocalDateTime currentDate = LocalDateTime.now();
|
||||
final LocalDateTime lastPollDate = LocalDateTime.ofInstant(Instant.ofEpochMilli(lastTargetQuery),
|
||||
ZoneId.systemDefault());
|
||||
final LocalDateTime nextPollDate = lastPollDate.plus(pollTime);
|
||||
final LocalDateTime overdueDate = nextPollDate.plus(overdueTime);
|
||||
return new PollStatus(lastPollDate, nextPollDate, overdueDate, currentDate);
|
||||
|
||||
if (!ObjectUtils.isEmpty(pollingTime.getOverrides()) && target instanceof JpaTarget jpaTarget) {
|
||||
for (final PollingTime.Override override : pollingTime.getOverrides()) {
|
||||
try {
|
||||
if (EntityMatcher.forRsql(override.qlStr()).match(jpaTarget)) {
|
||||
return pollStatus(lastTargetQuery, override.pollingInterval(), pollingOverdueTime);
|
||||
}
|
||||
} catch (final Exception e) {
|
||||
log.warn("Error while evaluating polling override for target {}: {}", jpaTarget.getId(), e.getMessage());
|
||||
}
|
||||
}
|
||||
}
|
||||
// returns default - no overrides or not applicable for the target
|
||||
return pollStatus(lastTargetQuery, pollingTime.getPollingInterval(), pollingOverdueTime);
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Validates the data type of the tenant configuration. If it is possible to
|
||||
* cast to the given data type.
|
||||
*
|
||||
* @param configurationKey the key
|
||||
* @param propertyType the class
|
||||
*/
|
||||
private static <T> void validateTenantConfigurationDataType(final TenantConfigurationKey configurationKey,
|
||||
final Class<T> propertyType) {
|
||||
private static PollStatus pollStatus(
|
||||
final long lastTargetQuery,
|
||||
final PollingTime.PollingInterval pollingInterval, final Duration pollingOverdueTime) {
|
||||
final LocalDateTime currentDate = LocalDateTime.now();
|
||||
final LocalDateTime lastPollDate = LocalDateTime.ofInstant(Instant.ofEpochMilli(lastTargetQuery), ZoneId.systemDefault());
|
||||
LocalDateTime nextPollDate = lastPollDate.plus(pollingInterval.getInterval());
|
||||
if (pollingInterval.getDeviationPercent() > 0) {
|
||||
nextPollDate = nextPollDate.plus(
|
||||
pollingInterval.getInterval().toMillis() * pollingInterval.getDeviationPercent() / 100,
|
||||
ChronoUnit.MILLIS);
|
||||
}
|
||||
final LocalDateTime overdueDate = nextPollDate.plus(pollingOverdueTime);
|
||||
return new PollStatus(lastPollDate, nextPollDate, overdueDate, currentDate);
|
||||
}
|
||||
|
||||
/**
|
||||
* Validates the data type of the tenant configuration. If it is possible to cast to the given data type.
|
||||
*/
|
||||
private static <T> void validateTenantConfigurationDataType(final TenantConfigurationKey configurationKey, final Class<T> propertyType) {
|
||||
if (!configurationKey.getDataType().isAssignableFrom(propertyType)) {
|
||||
throw new TenantConfigurationValidatorException(
|
||||
String.format("Cannot parse the database value of type %s into the type %s.",
|
||||
String.format(
|
||||
"Cannot parse the database value of type %s into the type %s.",
|
||||
configurationKey.getDataType(), propertyType));
|
||||
}
|
||||
}
|
||||
|
||||
private void checkAccess(final String configurationKeyName) {
|
||||
if (TenantConfigurationProperties.TenantConfigurationKey.AUTHENTICATION_MODE_GATEWAY_SECURITY_TOKEN_KEY
|
||||
.equalsIgnoreCase(configurationKeyName)) {
|
||||
final SystemSecurityContext systemSecurityContext =
|
||||
SystemSecurityContextHolder.getInstance().getSystemSecurityContext();
|
||||
if (AUTHENTICATION_GATEWAY_SECURITY_TOKEN_KEY.equalsIgnoreCase(configurationKeyName)) {
|
||||
final SystemSecurityContext systemSecurityContext = SystemSecurityContextHolder.getInstance().getSystemSecurityContext();
|
||||
if (!systemSecurityContext.isCurrentThreadSystemCode() &&
|
||||
!systemSecurityContext.hasPermission(SpPermission.READ_GATEWAY_SEC_TOKEN)) {
|
||||
throw new InsufficientPermissionException(
|
||||
@@ -211,22 +228,28 @@ public class JpaTenantConfigurationManagement implements TenantConfigurationMana
|
||||
}
|
||||
}
|
||||
|
||||
private <T extends Serializable> Map<String, TenantConfigurationValue<T>> addOrUpdateConfiguration0(Map<String, T> configurations) {
|
||||
List<JpaTenantConfiguration> configurationList = new ArrayList<>();
|
||||
private <T extends Serializable> Map<String, TenantConfigurationValue<T>> addOrUpdateConfiguration0(final Map<String, T> configurations) {
|
||||
final List<JpaTenantConfiguration> configurationList = new ArrayList<>();
|
||||
configurations.forEach((configurationKeyName, value) -> {
|
||||
final TenantConfigurationKey configurationKey = tenantConfigurationProperties.fromKeyName(configurationKeyName);
|
||||
|
||||
if (!configurationKey.getDataType().isAssignableFrom(value.getClass())) {
|
||||
throw new TenantConfigurationValidatorException(String.format(
|
||||
"Cannot parse the value %s of type %s into the type %s defined by the configuration key.", value,
|
||||
value.getClass(), configurationKey.getDataType()));
|
||||
}
|
||||
configurationKey.validate(value, applicationContext);
|
||||
// additional validation for specific configuration keys
|
||||
if (POLLING_TIME.equals(configurationKey.getKeyName())) {
|
||||
final PollingTime pollingTime = new PollingTime(value.toString());
|
||||
if (!ObjectUtils.isEmpty(pollingTime.getOverrides())) {
|
||||
// validate that the QL strings are valid RSQL queries,
|
||||
// nevertheless always when parse them we shall be prepared to catch exceptions if the parsers
|
||||
// has been changed in non backward compatible way
|
||||
pollingTime.getOverrides().forEach(override -> EntityMatcher.forRsql(override.qlStr()));
|
||||
}
|
||||
}
|
||||
|
||||
configurationKey.validate(applicationContext, value);
|
||||
|
||||
JpaTenantConfiguration tenantConfiguration = tenantConfigurationRepository
|
||||
.findByKey(configurationKey.getKeyName());
|
||||
|
||||
JpaTenantConfiguration tenantConfiguration = tenantConfigurationRepository.findByKey(configurationKey.getKeyName());
|
||||
if (tenantConfiguration == null) {
|
||||
tenantConfiguration = new JpaTenantConfiguration(configurationKey.getKeyName(), value.toString());
|
||||
} else {
|
||||
@@ -237,15 +260,12 @@ public class JpaTenantConfigurationManagement implements TenantConfigurationMana
|
||||
configurationList.add(tenantConfiguration);
|
||||
});
|
||||
|
||||
List<JpaTenantConfiguration> jpaTenantConfigurations = tenantConfigurationRepository
|
||||
.saveAll(configurationList);
|
||||
|
||||
final List<JpaTenantConfiguration> jpaTenantConfigurations = tenantConfigurationRepository.saveAll(configurationList);
|
||||
return jpaTenantConfigurations.stream().collect(Collectors.toMap(
|
||||
JpaTenantConfiguration::getKey,
|
||||
updatedTenantConfiguration -> {
|
||||
|
||||
@SuppressWarnings("unchecked") final Class<T> clazzT = (Class<T>) configurations.get(updatedTenantConfiguration.getKey())
|
||||
.getClass();
|
||||
@SuppressWarnings("unchecked")
|
||||
final Class<T> clazzT = (Class<T>) configurations.get(updatedTenantConfiguration.getKey()).getClass();
|
||||
return TenantConfigurationValue.<T> builder().global(false)
|
||||
.createdBy(updatedTenantConfiguration.getCreatedBy())
|
||||
.createdAt(updatedTenantConfiguration.getCreatedAt())
|
||||
@@ -257,27 +277,25 @@ public class JpaTenantConfigurationManagement implements TenantConfigurationMana
|
||||
}
|
||||
|
||||
private <T extends Serializable> TenantConfigurationValue<T> buildTenantConfigurationValueByKey(
|
||||
final TenantConfigurationKey configurationKey, final Class<T> propertyType,
|
||||
final TenantConfiguration tenantConfiguration) {
|
||||
final TenantConfigurationKey configurationKey, final Class<T> propertyType, final TenantConfiguration tenantConfiguration) {
|
||||
if (tenantConfiguration != null) {
|
||||
return TenantConfigurationValue.<T> builder().global(false).createdBy(tenantConfiguration.getCreatedBy())
|
||||
.createdAt(tenantConfiguration.getCreatedAt())
|
||||
.lastModifiedAt(tenantConfiguration.getLastModifiedAt())
|
||||
.lastModifiedBy(tenantConfiguration.getLastModifiedBy())
|
||||
.value(CONVERSION_SERVICE.convert(tenantConfiguration.getValue(), propertyType)).build();
|
||||
|
||||
} else if (configurationKey.getDefaultValue() != null) {
|
||||
|
||||
return TenantConfigurationValue.<T> builder().global(true).createdBy(null).createdAt(null)
|
||||
.lastModifiedAt(null).lastModifiedBy(null)
|
||||
.value(getGlobalConfigurationValue(configurationKey.getKeyName(), propertyType)).build();
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Asserts that the requested configuration value change is allowed. Throws
|
||||
* a {@link TenantConfigurationValueChangeNotAllowedException} otherwise.
|
||||
* Asserts that the requested configuration value change is allowed. Throws a {@link TenantConfigurationValueChangeNotAllowedException}
|
||||
* otherwise.
|
||||
*
|
||||
* @param key The configuration key.
|
||||
* @param valueChange The configuration to be validated.
|
||||
@@ -293,9 +311,7 @@ public class JpaTenantConfigurationManagement implements TenantConfigurationMana
|
||||
private void assertAutoCloseValueChange(final String key, final JpaTenantConfiguration valueChange) {
|
||||
if (REPOSITORY_ACTIONS_AUTOCLOSE_ENABLED.equals(key)
|
||||
&& Boolean.TRUE.equals(getConfigurationValue(MULTI_ASSIGNMENTS_ENABLED, Boolean.class).getValue())) {
|
||||
log.debug(
|
||||
"The property '{}' must not be changed because the Multi-Assignments feature is currently enabled.",
|
||||
key);
|
||||
log.debug("The property '{}' must not be changed because the Multi-Assignments feature is currently enabled.", key);
|
||||
throw new TenantConfigurationValueChangeNotAllowedException();
|
||||
}
|
||||
}
|
||||
@@ -308,8 +324,7 @@ public class JpaTenantConfigurationManagement implements TenantConfigurationMana
|
||||
if (MULTI_ASSIGNMENTS_ENABLED.equals(key) && Boolean.parseBoolean(valueChange.getValue())) {
|
||||
JpaTenantConfiguration batchConfig = tenantConfigurationRepository.findByKey(BATCH_ASSIGNMENTS_ENABLED);
|
||||
if (batchConfig != null && Boolean.parseBoolean(batchConfig.getValue())) {
|
||||
log.debug("The Multi-Assignments '{}' feature cannot be enabled as it contradicts with " +
|
||||
"The Batch-Assignments feature, which is already enabled .", key);
|
||||
log.debug("The Multi-Assignments '{}' feature cannot be enabled as it contradicts with the Batch-Assignments feature, which is already enabled .", key);
|
||||
throw new TenantConfigurationValueChangeNotAllowedException();
|
||||
}
|
||||
}
|
||||
@@ -319,10 +334,9 @@ public class JpaTenantConfigurationManagement implements TenantConfigurationMana
|
||||
if (BATCH_ASSIGNMENTS_ENABLED.equals(key) && Boolean.parseBoolean(valueChange.getValue())) {
|
||||
JpaTenantConfiguration multiConfig = tenantConfigurationRepository.findByKey(MULTI_ASSIGNMENTS_ENABLED);
|
||||
if (multiConfig != null && Boolean.parseBoolean(multiConfig.getValue())) {
|
||||
log.debug("The Batch-Assignments '{}' feature cannot be enabled as it contradicts with " +
|
||||
"The Multi-Assignments feature, which is already enabled .", key);
|
||||
log.debug("The Batch-Assignments '{}' feature cannot be enabled as it contradicts with the Multi-Assignments feature, which is already enabled .", key);
|
||||
throw new TenantConfigurationValueChangeNotAllowedException();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -139,24 +139,7 @@ class ControllerManagementSecurityTest extends AbstractJpaIntegrationTest {
|
||||
*/
|
||||
@Test
|
||||
void getPollingTimePermissionsCheck() {
|
||||
assertPermissions(() -> controllerManagement.getPollingTime(), List.of(SpPermission.SpringEvalExpressions.CONTROLLER_ROLE));
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests ControllerManagement#getMinPollingTime() method
|
||||
*/
|
||||
@Test
|
||||
void getMinPollingTimePermissionsCheck() {
|
||||
assertPermissions(() -> controllerManagement.getMinPollingTime(), List.of(SpPermission.SpringEvalExpressions.CONTROLLER_ROLE));
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests ControllerManagement#getMaxPollingTime() method
|
||||
*/
|
||||
@Test
|
||||
void getMaintenanceWindowPollCountPermissionsCheck() {
|
||||
assertPermissions(() -> controllerManagement.getMaintenanceWindowPollCount(),
|
||||
List.of(SpPermission.SpringEvalExpressions.CONTROLLER_ROLE));
|
||||
assertPermissions(() -> controllerManagement.getPollingTime(null), List.of(SpPermission.SpringEvalExpressions.CONTROLLER_ROLE));
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -168,7 +151,7 @@ class ControllerManagementSecurityTest extends AbstractJpaIntegrationTest {
|
||||
action.setId(1L);
|
||||
assertPermissions(() -> {
|
||||
try {
|
||||
controllerManagement.getPollingTimeForAction(action);
|
||||
controllerManagement.getPollingTimeForAction(action.getTarget(), action);
|
||||
} catch (final CancelActionNotAllowedException e) {
|
||||
// expected since action is not found
|
||||
}
|
||||
|
||||
@@ -51,7 +51,7 @@ class TenantConfigurationManagementTest extends AbstractJpaIntegrationTest imple
|
||||
|
||||
// get the configuration from the system management
|
||||
final TenantConfigurationValue<String> defaultConfigValue = tenantConfigurationManagement.getConfigurationValue(
|
||||
TenantConfigurationKey.AUTHENTICATION_MODE_GATEWAY_SECURITY_TOKEN_KEY, String.class);
|
||||
TenantConfigurationKey.AUTHENTICATION_GATEWAY_SECURITY_TOKEN_KEY, String.class);
|
||||
|
||||
assertThat(defaultConfigValue.isGlobal()).isTrue();
|
||||
assertThat(defaultConfigValue.getValue()).isEqualTo(envPropertyDefault);
|
||||
@@ -60,11 +60,11 @@ class TenantConfigurationManagementTest extends AbstractJpaIntegrationTest imple
|
||||
final String newConfigurationValue = "thisIsAnotherTokenName";
|
||||
assertThat(newConfigurationValue).isNotEqualTo(defaultConfigValue.getValue());
|
||||
tenantConfigurationManagement.addOrUpdateConfiguration(
|
||||
TenantConfigurationKey.AUTHENTICATION_MODE_GATEWAY_SECURITY_TOKEN_KEY, newConfigurationValue);
|
||||
TenantConfigurationKey.AUTHENTICATION_GATEWAY_SECURITY_TOKEN_KEY, newConfigurationValue);
|
||||
|
||||
// verify that new configuration value is used
|
||||
final TenantConfigurationValue<String> updatedConfigurationValue = tenantConfigurationManagement
|
||||
.getConfigurationValue(TenantConfigurationKey.AUTHENTICATION_MODE_GATEWAY_SECURITY_TOKEN_KEY, String.class);
|
||||
.getConfigurationValue(TenantConfigurationKey.AUTHENTICATION_GATEWAY_SECURITY_TOKEN_KEY, String.class);
|
||||
|
||||
assertThat(updatedConfigurationValue.isGlobal()).isFalse();
|
||||
assertThat(updatedConfigurationValue.getValue()).isEqualTo(newConfigurationValue);
|
||||
@@ -76,7 +76,7 @@ class TenantConfigurationManagementTest extends AbstractJpaIntegrationTest imple
|
||||
*/
|
||||
@Test
|
||||
void updateTenantSpecificConfiguration() {
|
||||
final String configKey = TenantConfigurationKey.AUTHENTICATION_MODE_GATEWAY_SECURITY_TOKEN_KEY;
|
||||
final String configKey = TenantConfigurationKey.AUTHENTICATION_GATEWAY_SECURITY_TOKEN_KEY;
|
||||
final String value1 = "firstValue";
|
||||
final String value2 = "secondValue";
|
||||
|
||||
@@ -95,14 +95,14 @@ class TenantConfigurationManagementTest extends AbstractJpaIntegrationTest imple
|
||||
@Test
|
||||
void batchUpdateTenantSpecificConfiguration() {
|
||||
Map<String, Serializable> configuration = new HashMap<>() {{
|
||||
put(TenantConfigurationKey.AUTHENTICATION_MODE_GATEWAY_SECURITY_TOKEN_KEY, "token_123");
|
||||
put(TenantConfigurationKey.AUTHENTICATION_GATEWAY_SECURITY_TOKEN_KEY, "token_123");
|
||||
put(TenantConfigurationKey.ROLLOUT_APPROVAL_ENABLED, true);
|
||||
}};
|
||||
|
||||
// add value first
|
||||
tenantConfigurationManagement.addOrUpdateConfiguration(configuration);
|
||||
assertThat(tenantConfigurationManagement.getConfigurationValue(
|
||||
TenantConfigurationKey.AUTHENTICATION_MODE_GATEWAY_SECURITY_TOKEN_KEY, String.class).getValue())
|
||||
TenantConfigurationKey.AUTHENTICATION_GATEWAY_SECURITY_TOKEN_KEY, String.class).getValue())
|
||||
.isEqualTo("token_123");
|
||||
assertThat(
|
||||
tenantConfigurationManagement.getConfigurationValue(TenantConfigurationKey.ROLLOUT_APPROVAL_ENABLED, Boolean.class).getValue())
|
||||
@@ -114,7 +114,7 @@ class TenantConfigurationManagementTest extends AbstractJpaIntegrationTest imple
|
||||
*/
|
||||
@Test
|
||||
void storeAndUpdateTenantSpecificConfigurationAsBoolean() {
|
||||
final String configKey = TenantConfigurationKey.AUTHENTICATION_MODE_HEADER_ENABLED;
|
||||
final String configKey = TenantConfigurationKey.AUTHENTICATION_HEADER_ENABLED;
|
||||
final Boolean value1 = true;
|
||||
tenantConfigurationManagement.addOrUpdateConfiguration(configKey, value1);
|
||||
assertThat(tenantConfigurationManagement.getConfigurationValue(configKey, Boolean.class).getValue()).isEqualTo(value1);
|
||||
@@ -128,7 +128,7 @@ class TenantConfigurationManagementTest extends AbstractJpaIntegrationTest imple
|
||||
*/
|
||||
@Test
|
||||
void wrongTenantConfigurationValueTypeThrowsException() {
|
||||
final String configKey = TenantConfigurationKey.AUTHENTICATION_MODE_HEADER_ENABLED;
|
||||
final String configKey = TenantConfigurationKey.AUTHENTICATION_HEADER_ENABLED;
|
||||
final String value1 = "thisIsNotABoolean";
|
||||
|
||||
// add value as String
|
||||
@@ -143,9 +143,9 @@ class TenantConfigurationManagementTest extends AbstractJpaIntegrationTest imple
|
||||
@Test
|
||||
void batchWrongTenantConfigurationValueTypeThrowsException() {
|
||||
final Map<String, Serializable> configuration = new HashMap<>() {{
|
||||
put(TenantConfigurationKey.AUTHENTICATION_MODE_GATEWAY_SECURITY_TOKEN_KEY, "token_123");
|
||||
put(TenantConfigurationKey.AUTHENTICATION_GATEWAY_SECURITY_TOKEN_KEY, "token_123");
|
||||
put(TenantConfigurationKey.ROLLOUT_APPROVAL_ENABLED, true);
|
||||
put(TenantConfigurationKey.AUTHENTICATION_MODE_GATEWAY_SECURITY_TOKEN_ENABLED, "wrong");
|
||||
put(TenantConfigurationKey.AUTHENTICATION_GATEWAY_SECURITY_TOKEN_ENABLED, "wrong");
|
||||
}};
|
||||
|
||||
try {
|
||||
@@ -154,7 +154,7 @@ class TenantConfigurationManagementTest extends AbstractJpaIntegrationTest imple
|
||||
} catch (final TenantConfigurationValidatorException e) {
|
||||
assertThat(
|
||||
tenantConfigurationManagement.getConfigurationValue(
|
||||
TenantConfigurationKey.AUTHENTICATION_MODE_GATEWAY_SECURITY_TOKEN_KEY, String.class).getValue())
|
||||
TenantConfigurationKey.AUTHENTICATION_GATEWAY_SECURITY_TOKEN_KEY, String.class).getValue())
|
||||
.isNotEqualTo("token_123");
|
||||
assertThat(
|
||||
tenantConfigurationManagement.getConfigurationValue(
|
||||
@@ -168,7 +168,7 @@ class TenantConfigurationManagementTest extends AbstractJpaIntegrationTest imple
|
||||
*/
|
||||
@Test
|
||||
void deleteConfigurationReturnNullConfiguration() {
|
||||
final String configKey = TenantConfigurationKey.AUTHENTICATION_MODE_GATEWAY_SECURITY_TOKEN_KEY;
|
||||
final String configKey = TenantConfigurationKey.AUTHENTICATION_GATEWAY_SECURITY_TOKEN_KEY;
|
||||
|
||||
// gateway token does not have default value so no configuration value should be available
|
||||
final String defaultConfigValue = tenantConfigurationManagement.getConfigurationValue(configKey, String.class).getValue();
|
||||
@@ -195,7 +195,7 @@ class TenantConfigurationManagementTest extends AbstractJpaIntegrationTest imple
|
||||
*/
|
||||
@Test
|
||||
void storesIntegerWhenStringIsExpected() {
|
||||
final String configKey = TenantConfigurationKey.AUTHENTICATION_MODE_GATEWAY_SECURITY_TOKEN_KEY;
|
||||
final String configKey = TenantConfigurationKey.AUTHENTICATION_GATEWAY_SECURITY_TOKEN_KEY;
|
||||
final Integer wrongDatType = 123;
|
||||
assertThatThrownBy(() -> tenantConfigurationManagement.addOrUpdateConfiguration(configKey, wrongDatType))
|
||||
.as("Should not have worked as integer is not a string")
|
||||
@@ -207,7 +207,7 @@ class TenantConfigurationManagementTest extends AbstractJpaIntegrationTest imple
|
||||
*/
|
||||
@Test
|
||||
void storesIntegerWhenBooleanIsExpected() {
|
||||
final String configKey = TenantConfigurationKey.AUTHENTICATION_MODE_GATEWAY_SECURITY_TOKEN_ENABLED;
|
||||
final String configKey = TenantConfigurationKey.AUTHENTICATION_GATEWAY_SECURITY_TOKEN_ENABLED;
|
||||
final Integer wrongDataType = 123;
|
||||
assertThatThrownBy(() -> tenantConfigurationManagement.addOrUpdateConfiguration(configKey, wrongDataType))
|
||||
.as("Should not have worked as integer is not a boolean")
|
||||
@@ -219,7 +219,7 @@ class TenantConfigurationManagementTest extends AbstractJpaIntegrationTest imple
|
||||
*/
|
||||
@Test
|
||||
void storesIntegerWhenPollingIntervalIsExpected() {
|
||||
final String configKey = TenantConfigurationKey.POLLING_TIME_INTERVAL;
|
||||
final String configKey = TenantConfigurationKey.POLLING_TIME;
|
||||
final Integer wrongDataType = 123;
|
||||
assertThatThrownBy(() -> tenantConfigurationManagement.addOrUpdateConfiguration(configKey, wrongDataType))
|
||||
.as("Should not have worked as integer is not a time field")
|
||||
@@ -231,7 +231,7 @@ class TenantConfigurationManagementTest extends AbstractJpaIntegrationTest imple
|
||||
*/
|
||||
@Test
|
||||
void storesWrongFormattedStringAsPollingInterval() {
|
||||
final String configKey = TenantConfigurationKey.POLLING_TIME_INTERVAL;
|
||||
final String configKey = TenantConfigurationKey.POLLING_TIME;
|
||||
final String wrongFormatted = "wrongFormatted";
|
||||
assertThatThrownBy(() -> tenantConfigurationManagement.addOrUpdateConfiguration(configKey, wrongFormatted))
|
||||
.as("should not have worked as string is not a time field")
|
||||
@@ -243,10 +243,9 @@ class TenantConfigurationManagementTest extends AbstractJpaIntegrationTest imple
|
||||
*/
|
||||
@Test
|
||||
void storesTooSmallDurationAsPollingInterval() {
|
||||
final String configKey = TenantConfigurationKey.POLLING_TIME_INTERVAL;
|
||||
final String configKey = TenantConfigurationKey.POLLING_TIME;
|
||||
|
||||
final String tooSmallDuration = DurationHelper
|
||||
.durationToFormattedString(DurationHelper.getDurationByTimeValues(0, 0, 1));
|
||||
final String tooSmallDuration = DurationHelper.toString(getDurationByTimeValues(0, 0, 1));
|
||||
assertThatThrownBy(() -> tenantConfigurationManagement.addOrUpdateConfiguration(configKey, tooSmallDuration))
|
||||
.as("Should not have worked as string has an invalid format")
|
||||
.isInstanceOf(TenantConfigurationValidatorException.class);
|
||||
@@ -257,15 +256,15 @@ class TenantConfigurationManagementTest extends AbstractJpaIntegrationTest imple
|
||||
*/
|
||||
@Test
|
||||
void storesCorrectDurationAsPollingInterval() {
|
||||
final String configKey = TenantConfigurationKey.POLLING_TIME_INTERVAL;
|
||||
final String configKey = TenantConfigurationKey.POLLING_TIME;
|
||||
|
||||
final Duration duration = DurationHelper.getDurationByTimeValues(1, 2, 0);
|
||||
final Duration duration = getDurationByTimeValues(1, 2, 0);
|
||||
assertThat(duration).isEqualTo(Duration.ofHours(1).plusMinutes(2));
|
||||
|
||||
tenantConfigurationManagement.addOrUpdateConfiguration(configKey, DurationHelper.durationToFormattedString(duration));
|
||||
tenantConfigurationManagement.addOrUpdateConfiguration(configKey, DurationHelper.toString(duration));
|
||||
|
||||
final String storedDurationString = tenantConfigurationManagement.getConfigurationValue(configKey, String.class).getValue();
|
||||
assertThat(duration).isEqualTo(DurationHelper.formattedStringToDuration(storedDurationString));
|
||||
assertThat(duration).isEqualTo(DurationHelper.fromString(storedDurationString));
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -274,7 +273,7 @@ class TenantConfigurationManagementTest extends AbstractJpaIntegrationTest imple
|
||||
@Test
|
||||
void requestConfigValueWithWrongType() {
|
||||
assertThatThrownBy(() -> tenantConfigurationManagement.getConfigurationValue(
|
||||
TenantConfigurationKey.POLLING_TIME_INTERVAL, Serializable.class))
|
||||
TenantConfigurationKey.POLLING_TIME, Serializable.class))
|
||||
.isInstanceOf(TenantConfigurationValidatorException.class);
|
||||
}
|
||||
|
||||
@@ -297,7 +296,7 @@ class TenantConfigurationManagementTest extends AbstractJpaIntegrationTest imple
|
||||
*/
|
||||
@Test
|
||||
void getTenantConfigurationKeyByName() {
|
||||
final String configKey = TenantConfigurationKey.POLLING_TIME_INTERVAL;
|
||||
final String configKey = TenantConfigurationKey.POLLING_TIME;
|
||||
assertThat(tenantConfigurationProperties.fromKeyName(configKey).getKeyName()).isEqualTo(configKey);
|
||||
}
|
||||
|
||||
@@ -311,4 +310,8 @@ class TenantConfigurationManagementTest extends AbstractJpaIntegrationTest imple
|
||||
.as("Expected InvalidTenantConfigurationKeyException for tenant configuration key which is not declared")
|
||||
.isInstanceOf(InvalidTenantConfigurationKeyException.class);
|
||||
}
|
||||
|
||||
private static Duration getDurationByTimeValues(final long hours, final long minutes, final long seconds) {
|
||||
return Duration.ofHours(hours).plusMinutes(minutes).plusSeconds(seconds);
|
||||
}
|
||||
}
|
||||
@@ -411,9 +411,9 @@ class RsqlUtilityTest {
|
||||
VirtualPropertyReplacer setupMacroLookup() {
|
||||
when(securityContext.runAsSystem(Mockito.any())).thenAnswer(a -> ((Callable<?>) a.getArgument(0)).call());
|
||||
|
||||
when(confMgmt.getConfigurationValue(TenantConfigurationKey.POLLING_TIME_INTERVAL, String.class))
|
||||
when(confMgmt.getConfigurationValue(TenantConfigurationKey.POLLING_TIME, String.class))
|
||||
.thenReturn(TEST_POLLING_TIME_INTERVAL);
|
||||
when(confMgmt.getConfigurationValue(TenantConfigurationKey.POLLING_OVERDUE_TIME_INTERVAL, String.class))
|
||||
when(confMgmt.getConfigurationValue(TenantConfigurationKey.POLLING_OVERDUE_TIME, String.class))
|
||||
.thenReturn(TEST_POLLING_OVERDUE_TIME_INTERVAL);
|
||||
|
||||
return macroResolver;
|
||||
|
||||
@@ -54,9 +54,9 @@ class VirtualPropertyResolverTest {
|
||||
|
||||
@BeforeEach
|
||||
void before() {
|
||||
when(confMgmt.getConfigurationValue(TenantConfigurationKey.POLLING_TIME_INTERVAL, String.class))
|
||||
when(confMgmt.getConfigurationValue(TenantConfigurationKey.POLLING_TIME, String.class))
|
||||
.thenReturn(TEST_POLLING_TIME_INTERVAL);
|
||||
when(confMgmt.getConfigurationValue(TenantConfigurationKey.POLLING_OVERDUE_TIME_INTERVAL, String.class))
|
||||
when(confMgmt.getConfigurationValue(TenantConfigurationKey.POLLING_OVERDUE_TIME, String.class))
|
||||
.thenReturn(TEST_POLLING_OVERDUE_TIME_INTERVAL);
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user