From 988f7dc23bdd94a12707e8022fde82badbd7778a Mon Sep 17 00:00:00 2001 From: Michael Hoennig Date: Wed, 19 Jun 2024 15:47:12 +0200 Subject: [PATCH] implement asTotalLimitFor(...) validation for SLA-Infrastructure and SLA-Platform --- .../HsBookingItemEntityValidator.java | 4 +-- .../HsCloudServerBookingItemValidator.java | 18 ++++++---- .../HsPrivateCloudBookingItemValidator.java | 36 +++++++++++++++---- .../HsHostingAssetEntityValidator.java | 4 +-- .../HsManagedServerHostingAssetValidator.java | 14 ++++---- .../hs/validation/HsEntityValidator.java | 12 +++++-- .../hs/validation/ValidatableProperty.java | 23 ++++++++++++ ...vateCloudBookingItemValidatorUnitTest.java | 22 +++++++----- 8 files changed, 97 insertions(+), 36 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 7d002bac..f99a8cb8 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 @@ -59,9 +59,9 @@ public class HsBookingItemEntityValidator extends HsEntityValidator propDef.getValue(subItem.getResources())) - .map(HsBookingItemEntityValidator::toNonNullInteger) + .map(HsBookingItemEntityValidator::toIntegerWithDefault0) .reduce(0, Integer::sum); - final var maxValue = getNonNullIntegerValue(propDef, bookingItem.getResources()); + final var maxValue = getIntegerValueWithDefault0(propDef, bookingItem.getResources()); if (propDef.thresholdPercentage() != null ) { return totalValue > (maxValue * propDef.thresholdPercentage() / 100) ? "%s' maximum total is %d%s, but actual total %s %d%s, which exceeds threshold of %d%%" diff --git a/src/main/java/net/hostsharing/hsadminng/hs/booking/item/validators/HsCloudServerBookingItemValidator.java b/src/main/java/net/hostsharing/hsadminng/hs/booking/item/validators/HsCloudServerBookingItemValidator.java index acb82674..fe39cb0a 100644 --- a/src/main/java/net/hostsharing/hsadminng/hs/booking/item/validators/HsCloudServerBookingItemValidator.java +++ b/src/main/java/net/hostsharing/hsadminng/hs/booking/item/validators/HsCloudServerBookingItemValidator.java @@ -10,13 +10,17 @@ class HsCloudServerBookingItemValidator extends HsBookingItemEntityValidator { HsCloudServerBookingItemValidator() { super( - booleanProperty("active").withDefault(true), - integerProperty("CPUs").min(1).max(32).required(), - integerProperty("RAM").unit("GB").min(1).max(128).required(), - integerProperty("SSD").unit("GB").min(25).max(1000).step(25).required(), - integerProperty("HDD").unit("GB").min(0).max(4000).step(250).withDefault(0), - integerProperty("Traffic").unit("GB").min(250).max(10000).step(250).required(), - enumerationProperty("SLA-Infrastructure").values("BASIC", "EXT8H", "EXT4H", "EXT2H").optional() + // @formatter:off + booleanProperty("active") .withDefault(true), + + integerProperty("CPUs") .min( 1) .max( 32) .required(), + integerProperty("RAM").unit("GB") .min( 1) .max( 128) .required(), + integerProperty("SSD").unit("GB") .min( 25) .max( 1000) .step(25).required(), + integerProperty("HDD").unit("GB") .min( 0) .max( 4000) .step(250).withDefault(0), + integerProperty("Traffic").unit("GB") .min(250) .max(10000) .step(250).required(), + + enumerationProperty("SLA-Infrastructure").values("BASIC", "EXT8H", "EXT4H", "EXT2H").optional() + // @formatter:on ); } } diff --git a/src/main/java/net/hostsharing/hsadminng/hs/booking/item/validators/HsPrivateCloudBookingItemValidator.java b/src/main/java/net/hostsharing/hsadminng/hs/booking/item/validators/HsPrivateCloudBookingItemValidator.java index e9bf7f83..c869af6f 100644 --- a/src/main/java/net/hostsharing/hsadminng/hs/booking/item/validators/HsPrivateCloudBookingItemValidator.java +++ b/src/main/java/net/hostsharing/hsadminng/hs/booking/item/validators/HsPrivateCloudBookingItemValidator.java @@ -1,18 +1,40 @@ package net.hostsharing.hsadminng.hs.booking.item.validators; -import static net.hostsharing.hsadminng.hs.validation.EnumerationProperty.enumerationProperty; import static net.hostsharing.hsadminng.hs.validation.IntegerProperty.integerProperty; class HsPrivateCloudBookingItemValidator extends HsBookingItemEntityValidator { HsPrivateCloudBookingItemValidator() { super( - integerProperty("CPUs").min(1).max(128).required().asTotalLimit(), - integerProperty("RAM").unit("GB").min(1).max(512).required().asTotalLimit(), - integerProperty("SSD").unit("GB").min(25).max(4000).step(25).required().asTotalLimit(), - integerProperty("HDD").unit("GB").min(0).max(16000).step(250).withDefault(0).asTotalLimit(), - integerProperty("Traffic").unit("GB").min(250).max(40000).step(250).required().asTotalLimit(), - enumerationProperty("SLA-Infrastructure").values("BASIC", "EXT8H", "EXT4H", "EXT2H").withDefault("BASIC") + // @formatter:off + integerProperty("CPUs") .min( 1).max( 128).required().asTotalLimit(), + integerProperty("RAM").unit("GB") .min( 1).max( 512).required().asTotalLimit(), + integerProperty("SSD").unit("GB") .min( 25).max( 4000).step(25).required().asTotalLimit(), + integerProperty("HDD").unit("GB") .min( 0).max(16000).step(250).withDefault(0).asTotalLimit(), + integerProperty("Traffic").unit("GB") .min(250).max(40000).step(250).required().asTotalLimit(), + +// Alternatively we could specify it similarly to "Multi" option but exclusively counting: +// integerProperty("Resource-Points") .min(4).max(100).required() +// .each("CPUs").countsAs(64) +// .each("RAM").countsAs(64) +// .each("SSD").countsAs(18) +// .each("HDD").countsAs(2) +// .each("Traffic").countsAs(1), + + integerProperty("SLA-Infrastructure EXT8H") .min( 0).max( 20).withDefault(0).asTotalLimitFor("SLA-Infrastructure", "EXT8H"), + integerProperty("SLA-Infrastructure EXT4H") .min( 0).max( 20).withDefault(0).asTotalLimitFor("SLA-Infrastructure", "EXT4H"), + integerProperty("SLA-Infrastructure EXT2H") .min( 0).max( 20).withDefault(0).asTotalLimitFor("SLA-Infrastructure", "EXT2H"), + + integerProperty("SLA-Platform EXT8H") .min( 0).max( 20).withDefault(0).asTotalLimitFor("SLA-Platform", "EXT8H"), + integerProperty("SLA-Platform EXT4H") .min( 0).max( 20).withDefault(0).asTotalLimitFor("SLA-Platform", "EXT4H"), + integerProperty("SLA-Platform EXT2H") .min( 0).max( 20).withDefault(0).asTotalLimitFor("SLA-Platform", "EXT2H"), + + integerProperty("SLA-EMail") .min( 0).max( 20).optional().asTotalLimitFor("SLA-Platform", "BASIC"), + integerProperty("SLA-Maria") .min( 0).max( 20).optional().asTotalLimitFor("SLA-Platform", "BASIC"), + integerProperty("SLA-PgSQL") .min( 0).max( 20).optional().asTotalLimitFor("SLA-Platform", "BASIC"), + integerProperty("SLA-Office") .min( 0).max( 20).optional().asTotalLimitFor("SLA-Platform", "BASIC"), + integerProperty("SLA-Web") .min( 0).max( 20).optional().asTotalLimitFor("SLA-Platform", "BASIC") + // @formatter:on ); } } 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 3a0438ee..ec844cf7 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 @@ -65,9 +65,9 @@ public class HsHostingAssetEntityValidator extends HsEntityValidator propDef.getValue(subItem.getConfig())) - .map(HsEntityValidator::toNonNullInteger) + .map(HsEntityValidator::toIntegerWithDefault0) .reduce(0, Integer::sum); - final var maxValue = getNonNullIntegerValue(propDef, hostingAsset.getConfig()); + final var maxValue = getIntegerValueWithDefault0(propDef, hostingAsset.getConfig()); return totalValue > maxValue ? "%s' maximum total is %d%s, but actual total is %s %d%s".formatted( propName, maxValue, propUnit, propName, totalValue, propUnit) diff --git a/src/main/java/net/hostsharing/hsadminng/hs/hosting/asset/validators/HsManagedServerHostingAssetValidator.java b/src/main/java/net/hostsharing/hsadminng/hs/hosting/asset/validators/HsManagedServerHostingAssetValidator.java index 9b3031eb..839309a0 100644 --- a/src/main/java/net/hostsharing/hsadminng/hs/hosting/asset/validators/HsManagedServerHostingAssetValidator.java +++ b/src/main/java/net/hostsharing/hsadminng/hs/hosting/asset/validators/HsManagedServerHostingAssetValidator.java @@ -15,10 +15,10 @@ class HsManagedServerHostingAssetValidator extends HsHostingAssetEntityValidator integerProperty("monit_min_free_ssd").min(1).max(1000).withDefault(5), integerProperty("monit_max_hdd_usage").unit("%").min(10).max(100).withDefault(95), integerProperty("monit_min_free_hdd").min(1).max(4000).withDefault(10), - // stringProperty("monit_alarm_email").unit("GB").optional() TODO.spec: via Contact? + // stringProperty("monit_alarm_email").unit("GB").optional() FIXME: via Contact? // other settings - integerProperty("fastcgi_small").min(0).max(16).withDefault(4), // TODO.spec: check limits + // booleanProperty("fastcgi_small").withDefault(false), TODO.spec: clarify Salt-Grains // database software booleanProperty("software-pgsql").withDefault(true), @@ -33,16 +33,16 @@ class HsManagedServerHostingAssetValidator extends HsHostingAssetEntityValidator booleanProperty("software-php-7.3").withDefault(false), booleanProperty("software-php-7.4").withDefault(true), booleanProperty("software-php-8.0").withDefault(false), - booleanProperty("software-php-8.1").withDefault(true), - booleanProperty("software-php-8.2").withDefault(false), + booleanProperty("software-php-8.1").withDefault(false), + booleanProperty("software-php-8.2").withDefault(true), // other software - // TODO.spec: booleanProperty("software-postfix-tls-1.0").withDefault(false), - // TODO.spec: booleanProperty("software-dovecot-tls-1.0").withDefault(false), + booleanProperty("software-postfix-tls-1.0").withDefault(false), + booleanProperty("software-dovecot-tls-1.0").withDefault(false), booleanProperty("software-clamav").withDefault(true), booleanProperty("software-collabora").withDefault(false), booleanProperty("software-libreoffice").withDefault(false), - booleanProperty("software-imagick-ghostscript").withDefault(true) // TODO.spec: default + booleanProperty("software-imagemagick-ghostscript").withDefault(false) ); } } 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 cb53e997..4c20f2a5 100644 --- a/src/main/java/net/hostsharing/hsadminng/hs/validation/HsEntityValidator.java +++ b/src/main/java/net/hostsharing/hsadminng/hs/validation/HsEntityValidator.java @@ -60,18 +60,24 @@ public abstract class HsEntityValidator { .orElse(emptyList())); } - protected static Integer getNonNullIntegerValue(final ValidatableProperty prop, final Map propValues) { + protected static Integer getIntegerValueWithDefault0(final ValidatableProperty prop, final Map propValues) { final var value = prop.getValue(propValues); if (value instanceof Integer) { return (Integer) value; } + if (value == null) { + return 0; + } throw new IllegalArgumentException(prop.propertyName + " Integer value expected, but got " + value); } - protected static Integer toNonNullInteger(final Object value) { + protected static Integer toIntegerWithDefault0(final Object value) { if (value instanceof Integer) { return (Integer) value; } - throw new IllegalArgumentException("Integer value expected, but got " + value); + if (value == null) { + return 0; + } + throw new IllegalArgumentException("Integer value (or null) expected, but got " + value); } } diff --git a/src/main/java/net/hostsharing/hsadminng/hs/validation/ValidatableProperty.java b/src/main/java/net/hostsharing/hsadminng/hs/validation/ValidatableProperty.java index 9e8778e6..f26485d4 100644 --- a/src/main/java/net/hostsharing/hsadminng/hs/validation/ValidatableProperty.java +++ b/src/main/java/net/hostsharing/hsadminng/hs/validation/ValidatableProperty.java @@ -19,6 +19,7 @@ import java.util.function.Function; import static java.lang.Boolean.FALSE; import static java.lang.Boolean.TRUE; import static java.util.Collections.emptyList; +import static java.util.Optional.ofNullable; @RequiredArgsConstructor public abstract class ValidatableProperty { @@ -66,6 +67,28 @@ public abstract class ValidatableProperty { return this; } + public ValidatableProperty asTotalLimitFor(final String propertyName, final String propertyValue) { + if (asTotalLimitValidators == null) { + asTotalLimitValidators = new ArrayList<>(); + } + final TriFunction> validator = + (final HsBookingItemEntity entity, final IntegerProperty prop, final Integer factor) -> { + + final var total = entity.getSubBookingItems().stream() + .map(server -> server.getResources().get(propertyName)) + .filter(propertyValue::equals) + .count(); + + final long limitingValue = ofNullable(prop.getValue(entity.getResources())).orElse(0); + if (total > factor*limitingValue) { + return List.of(limitingValue*factor + " total " + propertyName + "=" + propertyValue + " booked, but " + total + " utilized"); + } + return emptyList(); + }; + asTotalLimitValidators.add((final HsBookingItemEntity entity) -> validator.apply(entity, (IntegerProperty)this, 1)); + return this; + } + public String propertyName() { return propertyName; } diff --git a/src/test/java/net/hostsharing/hsadminng/hs/booking/item/validators/HsPrivateCloudBookingItemValidatorUnitTest.java b/src/test/java/net/hostsharing/hsadminng/hs/booking/item/validators/HsPrivateCloudBookingItemValidatorUnitTest.java index 5079f340..3023c5b9 100644 --- a/src/test/java/net/hostsharing/hsadminng/hs/booking/item/validators/HsPrivateCloudBookingItemValidatorUnitTest.java +++ b/src/test/java/net/hostsharing/hsadminng/hs/booking/item/validators/HsPrivateCloudBookingItemValidatorUnitTest.java @@ -32,7 +32,8 @@ class HsPrivateCloudBookingItemValidatorUnitTest { entry("CPUs", 4), entry("RAM", 20), entry("SSD", 100), - entry("Traffic", 5000) + entry("Traffic", 5000), + entry("SLA-Platform EXT4H", 2) )) .subBookingItems(of( HsBookingItemEntity.builder() @@ -41,7 +42,8 @@ class HsPrivateCloudBookingItemValidatorUnitTest { entry("CPUs", 2), entry("RAM", 10), entry("SSD", 50), - entry("Traffic", 2500) + entry("Traffic", 2500), + entry("SLA-Platform", "EXT4H") )) .build(), HsBookingItemEntity.builder() @@ -50,7 +52,8 @@ class HsPrivateCloudBookingItemValidatorUnitTest { entry("CPUs", 2), entry("RAM", 10), entry("SSD", 50), - entry("Traffic", 2500) + entry("Traffic", 2500), + entry("SLA-Platform", "EXT4H") )) .build() )) @@ -73,7 +76,8 @@ class HsPrivateCloudBookingItemValidatorUnitTest { entry("CPUs", 4), entry("RAM", 20), entry("SSD", 100), - entry("Traffic", 5000) + entry("Traffic", 5000), + entry("SLA-Platform EXT2H", 1) )) .subBookingItems(of( HsBookingItemEntity.builder() @@ -82,7 +86,8 @@ class HsPrivateCloudBookingItemValidatorUnitTest { entry("CPUs", 3), entry("RAM", 20), entry("SSD", 100), - entry("Traffic", 3000) + entry("Traffic", 3000), + entry("SLA-Platform", "EXT2H") )) .build(), HsBookingItemEntity.builder() @@ -91,7 +96,8 @@ class HsPrivateCloudBookingItemValidatorUnitTest { entry("CPUs", 2), entry("RAM", 10), entry("SSD", 50), - entry("Traffic", 2500) + entry("Traffic", 2500), + entry("SLA-Platform", "EXT2H") )) .build() )) @@ -105,8 +111,8 @@ class HsPrivateCloudBookingItemValidatorUnitTest { "'D-12345:Test-Project:null.resources.CPUs' maximum total is 4, but actual total CPUs 5", "'D-12345:Test-Project:null.resources.RAM' maximum total is 20 GB, but actual total RAM 30 GB", "'D-12345:Test-Project:null.resources.SSD' maximum total is 100 GB, but actual total SSD 150 GB", - "'D-12345:Test-Project:null.resources.Traffic' maximum total is 5000 GB, but actual total Traffic 5500 GB" + "'D-12345:Test-Project:null.resources.Traffic' maximum total is 5000 GB, but actual total Traffic 5500 GB", + "'D-12345:Test-Project:null.resources.1 total SLA-Platform=EXT2H booked, but 2 utilized" ); } - }