implement asTotalLimitFor(...) validation for SLA-Infrastructure and SLA-Platform

This commit is contained in:
Michael Hoennig 2024-06-19 15:47:12 +02:00
parent de9f5b617f
commit 988f7dc23b
8 changed files with 97 additions and 36 deletions

View File

@ -59,9 +59,9 @@ public class HsBookingItemEntityValidator extends HsEntityValidator<HsBookingIte
final var totalValue = ofNullable(bookingItem.getSubBookingItems()).orElse(emptyList())
.stream()
.map(subItem -> 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%%"

View File

@ -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
);
}
}

View File

@ -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
);
}
}

View File

@ -65,9 +65,9 @@ public class HsHostingAssetEntityValidator extends HsEntityValidator<HsHostingAs
final var totalValue = ofNullable(hostingAsset.getSubHostingAssets()).orElse(emptyList())
.stream()
.map(subItem -> 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)

View File

@ -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)
);
}
}

View File

@ -60,18 +60,24 @@ public abstract class HsEntityValidator<E> {
.orElse(emptyList()));
}
protected static Integer getNonNullIntegerValue(final ValidatableProperty<?> prop, final Map<String, Object> propValues) {
protected static Integer getIntegerValueWithDefault0(final ValidatableProperty<?> prop, final Map<String, Object> 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);
}
}

View File

@ -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<T> {
@ -66,6 +67,28 @@ public abstract class ValidatableProperty<T> {
return this;
}
public ValidatableProperty<T> asTotalLimitFor(final String propertyName, final String propertyValue) {
if (asTotalLimitValidators == null) {
asTotalLimitValidators = new ArrayList<>();
}
final TriFunction<HsBookingItemEntity, IntegerProperty, Integer, List<String>> 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;
}

View File

@ -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"
);
}
}