Compare commits

..

No commits in common. "f379ae5cb248a14287f0f4b79bff1526fb77a0c2" and "24811661d430df7244735c675abe31ab6f68cf7e" have entirely different histories.

10 changed files with 65 additions and 93 deletions

View File

@ -62,7 +62,8 @@ public class HsBookingItemController implements HsBookingItemsApi {
final var entityToSave = mapper.map(body, HsBookingItemEntity.class, RESOURCE_TO_ENTITY_POSTMAPPER); final var entityToSave = mapper.map(body, HsBookingItemEntity.class, RESOURCE_TO_ENTITY_POSTMAPPER);
final var saved = HsBookingItemEntityValidatorRegistry.validated(bookingItemRepo.save(entityToSave)); final HsBookingItemEntity entityToSave1 = bookingItemRepo.save(entityToSave);
final var saved = HsBookingItemEntityValidatorRegistry.validated(entityToSave1);
final var uri = final var uri =
MvcUriComponentsBuilder.fromController(getClass()) MvcUriComponentsBuilder.fromController(getClass())
@ -83,7 +84,7 @@ public class HsBookingItemController implements HsBookingItemsApi {
context.define(currentUser, assumedRoles); context.define(currentUser, assumedRoles);
final var result = bookingItemRepo.findByUuid(bookingItemUuid); final var result = bookingItemRepo.findByUuid(bookingItemUuid);
result.ifPresent(entity -> em.detach(entity)); // prevent further LAZY-loading result.ifPresent(entity -> em.detach(entity));
return result return result
.map(bookingItemEntity -> ResponseEntity.ok( .map(bookingItemEntity -> ResponseEntity.ok(
mapper.map(bookingItemEntity, HsBookingItemResource.class, ENTITY_TO_RESOURCE_POSTMAPPER))) mapper.map(bookingItemEntity, HsBookingItemResource.class, ENTITY_TO_RESOURCE_POSTMAPPER)))

View File

@ -21,33 +21,25 @@ public class HsBookingItemEntityValidator extends HsEntityValidator<HsBookingIte
public List<String> validate(final HsBookingItemEntity bookingItem) { public List<String> validate(final HsBookingItemEntity bookingItem) {
return sequentiallyValidate( return sequentiallyValidate(
() -> validateProperties(bookingItem), () -> enrich(prefix(bookingItem.toShortString(), "resources"), validateProperties(bookingItem.getResources())),
() -> optionallyValidate(bookingItem.getParentItem()), () -> enrich(prefix(bookingItem.toShortString(), "parentItem"), optionallyValidate(bookingItem.getParentItem())),
() -> validateAgainstSubEntities(bookingItem) () -> validateAgainstSubEntities(bookingItem)
); );
} }
private List<String> validateProperties(final HsBookingItemEntity bookingItem) {
return enrich(prefix(bookingItem.toShortString(), "resources"), validateProperties(bookingItem.getResources()));
}
private static List<String> optionallyValidate(final HsBookingItemEntity bookingItem) { private static List<String> optionallyValidate(final HsBookingItemEntity bookingItem) {
return bookingItem != null return bookingItem != null ? HsBookingItemEntityValidatorRegistry.doValidate(bookingItem) : emptyList();
? enrich(prefix(bookingItem.toShortString(), ""),
HsBookingItemEntityValidatorRegistry.doValidate(bookingItem))
: emptyList();
} }
protected List<String> validateAgainstSubEntities(final HsBookingItemEntity bookingItem) { protected List<String> validateAgainstSubEntities(final HsBookingItemEntity bookingItem) {
return enrich(prefix(bookingItem.toShortString(), "resources"), return Stream.concat(
Stream.concat(
stream(propertyValidators) stream(propertyValidators)
.map(propDef -> propDef.validateTotals(bookingItem)) .map(propDef -> propDef.validateTotals(bookingItem))
.flatMap(Collection::stream), .flatMap(Collection::stream),
stream(propertyValidators) stream(propertyValidators)
.filter(ValidatableProperty::isTotalsValidator) .filter(ValidatableProperty::isTotalsValidator)
.map(prop -> validateMaxTotalValue(bookingItem, prop)) .map(prop -> validateMaxTotalValue(bookingItem, prop))
).filter(Objects::nonNull).toList()); ).filter(Objects::nonNull).toList();
} }
// TODO.refa: convert into generic shape like multi-options validator // TODO.refa: convert into generic shape like multi-options validator
@ -64,13 +56,13 @@ public class HsBookingItemEntityValidator extends HsEntityValidator<HsBookingIte
final var maxValue = getNonNullIntegerValue(propDef, bookingItem.getResources()); final var maxValue = getNonNullIntegerValue(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' total is %d%s, thus exceeds max total %s %d%s, which is above threshold of %d%%"
.formatted(propName, maxValue, propUnit, propName, totalValue, propUnit, propDef.thresholdPercentage()) .formatted(propName, totalValue, propUnit, propName, maxValue, propUnit, propDef.thresholdPercentage())
: null; : null;
} else { } else {
return totalValue > maxValue return totalValue > maxValue
? "%s' maximum total is %d%s, but actual total %s %d%s" ? "%s' total is %d%s, thus exceeds max total %s %d%s"
.formatted(propName, maxValue, propUnit, propName, totalValue, propUnit) .formatted(propName, totalValue, propUnit, propName, maxValue, propUnit)
: null; : null;
} }
} }

View File

@ -21,8 +21,6 @@ import java.util.List;
import java.util.UUID; import java.util.UUID;
import java.util.function.BiConsumer; import java.util.function.BiConsumer;
import static net.hostsharing.hsadminng.hs.hosting.asset.validators.HsHostingAssetEntityValidatorRegistry.validated;
@RestController @RestController
public class HsHostingAssetController implements HsHostingAssetsApi { public class HsHostingAssetController implements HsHostingAssetsApi {
@ -63,7 +61,8 @@ public class HsHostingAssetController implements HsHostingAssetsApi {
final var entityToSave = mapper.map(body, HsHostingAssetEntity.class, RESOURCE_TO_ENTITY_POSTMAPPER); final var entityToSave = mapper.map(body, HsHostingAssetEntity.class, RESOURCE_TO_ENTITY_POSTMAPPER);
final var saved = validated(assetRepo.save(entityToSave)); final HsHostingAssetEntity persistentEntity = assetRepo.save(entityToSave);
final var saved = HsHostingAssetEntityValidatorRegistry.validated(persistentEntity);
final var uri = final var uri =
MvcUriComponentsBuilder.fromController(getClass()) MvcUriComponentsBuilder.fromController(getClass())
@ -118,7 +117,8 @@ public class HsHostingAssetController implements HsHostingAssetsApi {
new HsHostingAssetEntityPatcher(current).apply(body); new HsHostingAssetEntityPatcher(current).apply(body);
final var saved = validated(assetRepo.save(current)); final HsHostingAssetEntity persistentEntity = assetRepo.save(current);
final var saved = HsHostingAssetEntityValidatorRegistry.validated(persistentEntity);
final var mapped = mapper.map(saved, HsHostingAssetResource.class); final var mapped = mapper.map(saved, HsHostingAssetResource.class);
return ResponseEntity.ok(mapped); return ResponseEntity.ok(mapped);
} }

View File

@ -23,38 +23,29 @@ public class HsHostingAssetEntityValidator extends HsEntityValidator<HsHostingAs
@Override @Override
public List<String> validate(final HsHostingAssetEntity assetEntity) { public List<String> validate(final HsHostingAssetEntity assetEntity) {
return sequentiallyValidate( return sequentiallyValidate(
() -> validateProperties(assetEntity), () -> enrich(prefix(assetEntity.toShortString(), "config"), validateProperties(assetEntity.getConfig())),
() -> optionallyValidate(assetEntity.getBookingItem()), () -> enrich(prefix(assetEntity.toShortString(), "bookingItem"), optionallyValidate(assetEntity.getBookingItem())),
() -> optionallyValidate(assetEntity.getParentAsset()), () -> enrich(prefix(assetEntity.toShortString(), "parentAsset"), optionallyValidate(assetEntity.getParentAsset())),
() -> validateAgainstSubEntities(assetEntity) () -> validateSubEntities(assetEntity)
); );
} }
private List<String> validateProperties(final HsHostingAssetEntity assetEntity) {
return enrich(prefix(assetEntity.toShortString(), "config"), validateProperties(assetEntity.getConfig()));
}
private static List<String> optionallyValidate(final HsHostingAssetEntity assetEntity) { private static List<String> optionallyValidate(final HsHostingAssetEntity assetEntity) {
return assetEntity != null return assetEntity != null ?
? enrich(prefix(assetEntity.toShortString(), "parentAsset"), HsHostingAssetEntityValidatorRegistry.forType(assetEntity.getType()).validate(assetEntity) :
HsHostingAssetEntityValidatorRegistry.forType(assetEntity.getType()).validate(assetEntity)) emptyList();
: emptyList();
} }
private static List<String> optionallyValidate(final HsBookingItemEntity bookingItem) { private static List<String> optionallyValidate(final HsBookingItemEntity bookingItem) {
return bookingItem != null return bookingItem != null ? HsBookingItemEntityValidatorRegistry.doValidate(bookingItem) : emptyList();
? enrich(prefix(bookingItem.toShortString(), "bookingItem"),
HsBookingItemEntityValidatorRegistry.doValidate(bookingItem))
: emptyList();
} }
protected List<String> validateAgainstSubEntities(final HsHostingAssetEntity assetEntity) { protected List<String> validateSubEntities(final HsHostingAssetEntity assetEntity) {
return enrich(prefix(assetEntity.toShortString(), "config"), return stream(propertyValidators)
stream(propertyValidators)
.filter(ValidatableProperty::isTotalsValidator) .filter(ValidatableProperty::isTotalsValidator)
.map(prop -> validateMaxTotalValue(assetEntity, prop)) .map(prop -> validateMaxTotalValue(assetEntity, prop))
.filter(Objects::nonNull) .filter(Objects::nonNull)
.toList()); .toList();
} }
private String validateMaxTotalValue( private String validateMaxTotalValue(
@ -69,8 +60,8 @@ public class HsHostingAssetEntityValidator extends HsEntityValidator<HsHostingAs
.reduce(0, Integer::sum); .reduce(0, Integer::sum);
final var maxValue = getNonNullIntegerValue(propDef, hostingAsset.getConfig()); final var maxValue = getNonNullIntegerValue(propDef, hostingAsset.getConfig());
return totalValue > maxValue return totalValue > maxValue
? "%s' maximum total is %d%s, but actual total is %s %d%s".formatted( ? "%s' total is %d%s, thus exceeds max total %s %d%s".formatted(
propName, maxValue, propUnit, propName, totalValue, propUnit) propName, totalValue, propUnit, propName, maxValue, propUnit)
: null; : null;
} }
} }

View File

@ -38,10 +38,10 @@ class HsBookingItemEntityValidatorUnitTest {
// then // then
assertThat(result).isInstanceOf(ValidationException.class) assertThat(result).isInstanceOf(ValidationException.class)
.hasMessageContaining( .hasMessageContaining(
"'D-12345:test project:Test-Server.resources.CPUs' is required but missing", "D-12345:test project:Test-Server.resources.CPUs' is required but missing",
"'D-12345:test project:Test-Server.resources.RAM' is required but missing", "D-12345:test project:Test-Server.resources.RAM' is required but missing",
"'D-12345:test project:Test-Server.resources.SSD' is required but missing", "D-12345:test project:Test-Server.resources.SSD' is required but missing",
"'D-12345:test project:Test-Server.resources.Traffic' is required but missing"); "D-12345:test project:Test-Server.resources.Traffic' is required but missing");
} }
@Test @Test

View File

@ -109,10 +109,10 @@ class HsCloudServerBookingItemValidatorUnitTest {
// then // then
assertThat(result).containsExactlyInAnyOrder( assertThat(result).containsExactlyInAnyOrder(
"'D-12345:Test-Project:Test Cloud.resources.CPUs' maximum total is 4, but actual total CPUs 5", "'D-12345:Test-Project:Test Cloud-Server.parentItem.CPUs' total is 5, thus exceeds max total CPUs 4",
"'D-12345:Test-Project:Test Cloud.resources.RAM' maximum total is 20 GB, but actual total RAM 30 GB", "'D-12345:Test-Project:Test Cloud-Server.parentItem.RAM' total is 30 GB, thus exceeds max total RAM 20 GB",
"'D-12345:Test-Project:Test Cloud.resources.SSD' maximum total is 100 GB, but actual total SSD 150 GB", "'D-12345:Test-Project:Test Cloud-Server.parentItem.SSD' total is 150 GB, thus exceeds max total SSD 100 GB",
"'D-12345:Test-Project:Test Cloud.resources.Traffic' maximum total is 5000 GB, but actual total Traffic 5500 GB" "'D-12345:Test-Project:Test Cloud-Server.parentItem.Traffic' total is 5500 GB, thus exceeds max total Traffic 5000 GB"
); );
} }
} }

View File

@ -120,10 +120,10 @@ class HsManagedServerBookingItemValidatorUnitTest {
// then // then
assertThat(result).containsExactlyInAnyOrder( assertThat(result).containsExactlyInAnyOrder(
"'D-12345:Test-Project:null.resources.CPUs' maximum total is 4, but actual total CPUs 5", "'D-12345:Test-Project:null.parentItem.CPUs' total is 5, thus exceeds max total CPUs 4",
"'D-12345:Test-Project:null.resources.RAM' maximum total is 20 GB, but actual total RAM 30 GB", "'D-12345:Test-Project:null.parentItem.RAM' total is 30 GB, thus exceeds max total RAM 20 GB",
"'D-12345:Test-Project:null.resources.SSD' maximum total is 100 GB, but actual total SSD 150 GB", "'D-12345:Test-Project:null.parentItem.SSD' total is 150 GB, thus exceeds max total SSD 100 GB",
"'D-12345:Test-Project:null.resources.Traffic' maximum total is 5000 GB, but actual total Traffic 5500 GB" "'D-12345:Test-Project:null.parentItem.Traffic' total is 5500 GB, thus exceeds max total Traffic 5000 GB"
); );
} }
@ -132,7 +132,6 @@ class HsManagedServerBookingItemValidatorUnitTest {
// given // given
final var managedWebspaceBookingItem = HsBookingItemEntity.builder() final var managedWebspaceBookingItem = HsBookingItemEntity.builder()
.type(MANAGED_WEBSPACE) .type(MANAGED_WEBSPACE)
.project(project)
.caption("test Managed-Webspace") .caption("test Managed-Webspace")
.resources(ofEntries( .resources(ofEntries(
entry("SSD", 100), entry("SSD", 100),
@ -167,10 +166,10 @@ class HsManagedServerBookingItemValidatorUnitTest {
// then // then
assertThat(result).containsExactlyInAnyOrder( assertThat(result).containsExactlyInAnyOrder(
"'D-12345:Test-Project:test Managed-Webspace.resources.Multi=1 allows at maximum 25 unix users, but 26 found", "Multi=1 allows at maximum 25 unix users, but 26 found",
"'D-12345:Test-Project:test Managed-Webspace.resources.Multi=1 allows at maximum 5 database users, but 6 found", "Multi=1 allows at maximum 5 database users, but 6 found",
"'D-12345:Test-Project:test Managed-Webspace.resources.Multi=1 allows at maximum 5 databases, but 9 found", "Multi=1 allows at maximum 5 databases, but 9 found",
"'D-12345:Test-Project:test Managed-Webspace.resources.Multi=1 allows at maximum 250 databases, but 260 found" "Multi=1 allows at maximum 250 databases, but 260 found"
); );
} }

View File

@ -1,8 +1,6 @@
package net.hostsharing.hsadminng.hs.booking.item.validators; package net.hostsharing.hsadminng.hs.booking.item.validators;
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 org.junit.jupiter.api.Test; import org.junit.jupiter.api.Test;
import static java.util.List.of; import static java.util.List.of;
@ -15,14 +13,6 @@ import static org.assertj.core.api.Assertions.assertThat;
class HsPrivateCloudBookingItemValidatorTest { class HsPrivateCloudBookingItemValidatorTest {
final HsBookingDebitorEntity debitor = HsBookingDebitorEntity.builder()
.debitorNumber(12345)
.build();
final HsBookingProjectEntity project = HsBookingProjectEntity.builder()
.debitor(debitor)
.caption("Test-Project")
.build();
@Test @Test
void validatesPropertyTotals() { void validatesPropertyTotals() {
// given // given
@ -67,7 +57,6 @@ class HsPrivateCloudBookingItemValidatorTest {
void validatesExceedingPropertyTotals() { void validatesExceedingPropertyTotals() {
// given // given
final var privateCloudBookingItemEntity = HsBookingItemEntity.builder() final var privateCloudBookingItemEntity = HsBookingItemEntity.builder()
.project(project)
.type(PRIVATE_CLOUD) .type(PRIVATE_CLOUD)
.resources(ofEntries( .resources(ofEntries(
entry("CPUs", 4), entry("CPUs", 4),
@ -102,10 +91,10 @@ class HsPrivateCloudBookingItemValidatorTest {
// then // then
assertThat(result).containsExactlyInAnyOrder( assertThat(result).containsExactlyInAnyOrder(
"'D-12345:Test-Project:null.resources.CPUs' maximum total is 4, but actual total CPUs 5", "CPUs' total is 5, thus exceeds max total CPUs 4",
"'D-12345:Test-Project:null.resources.RAM' maximum total is 20 GB, but actual total RAM 30 GB", "RAM' total is 30 GB, thus exceeds max total RAM 20 GB",
"'D-12345:Test-Project:null.resources.SSD' maximum total is 100 GB, but actual total SSD 150 GB", "SSD' total is 150 GB, thus exceeds max total SSD 100 GB",
"'D-12345:Test-Project:null.resources.Traffic' maximum total is 5000 GB, but actual total Traffic 5500 GB" "Traffic' total is 5500 GB, thus exceeds max total Traffic 5000 GB"
); );
} }

View File

@ -199,10 +199,10 @@ class HsHostingAssetControllerAcceptanceTest extends ContextBasedTestWithCleanup
.extract().header("Location"); // @formatter:on .extract().header("Location"); // @formatter:on
// finally, the new asset can be accessed under the generated UUID // finally, the new asset can be accessed under the generated UUID
final var newWebspace = UUID.fromString( final var newUserUuid = UUID.fromString(
location.substring(location.lastIndexOf('/') + 1)); location.substring(location.lastIndexOf('/') + 1));
assertThat(newWebspace).isNotNull(); assertThat(newUserUuid).isNotNull();
toCleanup(HsHostingAssetEntity.class, newWebspace); toCleanup(HsHostingAssetEntity.class, newUserUuid);
} }
@Test @Test
@ -334,7 +334,7 @@ class HsHostingAssetControllerAcceptanceTest extends ContextBasedTestWithCleanup
{ {
"statusPhrase": "Bad Request", "statusPhrase": "Bad Request",
"message": "[ "message": "[
<<<'D-1000111:D-1000111 default project:separate ManagedWebspace.resources.Multi=1 allows at maximum 25 unix users, but 26 found <<<'MANAGED_WEBSPACE:fir01.bookingItem.Multi=1 allows at maximum 25 unix users, but 26 found
<<<]" <<<]"
} }
""".replaceAll(" +<<<", ""))); // @formatter:on """.replaceAll(" +<<<", ""))); // @formatter:on

View File

@ -25,8 +25,8 @@ class HsHostingAssetEntityValidatorUnitTest {
// then // then
assertThat(result).isInstanceOf(ValidationException.class) assertThat(result).isInstanceOf(ValidationException.class)
.hasMessageContaining( .hasMessageContaining(
"'MANAGED_SERVER:vm1234.config.monit_max_ssd_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_cpu_usage is required but missing",
"'MANAGED_SERVER:vm1234.config.monit_max_ram_usage' is required but missing"); "MANAGED_SERVER:vm1234.config.monit_max_ram_usage is required but missing");
} }
} }