Refactor the tenantcy configuration (#2080)

Signed-off-by: Avgustin Marinov <Avgustin.Marinov@bosch.com>
This commit is contained in:
Avgustin Marinov
2024-11-19 08:45:50 +02:00
committed by GitHub
parent 7ac0a13054
commit 4861e7c29f
10 changed files with 102 additions and 159 deletions

View File

@@ -19,7 +19,7 @@ import lombok.ToString;
import org.eclipse.hawkbit.repository.model.Target; import org.eclipse.hawkbit.repository.model.Target;
/** /**
* Event is send in case a target polls either through DDI or DMF. * Event is sent in case a target polls either through DDI or DMF.
*/ */
@NoArgsConstructor(access = AccessLevel.PUBLIC) // for serialization libs like jackson @NoArgsConstructor(access = AccessLevel.PUBLIC) // for serialization libs like jackson
@Getter @Getter

View File

@@ -13,11 +13,15 @@ import java.io.Serializable;
import java.util.Collection; import java.util.Collection;
import java.util.HashMap; import java.util.HashMap;
import java.util.Map; import java.util.Map;
import java.util.Objects;
import lombok.Data; import lombok.Data;
import lombok.ToString; import lombok.ToString;
import org.eclipse.hawkbit.repository.exception.InvalidTenantConfigurationKeyException; import org.eclipse.hawkbit.repository.exception.InvalidTenantConfigurationKeyException;
import org.eclipse.hawkbit.repository.exception.TenantConfigurationValidatorException; import org.eclipse.hawkbit.repository.exception.TenantConfigurationValidatorException;
import org.eclipse.hawkbit.tenancy.configuration.validator.TenantConfigurationBooleanValidator;
import org.eclipse.hawkbit.tenancy.configuration.validator.TenantConfigurationIntegerValidator;
import org.eclipse.hawkbit.tenancy.configuration.validator.TenantConfigurationLongValidator;
import org.eclipse.hawkbit.tenancy.configuration.validator.TenantConfigurationStringValidator; import org.eclipse.hawkbit.tenancy.configuration.validator.TenantConfigurationStringValidator;
import org.eclipse.hawkbit.tenancy.configuration.validator.TenantConfigurationValidator; import org.eclipse.hawkbit.tenancy.configuration.validator.TenantConfigurationValidator;
import org.springframework.boot.context.properties.ConfigurationProperties; import org.springframework.boot.context.properties.ConfigurationProperties;
@@ -25,6 +29,8 @@ import org.springframework.context.ApplicationContext;
/** /**
* Properties for tenant configuration default values. * Properties for tenant configuration default values.
* <p/>
* Configuration are read from the properties where their type and validators could be specified also. Default type and validator is String.
*/ */
@Data @Data
@ToString @ToString
@@ -45,14 +51,14 @@ public class TenantConfigurationProperties {
* @return the TenantConfigurationKey with the name keyName * @return the TenantConfigurationKey with the name keyName
*/ */
public TenantConfigurationKey fromKeyName(final String keyName) { public TenantConfigurationKey fromKeyName(final String keyName) {
return configuration.values().stream().filter(conf -> conf.getKeyName().equals(keyName)).findAny() return configuration.values().stream()
.orElseThrow(() -> new InvalidTenantConfigurationKeyException( .filter(conf -> conf.getKeyName().equals(keyName))
"The given configuration key " + keyName + " does not exist.")); .findAny()
.orElseThrow(() -> new InvalidTenantConfigurationKeyException("The given configuration key " + keyName + " does not exist."));
} }
/** /**
* Tenant specific configurations which can be configured for each tenant * Tenant specific configurations which can be configured for each tenant separately by means of override of the system defaults.
* separately by means of override of the system defaults.
*/ */
@Data @Data
@ToString @ToString
@@ -62,121 +68,112 @@ public class TenantConfigurationProperties {
* Header based authentication enabled. * Header based authentication enabled.
*/ */
public static final String AUTHENTICATION_MODE_HEADER_ENABLED = "authentication.header.enabled"; public static final String AUTHENTICATION_MODE_HEADER_ENABLED = "authentication.header.enabled";
/** /**
* Header based authentication authority name. * Header based authentication authority name.
*/ */
public static final String AUTHENTICATION_MODE_HEADER_AUTHORITY_NAME = "authentication.header.authority"; public static final String AUTHENTICATION_MODE_HEADER_AUTHORITY_NAME = "authentication.header.authority";
/** /**
* Target token based authentication enabled. * Target token based authentication enabled.
*/ */
public static final String AUTHENTICATION_MODE_TARGET_SECURITY_TOKEN_ENABLED = "authentication.targettoken.enabled"; public static final String AUTHENTICATION_MODE_TARGET_SECURITY_TOKEN_ENABLED = "authentication.targettoken.enabled";
/** /**
* Gateway token based authentication enabled. * Gateway token based authentication enabled.
*/ */
public static final String AUTHENTICATION_MODE_GATEWAY_SECURITY_TOKEN_ENABLED = "authentication.gatewaytoken.enabled"; public static final String AUTHENTICATION_MODE_GATEWAY_SECURITY_TOKEN_ENABLED = "authentication.gatewaytoken.enabled";
/** /**
* Gateway token value. * Gateway token value.
*/ */
public static final String AUTHENTICATION_MODE_GATEWAY_SECURITY_TOKEN_KEY = "authentication.gatewaytoken.key"; public static final String AUTHENTICATION_MODE_GATEWAY_SECURITY_TOKEN_KEY = "authentication.gatewaytoken.key";
/** /**
* See system default in * See system default in
* {@link ControllerPollProperties#getPollingTime()}. * {@link ControllerPollProperties#getPollingTime()}.
*/ */
public static final String POLLING_TIME_INTERVAL = "pollingTime"; public static final String POLLING_TIME_INTERVAL = "pollingTime";
/** /**
* See system default in * See system default in
* {@link ControllerPollProperties#getMinPollingTime()}. * {@link ControllerPollProperties#getMinPollingTime()}.
*/ */
public static final String MIN_POLLING_TIME_INTERVAL = "minPollingTime"; public static final String MIN_POLLING_TIME_INTERVAL = "minPollingTime";
/** /**
* See system default in * See system default in
* {@link ControllerPollProperties#getMaintenanceWindowPollCount()}. * {@link ControllerPollProperties#getMaintenanceWindowPollCount()}.
*/ */
public static final String MAINTENANCE_WINDOW_POLL_COUNT = "maintenanceWindowPollCount"; public static final String MAINTENANCE_WINDOW_POLL_COUNT = "maintenanceWindowPollCount";
/** /**
* See system default in * See system default in
* {@link ControllerPollProperties#getPollingOverdueTime()}. * {@link ControllerPollProperties#getPollingOverdueTime()}.
*/ */
public static final String POLLING_OVERDUE_TIME_INTERVAL = "pollingOverdueTime"; public static final String POLLING_OVERDUE_TIME_INTERVAL = "pollingOverdueTime";
/** /**
* If anonymous downloads are enabled * If anonymous downloads are enabled
*/ */
public static final String ANONYMOUS_DOWNLOAD_MODE_ENABLED = "anonymous.download.enabled"; public static final String ANONYMOUS_DOWNLOAD_MODE_ENABLED = "anonymous.download.enabled";
/** /**
* Represents setting if approval for a rollout is needed. * Represents setting if approval for a rollout is needed.
*/ */
public static final String ROLLOUT_APPROVAL_ENABLED = "rollout.approval.enabled"; public static final String ROLLOUT_APPROVAL_ENABLED = "rollout.approval.enabled";
/** /**
* Repository on autoclose mode instead of canceling in case of new DS * Repository on autoclose mode instead of canceling in case of new DS
* assignment over active actions. * assignment over active actions.
*/ */
public static final String REPOSITORY_ACTIONS_AUTOCLOSE_ENABLED = "repository.actions.autoclose.enabled"; public static final String REPOSITORY_ACTIONS_AUTOCLOSE_ENABLED = "repository.actions.autoclose.enabled";
/** /**
* Switch to enable/disable automatic action cleanup. * Switch to enable/disable automatic action cleanup.
*/ */
public static final String ACTION_CLEANUP_ENABLED = "action.cleanup.enabled"; public static final String ACTION_CLEANUP_ENABLED = "action.cleanup.enabled";
/** /**
* Specifies the action expiry in milli-seconds. * Specifies the action expiry in milli-seconds.
*/ */
public static final String ACTION_CLEANUP_ACTION_EXPIRY = "action.cleanup.actionExpiry"; public static final String ACTION_CLEANUP_ACTION_EXPIRY = "action.cleanup.actionExpiry";
/** /**
* Specifies the action status. * Specifies the action status.
*/ */
public static final String ACTION_CLEANUP_ACTION_STATUS = "action.cleanup.actionStatus"; public static final String ACTION_CLEANUP_ACTION_STATUS = "action.cleanup.actionStatus";
/** /**
* Switch to enable/disable the multi-assignment feature. * Switch to enable/disable the multi-assignment feature.
*/ */
public static final String MULTI_ASSIGNMENTS_ENABLED = "multi.assignments.enabled"; public static final String MULTI_ASSIGNMENTS_ENABLED = "multi.assignments.enabled";
/** /**
* Switch to enable/disable the batch-assignment feature. * Switch to enable/disable the batch-assignment feature.
*/ */
public static final String BATCH_ASSIGNMENTS_ENABLED = "batch.assignments.enabled"; public static final String BATCH_ASSIGNMENTS_ENABLED = "batch.assignments.enabled";
/** /**
* Switch to enable/disable the user-confirmation flow * Switch to enable/disable the user-confirmation flow
*/ */
public static final String USER_CONFIRMATION_ENABLED = "user.confirmation.flow.enabled"; public static final String USER_CONFIRMATION_ENABLED = "user.confirmation.flow.enabled";
/** /**
* Switch to enable/disable the implicit locking * Switch to enable/disable the implicit locking
*/ */
public static final String IMPLICIT_LOCK_ENABLED = "implicit.lock.enabled"; public static final String IMPLICIT_LOCK_ENABLED = "implicit.lock.enabled";
private static final Map<Class<? extends Serializable>, TenantConfigurationValidator> DEFAULT_TYPE_VALIDATORS = Map.of(
Boolean.class, new TenantConfigurationBooleanValidator(),
Integer.class, new TenantConfigurationIntegerValidator(),
Long.class, new TenantConfigurationLongValidator(),
String.class, new TenantConfigurationStringValidator());
private String keyName; private String keyName;
private String defaultValue = ""; private String defaultValue = "";
private Class<? extends Serializable> dataType = String.class; private Class<? extends Serializable> dataType = String.class;
private Class<? extends TenantConfigurationValidator> validator = TenantConfigurationStringValidator.class; private Class<? extends TenantConfigurationValidator> validator;
/** /**
* validates if a object matches the allowed data format of the corresponding key * Validates if an object matches the allowed data format of the corresponding key
* *
* @param context application context * @param context application context
* @param value which will be validated * @param value which will be validated
* @throws TenantConfigurationValidatorException is thrown, when object is invalid * @throws TenantConfigurationValidatorException is thrown, when object is invalid
*/ */
public void validate(final ApplicationContext context, final Object value) { public void validate(final ApplicationContext context, final Object value) {
final TenantConfigurationValidator createdBean = context.getAutowireCapableBeanFactory() if (validator == null) {
.createBean(validator); Objects.requireNonNull(DEFAULT_TYPE_VALIDATORS.get(dataType), "No validator defined for " + keyName).validate(value);
try { } else {
createdBean.validate(value); final TenantConfigurationValidator createdBean = context.getAutowireCapableBeanFactory().createBean(validator);
} finally { try {
context.getAutowireCapableBeanFactory().destroyBean(createdBean); createdBean.validate(value);
} finally {
context.getAutowireCapableBeanFactory().destroyBean(createdBean);
}
} }
} }
} }

View File

@@ -10,8 +10,7 @@
package org.eclipse.hawkbit.tenancy.configuration.validator; package org.eclipse.hawkbit.tenancy.configuration.validator;
/** /**
* specific tenant configuration validator, which validates that the given value * specific tenant configuration validator, which validates that the given value is a booleans.
* is a booleans.
*/ */
public class TenantConfigurationBooleanValidator implements TenantConfigurationValidator { public class TenantConfigurationBooleanValidator implements TenantConfigurationValidator {

View File

@@ -11,8 +11,7 @@
package org.eclipse.hawkbit.tenancy.configuration.validator; package org.eclipse.hawkbit.tenancy.configuration.validator;
/** /**
* Specific tenant configuration validator, which validates that the given value * Specific tenant configuration validator, which validates that the given value is an Integer.
* is an Integer.
*/ */
public class TenantConfigurationIntegerValidator implements TenantConfigurationValidator { public class TenantConfigurationIntegerValidator implements TenantConfigurationValidator {

View File

@@ -10,8 +10,7 @@
package org.eclipse.hawkbit.tenancy.configuration.validator; package org.eclipse.hawkbit.tenancy.configuration.validator;
/** /**
* Specific tenant configuration validator, which validates that the given value * Specific tenant configuration validator, which validates that the given value is a {@link Long}.
* is a {@link Long}.
*/ */
public class TenantConfigurationLongValidator implements TenantConfigurationValidator { public class TenantConfigurationLongValidator implements TenantConfigurationValidator {

View File

@@ -18,21 +18,20 @@ import org.eclipse.hawkbit.tenancy.configuration.DurationHelper;
import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Autowired;
/** /**
* This class is used to validate, that the property is a String and that it is * This class is used to validate, that the property is a String and that it is in the correct duration format.
* in the correct duration format.
*/ */
public class TenantConfigurationPollingDurationValidator implements TenantConfigurationValidator { public class TenantConfigurationPollingDurationValidator implements TenantConfigurationValidator {
private final Duration minDuration; private final Duration minDuration;
private final Duration maxDuration; private final Duration maxDuration;
/** /**
* Constructor. * 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 * @param properties property accessor for poll configuration
*/ */
@Autowired
public TenantConfigurationPollingDurationValidator(final ControllerPollProperties properties) { public TenantConfigurationPollingDurationValidator(final ControllerPollProperties properties) {
minDuration = DurationHelper.formattedStringToDuration(properties.getMinPollingTime()); minDuration = DurationHelper.formattedStringToDuration(properties.getMinPollingTime());
maxDuration = DurationHelper.formattedStringToDuration(properties.getMaxPollingTime()); maxDuration = DurationHelper.formattedStringToDuration(properties.getMaxPollingTime());

View File

@@ -23,12 +23,10 @@ public interface TenantConfigurationValidator {
* @throws TenantConfigurationValidatorException is thrown, when parameter is invalid. * @throws TenantConfigurationValidatorException is thrown, when parameter is invalid.
*/ */
default void validate(final Object tenantConfigurationValue) { default void validate(final Object tenantConfigurationValue) {
if (tenantConfigurationValue != null if (tenantConfigurationValue != null && validateToClass().isAssignableFrom(tenantConfigurationValue.getClass())) {
&& validateToClass().isAssignableFrom(tenantConfigurationValue.getClass())) {
return; return;
} }
throw new TenantConfigurationValidatorException( throw new TenantConfigurationValidatorException("The given configuration value is expected as a " + validateToClass().getSimpleName());
"The given configuration value is expected as a " + validateToClass().getSimpleName());
} }
/** /**

View File

@@ -14,26 +14,27 @@ hawkbit.controller.pollingOverdueTime=00:05:00
hawkbit.controller.maxPollingTime=23:59:59 hawkbit.controller.maxPollingTime=23:59:59
hawkbit.controller.minPollingTime=00:00:30 hawkbit.controller.minPollingTime=00:00:30
# This configuration value is used to change the polling interval so that # This configuration value is used to change the polling interval so that controller tries to poll at least these many
# controller tries to poll at least these many times between the last polling # times between the last polling and before start of maintenance window. The polling interval is bounded by configured
# and before start of maintenance window. The polling interval is bounded by # pollingTime and minPollingTime. The polling interval is modified as per following scheme:
# configured pollingTime and minPollingTime. The polling interval is #
# modified as per following scheme:
# pollingTime(@time=t) = (maintenanceStartTime - t)/maintenanceWindowPollCount # pollingTime(@time=t) = (maintenanceStartTime - t)/maintenanceWindowPollCount
hawkbit.controller.maintenanceWindowPollCount=3 hawkbit.controller.maintenanceWindowPollCount=3
# Attention: if you want to use a maximumPollingTime greater 23:59:59 you have to update the DurationField in the configuration window # Attention: if you want to use a maximumPollingTime greater 23:59:59 you have to update the DurationField in the configuration window
# Default tenant configuration - START # Default tenant configuration - START
# DefaultType is java.lang.String, with default value - empty string
# There are default java.lang.Boolean, java.lang.Integer, java.lang.Long, java.lang.String validators. If their
# validation is sufficient - no need to be specified explicitly. Could be explicitly overridden by specifying a
# validator
hawkbit.server.tenant.configuration.authentication-header-enabled.keyName=authentication.header.enabled hawkbit.server.tenant.configuration.authentication-header-enabled.keyName=authentication.header.enabled
hawkbit.server.tenant.configuration.authentication-header-enabled.defaultValue=${hawkbit.server.ddi.security.authentication.header.enabled} hawkbit.server.tenant.configuration.authentication-header-enabled.defaultValue=${hawkbit.server.ddi.security.authentication.header.enabled}
hawkbit.server.tenant.configuration.authentication-header-enabled.dataType=java.lang.Boolean hawkbit.server.tenant.configuration.authentication-header-enabled.dataType=java.lang.Boolean
hawkbit.server.tenant.configuration.authentication-header-enabled.validator=org.eclipse.hawkbit.tenancy.configuration.validator.TenantConfigurationBooleanValidator
hawkbit.server.tenant.configuration.repository-actions-autoclose-enabled.keyName=repository.actions.autoclose.enabled hawkbit.server.tenant.configuration.repository-actions-autoclose-enabled.keyName=repository.actions.autoclose.enabled
hawkbit.server.tenant.configuration.repository-actions-autoclose-enabled.defaultValue=false hawkbit.server.tenant.configuration.repository-actions-autoclose-enabled.defaultValue=false
hawkbit.server.tenant.configuration.repository-actions-autoclose-enabled.dataType=java.lang.Boolean hawkbit.server.tenant.configuration.repository-actions-autoclose-enabled.dataType=java.lang.Boolean
hawkbit.server.tenant.configuration.repository-actions-autoclose-enabled.validator=org.eclipse.hawkbit.tenancy.configuration.validator.TenantConfigurationBooleanValidator
hawkbit.server.tenant.configuration.authentication-header-authority.keyName=authentication.header.authority hawkbit.server.tenant.configuration.authentication-header-authority.keyName=authentication.header.authority
hawkbit.server.tenant.configuration.authentication-header-authority.defaultValue=${hawkbit.server.ddi.security.authentication.header.authority} hawkbit.server.tenant.configuration.authentication-header-authority.defaultValue=${hawkbit.server.ddi.security.authentication.header.authority}
@@ -41,12 +42,10 @@ hawkbit.server.tenant.configuration.authentication-header-authority.defaultValue
hawkbit.server.tenant.configuration.authentication-targettoken-enabled.keyName=authentication.targettoken.enabled hawkbit.server.tenant.configuration.authentication-targettoken-enabled.keyName=authentication.targettoken.enabled
hawkbit.server.tenant.configuration.authentication-targettoken-enabled.defaultValue=${hawkbit.server.ddi.security.authentication.targettoken.enabled} hawkbit.server.tenant.configuration.authentication-targettoken-enabled.defaultValue=${hawkbit.server.ddi.security.authentication.targettoken.enabled}
hawkbit.server.tenant.configuration.authentication-targettoken-enabled.dataType=java.lang.Boolean hawkbit.server.tenant.configuration.authentication-targettoken-enabled.dataType=java.lang.Boolean
hawkbit.server.tenant.configuration.authentication-targettoken-enabled.validator=org.eclipse.hawkbit.tenancy.configuration.validator.TenantConfigurationBooleanValidator
hawkbit.server.tenant.configuration.authentication-gatewaytoken-enabled.keyName=authentication.gatewaytoken.enabled hawkbit.server.tenant.configuration.authentication-gatewaytoken-enabled.keyName=authentication.gatewaytoken.enabled
hawkbit.server.tenant.configuration.authentication-gatewaytoken-enabled.defaultValue=${hawkbit.server.ddi.security.authentication.gatewaytoken.enabled} hawkbit.server.tenant.configuration.authentication-gatewaytoken-enabled.defaultValue=${hawkbit.server.ddi.security.authentication.gatewaytoken.enabled}
hawkbit.server.tenant.configuration.authentication-gatewaytoken-enabled.dataType=java.lang.Boolean hawkbit.server.tenant.configuration.authentication-gatewaytoken-enabled.dataType=java.lang.Boolean
hawkbit.server.tenant.configuration.authentication-gatewaytoken-enabled.validator=org.eclipse.hawkbit.tenancy.configuration.validator.TenantConfigurationBooleanValidator
hawkbit.server.tenant.configuration.authentication-gatewaytoken-key.keyName=authentication.gatewaytoken.key hawkbit.server.tenant.configuration.authentication-gatewaytoken-key.keyName=authentication.gatewaytoken.key
hawkbit.server.tenant.configuration.authentication-gatewaytoken-key.defaultValue=${hawkbit.server.ddi.security.authentication.gatewaytoken.key} hawkbit.server.tenant.configuration.authentication-gatewaytoken-key.defaultValue=${hawkbit.server.ddi.security.authentication.gatewaytoken.key}
@@ -66,28 +65,23 @@ hawkbit.server.tenant.configuration.polling-overdue-time.validator=org.eclipse.h
hawkbit.server.tenant.configuration.maintenance-window-poll-count.keyName=maintenanceWindowPollCount 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.defaultValue=${hawkbit.controller.maintenanceWindowPollCount}
hawkbit.server.tenant.configuration.maintenance-window-poll-count.dataType=java.lang.Integer hawkbit.server.tenant.configuration.maintenance-window-poll-count.dataType=java.lang.Integer
hawkbit.server.tenant.configuration.maintenance-window-poll-count.validator=org.eclipse.hawkbit.tenancy.configuration.validator.TenantConfigurationIntegerValidator
hawkbit.server.tenant.configuration.anonymous-download-enabled.keyName=anonymous.download.enabled 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.defaultValue=${hawkbit.server.download.anonymous.enabled}
hawkbit.server.tenant.configuration.anonymous-download-enabled.dataType=java.lang.Boolean hawkbit.server.tenant.configuration.anonymous-download-enabled.dataType=java.lang.Boolean
hawkbit.server.tenant.configuration.anonymous-download-enabled.validator=org.eclipse.hawkbit.tenancy.configuration.validator.TenantConfigurationBooleanValidator
hawkbit.server.tenant.configuration.rollout-approval-enabled.keyName=rollout.approval.enabled 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.defaultValue=false
hawkbit.server.tenant.configuration.rollout-approval-enabled.dataType=java.lang.Boolean hawkbit.server.tenant.configuration.rollout-approval-enabled.dataType=java.lang.Boolean
hawkbit.server.tenant.configuration.rollout-approval-enabled.validator=org.eclipse.hawkbit.tenancy.configuration.validator.TenantConfigurationBooleanValidator
hawkbit.server.tenant.configuration.action-cleanup-enabled.keyName=action.cleanup.enabled hawkbit.server.tenant.configuration.action-cleanup-enabled.keyName=action.cleanup.enabled
hawkbit.server.tenant.configuration.action-cleanup-enabled.defaultValue=false hawkbit.server.tenant.configuration.action-cleanup-enabled.defaultValue=false
hawkbit.server.tenant.configuration.action-cleanup-enabled.dataType=java.lang.Boolean hawkbit.server.tenant.configuration.action-cleanup-enabled.dataType=java.lang.Boolean
hawkbit.server.tenant.configuration.action-cleanup-enabled.validator=org.eclipse.hawkbit.tenancy.configuration.validator.TenantConfigurationBooleanValidator
hawkbit.server.tenant.configuration.action-cleanup-action-expiry.keyName=action.cleanup.actionExpiry hawkbit.server.tenant.configuration.action-cleanup-action-expiry.keyName=action.cleanup.actionExpiry
# default: 30 days # default: 30 days
hawkbit.server.tenant.configuration.action-cleanup-action-expiry.defaultValue=2592000000 hawkbit.server.tenant.configuration.action-cleanup-action-expiry.defaultValue=2592000000
hawkbit.server.tenant.configuration.action-cleanup-action-expiry.dataType=java.lang.Long hawkbit.server.tenant.configuration.action-cleanup-action-expiry.dataType=java.lang.Long
hawkbit.server.tenant.configuration.action-cleanup-action-expiry.validator=org.eclipse.hawkbit.tenancy.configuration.validator.TenantConfigurationLongValidator
hawkbit.server.tenant.configuration.action-cleanup-action-status.keyName=action.cleanup.actionStatus hawkbit.server.tenant.configuration.action-cleanup-action-status.keyName=action.cleanup.actionStatus
hawkbit.server.tenant.configuration.action-cleanup-action-status.defaultValue=CANCELED,ERROR hawkbit.server.tenant.configuration.action-cleanup-action-status.defaultValue=CANCELED,ERROR
@@ -95,21 +89,17 @@ hawkbit.server.tenant.configuration.action-cleanup-action-status.defaultValue=CA
hawkbit.server.tenant.configuration.multi-assignments-enabled.keyName=multi.assignments.enabled hawkbit.server.tenant.configuration.multi-assignments-enabled.keyName=multi.assignments.enabled
hawkbit.server.tenant.configuration.multi-assignments-enabled.defaultValue=false hawkbit.server.tenant.configuration.multi-assignments-enabled.defaultValue=false
hawkbit.server.tenant.configuration.multi-assignments-enabled.dataType=java.lang.Boolean hawkbit.server.tenant.configuration.multi-assignments-enabled.dataType=java.lang.Boolean
hawkbit.server.tenant.configuration.multi-assignments-enabled.validator=org.eclipse.hawkbit.tenancy.configuration.validator.TenantConfigurationBooleanValidator
hawkbit.server.tenant.configuration.batch-assignments-enabled.keyName=batch.assignments.enabled hawkbit.server.tenant.configuration.batch-assignments-enabled.keyName=batch.assignments.enabled
hawkbit.server.tenant.configuration.batch-assignments-enabled.defaultValue=false hawkbit.server.tenant.configuration.batch-assignments-enabled.defaultValue=false
hawkbit.server.tenant.configuration.batch-assignments-enabled.dataType=java.lang.Boolean hawkbit.server.tenant.configuration.batch-assignments-enabled.dataType=java.lang.Boolean
hawkbit.server.tenant.configuration.batch-assignments-enabled.validator=org.eclipse.hawkbit.tenancy.configuration.validator.TenantConfigurationBooleanValidator
hawkbit.server.tenant.configuration.user-confirmation-enabled.keyName=user.confirmation.flow.enabled hawkbit.server.tenant.configuration.user-confirmation-enabled.keyName=user.confirmation.flow.enabled
hawkbit.server.tenant.configuration.user-confirmation-enabled.defaultValue=false hawkbit.server.tenant.configuration.user-confirmation-enabled.defaultValue=false
hawkbit.server.tenant.configuration.user-confirmation-enabled.dataType=java.lang.Boolean hawkbit.server.tenant.configuration.user-confirmation-enabled.dataType=java.lang.Boolean
hawkbit.server.tenant.configuration.user-confirmation-enabled.validator=org.eclipse.hawkbit.tenancy.configuration.validator.TenantConfigurationBooleanValidator
hawkbit.server.tenant.configuration.implicit-lock-enabled.keyName=implicit.lock.enabled hawkbit.server.tenant.configuration.implicit-lock-enabled.keyName=implicit.lock.enabled
hawkbit.server.tenant.configuration.implicit-lock-enabled.defaultValue=true hawkbit.server.tenant.configuration.implicit-lock-enabled.defaultValue=true
hawkbit.server.tenant.configuration.implicit-lock-enabled.dataType=java.lang.Boolean hawkbit.server.tenant.configuration.implicit-lock-enabled.dataType=java.lang.Boolean
hawkbit.server.tenant.configuration.implicit-lock-enabled.validator=org.eclipse.hawkbit.tenancy.configuration.validator.TenantConfigurationBooleanValidator
# Default tenant configuration - END # Default tenant configuration - END

View File

@@ -10,6 +10,7 @@
package org.eclipse.hawkbit.repository.jpa.management; package org.eclipse.hawkbit.repository.jpa.management;
import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThat;
import static org.assertj.core.api.Assertions.assertThatThrownBy;
import static org.assertj.core.api.Assertions.fail; import static org.assertj.core.api.Assertions.fail;
import java.io.Serializable; import java.io.Serializable;
@@ -26,7 +27,6 @@ import org.eclipse.hawkbit.repository.jpa.AbstractJpaIntegrationTest;
import org.eclipse.hawkbit.repository.model.TenantConfigurationValue; import org.eclipse.hawkbit.repository.model.TenantConfigurationValue;
import org.eclipse.hawkbit.tenancy.configuration.DurationHelper; import org.eclipse.hawkbit.tenancy.configuration.DurationHelper;
import org.eclipse.hawkbit.tenancy.configuration.TenantConfigurationProperties.TenantConfigurationKey; import org.eclipse.hawkbit.tenancy.configuration.TenantConfigurationProperties.TenantConfigurationKey;
import org.junit.jupiter.api.Assertions;
import org.junit.jupiter.api.Test; import org.junit.jupiter.api.Test;
import org.springframework.context.EnvironmentAware; import org.springframework.context.EnvironmentAware;
import org.springframework.core.env.Environment; import org.springframework.core.env.Environment;
@@ -35,7 +35,7 @@ import org.springframework.core.env.Environment;
@Story("Tenant Configuration Management") @Story("Tenant Configuration Management")
public class TenantConfigurationManagementTest extends AbstractJpaIntegrationTest implements EnvironmentAware { public class TenantConfigurationManagementTest extends AbstractJpaIntegrationTest implements EnvironmentAware {
private Environment environment = null; private Environment environment;
@Override @Override
public void setEnvironment(final Environment environment) { public void setEnvironment(final Environment environment) {
@@ -64,8 +64,7 @@ public class TenantConfigurationManagementTest extends AbstractJpaIntegrationTes
// verify that new configuration value is used // verify that new configuration value is used
final TenantConfigurationValue<String> updatedConfigurationValue = tenantConfigurationManagement final TenantConfigurationValue<String> updatedConfigurationValue = tenantConfigurationManagement
.getConfigurationValue(TenantConfigurationKey.AUTHENTICATION_MODE_GATEWAY_SECURITY_TOKEN_KEY, .getConfigurationValue(TenantConfigurationKey.AUTHENTICATION_MODE_GATEWAY_SECURITY_TOKEN_KEY, String.class);
String.class);
assertThat(updatedConfigurationValue.isGlobal()).isEqualTo(false); assertThat(updatedConfigurationValue.isGlobal()).isEqualTo(false);
assertThat(updatedConfigurationValue.getValue()).isEqualTo(newConfigurationValue); assertThat(updatedConfigurationValue.getValue()).isEqualTo(newConfigurationValue);
@@ -81,13 +80,11 @@ public class TenantConfigurationManagementTest extends AbstractJpaIntegrationTes
// add value first // add value first
tenantConfigurationManagement.addOrUpdateConfiguration(configKey, value1); tenantConfigurationManagement.addOrUpdateConfiguration(configKey, value1);
assertThat(tenantConfigurationManagement.getConfigurationValue(configKey, String.class).getValue()) assertThat(tenantConfigurationManagement.getConfigurationValue(configKey, String.class).getValue()).isEqualTo(value1);
.isEqualTo(value1);
// update to value second // update to value second
tenantConfigurationManagement.addOrUpdateConfiguration(configKey, value2); tenantConfigurationManagement.addOrUpdateConfiguration(configKey, value2);
assertThat(tenantConfigurationManagement.getConfigurationValue(configKey, String.class).getValue()) assertThat(tenantConfigurationManagement.getConfigurationValue(configKey, String.class).getValue()).isEqualTo(value2);
.isEqualTo(value2);
} }
@Test @Test
@@ -100,8 +97,8 @@ public class TenantConfigurationManagementTest extends AbstractJpaIntegrationTes
// add value first // add value first
tenantConfigurationManagement.addOrUpdateConfiguration(configuration); tenantConfigurationManagement.addOrUpdateConfiguration(configuration);
assertThat(tenantConfigurationManagement.getConfigurationValue(TenantConfigurationKey.AUTHENTICATION_MODE_GATEWAY_SECURITY_TOKEN_KEY, assertThat(tenantConfigurationManagement.getConfigurationValue(
String.class).getValue()) TenantConfigurationKey.AUTHENTICATION_MODE_GATEWAY_SECURITY_TOKEN_KEY, String.class).getValue())
.isEqualTo("token_123"); .isEqualTo("token_123");
assertThat( assertThat(
tenantConfigurationManagement.getConfigurationValue(TenantConfigurationKey.ROLLOUT_APPROVAL_ENABLED, Boolean.class).getValue()) tenantConfigurationManagement.getConfigurationValue(TenantConfigurationKey.ROLLOUT_APPROVAL_ENABLED, Boolean.class).getValue())
@@ -114,12 +111,10 @@ public class TenantConfigurationManagementTest extends AbstractJpaIntegrationTes
final String configKey = TenantConfigurationKey.AUTHENTICATION_MODE_HEADER_ENABLED; final String configKey = TenantConfigurationKey.AUTHENTICATION_MODE_HEADER_ENABLED;
final Boolean value1 = true; final Boolean value1 = true;
tenantConfigurationManagement.addOrUpdateConfiguration(configKey, value1); tenantConfigurationManagement.addOrUpdateConfiguration(configKey, value1);
assertThat(tenantConfigurationManagement.getConfigurationValue(configKey, Boolean.class).getValue()) assertThat(tenantConfigurationManagement.getConfigurationValue(configKey, Boolean.class).getValue()).isEqualTo(value1);
.isEqualTo(value1);
final Boolean value2 = false; final Boolean value2 = false;
tenantConfigurationManagement.addOrUpdateConfiguration(configKey, value2); tenantConfigurationManagement.addOrUpdateConfiguration(configKey, value2);
assertThat(tenantConfigurationManagement.getConfigurationValue(configKey, Boolean.class).getValue()) assertThat(tenantConfigurationManagement.getConfigurationValue(configKey, Boolean.class).getValue()).isEqualTo(value2);
.isEqualTo(value2);
} }
@Test @Test
@@ -129,18 +124,15 @@ public class TenantConfigurationManagementTest extends AbstractJpaIntegrationTes
final String value1 = "thisIsNotABoolean"; final String value1 = "thisIsNotABoolean";
// add value as String // add value as String
try { assertThatThrownBy(() -> tenantConfigurationManagement.addOrUpdateConfiguration(configKey, value1))
tenantConfigurationManagement.addOrUpdateConfiguration(configKey, value1); .as("Should not have worked as value is not a boolean")
fail("should not have worked as string is not a boolean"); .isInstanceOf(TenantConfigurationValidatorException.class);
} catch (final TenantConfigurationValidatorException e) {
}
} }
@Test @Test
@Description("Tests that the get configuration throws exception in case the value is the wrong type") @Description("Tests that the get configuration throws exception in case the value is the wrong type")
public void batchWrongTenantConfigurationValueTypeThrowsException() { public void batchWrongTenantConfigurationValueTypeThrowsException() {
Map<String, Serializable> configuration = new HashMap<>() {{ final Map<String, Serializable> configuration = new HashMap<>() {{
put(TenantConfigurationKey.AUTHENTICATION_MODE_GATEWAY_SECURITY_TOKEN_KEY, "token_123"); put(TenantConfigurationKey.AUTHENTICATION_MODE_GATEWAY_SECURITY_TOKEN_KEY, "token_123");
put(TenantConfigurationKey.ROLLOUT_APPROVAL_ENABLED, true); put(TenantConfigurationKey.ROLLOUT_APPROVAL_ENABLED, true);
put(TenantConfigurationKey.AUTHENTICATION_MODE_GATEWAY_SECURITY_TOKEN_ENABLED, "wrong"); put(TenantConfigurationKey.AUTHENTICATION_MODE_GATEWAY_SECURITY_TOKEN_ENABLED, "wrong");
@@ -151,11 +143,12 @@ public class TenantConfigurationManagementTest extends AbstractJpaIntegrationTes
fail("should not have worked as type is wrong"); fail("should not have worked as type is wrong");
} catch (final TenantConfigurationValidatorException e) { } catch (final TenantConfigurationValidatorException e) {
assertThat( assertThat(
tenantConfigurationManagement.getConfigurationValue(TenantConfigurationKey.AUTHENTICATION_MODE_GATEWAY_SECURITY_TOKEN_KEY, tenantConfigurationManagement.getConfigurationValue(
String.class).getValue()) TenantConfigurationKey.AUTHENTICATION_MODE_GATEWAY_SECURITY_TOKEN_KEY, String.class).getValue())
.isNotEqualTo("token_123"); .isNotEqualTo("token_123");
assertThat(tenantConfigurationManagement.getConfigurationValue(TenantConfigurationKey.ROLLOUT_APPROVAL_ENABLED, Boolean.class) assertThat(
.getValue()) tenantConfigurationManagement.getConfigurationValue(
TenantConfigurationKey.ROLLOUT_APPROVAL_ENABLED, Boolean.class).getValue())
.isNotEqualTo(true); .isNotEqualTo(true);
} }
} }
@@ -165,10 +158,8 @@ public class TenantConfigurationManagementTest extends AbstractJpaIntegrationTes
public void deleteConfigurationReturnNullConfiguration() { public void deleteConfigurationReturnNullConfiguration() {
final String configKey = TenantConfigurationKey.AUTHENTICATION_MODE_GATEWAY_SECURITY_TOKEN_KEY; final String configKey = TenantConfigurationKey.AUTHENTICATION_MODE_GATEWAY_SECURITY_TOKEN_KEY;
// gateway token does not have default value so no configuration value // gateway token does not have default value so no configuration value should be available
// should be available final String defaultConfigValue = tenantConfigurationManagement.getConfigurationValue(configKey, String.class).getValue();
final String defaultConfigValue = tenantConfigurationManagement.getConfigurationValue(configKey, String.class)
.getValue();
assertThat(defaultConfigValue).isEmpty(); assertThat(defaultConfigValue).isEmpty();
// update the tenant specific configuration // update the tenant specific configuration
@@ -177,8 +168,7 @@ public class TenantConfigurationManagementTest extends AbstractJpaIntegrationTes
tenantConfigurationManagement.addOrUpdateConfiguration(configKey, newConfigurationValue); tenantConfigurationManagement.addOrUpdateConfiguration(configKey, newConfigurationValue);
// verify that new configuration value is used // verify that new configuration value is used
final String updatedConfigurationValue = tenantConfigurationManagement final String updatedConfigurationValue = tenantConfigurationManagement.getConfigurationValue(configKey, String.class).getValue();
.getConfigurationValue(configKey, String.class).getValue();
assertThat(updatedConfigurationValue).isEqualTo(newConfigurationValue); assertThat(updatedConfigurationValue).isEqualTo(newConfigurationValue);
// delete the tenant specific configuration // delete the tenant specific configuration
@@ -192,39 +182,30 @@ public class TenantConfigurationManagementTest extends AbstractJpaIntegrationTes
@Description("Test that an Exception is thrown, when an integer is stored but a string expected.") @Description("Test that an Exception is thrown, when an integer is stored but a string expected.")
public void storesIntegerWhenStringIsExpected() { public void storesIntegerWhenStringIsExpected() {
final String configKey = TenantConfigurationKey.AUTHENTICATION_MODE_GATEWAY_SECURITY_TOKEN_KEY; final String configKey = TenantConfigurationKey.AUTHENTICATION_MODE_GATEWAY_SECURITY_TOKEN_KEY;
final Integer wrongDataype = 123; final Integer wrongDatType = 123;
try { assertThatThrownBy(() -> tenantConfigurationManagement.addOrUpdateConfiguration(configKey, wrongDatType))
tenantConfigurationManagement.addOrUpdateConfiguration(configKey, wrongDataype); .as("Should not have worked as integer is not a string")
fail("should not have worked as integer is not a string"); .isInstanceOf(TenantConfigurationValidatorException.class);
} catch (final TenantConfigurationValidatorException e) {
}
} }
@Test @Test
@Description("Test that an Exception is thrown, when an integer is stored but a boolean expected.") @Description("Test that an Exception is thrown, when an integer is stored but a boolean expected.")
public void storesIntegerWhenBooleanIsExpected() { public void storesIntegerWhenBooleanIsExpected() {
final String configKey = TenantConfigurationKey.AUTHENTICATION_MODE_GATEWAY_SECURITY_TOKEN_ENABLED; final String configKey = TenantConfigurationKey.AUTHENTICATION_MODE_GATEWAY_SECURITY_TOKEN_ENABLED;
final Integer wrongDataype = 123; final Integer wrongDataType = 123;
try { assertThatThrownBy(() -> tenantConfigurationManagement.addOrUpdateConfiguration(configKey, wrongDataType))
tenantConfigurationManagement.addOrUpdateConfiguration(configKey, wrongDataype); .as("Should not have worked as integer is not a boolean")
fail("should not have worked as integer is not a boolean"); .isInstanceOf(TenantConfigurationValidatorException.class);
} catch (final TenantConfigurationValidatorException e) {
}
} }
@Test @Test
@Description("Test that an Exception is thrown, when an integer is stored as PollingTime.") @Description("Test that an Exception is thrown, when an integer is stored as PollingTime.")
public void storesIntegerWhenPollingIntervalIsExpected() { public void storesIntegerWhenPollingIntervalIsExpected() {
final String configKey = TenantConfigurationKey.POLLING_TIME_INTERVAL; final String configKey = TenantConfigurationKey.POLLING_TIME_INTERVAL;
final Integer wrongDataype = 123; final Integer wrongDataType = 123;
try { assertThatThrownBy(() -> tenantConfigurationManagement.addOrUpdateConfiguration(configKey, wrongDataType))
tenantConfigurationManagement.addOrUpdateConfiguration(configKey, wrongDataype); .as("Should not have worked as integer is not a time field")
fail("should not have worked as integer is not a time field"); .isInstanceOf(TenantConfigurationValidatorException.class);
} catch (final TenantConfigurationValidatorException e) {
}
} }
@Test @Test
@@ -232,12 +213,9 @@ public class TenantConfigurationManagementTest extends AbstractJpaIntegrationTes
public void storesWrongFormattedStringAsPollingInterval() { public void storesWrongFormattedStringAsPollingInterval() {
final String configKey = TenantConfigurationKey.POLLING_TIME_INTERVAL; final String configKey = TenantConfigurationKey.POLLING_TIME_INTERVAL;
final String wrongFormatted = "wrongFormatted"; final String wrongFormatted = "wrongFormatted";
try { assertThatThrownBy(() -> tenantConfigurationManagement.addOrUpdateConfiguration(configKey, wrongFormatted))
tenantConfigurationManagement.addOrUpdateConfiguration(configKey, wrongFormatted); .as("should not have worked as string is not a time field")
fail("should not have worked as string is not a time field"); .isInstanceOf(TenantConfigurationValidatorException.class);
} catch (final TenantConfigurationValidatorException e) {
}
} }
@Test @Test
@@ -247,12 +225,9 @@ public class TenantConfigurationManagementTest extends AbstractJpaIntegrationTes
final String tooSmallDuration = DurationHelper final String tooSmallDuration = DurationHelper
.durationToFormattedString(DurationHelper.getDurationByTimeValues(0, 0, 1)); .durationToFormattedString(DurationHelper.getDurationByTimeValues(0, 0, 1));
try { assertThatThrownBy(() -> tenantConfigurationManagement.addOrUpdateConfiguration(configKey, tooSmallDuration))
tenantConfigurationManagement.addOrUpdateConfiguration(configKey, tooSmallDuration); .as("Should not have worked as string has an invalid format")
fail("should not have worked as string has an invalid format"); .isInstanceOf(TenantConfigurationValidatorException.class);
} catch (final TenantConfigurationValidatorException e) {
}
} }
@Test @Test
@@ -263,38 +238,29 @@ public class TenantConfigurationManagementTest extends AbstractJpaIntegrationTes
final Duration duration = DurationHelper.getDurationByTimeValues(1, 2, 0); final Duration duration = DurationHelper.getDurationByTimeValues(1, 2, 0);
assertThat(duration).isEqualTo(Duration.ofHours(1).plusMinutes(2)); assertThat(duration).isEqualTo(Duration.ofHours(1).plusMinutes(2));
tenantConfigurationManagement.addOrUpdateConfiguration(configKey, tenantConfigurationManagement.addOrUpdateConfiguration(configKey, DurationHelper.durationToFormattedString(duration));
DurationHelper.durationToFormattedString(duration));
final String storedDurationString = tenantConfigurationManagement.getConfigurationValue(configKey, String.class) final String storedDurationString = tenantConfigurationManagement.getConfigurationValue(configKey, String.class).getValue();
.getValue();
assertThat(duration).isEqualTo(DurationHelper.formattedStringToDuration(storedDurationString)); assertThat(duration).isEqualTo(DurationHelper.formattedStringToDuration(storedDurationString));
} }
@Test @Test
@Description("Request a config value in a wrong Value") @Description("Request a config value in a wrong Value")
public void requestConfigValueWithWrongType() { public void requestConfigValueWithWrongType() {
try { assertThatThrownBy(() -> tenantConfigurationManagement.getConfigurationValue(
tenantConfigurationManagement.getConfigurationValue(TenantConfigurationKey.POLLING_TIME_INTERVAL, TenantConfigurationKey.POLLING_TIME_INTERVAL, Serializable.class))
Serializable.class); .isInstanceOf(TenantConfigurationValidatorException.class);
Assertions.fail("");
} catch (final TenantConfigurationValidatorException e) {
}
} }
@Test @Test
@Description("Verifies that every TenenatConfiguraationKeyName exists only once") @Description("Verifies that every TenenatConfiguraationKeyName exists only once")
public void verifyThatAllKeysAreDifferent() { public void verifyThatAllKeysAreDifferent() {
final Map<String, Void> keynames = new HashMap<>(); final Map<String, Void> keyNames = new HashMap<>();
tenantConfigurationProperties.getConfigurationKeys().forEach(key -> { tenantConfigurationProperties.getConfigurationKeys().forEach(key -> {
if (keyNames.containsKey(key.getKeyName())) {
if (keynames.containsKey(key.getKeyName())) {
throw new IllegalStateException("The key names are not unique"); throw new IllegalStateException("The key names are not unique");
} }
keyNames.put(key.getKeyName(), null);
keynames.put(key.getKeyName(), null);
}); });
} }
@@ -302,7 +268,6 @@ public class TenantConfigurationManagementTest extends AbstractJpaIntegrationTes
@Description("Get TenantConfigurationKeyByName") @Description("Get TenantConfigurationKeyByName")
public void getTenantConfigurationKeyByName() { public void getTenantConfigurationKeyByName() {
final String configKey = TenantConfigurationKey.POLLING_TIME_INTERVAL; final String configKey = TenantConfigurationKey.POLLING_TIME_INTERVAL;
assertThat(tenantConfigurationProperties.fromKeyName(configKey).getKeyName()).isEqualTo(configKey); assertThat(tenantConfigurationProperties.fromKeyName(configKey).getKeyName()).isEqualTo(configKey);
} }
@@ -310,11 +275,8 @@ public class TenantConfigurationManagementTest extends AbstractJpaIntegrationTes
@Description("Tenant configuration which is not declared throws exception") @Description("Tenant configuration which is not declared throws exception")
public void storeTenantConfigurationWhichIsNotDeclaredThrowsException() { public void storeTenantConfigurationWhichIsNotDeclaredThrowsException() {
final String configKeyWhichDoesNotExists = "configKeyWhichDoesNotExists"; final String configKeyWhichDoesNotExists = "configKeyWhichDoesNotExists";
try { assertThatThrownBy(() -> tenantConfigurationManagement.addOrUpdateConfiguration(configKeyWhichDoesNotExists, "value"))
tenantConfigurationManagement.addOrUpdateConfiguration(configKeyWhichDoesNotExists, "value"); .as("Expected InvalidTenantConfigurationKeyException for tenant configuration key which is not declared")
fail("Expected InvalidTenantConfigurationKeyException for tenant configuration key which is not declared"); .isInstanceOf(InvalidTenantConfigurationKeyException.class);
} catch (final InvalidTenantConfigurationKeyException e) {
// expected exception
}
} }
} }