From 7b63d867e016d2be80972198c84525ed862b4aca Mon Sep 17 00:00:00 2001 From: Michael Hoennig Date: Tue, 11 Jun 2024 09:44:57 +0200 Subject: [PATCH] hierarchical validation for hosting-assets, no concrete rules yet --- .../HsBookingItemEntityValidator.java | 27 ++-------- .../HsManagedServerBookingItemValidator.java | 2 +- .../hosting/asset/HsHostingAssetEntity.java | 7 +++ .../HsHostingAssetEntityValidator.java | 53 +++++++++++++++---- .../hs/validation/BooleanProperty.java | 7 +-- .../hs/validation/HsEntityValidator.java | 17 ++++++ ...gedServerBookingItemValidatorUnitTest.java | 3 +- ...udServerHostingAssetValidatorUnitTest.java | 3 +- ...HsHostingAssetEntityValidatorUnitTest.java | 7 +-- ...edServerHostingAssetValidatorUnitTest.java | 9 ++-- ...WebspaceHostingAssetValidatorUnitTest.java | 12 ++++- 11 files changed, 100 insertions(+), 47 deletions(-) diff --git a/src/main/java/net/hostsharing/hsadminng/hs/booking/item/validators/HsBookingItemEntityValidator.java b/src/main/java/net/hostsharing/hsadminng/hs/booking/item/validators/HsBookingItemEntityValidator.java index 113210db..eef8392d 100644 --- a/src/main/java/net/hostsharing/hsadminng/hs/booking/item/validators/HsBookingItemEntityValidator.java +++ b/src/main/java/net/hostsharing/hsadminng/hs/booking/item/validators/HsBookingItemEntityValidator.java @@ -11,7 +11,6 @@ import java.util.List; import java.util.Map; import java.util.Objects; import java.util.Set; -import java.util.function.Supplier; import static java.util.Arrays.stream; import static java.util.Collections.emptyList; @@ -49,6 +48,10 @@ public class HsBookingItemEntityValidator extends HsEntityValidator doValidate(final HsBookingItemEntity bookingItem) { + return HsBookingItemEntityValidator.forType(bookingItem.getType()).validate(bookingItem); + } + public static HsBookingItemEntity valid(final HsBookingItemEntity entityToSave) { final var violations = doValidate(entityToSave); if (!violations.isEmpty()) { @@ -57,10 +60,6 @@ public class HsBookingItemEntityValidator extends HsEntityValidator doValidate(final HsBookingItemEntity bookingItem) { - return HsBookingItemEntityValidator.forType(bookingItem.getType()).validate(bookingItem); - } - public HsBookingItemEntityValidator(final ValidatableProperty... properties) { super(properties); } @@ -79,21 +78,12 @@ public class HsBookingItemEntityValidator extends HsEntityValidator validateSubEntities(final HsBookingItemEntity bookingItem) { return stream(propertyValidators) - .filter(propDef -> propDef.isTotalsValidator()) + .filter(ValidatableProperty::isTotalsValidator) .map(prop -> validateMaxTotalValue(bookingItem, prop)) .filter(Objects::nonNull) .toList(); } - @SafeVarargs - private List sequentiallyValidate(final Supplier>... validators) { - return stream(validators) - .map(Supplier::get) - .filter(violations -> !violations.isEmpty()) - .findFirst() - .orElse(emptyList()); - } - private String validateMaxTotalValue( final HsBookingItemEntity bookingItem, final ValidatableProperty propDef) { @@ -110,11 +100,4 @@ public class HsBookingItemEntityValidator extends HsEntityValidator subHostingAssets; + @Column(name = "identifier") private String identifier; // vm1234, xyz00, example.org, xyz00_abc diff --git a/src/main/java/net/hostsharing/hsadminng/hs/hosting/asset/validators/HsHostingAssetEntityValidator.java b/src/main/java/net/hostsharing/hsadminng/hs/hosting/asset/validators/HsHostingAssetEntityValidator.java index 2c66b766..0b57bbd9 100644 --- a/src/main/java/net/hostsharing/hsadminng/hs/hosting/asset/validators/HsHostingAssetEntityValidator.java +++ b/src/main/java/net/hostsharing/hsadminng/hs/hosting/asset/validators/HsHostingAssetEntityValidator.java @@ -6,16 +6,17 @@ import net.hostsharing.hsadminng.hs.hosting.asset.HsHostingAssetEntity; import net.hostsharing.hsadminng.hs.hosting.asset.HsHostingAssetType; import net.hostsharing.hsadminng.hs.validation.HsEntityValidator; import net.hostsharing.hsadminng.hs.validation.ValidatableProperty; -import org.apache.commons.collections4.ListUtils; import jakarta.validation.ValidationException; import java.util.HashMap; import java.util.List; import java.util.Map; +import java.util.Objects; import java.util.Set; import static java.util.Arrays.stream; import static java.util.Collections.emptyList; +import static java.util.Optional.ofNullable; import static net.hostsharing.hsadminng.hs.hosting.asset.HsHostingAssetType.CLOUD_SERVER; import static net.hostsharing.hsadminng.hs.hosting.asset.HsHostingAssetType.MANAGED_SERVER; import static net.hostsharing.hsadminng.hs.hosting.asset.HsHostingAssetType.MANAGED_WEBSPACE; @@ -37,22 +38,28 @@ public class HsHostingAssetEntityValidator extends HsEntityValidator forType(final Enum type) { - return validators.get(type); + if ( validators.containsKey(type)) { + return validators.get(type); + } + throw new IllegalArgumentException("no validator found for type " + type); } public static Set> types() { return validators.keySet(); } + public static List doValidate(final HsHostingAssetEntity hostingAsset) { + return HsHostingAssetEntityValidator.forType(hostingAsset.getType()).validate(hostingAsset); + } + public static HsHostingAssetEntity valid(final HsHostingAssetEntity entityToSave) { - final var violations = HsHostingAssetEntityValidator.forType(entityToSave.getType()).validate(entityToSave); + final var violations = doValidate(entityToSave); if (!violations.isEmpty()) { throw new ValidationException(violations.toString()); } return entityToSave; } - @SafeVarargs public HsHostingAssetEntityValidator(final ValidatableProperty... properties) { super(properties); } @@ -60,13 +67,12 @@ public class HsHostingAssetEntityValidator extends HsEntityValidator validate(final HsHostingAssetEntity assetEntity) { - final var selfValidation = enrich(prefix(assetEntity.toShortString(), "config"), validateProperties(assetEntity.getConfig())); - return selfValidation.isEmpty() - // higher levels are only validated with valid sub-entity - ? ListUtils.union( - optionallyValidate(assetEntity.getParentAsset()), - optionallyValidate(assetEntity.getBookingItem())) - : selfValidation; + return sequentiallyValidate( + () -> enrich(prefix(assetEntity.toShortString(), "config"), validateProperties(assetEntity.getConfig())), + () -> enrich(prefix(assetEntity.toShortString(), "bookingItem"), optionallyValidate(assetEntity.getBookingItem())), + () -> enrich(prefix(assetEntity.toShortString(), "parentAsset"), optionallyValidate(assetEntity.getParentAsset())), + () -> validateSubEntities(assetEntity) + ); } private static List optionallyValidate(final HsHostingAssetEntity assetEntity) { @@ -76,4 +82,29 @@ public class HsHostingAssetEntityValidator extends HsEntityValidator optionallyValidate(final HsBookingItemEntity bookingItem) { return bookingItem != null ? HsBookingItemEntityValidator.doValidate(bookingItem) : emptyList(); } + + protected List validateSubEntities(final HsHostingAssetEntity assetEntity) { + return stream(propertyValidators) + .filter(ValidatableProperty::isTotalsValidator) + .map(prop -> validateMaxTotalValue(assetEntity, prop)) + .filter(Objects::nonNull) + .toList(); + } + + private String validateMaxTotalValue( + final HsHostingAssetEntity hostingAsset, + final ValidatableProperty propDef) { + final var propName = propDef.propertyName(); + final var propUnit = ofNullable(propDef.unit()).map(u -> " " + u).orElse(""); + final var totalValue = ofNullable(hostingAsset.getSubHostingAssets()).orElse(emptyList()) + .stream() + .map(subItem -> propDef.getValue(subItem.getConfig())) + .map(HsEntityValidator::toInteger) + .reduce(0, Integer::sum); + final var maxValue = toInteger(propDef.getValue(hostingAsset.getConfig())); + return totalValue > maxValue + ? "total %s is %d%s exceeds max total %s %d%s".formatted( + propName, totalValue, propUnit, propName, maxValue, propUnit) + : null; + } } diff --git a/src/main/java/net/hostsharing/hsadminng/hs/validation/BooleanProperty.java b/src/main/java/net/hostsharing/hsadminng/hs/validation/BooleanProperty.java index 38e82c36..cebaafe8 100644 --- a/src/main/java/net/hostsharing/hsadminng/hs/validation/BooleanProperty.java +++ b/src/main/java/net/hostsharing/hsadminng/hs/validation/BooleanProperty.java @@ -27,10 +27,11 @@ public class BooleanProperty extends ValidatableProperty { @Override protected void validate(final ArrayList result, final Boolean propValue, final Map props) { - if (falseIf != null && !Objects.equals(props.get(falseIf.getKey()), falseIf.getValue())) { - if (propValue) { + if (falseIf != null && propValue) { + final Object referencedValue = props.get(falseIf.getKey()); + if (Objects.equals(referencedValue, falseIf.getValue())) { result.add(propertyName + " is expected to be false because " + - falseIf.getKey()+ "=" + falseIf.getValue() + " but is " + propValue); + falseIf.getKey() + "=" + referencedValue + " but is " + propValue); } } } diff --git a/src/main/java/net/hostsharing/hsadminng/hs/validation/HsEntityValidator.java b/src/main/java/net/hostsharing/hsadminng/hs/validation/HsEntityValidator.java index 5acced97..9a00d935 100644 --- a/src/main/java/net/hostsharing/hsadminng/hs/validation/HsEntityValidator.java +++ b/src/main/java/net/hostsharing/hsadminng/hs/validation/HsEntityValidator.java @@ -10,6 +10,7 @@ import java.util.ArrayList; import java.util.Arrays; import java.util.List; import java.util.Map; +import java.util.function.Supplier; import static java.util.Arrays.stream; import static java.util.Collections.emptyList; @@ -66,4 +67,20 @@ public abstract class HsEntityValidator { ? HsBookingItemEntityValidator.doValidate(bookingItem.getParentItem()) : emptyList(); } + + @SafeVarargs + protected static List sequentiallyValidate(final Supplier>... validators) { + return new ArrayList<>(stream(validators) + .map(Supplier::get) + .filter(violations -> !violations.isEmpty()) + .findFirst() + .orElse(emptyList())); + } + + protected static Integer toInteger(final Object value) { + if (value instanceof Integer) { + return (Integer) value; + } + throw new IllegalArgumentException("Integer value expected, but got " + value); + } } diff --git a/src/test/java/net/hostsharing/hsadminng/hs/booking/item/validators/HsManagedServerBookingItemValidatorUnitTest.java b/src/test/java/net/hostsharing/hsadminng/hs/booking/item/validators/HsManagedServerBookingItemValidatorUnitTest.java index ee46f79f..7975215d 100644 --- a/src/test/java/net/hostsharing/hsadminng/hs/booking/item/validators/HsManagedServerBookingItemValidatorUnitTest.java +++ b/src/test/java/net/hostsharing/hsadminng/hs/booking/item/validators/HsManagedServerBookingItemValidatorUnitTest.java @@ -37,6 +37,7 @@ class HsManagedServerBookingItemValidatorUnitTest { entry("RAM", 25), entry("SSD", 25), entry("Traffic", 250), + entry("SLA-Platform", "BASIC"), entry("SLA-EMail", true) )) .build(); @@ -60,7 +61,7 @@ class HsManagedServerBookingItemValidatorUnitTest { "{type=integer, propertyName=SSD, required=true, defaultValue=null, asTotalLimitValidator=null, unit=GB, min=25, max=1000, step=25, totalsValidator=false}", "{type=integer, propertyName=HDD, required=false, defaultValue=null, asTotalLimitValidator=null, unit=GB, min=0, max=4000, step=250, totalsValidator=false}", "{type=integer, propertyName=Traffic, required=true, defaultValue=null, asTotalLimitValidator=null, unit=GB, min=250, max=10000, step=250, totalsValidator=false}", - "{type=enumeration, propertyName=SLA-Platform, required=false, defaultValue=null, asTotalLimitValidator=null, values=[BASIC, EXT8H, EXT4H, EXT2H], totalsValidator=false}", + "{type=enumeration, propertyName=SLA-Platform, required=false, defaultValue=BASIC, asTotalLimitValidator=null, values=[BASIC, EXT8H, EXT4H, EXT2H], totalsValidator=false}", "{type=boolean, propertyName=SLA-EMail, required=false, defaultValue=null, asTotalLimitValidator=null, falseIf={SLA-Platform=BASIC}, totalsValidator=false}", "{type=boolean, propertyName=SLA-Maria, required=false, defaultValue=null, asTotalLimitValidator=null, falseIf={SLA-Platform=BASIC}, totalsValidator=false}", "{type=boolean, propertyName=SLA-PgSQL, required=false, defaultValue=null, asTotalLimitValidator=null, falseIf={SLA-Platform=BASIC}, totalsValidator=false}", diff --git a/src/test/java/net/hostsharing/hsadminng/hs/hosting/asset/validators/HsCloudServerHostingAssetValidatorUnitTest.java b/src/test/java/net/hostsharing/hsadminng/hs/hosting/asset/validators/HsCloudServerHostingAssetValidatorUnitTest.java index 8f97e630..0e6e37b4 100644 --- a/src/test/java/net/hostsharing/hsadminng/hs/hosting/asset/validators/HsCloudServerHostingAssetValidatorUnitTest.java +++ b/src/test/java/net/hostsharing/hsadminng/hs/hosting/asset/validators/HsCloudServerHostingAssetValidatorUnitTest.java @@ -17,6 +17,7 @@ class HsCloudServerHostingAssetValidatorUnitTest { // given final var cloudServerHostingAssetEntity = HsHostingAssetEntity.builder() .type(CLOUD_SERVER) + .identifier("vm1234") .config(Map.ofEntries( entry("RAM", 2000) )) @@ -28,7 +29,7 @@ class HsCloudServerHostingAssetValidatorUnitTest { final var result = validator.validate(cloudServerHostingAssetEntity); // then - assertThat(result).containsExactly("'config.RAM' is not expected but is set to '2000'"); + assertThat(result).containsExactly("CLOUD_SERVER:vm1234.config.RAM is not expected but is set to '2000'"); } @Test diff --git a/src/test/java/net/hostsharing/hsadminng/hs/hosting/asset/validators/HsHostingAssetEntityValidatorUnitTest.java b/src/test/java/net/hostsharing/hsadminng/hs/hosting/asset/validators/HsHostingAssetEntityValidatorUnitTest.java index 2e4f01a2..a648463f 100644 --- a/src/test/java/net/hostsharing/hsadminng/hs/hosting/asset/validators/HsHostingAssetEntityValidatorUnitTest.java +++ b/src/test/java/net/hostsharing/hsadminng/hs/hosting/asset/validators/HsHostingAssetEntityValidatorUnitTest.java @@ -17,6 +17,7 @@ class HsHostingAssetEntityValidatorUnitTest { // given final var managedServerHostingAssetEntity = HsHostingAssetEntity.builder() .type(MANAGED_SERVER) + .identifier("vm1234") .build(); // when @@ -25,8 +26,8 @@ class HsHostingAssetEntityValidatorUnitTest { // then assertThat(result).isInstanceOf(ValidationException.class) .hasMessageContaining( - "'config.monit_max_ssd_usage' is required but missing", - "'config.monit_max_cpu_usage' is required but missing", - "'config.monit_max_ram_usage' is required but missing"); + "MANAGED_SERVER:vm1234.config.monit_max_ssd_usage is required but missing", + "MANAGED_SERVER:vm1234.config.monit_max_cpu_usage is required but missing", + "MANAGED_SERVER:vm1234.config.monit_max_ram_usage is required but missing"); } } diff --git a/src/test/java/net/hostsharing/hsadminng/hs/hosting/asset/validators/HsManagedServerHostingAssetValidatorUnitTest.java b/src/test/java/net/hostsharing/hsadminng/hs/hosting/asset/validators/HsManagedServerHostingAssetValidatorUnitTest.java index 6da559c5..423da194 100644 --- a/src/test/java/net/hostsharing/hsadminng/hs/hosting/asset/validators/HsManagedServerHostingAssetValidatorUnitTest.java +++ b/src/test/java/net/hostsharing/hsadminng/hs/hosting/asset/validators/HsManagedServerHostingAssetValidatorUnitTest.java @@ -17,6 +17,7 @@ class HsManagedServerHostingAssetValidatorUnitTest { // given final var mangedWebspaceHostingAssetEntity = HsHostingAssetEntity.builder() .type(MANAGED_SERVER) + .identifier("vm1234") .config(Map.ofEntries( entry("monit_max_hdd_usage", "90"), entry("monit_max_cpu_usage", 2), @@ -30,9 +31,9 @@ class HsManagedServerHostingAssetValidatorUnitTest { // then assertThat(result).containsExactlyInAnyOrder( - "'config.monit_max_ssd_usage' is required but missing", - "'config.monit_max_hdd_usage' is expected to be of type class java.lang.Integer, but is of type 'String'", - "'config.monit_max_cpu_usage' is expected to be >= 10 but is 2", - "'config.monit_max_ram_usage' is expected to be <= 100 but is 101"); + "MANAGED_SERVER:vm1234.config.monit_max_ssd_usage is required but missing", + "MANAGED_SERVER:vm1234.config.monit_max_hdd_usage is expected to be of type class java.lang.Integer, but is of type 'String'", + "MANAGED_SERVER:vm1234.config.monit_max_cpu_usage is expected to be >= 10 but is 2", + "MANAGED_SERVER:vm1234.config.monit_max_ram_usage is expected to be <= 100 but is 101"); } } diff --git a/src/test/java/net/hostsharing/hsadminng/hs/hosting/asset/validators/HsManagedWebspaceHostingAssetValidatorUnitTest.java b/src/test/java/net/hostsharing/hsadminng/hs/hosting/asset/validators/HsManagedWebspaceHostingAssetValidatorUnitTest.java index a64f14d6..5bb39f92 100644 --- a/src/test/java/net/hostsharing/hsadminng/hs/hosting/asset/validators/HsManagedWebspaceHostingAssetValidatorUnitTest.java +++ b/src/test/java/net/hostsharing/hsadminng/hs/hosting/asset/validators/HsManagedWebspaceHostingAssetValidatorUnitTest.java @@ -18,10 +18,20 @@ class HsManagedWebspaceHostingAssetValidatorUnitTest { final HsBookingItemEntity managedServerBookingItem = HsBookingItemEntity.builder() .project(TEST_PROJECT) .type(HsBookingItemType.MANAGED_SERVER) + .caption("Test Managed-Server") + .resources(Map.ofEntries( + entry("CPUs", 2), + entry("RAM", 25), + entry("SSD", 25), + entry("Traffic", 250), + entry("SLA-Platform", "EXT4H"), + entry("SLA-EMail", true) + )) .build(); final HsHostingAssetEntity mangedServerAssetEntity = HsHostingAssetEntity.builder() .type(HsHostingAssetType.MANAGED_SERVER) .bookingItem(managedServerBookingItem) + .identifier("vm1234") .config(Map.ofEntries( entry("monit_max_ssd_usage", 70), entry("monit_max_cpu_usage", 80), @@ -63,7 +73,7 @@ class HsManagedWebspaceHostingAssetValidatorUnitTest { final var result = validator.validate(mangedWebspaceHostingAssetEntity); // then - assertThat(result).containsExactly("'config.unknown' is not expected but is set to 'some value'"); + assertThat(result).containsExactly("MANAGED_WEBSPACE:abc00.config.unknown is not expected but is set to 'some value'"); } @Test