hierarchical-validation-baseline #59

Merged
hsh-michaelhoennig merged 18 commits from hierarchical-validation-baseline into master 2024-06-14 16:48:01 +02:00
4 changed files with 123 additions and 11 deletions
Showing only changes of commit 5f28f12676 - Show all commits

View File

@ -6,11 +6,13 @@ import net.hostsharing.hsadminng.hs.validation.HsEntityValidator;
import net.hostsharing.hsadminng.hs.validation.ValidatableProperty; import net.hostsharing.hsadminng.hs.validation.ValidatableProperty;
import jakarta.validation.ValidationException; import jakarta.validation.ValidationException;
import java.util.Collection;
import java.util.HashMap; import java.util.HashMap;
import java.util.List; import java.util.List;
import java.util.Map; import java.util.Map;
import java.util.Objects; import java.util.Objects;
import java.util.Set; import java.util.Set;
import java.util.stream.Stream;
import static java.util.Arrays.stream; import static java.util.Arrays.stream;
import static java.util.Collections.emptyList; import static java.util.Collections.emptyList;
@ -77,14 +79,18 @@ public class HsBookingItemEntityValidator extends HsEntityValidator<HsBookingIte
} }
protected List<String> validateSubEntities(final HsBookingItemEntity bookingItem) { protected List<String> validateSubEntities(final HsBookingItemEntity bookingItem) {
return stream(propertyValidators) return Stream.concat(
stream(propertyValidators)
.map(propDef -> propDef.validateTotals(bookingItem))
.flatMap(Collection::stream),
stream(propertyValidators)
.filter(ValidatableProperty::isTotalsValidator) .filter(ValidatableProperty::isTotalsValidator)
.map(prop -> validateMaxTotalValue(bookingItem, prop)) .map(prop -> validateMaxTotalValue(bookingItem, prop))
.filter(Objects::nonNull) ).filter(Objects::nonNull).toList();
.toList();
} }
private String validateMaxTotalValue( // FIXME: convert into generic shape like multi-options validator
private static String validateMaxTotalValue(
final HsBookingItemEntity bookingItem, final HsBookingItemEntity bookingItem,
final ValidatableProperty<?> propDef) { final ValidatableProperty<?> propDef) {
final var propName = propDef.propertyName(); final var propName = propDef.propertyName();

View File

@ -25,21 +25,22 @@ class HsManagedWebspaceBookingItemValidator extends HsBookingItemEntityValidator
integerProperty("SSD").unit("GB").min(1).max(100).step(1).required(), integerProperty("SSD").unit("GB").min(1).max(100).step(1).required(),
integerProperty("HDD").unit("GB").min(0).max(250).step(10).optional(), integerProperty("HDD").unit("GB").min(0).max(250).step(10).optional(),
integerProperty("Traffic").unit("GB").min(10).max(1000).step(10).required(), integerProperty("Traffic").unit("GB").min(10).max(1000).step(10).required(),
integerProperty("MultiOptions").min(1).max(100).step(1).required() integerProperty("MultiOptions").min(1).max(100).step(1).withDefault(1)
.eachComprising( 25, unixUsers()) .eachComprising( 25, unixUsers())
.eachComprising( 5, databaseUsers()) .eachComprising( 5, databaseUsers())
.eachComprising( 5, databases()) .eachComprising( 5, databases())
.eachComprising(250, eMailAddresses()), .eachComprising(250, eMailAddresses()),
integerProperty("Daemons").min(0).max(10).optional(), integerProperty("Daemons").min(0).max(10).withDefault(0),
booleanProperty("Online Office Server").optional(), booleanProperty("Online Office Server").optional(),
enumerationProperty("SLA-Platform").values("BASIC", "EXT24H").optional() enumerationProperty("SLA-Platform").values("BASIC", "EXT24H").withDefault("BASIC")
); );
} }
private static TriFunction<HsBookingItemEntity, IntegerProperty, Integer, List<String>> unixUsers() { private static TriFunction<HsBookingItemEntity, IntegerProperty, Integer, List<String>> unixUsers() {
return (final HsBookingItemEntity entity, final IntegerProperty prop, final Integer factor) -> { return (final HsBookingItemEntity entity, final IntegerProperty prop, final Integer factor) -> {
final var unixUserCount = entity.getSubHostingAssets().stream() final var unixUserCount = entity.getSubHostingAssets().stream()
.filter(bi -> bi.getType() == UNIX_USER) .flatMap(ha -> ha.getSubHostingAssets().stream())
.filter(ha -> ha.getType() == UNIX_USER)
.count(); .count();
final long limitingValue = prop.getValue(entity.getResources()); final long limitingValue = prop.getValue(entity.getResources());
if (unixUserCount > factor*limitingValue) { if (unixUserCount > factor*limitingValue) {
@ -52,6 +53,7 @@ class HsManagedWebspaceBookingItemValidator extends HsBookingItemEntityValidator
private static TriFunction<HsBookingItemEntity, IntegerProperty, Integer, List<String>> databaseUsers() { private static TriFunction<HsBookingItemEntity, IntegerProperty, Integer, List<String>> databaseUsers() {
return (final HsBookingItemEntity entity, final IntegerProperty prop, final Integer factor) -> { return (final HsBookingItemEntity entity, final IntegerProperty prop, final Integer factor) -> {
final var unixUserCount = entity.getSubHostingAssets().stream() final var unixUserCount = entity.getSubHostingAssets().stream()
.flatMap(ha -> ha.getSubHostingAssets().stream())
.filter(bi -> bi.getType() == PGSQL_USER || bi.getType() == MARIADB_USER ) .filter(bi -> bi.getType() == PGSQL_USER || bi.getType() == MARIADB_USER )
.count(); .count();
final long limitingValue = prop.getValue(entity.getResources()); final long limitingValue = prop.getValue(entity.getResources());
@ -65,6 +67,7 @@ class HsManagedWebspaceBookingItemValidator extends HsBookingItemEntityValidator
private static TriFunction<HsBookingItemEntity, IntegerProperty, Integer, List<String>> databases() { private static TriFunction<HsBookingItemEntity, IntegerProperty, Integer, List<String>> databases() {
return (final HsBookingItemEntity entity, final IntegerProperty prop, final Integer factor) -> { return (final HsBookingItemEntity entity, final IntegerProperty prop, final Integer factor) -> {
final var unixUserCount = entity.getSubHostingAssets().stream() final var unixUserCount = entity.getSubHostingAssets().stream()
.flatMap(ha -> ha.getSubHostingAssets().stream())
.filter(bi -> bi.getType()==PGSQL_USER || bi.getType()==MARIADB_USER ) .filter(bi -> bi.getType()==PGSQL_USER || bi.getType()==MARIADB_USER )
.flatMap(domainEMailSetup -> domainEMailSetup.getSubHostingAssets().stream() .flatMap(domainEMailSetup -> domainEMailSetup.getSubHostingAssets().stream()
.filter(ha -> ha.getType()==PGSQL_DATABASE || ha.getType()==MARIADB_DATABASE)) .filter(ha -> ha.getType()==PGSQL_DATABASE || ha.getType()==MARIADB_DATABASE))
@ -80,6 +83,7 @@ class HsManagedWebspaceBookingItemValidator extends HsBookingItemEntityValidator
private static TriFunction<HsBookingItemEntity, IntegerProperty, Integer, List<String>> eMailAddresses() { private static TriFunction<HsBookingItemEntity, IntegerProperty, Integer, List<String>> eMailAddresses() {
return (final HsBookingItemEntity entity, final IntegerProperty prop, final Integer factor) -> { return (final HsBookingItemEntity entity, final IntegerProperty prop, final Integer factor) -> {
final var unixUserCount = entity.getSubHostingAssets().stream() final var unixUserCount = entity.getSubHostingAssets().stream()
.flatMap(ha -> ha.getSubHostingAssets().stream())
.filter(bi -> bi.getType() == DOMAIN_EMAIL_SETUP) .filter(bi -> bi.getType() == DOMAIN_EMAIL_SETUP)
.flatMap(domainEMailSetup -> domainEMailSetup.getSubHostingAssets().stream() .flatMap(domainEMailSetup -> domainEMailSetup.getSubHostingAssets().stream()
.filter(ha -> ha.getType()==EMAIL_ADDRESS)) .filter(ha -> ha.getType()==EMAIL_ADDRESS))

View File

@ -8,14 +8,17 @@ import net.hostsharing.hsadminng.mapper.Array;
import org.apache.commons.lang3.function.TriFunction; import org.apache.commons.lang3.function.TriFunction;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Collection;
import java.util.LinkedHashMap; import java.util.LinkedHashMap;
import java.util.List; import java.util.List;
import java.util.Map; import java.util.Map;
import java.util.Objects;
import java.util.Optional; import java.util.Optional;
import java.util.function.Function; 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;
@RequiredArgsConstructor @RequiredArgsConstructor
public abstract class ValidatableProperty<T> { public abstract class ValidatableProperty<T> {
@ -64,7 +67,7 @@ public abstract class ValidatableProperty<T> {
} }
public boolean isTotalsValidator() { public boolean isTotalsValidator() {
return isTotalsValidator; return isTotalsValidator || asTotalLimitValidators != null;
} }
public Integer thresholdPercentage() { public Integer thresholdPercentage() {
@ -155,4 +158,20 @@ public abstract class ValidatableProperty<T> {
} }
return value; return value;
} }
public List<String> validateTotals(final HsBookingItemEntity bookingItem) {
if (asTotalLimitValidators==null) {
return emptyList();
}
return asTotalLimitValidators.stream()
.map(v -> v.apply(bookingItem))
.filter(Objects::nonNull)
.flatMap(Collection::stream)
.map(v -> wrap(v))
.toList();
}
private String wrap(final String v) {
return v;
}
} }

View File

@ -3,15 +3,23 @@ package net.hostsharing.hsadminng.hs.booking.item.validators;
import net.hostsharing.hsadminng.hs.booking.debitor.HsBookingDebitorEntity; import net.hostsharing.hsadminng.hs.booking.debitor.HsBookingDebitorEntity;
import net.hostsharing.hsadminng.hs.booking.item.HsBookingItemEntity; import net.hostsharing.hsadminng.hs.booking.item.HsBookingItemEntity;
import net.hostsharing.hsadminng.hs.booking.project.HsBookingProjectEntity; import net.hostsharing.hsadminng.hs.booking.project.HsBookingProjectEntity;
import net.hostsharing.hsadminng.hs.hosting.asset.HsHostingAssetEntity;
import net.hostsharing.hsadminng.hs.hosting.asset.HsHostingAssetType;
import org.junit.jupiter.api.Test; import org.junit.jupiter.api.Test;
import java.util.Collection;
import java.util.List;
import java.util.Map; import java.util.Map;
import java.util.stream.Collectors;
import java.util.stream.IntStream;
import static java.util.Arrays.stream;
import static java.util.List.of; import static java.util.List.of;
import static java.util.Map.entry; import static java.util.Map.entry;
import static java.util.Map.ofEntries; import static java.util.Map.ofEntries;
import static net.hostsharing.hsadminng.hs.booking.item.HsBookingItemType.CLOUD_SERVER; import static net.hostsharing.hsadminng.hs.booking.item.HsBookingItemType.CLOUD_SERVER;
import static net.hostsharing.hsadminng.hs.booking.item.HsBookingItemType.MANAGED_SERVER; import static net.hostsharing.hsadminng.hs.booking.item.HsBookingItemType.MANAGED_SERVER;
import static net.hostsharing.hsadminng.hs.booking.item.HsBookingItemType.MANAGED_WEBSPACE;
import static net.hostsharing.hsadminng.hs.booking.item.HsBookingItemType.PRIVATE_CLOUD; import static net.hostsharing.hsadminng.hs.booking.item.HsBookingItemType.PRIVATE_CLOUD;
import static net.hostsharing.hsadminng.hs.booking.item.validators.HsBookingItemEntityValidator.forType; import static net.hostsharing.hsadminng.hs.booking.item.validators.HsBookingItemEntityValidator.forType;
import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThat;
@ -119,4 +127,79 @@ class HsManagedServerBookingItemValidatorUnitTest {
"D-12345:Test-Project:null.parentItem.total Traffic is 5500 GB exceeds max total Traffic 5000 GB" "D-12345:Test-Project:null.parentItem.total Traffic is 5500 GB exceeds max total Traffic 5000 GB"
); );
} }
@Test
void validatesExceedingTotals() {
// given
final var managedWebspaceBookingItem = HsBookingItemEntity.builder()
.type(MANAGED_WEBSPACE)
.caption("test Managed-Webspace")
.resources(ofEntries(
entry("SSD", 100),
entry("Traffic", 1000),
entry("MultiOptions", 1)
))
.subHostingAssets(of(
HsHostingAssetEntity.builder()
.type(HsHostingAssetType.MANAGED_WEBSPACE)
.identifier("abc00")
.subHostingAssets(concat(
generate(26, HsHostingAssetType.UNIX_USER, "xyz00-%c%c"),
generateDbUsersWithDatabases(3, HsHostingAssetType.PGSQL_USER,
"xyz00_%c%c",
1, HsHostingAssetType.PGSQL_DATABASE
),
generateDbUsersWithDatabases(3, HsHostingAssetType.MARIADB_USER,
"xyz00_%c%c",
1, HsHostingAssetType.MARIADB_DATABASE
)
))
.build()
))
.build();
// when
final var result = HsBookingItemEntityValidator.doValidate(managedWebspaceBookingItem);
// then
assertThat(result).containsExactlyInAnyOrder(
"MultiOptions=1 allows at maximum 25 unix users, but 26 found",
"MultiOptions=1 allows at maximum 5 database users, but 6 found",
"MultiOptions=1 allows at maximum 5 databases, but 6 found"
);
}
@SafeVarargs
private List<HsHostingAssetEntity> concat(final List<HsHostingAssetEntity>... hostingAssets) {
return stream(hostingAssets)
.flatMap(Collection::stream)
.collect(Collectors.toList());
}
private List<HsHostingAssetEntity> generate(final int count, final HsHostingAssetType hostingAssetType,
final String identifierPattern) {
return IntStream.range(0, count)
.mapToObj(number -> HsHostingAssetEntity.builder()
.type(hostingAssetType)
.identifier(identifierPattern.formatted((number/'a')+'a', (number%'a')+'a'))
.build())
.toList();
}
private List<HsHostingAssetEntity> generateDbUsersWithDatabases(
final int userCount,
final HsHostingAssetType directAssetType,
final String directAssetIdentifierFormat, final int dbCount,
final HsHostingAssetType subAssetType) {
return IntStream.range(0, userCount)
.mapToObj(n -> HsHostingAssetEntity.builder()
.type(directAssetType)
.identifier(directAssetIdentifierFormat.formatted((n/'a')+'a', (n%'a')+'a'))
.subHostingAssets(
generate(dbCount, subAssetType, "xyz00_%c%c%%c%%c".formatted((n/'a')+'a', (n%'a')+'a'))
)
.build())
.toList();
}
} }