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