finalize PrivateCloud, Cloud- and ManagedServer and ManagedWebspace Billingtems and HostingAssets #63

Merged
hsh-michaelhoennig merged 9 commits from cloud-server-and-webspace-billing-items-and-hosting-assets into master 2024-06-20 11:03:59 +02:00
8 changed files with 97 additions and 36 deletions
Showing only changes of commit 988f7dc23b - Show all commits

View File

@ -59,9 +59,9 @@ public class HsBookingItemEntityValidator extends HsEntityValidator<HsBookingIte
final var totalValue = ofNullable(bookingItem.getSubBookingItems()).orElse(emptyList()) final var totalValue = ofNullable(bookingItem.getSubBookingItems()).orElse(emptyList())
.stream() .stream()
.map(subItem -> propDef.getValue(subItem.getResources())) .map(subItem -> propDef.getValue(subItem.getResources()))
.map(HsBookingItemEntityValidator::toNonNullInteger) .map(HsBookingItemEntityValidator::toIntegerWithDefault0)
.reduce(0, Integer::sum); .reduce(0, Integer::sum);
final var maxValue = getNonNullIntegerValue(propDef, bookingItem.getResources()); final var maxValue = getIntegerValueWithDefault0(propDef, bookingItem.getResources());
if (propDef.thresholdPercentage() != null ) { if (propDef.thresholdPercentage() != null ) {
return totalValue > (maxValue * propDef.thresholdPercentage() / 100) return totalValue > (maxValue * propDef.thresholdPercentage() / 100)
? "%s' maximum total is %d%s, but actual total %s %d%s, which exceeds threshold of %d%%" ? "%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() { HsCloudServerBookingItemValidator() {
super( super(
// @formatter:off
booleanProperty("active") .withDefault(true), booleanProperty("active") .withDefault(true),
integerProperty("CPUs") .min( 1) .max( 32) .required(), integerProperty("CPUs") .min( 1) .max( 32) .required(),
integerProperty("RAM").unit("GB") .min( 1) .max( 128) .required(), integerProperty("RAM").unit("GB") .min( 1) .max( 128) .required(),
integerProperty("SSD").unit("GB") .min( 25) .max( 1000) .step(25).required(), integerProperty("SSD").unit("GB") .min( 25) .max( 1000) .step(25).required(),
hsh-michaelhoennig marked this conversation as resolved
Review

min(0) für Bestands-Cloud-Server

min(0) für Bestands-Cloud-Server
integerProperty("HDD").unit("GB") .min( 0) .max( 4000) .step(250).withDefault(0), integerProperty("HDD").unit("GB") .min( 0) .max( 4000) .step(250).withDefault(0),
integerProperty("Traffic").unit("GB") .min(250) .max(10000) .step(250).required(), integerProperty("Traffic").unit("GB") .min(250) .max(10000) .step(250).required(),
enumerationProperty("SLA-Infrastructure").values("BASIC", "EXT8H", "EXT4H", "EXT2H").optional() 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; 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; import static net.hostsharing.hsadminng.hs.validation.IntegerProperty.integerProperty;
class HsPrivateCloudBookingItemValidator extends HsBookingItemEntityValidator { class HsPrivateCloudBookingItemValidator extends HsBookingItemEntityValidator {
HsPrivateCloudBookingItemValidator() { HsPrivateCloudBookingItemValidator() {
super( super(
// @formatter:off
integerProperty("CPUs") .min( 1).max( 128).required().asTotalLimit(), integerProperty("CPUs") .min( 1).max( 128).required().asTotalLimit(),
integerProperty("RAM").unit("GB") .min( 1).max( 512).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("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("HDD").unit("GB") .min( 0).max(16000).step(250).withDefault(0).asTotalLimit(),
integerProperty("Traffic").unit("GB") .min(250).max(40000).step(250).required().asTotalLimit(), integerProperty("Traffic").unit("GB") .min(250).max(40000).step(250).required().asTotalLimit(),
enumerationProperty("SLA-Infrastructure").values("BASIC", "EXT8H", "EXT4H", "EXT2H").withDefault("BASIC")
// 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()) final var totalValue = ofNullable(hostingAsset.getSubHostingAssets()).orElse(emptyList())
.stream() .stream()
.map(subItem -> propDef.getValue(subItem.getConfig())) .map(subItem -> propDef.getValue(subItem.getConfig()))
.map(HsEntityValidator::toNonNullInteger) .map(HsEntityValidator::toIntegerWithDefault0)
.reduce(0, Integer::sum); .reduce(0, Integer::sum);
final var maxValue = getNonNullIntegerValue(propDef, hostingAsset.getConfig()); final var maxValue = getIntegerValueWithDefault0(propDef, hostingAsset.getConfig());
return totalValue > maxValue return totalValue > maxValue
? "%s' maximum total is %d%s, but actual total is %s %d%s".formatted( ? "%s' maximum total is %d%s, but actual total is %s %d%s".formatted(
propName, maxValue, propUnit, propName, totalValue, propUnit) 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_min_free_ssd").min(1).max(1000).withDefault(5),
integerProperty("monit_max_hdd_usage").unit("%").min(10).max(100).withDefault(95), integerProperty("monit_max_hdd_usage").unit("%").min(10).max(100).withDefault(95),
integerProperty("monit_min_free_hdd").min(1).max(4000).withDefault(10), 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 // 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 // database software
booleanProperty("software-pgsql").withDefault(true), booleanProperty("software-pgsql").withDefault(true),
@ -33,16 +33,16 @@ class HsManagedServerHostingAssetValidator extends HsHostingAssetEntityValidator
booleanProperty("software-php-7.3").withDefault(false), booleanProperty("software-php-7.3").withDefault(false),
booleanProperty("software-php-7.4").withDefault(true), booleanProperty("software-php-7.4").withDefault(true),
booleanProperty("software-php-8.0").withDefault(false), booleanProperty("software-php-8.0").withDefault(false),
booleanProperty("software-php-8.1").withDefault(true), booleanProperty("software-php-8.1").withDefault(false),
booleanProperty("software-php-8.2").withDefault(false), booleanProperty("software-php-8.2").withDefault(true),
// other software // other software
// TODO.spec: booleanProperty("software-postfix-tls-1.0").withDefault(false), booleanProperty("software-postfix-tls-1.0").withDefault(false),
// TODO.spec: booleanProperty("software-dovecot-tls-1.0").withDefault(false), booleanProperty("software-dovecot-tls-1.0").withDefault(false),
booleanProperty("software-clamav").withDefault(true), booleanProperty("software-clamav").withDefault(true),
booleanProperty("software-collabora").withDefault(false), booleanProperty("software-collabora").withDefault(false),
booleanProperty("software-libreoffice").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())); .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); final var value = prop.getValue(propValues);
if (value instanceof Integer) { if (value instanceof Integer) {
return (Integer) value; return (Integer) value;
} }
if (value == null) {
return 0;
}
throw new IllegalArgumentException(prop.propertyName + " Integer value expected, but got " + value); 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) { if (value instanceof Integer) {
return (Integer) value; 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.FALSE;
import static java.lang.Boolean.TRUE; import static java.lang.Boolean.TRUE;
import static java.util.Collections.emptyList; import static java.util.Collections.emptyList;
import static java.util.Optional.ofNullable;
@RequiredArgsConstructor @RequiredArgsConstructor
public abstract class ValidatableProperty<T> { public abstract class ValidatableProperty<T> {
@ -66,6 +67,28 @@ public abstract class ValidatableProperty<T> {
return this; 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() { public String propertyName() {
return propertyName; return propertyName;
} }

View File

@ -32,7 +32,8 @@ class HsPrivateCloudBookingItemValidatorUnitTest {
entry("CPUs", 4), entry("CPUs", 4),
entry("RAM", 20), entry("RAM", 20),
entry("SSD", 100), entry("SSD", 100),
entry("Traffic", 5000) entry("Traffic", 5000),
entry("SLA-Platform EXT4H", 2)
)) ))
.subBookingItems(of( .subBookingItems(of(
HsBookingItemEntity.builder() HsBookingItemEntity.builder()
@ -41,7 +42,8 @@ class HsPrivateCloudBookingItemValidatorUnitTest {
entry("CPUs", 2), entry("CPUs", 2),
entry("RAM", 10), entry("RAM", 10),
entry("SSD", 50), entry("SSD", 50),
entry("Traffic", 2500) entry("Traffic", 2500),
entry("SLA-Platform", "EXT4H")
)) ))
.build(), .build(),
HsBookingItemEntity.builder() HsBookingItemEntity.builder()
@ -50,7 +52,8 @@ class HsPrivateCloudBookingItemValidatorUnitTest {
entry("CPUs", 2), entry("CPUs", 2),
entry("RAM", 10), entry("RAM", 10),
entry("SSD", 50), entry("SSD", 50),
entry("Traffic", 2500) entry("Traffic", 2500),
entry("SLA-Platform", "EXT4H")
)) ))
.build() .build()
)) ))
@ -73,7 +76,8 @@ class HsPrivateCloudBookingItemValidatorUnitTest {
entry("CPUs", 4), entry("CPUs", 4),
entry("RAM", 20), entry("RAM", 20),
entry("SSD", 100), entry("SSD", 100),
entry("Traffic", 5000) entry("Traffic", 5000),
entry("SLA-Platform EXT2H", 1)
)) ))
.subBookingItems(of( .subBookingItems(of(
HsBookingItemEntity.builder() HsBookingItemEntity.builder()
@ -82,7 +86,8 @@ class HsPrivateCloudBookingItemValidatorUnitTest {
entry("CPUs", 3), entry("CPUs", 3),
entry("RAM", 20), entry("RAM", 20),
entry("SSD", 100), entry("SSD", 100),
entry("Traffic", 3000) entry("Traffic", 3000),
entry("SLA-Platform", "EXT2H")
)) ))
.build(), .build(),
HsBookingItemEntity.builder() HsBookingItemEntity.builder()
@ -91,7 +96,8 @@ class HsPrivateCloudBookingItemValidatorUnitTest {
entry("CPUs", 2), entry("CPUs", 2),
entry("RAM", 10), entry("RAM", 10),
entry("SSD", 50), entry("SSD", 50),
entry("Traffic", 2500) entry("Traffic", 2500),
entry("SLA-Platform", "EXT2H")
)) ))
.build() .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.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.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.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"
); );
} }
} }