Compare commits

...

11 Commits

66 changed files with 1595 additions and 679 deletions

View File

@ -9,7 +9,7 @@ import org.springframework.web.context.request.WebRequest;
import java.time.LocalDateTime; import java.time.LocalDateTime;
@Getter @Getter
class CustomErrorResponse { public class CustomErrorResponse {
static ResponseEntity<CustomErrorResponse> errorResponse( static ResponseEntity<CustomErrorResponse> errorResponse(
final WebRequest request, final WebRequest request,

View File

@ -1,5 +1,6 @@
package net.hostsharing.hsadminng.errors; package net.hostsharing.hsadminng.errors;
import net.hostsharing.hsadminng.hs.validation.MultiValidationException;
import org.iban4j.Iban4jException; import org.iban4j.Iban4jException;
import org.springframework.core.NestedExceptionUtils; import org.springframework.core.NestedExceptionUtils;
import org.springframework.dao.DataIntegrityViolationException; import org.springframework.dao.DataIntegrityViolationException;
@ -75,7 +76,8 @@ public class RestResponseEntityExceptionHandler
@ExceptionHandler({ Iban4jException.class, ValidationException.class }) @ExceptionHandler({ Iban4jException.class, ValidationException.class })
protected ResponseEntity<CustomErrorResponse> handleIbanAndBicExceptions( protected ResponseEntity<CustomErrorResponse> handleIbanAndBicExceptions(
final Throwable exc, final WebRequest request) { final Throwable exc, final WebRequest request) {
final var message = line(NestedExceptionUtils.getMostSpecificCause(exc).getMessage(), 0); final String fullMessage = NestedExceptionUtils.getMostSpecificCause(exc).getMessage();
final var message = exc instanceof MultiValidationException ? fullMessage : line(fullMessage, 0);
return errorResponse(request, HttpStatus.BAD_REQUEST, message); return errorResponse(request, HttpStatus.BAD_REQUEST, message);
} }

View File

@ -13,11 +13,13 @@ import org.springframework.transaction.annotation.Transactional;
import org.springframework.web.bind.annotation.RestController; import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.servlet.mvc.method.annotation.MvcUriComponentsBuilder; import org.springframework.web.servlet.mvc.method.annotation.MvcUriComponentsBuilder;
import jakarta.persistence.EntityManager;
import jakarta.persistence.PersistenceContext;
import java.util.List; 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.booking.item.validators.HsBookingItemEntityValidators.valid; import static net.hostsharing.hsadminng.hs.booking.item.validators.HsBookingItemEntityValidator.validated;
import static net.hostsharing.hsadminng.mapper.PostgresDateRange.toPostgresDateRange; import static net.hostsharing.hsadminng.mapper.PostgresDateRange.toPostgresDateRange;
@RestController @RestController
@ -32,6 +34,9 @@ public class HsBookingItemController implements HsBookingItemsApi {
@Autowired @Autowired
private HsBookingItemRepository bookingItemRepo; private HsBookingItemRepository bookingItemRepo;
@PersistenceContext
private EntityManager em;
@Override @Override
@Transactional(readOnly = true) @Transactional(readOnly = true)
public ResponseEntity<List<HsBookingItemResource>> listBookingItemsByProjectUuid( public ResponseEntity<List<HsBookingItemResource>> listBookingItemsByProjectUuid(
@ -57,7 +62,7 @@ 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 = bookingItemRepo.save(valid(entityToSave)); final var saved = validated(bookingItemRepo.save(entityToSave));
final var uri = final var uri =
MvcUriComponentsBuilder.fromController(getClass()) MvcUriComponentsBuilder.fromController(getClass())
@ -78,6 +83,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));
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)))
@ -112,7 +118,7 @@ public class HsBookingItemController implements HsBookingItemsApi {
new HsBookingItemEntityPatcher(current).apply(body); new HsBookingItemEntityPatcher(current).apply(body);
final var saved = bookingItemRepo.save(valid(current)); final var saved = bookingItemRepo.save(validated(current));
final var mapped = mapper.map(saved, HsBookingItemResource.class, ENTITY_TO_RESOURCE_POSTMAPPER); final var mapped = mapper.map(saved, HsBookingItemResource.class, ENTITY_TO_RESOURCE_POSTMAPPER);
return ResponseEntity.ok(mapped); return ResponseEntity.ok(mapped);
} }

View File

@ -10,7 +10,7 @@ import lombok.Getter;
import lombok.NoArgsConstructor; import lombok.NoArgsConstructor;
import lombok.Setter; import lombok.Setter;
import net.hostsharing.hsadminng.hs.booking.project.HsBookingProjectEntity; import net.hostsharing.hsadminng.hs.booking.project.HsBookingProjectEntity;
import net.hostsharing.hsadminng.hs.validation.Validatable; import net.hostsharing.hsadminng.hs.hosting.asset.HsHostingAssetEntity;
import net.hostsharing.hsadminng.mapper.PatchableMapWrapper; import net.hostsharing.hsadminng.mapper.PatchableMapWrapper;
import net.hostsharing.hsadminng.rbac.rbacdef.RbacView; import net.hostsharing.hsadminng.rbac.rbacdef.RbacView;
import net.hostsharing.hsadminng.rbac.rbacdef.RbacView.SQL; import net.hostsharing.hsadminng.rbac.rbacdef.RbacView.SQL;
@ -19,6 +19,7 @@ import net.hostsharing.hsadminng.stringify.Stringify;
import net.hostsharing.hsadminng.stringify.Stringifyable; import net.hostsharing.hsadminng.stringify.Stringifyable;
import org.hibernate.annotations.Type; import org.hibernate.annotations.Type;
import jakarta.persistence.CascadeType;
import jakarta.persistence.Column; import jakarta.persistence.Column;
import jakarta.persistence.Entity; import jakarta.persistence.Entity;
import jakarta.persistence.EnumType; import jakarta.persistence.EnumType;
@ -27,12 +28,14 @@ import jakarta.persistence.GeneratedValue;
import jakarta.persistence.Id; import jakarta.persistence.Id;
import jakarta.persistence.JoinColumn; import jakarta.persistence.JoinColumn;
import jakarta.persistence.ManyToOne; import jakarta.persistence.ManyToOne;
import jakarta.persistence.OneToMany;
import jakarta.persistence.Table; import jakarta.persistence.Table;
import jakarta.persistence.Transient; import jakarta.persistence.Transient;
import jakarta.persistence.Version; import jakarta.persistence.Version;
import java.io.IOException; import java.io.IOException;
import java.time.LocalDate; import java.time.LocalDate;
import java.util.HashMap; import java.util.HashMap;
import java.util.List;
import java.util.Map; import java.util.Map;
import java.util.UUID; import java.util.UUID;
@ -62,7 +65,7 @@ import static net.hostsharing.hsadminng.stringify.Stringify.stringify;
@Setter @Setter
@NoArgsConstructor @NoArgsConstructor
@AllArgsConstructor @AllArgsConstructor
public class HsBookingItemEntity implements Stringifyable, RbacObject, Validatable<HsBookingItemEntity, HsBookingItemType> { public class HsBookingItemEntity implements Stringifyable, RbacObject {
private static Stringify<HsBookingItemEntity> stringify = stringify(HsBookingItemEntity.class) private static Stringify<HsBookingItemEntity> stringify = stringify(HsBookingItemEntity.class)
.withProp(HsBookingItemEntity::getProject) .withProp(HsBookingItemEntity::getProject)
@ -85,6 +88,7 @@ public class HsBookingItemEntity implements Stringifyable, RbacObject, Validatab
@ManyToOne @ManyToOne
@JoinColumn(name = "parentitemuuid") @JoinColumn(name = "parentitemuuid")
// @Transient
private HsBookingItemEntity parentItem; private HsBookingItemEntity parentItem;
@Column(name = "type") @Column(name = "type")
@ -105,6 +109,14 @@ public class HsBookingItemEntity implements Stringifyable, RbacObject, Validatab
@Column(columnDefinition = "resources") @Column(columnDefinition = "resources")
private Map<String, Object> resources = new HashMap<>(); private Map<String, Object> resources = new HashMap<>();
@OneToMany(cascade = CascadeType.REFRESH, orphanRemoval = true)
@JoinColumn(name="parentitemuuid", referencedColumnName="uuid")
private List<HsBookingItemEntity> subBookingItems;
@OneToMany(cascade = CascadeType.REFRESH, orphanRemoval = true)
@JoinColumn(name="bookingitemuuid", referencedColumnName="uuid")
private List<HsHostingAssetEntity> subHostingAssets;
@Transient @Transient
private PatchableMapWrapper<Object> resourcesWrapper; private PatchableMapWrapper<Object> resourcesWrapper;
@ -150,16 +162,6 @@ public class HsBookingItemEntity implements Stringifyable, RbacObject, Validatab
return parentItem == null ? null : parentItem.relatedProject(); return parentItem == null ? null : parentItem.relatedProject();
} }
@Override
public String getPropertiesName() {
return "resources";
}
@Override
public Map<String, Object> getProperties() {
return resources;
}
public HsBookingProjectEntity getRelatedProject() { public HsBookingProjectEntity getRelatedProject() {
return project != null ? project : parentItem.getRelatedProject(); return project != null ? project : parentItem.getRelatedProject();
} }

View File

@ -0,0 +1,83 @@
package net.hostsharing.hsadminng.hs.booking.item.validators;
import net.hostsharing.hsadminng.hs.booking.item.HsBookingItemEntity;
import net.hostsharing.hsadminng.hs.booking.item.HsBookingItemType;
import net.hostsharing.hsadminng.hs.validation.HsEntityValidator;
import net.hostsharing.hsadminng.hs.validation.ValidatableProperty;
import java.util.Collection;
import java.util.List;
import java.util.Objects;
import java.util.Set;
import java.util.stream.Stream;
import static java.util.Arrays.stream;
import static java.util.Collections.emptyList;
import static java.util.Optional.ofNullable;
public class HsBookingItemEntityValidator extends HsEntityValidator<HsBookingItemEntity> {
public static Set<Enum<HsBookingItemType>> types() {
return HsBookingItemEntityValidatorRegistry.types();
}
public static List<String> doValidate(final HsBookingItemEntity bookingItem) {
return HsBookingItemEntityValidatorRegistry.doValidate(bookingItem);
}
public static HsBookingItemEntity validated(final HsBookingItemEntity entityToSave) {
return HsBookingItemEntityValidatorRegistry.validated(entityToSave);
}
public HsBookingItemEntityValidator(final ValidatableProperty<?>... properties) {
super(properties);
}
public List<String> validate(final HsBookingItemEntity bookingItem) {
return sequentiallyValidate(
() -> enrich(prefix(bookingItem.toShortString(), "resources"), validateProperties(bookingItem.getResources())),
() -> enrich(prefix(bookingItem.toShortString(), "parentItem"), optionallyValidate(bookingItem.getParentItem())),
() -> validateAgainstSubEntities(bookingItem)
);
}
private static List<String> optionallyValidate(final HsBookingItemEntity bookingItem) {
return bookingItem != null ? HsBookingItemEntityValidator.doValidate(bookingItem) : emptyList();
}
protected List<String> validateAgainstSubEntities(final HsBookingItemEntity bookingItem) {
return Stream.concat(
stream(propertyValidators)
.map(propDef -> propDef.validateTotals(bookingItem))
.flatMap(Collection::stream),
stream(propertyValidators)
.filter(ValidatableProperty::isTotalsValidator)
.map(prop -> validateMaxTotalValue(bookingItem, prop))
).filter(Objects::nonNull).toList();
}
// FIXME: convert into generic shape like multi-options validator
private static String validateMaxTotalValue(
final HsBookingItemEntity bookingItem,
final ValidatableProperty<?> propDef) {
final var propName = propDef.propertyName();
final var propUnit = ofNullable(propDef.unit()).map(u -> " " + u).orElse("");
final var totalValue = ofNullable(bookingItem.getSubBookingItems()).orElse(emptyList())
.stream()
.map(subItem -> propDef.getValue(subItem.getResources()))
.map(HsBookingItemEntityValidator::toNonNullInteger)
.reduce(0, Integer::sum);
final var maxValue = getNonNullIntegerValue(propDef, bookingItem.getResources());
if (propDef.thresholdPercentage() != null ) {
return totalValue > (maxValue * propDef.thresholdPercentage() / 100)
? "total %s is %d%s exceeds max total %s %d%s, which is above threshold of %d%%"
.formatted(propName, totalValue, propUnit, propName, maxValue, propUnit, propDef.thresholdPercentage())
: null;
} else {
return totalValue > maxValue
? "total %s is %d%s exceeds max total %s %d%s"
.formatted(propName, totalValue, propUnit, propName, maxValue, propUnit)
: null;
}
}
}

View File

@ -1,50 +1,61 @@
package net.hostsharing.hsadminng.hs.booking.item.validators; package net.hostsharing.hsadminng.hs.booking.item.validators;
import lombok.experimental.UtilityClass;
import net.hostsharing.hsadminng.hs.booking.item.HsBookingItemEntity; import net.hostsharing.hsadminng.hs.booking.item.HsBookingItemEntity;
import net.hostsharing.hsadminng.hs.booking.item.HsBookingItemType; import net.hostsharing.hsadminng.hs.booking.item.HsBookingItemType;
import net.hostsharing.hsadminng.hs.validation.HsEntityValidator; import net.hostsharing.hsadminng.hs.validation.HsEntityValidator;
import net.hostsharing.hsadminng.hs.validation.MultiValidationException;
import net.hostsharing.hsadminng.hs.validation.ValidatableProperty;
import jakarta.validation.ValidationException; import java.util.Collection;
import java.util.HashMap; import java.util.HashMap;
import java.util.List;
import java.util.Map; import java.util.Map;
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.Optional.ofNullable;
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.MANAGED_WEBSPACE;
import static net.hostsharing.hsadminng.hs.booking.item.HsBookingItemType.PRIVATE_CLOUD;
@UtilityClass public class HsBookingItemEntityValidatorRegistry {
public class HsBookingItemEntityValidators {
private static final Map<Enum<HsBookingItemType>, HsEntityValidator<HsBookingItemEntity, HsBookingItemType>> validators = new HashMap<>(); private static final Map<Enum<HsBookingItemType>, HsEntityValidator<HsBookingItemEntity>> validators = new HashMap<>();
static { static {
register(PRIVATE_CLOUD, new HsPrivateCloudBookingItemValidator());
register(CLOUD_SERVER, new HsCloudServerBookingItemValidator()); register(CLOUD_SERVER, new HsCloudServerBookingItemValidator());
register(MANAGED_SERVER, new HsManagedServerBookingItemValidator()); register(MANAGED_SERVER, new HsManagedServerBookingItemValidator());
register(MANAGED_WEBSPACE, new HsManagedWebspaceBookingItemValidator()); register(MANAGED_WEBSPACE, new HsManagedWebspaceBookingItemValidator());
} }
private static void register(final Enum<HsBookingItemType> type, final HsEntityValidator<HsBookingItemEntity, HsBookingItemType> validator) { private static void register(final Enum<HsBookingItemType> type, final HsEntityValidator<HsBookingItemEntity> validator) {
stream(validator.propertyValidators).forEach( entry -> { stream(validator.propertyValidators).forEach( entry -> {
entry.verifyConsistency(Map.entry(type, validator)); entry.verifyConsistency(Map.entry(type, validator));
}); });
validators.put(type, validator); validators.put(type, validator);
} }
public static HsEntityValidator<HsBookingItemEntity, HsBookingItemType> forType(final Enum<HsBookingItemType> type) { public static HsEntityValidator<HsBookingItemEntity> forType(final Enum<HsBookingItemType> type) {
if ( validators.containsKey(type)) {
return validators.get(type); return validators.get(type);
} }
throw new IllegalArgumentException("no validator found for type " + type);
}
public static Set<Enum<HsBookingItemType>> types() { public static Set<Enum<HsBookingItemType>> types() {
return validators.keySet(); return validators.keySet();
} }
public static HsBookingItemEntity valid(final HsBookingItemEntity entityToSave) { public static List<String> doValidate(final HsBookingItemEntity bookingItem) {
final var violations = HsBookingItemEntityValidators.forType(entityToSave.getType()).validate(entityToSave); return HsBookingItemEntityValidatorRegistry.forType(bookingItem.getType()).validate(bookingItem);
if (!violations.isEmpty()) {
throw new ValidationException(violations.toString());
} }
public static HsBookingItemEntity validated(final HsBookingItemEntity entityToSave) {
MultiValidationException.throwInvalid(doValidate(entityToSave));
return entityToSave; return entityToSave;
} }
} }

View File

@ -1,20 +1,20 @@
package net.hostsharing.hsadminng.hs.booking.item.validators; package net.hostsharing.hsadminng.hs.booking.item.validators;
import net.hostsharing.hsadminng.hs.booking.item.HsBookingItemEntity; import net.hostsharing.hsadminng.hs.booking.item.HsBookingItemEntity;
import net.hostsharing.hsadminng.hs.booking.item.HsBookingItemType;
import net.hostsharing.hsadminng.hs.validation.HsEntityValidator;
import static net.hostsharing.hsadminng.hs.validation.EnumerationPropertyValidator.enumerationProperty; import java.util.List;
import static net.hostsharing.hsadminng.hs.validation.IntegerPropertyValidator.integerProperty;
class HsCloudServerBookingItemValidator extends HsEntityValidator<HsBookingItemEntity, HsBookingItemType> { import static net.hostsharing.hsadminng.hs.validation.EnumerationProperty.enumerationProperty;
import static net.hostsharing.hsadminng.hs.validation.IntegerProperty.integerProperty;
class HsCloudServerBookingItemValidator extends HsBookingItemEntityValidator {
HsCloudServerBookingItemValidator() { HsCloudServerBookingItemValidator() {
super( super(
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(),
integerProperty("HDD").unit("GB").min(0).max(4000).step(250).optional(), 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()
); );

View File

@ -1,24 +1,22 @@
package net.hostsharing.hsadminng.hs.booking.item.validators; package net.hostsharing.hsadminng.hs.booking.item.validators;
import net.hostsharing.hsadminng.hs.booking.item.HsBookingItemEntity;
import net.hostsharing.hsadminng.hs.booking.item.HsBookingItemType;
import net.hostsharing.hsadminng.hs.validation.HsEntityValidator;
import static net.hostsharing.hsadminng.hs.validation.BooleanPropertyValidator.booleanProperty;
import static net.hostsharing.hsadminng.hs.validation.EnumerationPropertyValidator.enumerationProperty;
import static net.hostsharing.hsadminng.hs.validation.IntegerPropertyValidator.integerProperty;
class HsManagedServerBookingItemValidator extends HsEntityValidator<HsBookingItemEntity, HsBookingItemType> { import static net.hostsharing.hsadminng.hs.validation.BooleanProperty.booleanProperty;
import static net.hostsharing.hsadminng.hs.validation.EnumerationProperty.enumerationProperty;
import static net.hostsharing.hsadminng.hs.validation.IntegerProperty.integerProperty;
class HsManagedServerBookingItemValidator extends HsBookingItemEntityValidator {
HsManagedServerBookingItemValidator() { HsManagedServerBookingItemValidator() {
super( super(
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().asTotalLimit().withThreshold(200),
integerProperty("HDD").unit("GB").min(0).max(4000).step(250).optional(), integerProperty("HDD").unit("GB").min(0).max(4000).step(250).withDefault(0).asTotalLimit().withThreshold(200),
integerProperty("Traffic").unit("GB").min(250).max(10000).step(250).required(), integerProperty("Traffic").unit("GB").min(250).max(10000).step(250).required().asTotalLimit().withThreshold(200),
enumerationProperty("SLA-Platform").values("BASIC", "EXT8H", "EXT4H", "EXT2H").optional(), enumerationProperty("SLA-Platform").values("BASIC", "EXT8H", "EXT4H", "EXT2H").withDefault("BASIC"),
booleanProperty("SLA-EMail").falseIf("SLA-Platform", "BASIC").optional(), booleanProperty("SLA-EMail").falseIf("SLA-Platform", "BASIC").withDefault(false),
booleanProperty("SLA-Maria").falseIf("SLA-Platform", "BASIC").optional(), booleanProperty("SLA-Maria").falseIf("SLA-Platform", "BASIC").optional(),
booleanProperty("SLA-PgSQL").falseIf("SLA-Platform", "BASIC").optional(), booleanProperty("SLA-PgSQL").falseIf("SLA-Platform", "BASIC").optional(),
booleanProperty("SLA-Office").falseIf("SLA-Platform", "BASIC").optional(), booleanProperty("SLA-Office").falseIf("SLA-Platform", "BASIC").optional(),

View File

@ -1,24 +1,98 @@
package net.hostsharing.hsadminng.hs.booking.item.validators; package net.hostsharing.hsadminng.hs.booking.item.validators;
import net.hostsharing.hsadminng.hs.booking.item.HsBookingItemEntity; import net.hostsharing.hsadminng.hs.booking.item.HsBookingItemEntity;
import net.hostsharing.hsadminng.hs.booking.item.HsBookingItemType; import net.hostsharing.hsadminng.hs.validation.IntegerProperty;
import net.hostsharing.hsadminng.hs.validation.HsEntityValidator; import org.apache.commons.lang3.function.TriFunction;
import java.util.List;
import static net.hostsharing.hsadminng.hs.validation.BooleanPropertyValidator.booleanProperty; import static java.util.Collections.emptyList;
import static net.hostsharing.hsadminng.hs.validation.EnumerationPropertyValidator.enumerationProperty; import static net.hostsharing.hsadminng.hs.hosting.asset.HsHostingAssetType.DOMAIN_EMAIL_SETUP;
import static net.hostsharing.hsadminng.hs.validation.IntegerPropertyValidator.integerProperty; import static net.hostsharing.hsadminng.hs.hosting.asset.HsHostingAssetType.EMAIL_ADDRESS;
import static net.hostsharing.hsadminng.hs.hosting.asset.HsHostingAssetType.MARIADB_DATABASE;
import static net.hostsharing.hsadminng.hs.hosting.asset.HsHostingAssetType.MARIADB_USER;
import static net.hostsharing.hsadminng.hs.hosting.asset.HsHostingAssetType.PGSQL_DATABASE;
import static net.hostsharing.hsadminng.hs.hosting.asset.HsHostingAssetType.PGSQL_USER;
import static net.hostsharing.hsadminng.hs.hosting.asset.HsHostingAssetType.UNIX_USER;
import static net.hostsharing.hsadminng.hs.validation.BooleanProperty.booleanProperty;
import static net.hostsharing.hsadminng.hs.validation.EnumerationProperty.enumerationProperty;
import static net.hostsharing.hsadminng.hs.validation.IntegerProperty.integerProperty;
class HsManagedWebspaceBookingItemValidator extends HsEntityValidator<HsBookingItemEntity, HsBookingItemType> { class HsManagedWebspaceBookingItemValidator extends HsBookingItemEntityValidator {
public HsManagedWebspaceBookingItemValidator() { public HsManagedWebspaceBookingItemValidator() {
super( super(
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(),
enumerationProperty("SLA-Platform").values("BASIC", "EXT24H").optional(), integerProperty("Multi").min(1).max(100).step(1).withDefault(1)
integerProperty("Daemons").min(0).max(10).optional(), .eachComprising( 25, unixUsers())
booleanProperty("Online Office Server").optional() .eachComprising( 5, databaseUsers())
.eachComprising( 5, databases())
.eachComprising(250, eMailAddresses()),
integerProperty("Daemons").min(0).max(10).withDefault(0),
booleanProperty("Online Office Server").optional(),
enumerationProperty("SLA-Platform").values("BASIC", "EXT24H").withDefault("BASIC")
); );
} }
private static TriFunction<HsBookingItemEntity, IntegerProperty, Integer, List<String>> unixUsers() {
return (final HsBookingItemEntity entity, final IntegerProperty prop, final Integer factor) -> {
final var unixUserCount = entity.getSubHostingAssets().stream()
.flatMap(ha -> ha.getSubHostingAssets().stream())
.filter(ha -> ha.getType() == UNIX_USER)
.count();
final long limitingValue = prop.getValue(entity.getResources());
if (unixUserCount > factor*limitingValue) {
return List.of(prop.propertyName() + "=" + limitingValue + " allows at maximum " + limitingValue*factor + " unix users, but " + unixUserCount + " found");
}
return emptyList();
};
}
private static TriFunction<HsBookingItemEntity, IntegerProperty, Integer, List<String>> databaseUsers() {
return (final HsBookingItemEntity entity, final IntegerProperty prop, final Integer factor) -> {
final var unixUserCount = entity.getSubHostingAssets().stream()
.flatMap(ha -> ha.getSubHostingAssets().stream())
.filter(bi -> bi.getType() == PGSQL_USER || bi.getType() == MARIADB_USER )
.count();
final long limitingValue = prop.getValue(entity.getResources());
if (unixUserCount > factor*limitingValue) {
return List.of(prop.propertyName() + "=" + limitingValue + " allows at maximum " + limitingValue*factor + " database users, but " + unixUserCount + " found");
}
return emptyList();
};
}
private static TriFunction<HsBookingItemEntity, IntegerProperty, Integer, List<String>> databases() {
return (final HsBookingItemEntity entity, final IntegerProperty prop, final Integer factor) -> {
final var unixUserCount = entity.getSubHostingAssets().stream()
.flatMap(ha -> ha.getSubHostingAssets().stream())
.filter(bi -> bi.getType()==PGSQL_USER || bi.getType()==MARIADB_USER )
.flatMap(domainEMailSetup -> domainEMailSetup.getSubHostingAssets().stream()
.filter(ha -> ha.getType()==PGSQL_DATABASE || ha.getType()==MARIADB_DATABASE))
.count();
final long limitingValue = prop.getValue(entity.getResources());
if (unixUserCount > factor*limitingValue) {
return List.of(prop.propertyName() + "=" + limitingValue + " allows at maximum " + limitingValue*factor + " databases, but " + unixUserCount + " found");
}
return emptyList();
};
}
private static TriFunction<HsBookingItemEntity, IntegerProperty, Integer, List<String>> eMailAddresses() {
return (final HsBookingItemEntity entity, final IntegerProperty prop, final Integer factor) -> {
final var unixUserCount = entity.getSubHostingAssets().stream()
.flatMap(ha -> ha.getSubHostingAssets().stream())
.filter(bi -> bi.getType() == DOMAIN_EMAIL_SETUP)
.flatMap(domainEMailSetup -> domainEMailSetup.getSubHostingAssets().stream()
.filter(ha -> ha.getType()==EMAIL_ADDRESS))
.count();
final long limitingValue = prop.getValue(entity.getResources());
if (unixUserCount > factor*limitingValue) {
return List.of(prop.propertyName() + "=" + limitingValue + " allows at maximum " + limitingValue*factor + " databases, but " + unixUserCount + " found");
}
return emptyList();
};
}
} }

View File

@ -0,0 +1,18 @@
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(4).max(128).required().asTotalLimit(),
integerProperty("RAM").unit("GB").min(4).max(512).required().asTotalLimit(),
integerProperty("SSD").unit("GB").min(100).max(4000).step(25).required().asTotalLimit(),
integerProperty("HDD").unit("GB").min(0).max(16000).step(25).withDefault(0).asTotalLimit(),
integerProperty("Traffic").unit("GB").min(1000).max(40000).step(250).required().asTotalLimit(),
enumerationProperty("SLA-Infrastructure").values("BASIC", "EXT8H", "EXT4H", "EXT2H").withDefault("BASIC")
);
}
}

View File

@ -20,7 +20,7 @@ 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.HsHostingAssetEntityValidators.valid; import static net.hostsharing.hsadminng.hs.hosting.asset.validators.HsHostingAssetEntityValidator.validated;
@RestController @RestController
public class HsHostingAssetController implements HsHostingAssetsApi { public class HsHostingAssetController implements HsHostingAssetsApi {
@ -62,7 +62,7 @@ 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 = assetRepo.save(valid(entityToSave)); final var saved = validated(assetRepo.save(entityToSave));
final var uri = final var uri =
MvcUriComponentsBuilder.fromController(getClass()) MvcUriComponentsBuilder.fromController(getClass())
@ -117,7 +117,7 @@ public class HsHostingAssetController implements HsHostingAssetsApi {
new HsHostingAssetEntityPatcher(current).apply(body); new HsHostingAssetEntityPatcher(current).apply(body);
final var saved = assetRepo.save(valid(current)); final var saved = validated(assetRepo.save(current));
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

@ -8,7 +8,6 @@ import lombok.Getter;
import lombok.NoArgsConstructor; import lombok.NoArgsConstructor;
import lombok.Setter; import lombok.Setter;
import net.hostsharing.hsadminng.hs.booking.item.HsBookingItemEntity; import net.hostsharing.hsadminng.hs.booking.item.HsBookingItemEntity;
import net.hostsharing.hsadminng.hs.validation.Validatable;
import net.hostsharing.hsadminng.mapper.PatchableMapWrapper; import net.hostsharing.hsadminng.mapper.PatchableMapWrapper;
import net.hostsharing.hsadminng.rbac.rbacdef.RbacView; import net.hostsharing.hsadminng.rbac.rbacdef.RbacView;
import net.hostsharing.hsadminng.rbac.rbacdef.RbacView.SQL; import net.hostsharing.hsadminng.rbac.rbacdef.RbacView.SQL;
@ -17,6 +16,7 @@ import net.hostsharing.hsadminng.stringify.Stringify;
import net.hostsharing.hsadminng.stringify.Stringifyable; import net.hostsharing.hsadminng.stringify.Stringifyable;
import org.hibernate.annotations.Type; import org.hibernate.annotations.Type;
import jakarta.persistence.CascadeType;
import jakarta.persistence.Column; import jakarta.persistence.Column;
import jakarta.persistence.Entity; import jakarta.persistence.Entity;
import jakarta.persistence.EnumType; import jakarta.persistence.EnumType;
@ -25,11 +25,13 @@ import jakarta.persistence.GeneratedValue;
import jakarta.persistence.Id; import jakarta.persistence.Id;
import jakarta.persistence.JoinColumn; import jakarta.persistence.JoinColumn;
import jakarta.persistence.ManyToOne; import jakarta.persistence.ManyToOne;
import jakarta.persistence.OneToMany;
import jakarta.persistence.Table; import jakarta.persistence.Table;
import jakarta.persistence.Transient; import jakarta.persistence.Transient;
import jakarta.persistence.Version; import jakarta.persistence.Version;
import java.io.IOException; import java.io.IOException;
import java.util.HashMap; import java.util.HashMap;
import java.util.List;
import java.util.Map; import java.util.Map;
import java.util.UUID; import java.util.UUID;
@ -56,7 +58,7 @@ import static net.hostsharing.hsadminng.stringify.Stringify.stringify;
@Setter @Setter
@NoArgsConstructor @NoArgsConstructor
@AllArgsConstructor @AllArgsConstructor
public class HsHostingAssetEntity implements Stringifyable, RbacObject, Validatable<HsHostingAssetEntity, HsHostingAssetType> { public class HsHostingAssetEntity implements Stringifyable, RbacObject {
private static Stringify<HsHostingAssetEntity> stringify = stringify(HsHostingAssetEntity.class) private static Stringify<HsHostingAssetEntity> stringify = stringify(HsHostingAssetEntity.class)
.withProp(HsHostingAssetEntity::getType) .withProp(HsHostingAssetEntity::getType)
@ -91,6 +93,10 @@ public class HsHostingAssetEntity implements Stringifyable, RbacObject, Validata
@Enumerated(EnumType.STRING) @Enumerated(EnumType.STRING)
private HsHostingAssetType type; private HsHostingAssetType type;
@OneToMany(cascade = CascadeType.REFRESH, orphanRemoval = true)
@JoinColumn(name="parentassetuuid", referencedColumnName="uuid")
private List<HsHostingAssetEntity> subHostingAssets;
@Column(name = "identifier") @Column(name = "identifier")
private String identifier; // vm1234, xyz00, example.org, xyz00_abc private String identifier; // vm1234, xyz00, example.org, xyz00_abc
@ -114,16 +120,6 @@ public class HsHostingAssetEntity implements Stringifyable, RbacObject, Validata
PatchableMapWrapper.of(configWrapper, (newWrapper) -> {configWrapper = newWrapper; }, config).assign(newConfg); PatchableMapWrapper.of(configWrapper, (newWrapper) -> {configWrapper = newWrapper; }, config).assign(newConfg);
} }
@Override
public String getPropertiesName() {
return "config";
}
@Override
public Map<String, Object> getProperties() {
return config;
}
@Override @Override
public String toString() { public String toString() {
return stringify.apply(this); return stringify.apply(this);

View File

@ -1,6 +1,6 @@
package net.hostsharing.hsadminng.hs.hosting.asset; package net.hostsharing.hsadminng.hs.hosting.asset;
import net.hostsharing.hsadminng.hs.hosting.asset.validators.HsHostingAssetEntityValidators; import net.hostsharing.hsadminng.hs.hosting.asset.validators.HsHostingAssetEntityValidator;
import net.hostsharing.hsadminng.hs.hosting.generated.api.v1.api.HsHostingAssetPropsApi; import net.hostsharing.hsadminng.hs.hosting.generated.api.v1.api.HsHostingAssetPropsApi;
import net.hostsharing.hsadminng.hs.hosting.generated.api.v1.model.HsHostingAssetTypeResource; import net.hostsharing.hsadminng.hs.hosting.generated.api.v1.model.HsHostingAssetTypeResource;
import org.springframework.http.ResponseEntity; import org.springframework.http.ResponseEntity;
@ -15,7 +15,7 @@ public class HsHostingAssetPropsController implements HsHostingAssetPropsApi {
@Override @Override
public ResponseEntity<List<String>> listAssetTypes() { public ResponseEntity<List<String>> listAssetTypes() {
final var resource = HsHostingAssetEntityValidators.types().stream() final var resource = HsHostingAssetEntityValidator.types().stream()
.map(Enum::name) .map(Enum::name)
.toList(); .toList();
return ResponseEntity.ok(resource); return ResponseEntity.ok(resource);
@ -25,7 +25,7 @@ public class HsHostingAssetPropsController implements HsHostingAssetPropsApi {
public ResponseEntity<List<Object>> listAssetTypeProps( public ResponseEntity<List<Object>> listAssetTypeProps(
final HsHostingAssetTypeResource assetType) { final HsHostingAssetTypeResource assetType) {
final var propValidators = HsHostingAssetEntityValidators.forType(HsHostingAssetType.of(assetType)); final var propValidators = HsHostingAssetEntityValidator.forType(HsHostingAssetType.of(assetType));
final List<Map<String, Object>> resource = propValidators.properties(); final List<Map<String, Object>> resource = propValidators.properties();
return ResponseEntity.ok(toListOfObjects(resource)); return ResponseEntity.ok(toListOfObjects(resource));
} }

View File

@ -0,0 +1,106 @@
package net.hostsharing.hsadminng.hs.hosting.asset.validators;
import net.hostsharing.hsadminng.hs.booking.item.HsBookingItemEntity;
import net.hostsharing.hsadminng.hs.booking.item.validators.HsBookingItemEntityValidator;
import net.hostsharing.hsadminng.hs.hosting.asset.HsHostingAssetEntity;
import net.hostsharing.hsadminng.hs.hosting.asset.HsHostingAssetType;
import net.hostsharing.hsadminng.hs.validation.HsEntityValidator;
import net.hostsharing.hsadminng.hs.validation.MultiValidationException;
import net.hostsharing.hsadminng.hs.validation.ValidatableProperty;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.Set;
import static java.util.Arrays.stream;
import static java.util.Collections.emptyList;
import static java.util.Optional.ofNullable;
import static net.hostsharing.hsadminng.hs.hosting.asset.HsHostingAssetType.*;
public class HsHostingAssetEntityValidator extends HsEntityValidator<HsHostingAssetEntity> {
private static final Map<Enum<HsHostingAssetType>, HsEntityValidator<HsHostingAssetEntity>> validators = new HashMap<>();
static {
register(CLOUD_SERVER, new HsHostingAssetEntityValidator());
register(MANAGED_SERVER, new HsManagedServerHostingAssetValidator());
register(MANAGED_WEBSPACE, new HsManagedWebspaceHostingAssetValidator());
register(UNIX_USER, new HsHostingAssetEntityValidator());
}
private static void register(final Enum<HsHostingAssetType> type, final HsEntityValidator<HsHostingAssetEntity> validator) {
stream(validator.propertyValidators).forEach( entry -> {
entry.verifyConsistency(Map.entry(type, validator));
});
validators.put(type, validator);
}
public static HsEntityValidator<HsHostingAssetEntity> forType(final Enum<HsHostingAssetType> type) {
if ( validators.containsKey(type)) {
return validators.get(type);
}
throw new IllegalArgumentException("no validator found for type " + type);
}
public static Set<Enum<HsHostingAssetType>> types() {
return validators.keySet();
}
public static List<String> doValidate(final HsHostingAssetEntity hostingAsset) {
return HsHostingAssetEntityValidator.forType(hostingAsset.getType()).validate(hostingAsset);
}
public static HsHostingAssetEntity validated(final HsHostingAssetEntity entityToSave) {
MultiValidationException.throwInvalid(doValidate(entityToSave));
return entityToSave;
}
public HsHostingAssetEntityValidator(final ValidatableProperty<?>... properties) {
super(properties);
}
@Override
public List<String> validate(final HsHostingAssetEntity assetEntity) {
return sequentiallyValidate(
() -> enrich(prefix(assetEntity.toShortString(), "config"), validateProperties(assetEntity.getConfig())),
() -> enrich(prefix(assetEntity.toShortString(), "bookingItem"), optionallyValidate(assetEntity.getBookingItem())),
() -> enrich(prefix(assetEntity.toShortString(), "parentAsset"), optionallyValidate(assetEntity.getParentAsset())),
() -> validateSubEntities(assetEntity)
);
}
private static List<String> optionallyValidate(final HsHostingAssetEntity assetEntity) {
return assetEntity != null ? forType(assetEntity.getType()).validate(assetEntity) : emptyList();
}
private static List<String> optionallyValidate(final HsBookingItemEntity bookingItem) {
return bookingItem != null ? HsBookingItemEntityValidator.doValidate(bookingItem) : emptyList();
}
protected List<String> validateSubEntities(final HsHostingAssetEntity assetEntity) {
return stream(propertyValidators)
.filter(ValidatableProperty::isTotalsValidator)
.map(prop -> validateMaxTotalValue(assetEntity, prop))
.filter(Objects::nonNull)
.toList();
}
private String validateMaxTotalValue(
final HsHostingAssetEntity hostingAsset,
final ValidatableProperty<?> propDef) {
final var propName = propDef.propertyName();
final var propUnit = ofNullable(propDef.unit()).map(u -> " " + u).orElse("");
final var totalValue = ofNullable(hostingAsset.getSubHostingAssets()).orElse(emptyList())
.stream()
.map(subItem -> propDef.getValue(subItem.getConfig()))
.map(HsEntityValidator::toNonNullInteger)
.reduce(0, Integer::sum);
final var maxValue = getNonNullIntegerValue(propDef, hostingAsset.getConfig());
return totalValue > maxValue
? "total %s is %d%s exceeds max total %s %d%s".formatted(
propName, totalValue, propUnit, propName, maxValue, propUnit)
: null;
}
}

View File

@ -1,51 +0,0 @@
package net.hostsharing.hsadminng.hs.hosting.asset.validators;
import lombok.experimental.UtilityClass;
import net.hostsharing.hsadminng.hs.hosting.asset.HsHostingAssetEntity;
import net.hostsharing.hsadminng.hs.hosting.asset.HsHostingAssetType;
import net.hostsharing.hsadminng.hs.validation.HsEntityValidator;
import jakarta.validation.ValidationException;
import java.util.HashMap;
import java.util.Map;
import java.util.Set;
import static java.util.Arrays.stream;
import static net.hostsharing.hsadminng.hs.hosting.asset.HsHostingAssetType.CLOUD_SERVER;
import static net.hostsharing.hsadminng.hs.hosting.asset.HsHostingAssetType.MANAGED_SERVER;
import static net.hostsharing.hsadminng.hs.hosting.asset.HsHostingAssetType.MANAGED_WEBSPACE;
@UtilityClass
public class HsHostingAssetEntityValidators {
private static final Map<Enum<HsHostingAssetType>, HsEntityValidator<HsHostingAssetEntity, HsHostingAssetType>> validators = new HashMap<>();
static {
register(CLOUD_SERVER, new HsEntityValidator<>());
register(MANAGED_SERVER, new HsManagedServerHostingAssetValidator());
register(MANAGED_WEBSPACE, new HsManagedWebspaceHostingAssetValidator());
}
private static void register(final Enum<HsHostingAssetType> type, final HsEntityValidator<HsHostingAssetEntity, HsHostingAssetType> validator) {
stream(validator.propertyValidators).forEach( entry -> {
entry.verifyConsistency(Map.entry(type, validator));
});
validators.put(type, validator);
}
public static HsEntityValidator<HsHostingAssetEntity, HsHostingAssetType> forType(final Enum<HsHostingAssetType> type) {
return validators.get(type);
}
public static Set<Enum<HsHostingAssetType>> types() {
return validators.keySet();
}
public static HsHostingAssetEntity valid(final HsHostingAssetEntity entityToSave) {
final var violations = HsHostingAssetEntityValidators.forType(entityToSave.getType()).validate(entityToSave);
if (!violations.isEmpty()) {
throw new ValidationException(violations.toString());
}
return entityToSave;
}
}

View File

@ -1,12 +1,8 @@
package net.hostsharing.hsadminng.hs.hosting.asset.validators; package net.hostsharing.hsadminng.hs.hosting.asset.validators;
import net.hostsharing.hsadminng.hs.hosting.asset.HsHostingAssetEntity; import static net.hostsharing.hsadminng.hs.validation.IntegerProperty.integerProperty;
import net.hostsharing.hsadminng.hs.hosting.asset.HsHostingAssetType;
import net.hostsharing.hsadminng.hs.validation.HsEntityValidator;
import static net.hostsharing.hsadminng.hs.validation.IntegerPropertyValidator.integerProperty; class HsManagedServerHostingAssetValidator extends HsHostingAssetEntityValidator {
class HsManagedServerHostingAssetValidator extends HsEntityValidator<HsHostingAssetEntity, HsHostingAssetType> {
public HsManagedServerHostingAssetValidator() { public HsManagedServerHostingAssetValidator() {
super( super(

View File

@ -1,28 +1,29 @@
package net.hostsharing.hsadminng.hs.hosting.asset.validators; package net.hostsharing.hsadminng.hs.hosting.asset.validators;
import net.hostsharing.hsadminng.hs.hosting.asset.HsHostingAssetEntity; import net.hostsharing.hsadminng.hs.hosting.asset.HsHostingAssetEntity;
import net.hostsharing.hsadminng.hs.hosting.asset.HsHostingAssetType;
import net.hostsharing.hsadminng.hs.validation.HsEntityValidator;
import java.util.Collection;
import java.util.stream.Stream;
import java.util.Collections;
import java.util.List; import java.util.List;
import java.util.stream.Collectors;
class HsManagedWebspaceHostingAssetValidator extends HsHostingAssetEntityValidator {
class HsManagedWebspaceHostingAssetValidator extends HsEntityValidator<HsHostingAssetEntity, HsHostingAssetType> {
public HsManagedWebspaceHostingAssetValidator() { public HsManagedWebspaceHostingAssetValidator() {
} }
@Override @Override
public List<String> validate(final HsHostingAssetEntity assetEntity) { public List<String> validate(final HsHostingAssetEntity assetEntity) {
final var result = super.validate(assetEntity); return Stream.of(validateIdentifierPattern(assetEntity), super.validate(assetEntity))
validateIdentifierPattern(result, assetEntity); .flatMap(Collection::stream)
.collect(Collectors.toList());
return result;
} }
private static void validateIdentifierPattern(final List<String> result, final HsHostingAssetEntity assetEntity) { private static List<String> validateIdentifierPattern(final HsHostingAssetEntity assetEntity) {
final var expectedIdentifierPattern = "^" + assetEntity.getParentAsset().getBookingItem().getProject().getDebitor().getDefaultPrefix() + "[0-9][0-9]$"; final var expectedIdentifierPattern = "^" + assetEntity.getParentAsset().getBookingItem().getProject().getDebitor().getDefaultPrefix() + "[0-9][0-9]$";
if ( !assetEntity.getIdentifier().matches(expectedIdentifierPattern)) { if ( !assetEntity.getIdentifier().matches(expectedIdentifierPattern)) {
result.add("'identifier' expected to match '"+expectedIdentifierPattern+"', but is '" + assetEntity.getIdentifier() + "'"); return List.of("'identifier' expected to match '"+expectedIdentifierPattern+"', but is '" + assetEntity.getIdentifier() + "'");
} }
return Collections.emptyList();
} }
} }

View File

@ -3,6 +3,7 @@ package net.hostsharing.hsadminng.hs.office.coopassets;
import net.hostsharing.hsadminng.context.Context; import net.hostsharing.hsadminng.context.Context;
import net.hostsharing.hsadminng.hs.office.generated.api.v1.api.HsOfficeCoopAssetsApi; import net.hostsharing.hsadminng.hs.office.generated.api.v1.api.HsOfficeCoopAssetsApi;
import net.hostsharing.hsadminng.hs.office.generated.api.v1.model.*; import net.hostsharing.hsadminng.hs.office.generated.api.v1.model.*;
import net.hostsharing.hsadminng.hs.validation.MultiValidationException;
import net.hostsharing.hsadminng.mapper.Mapper; import net.hostsharing.hsadminng.mapper.Mapper;
import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.format.annotation.DateTimeFormat; import org.springframework.format.annotation.DateTimeFormat;
@ -97,9 +98,7 @@ public class HsOfficeCoopAssetsTransactionController implements HsOfficeCoopAsse
validateDebitTransaction(requestBody, violations); validateDebitTransaction(requestBody, violations);
validateCreditTransaction(requestBody, violations); validateCreditTransaction(requestBody, violations);
validateAssetValue(requestBody, violations); validateAssetValue(requestBody, violations);
if (violations.size() > 0) { MultiValidationException.throwInvalid(violations);
throw new ValidationException("[" + join(", ", violations) + "]");
}
} }
private static void validateDebitTransaction( private static void validateDebitTransaction(

View File

@ -5,6 +5,7 @@ import net.hostsharing.hsadminng.context.Context;
import net.hostsharing.hsadminng.hs.office.generated.api.v1.api.HsOfficeCoopSharesApi; import net.hostsharing.hsadminng.hs.office.generated.api.v1.api.HsOfficeCoopSharesApi;
import net.hostsharing.hsadminng.hs.office.generated.api.v1.model.HsOfficeCoopSharesTransactionInsertResource; import net.hostsharing.hsadminng.hs.office.generated.api.v1.model.HsOfficeCoopSharesTransactionInsertResource;
import net.hostsharing.hsadminng.hs.office.generated.api.v1.model.HsOfficeCoopSharesTransactionResource; import net.hostsharing.hsadminng.hs.office.generated.api.v1.model.HsOfficeCoopSharesTransactionResource;
import net.hostsharing.hsadminng.hs.validation.MultiValidationException;
import net.hostsharing.hsadminng.mapper.Mapper; import net.hostsharing.hsadminng.mapper.Mapper;
import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.format.annotation.DateTimeFormat; import org.springframework.format.annotation.DateTimeFormat;
@ -99,9 +100,7 @@ public class HsOfficeCoopSharesTransactionController implements HsOfficeCoopShar
validateSubscriptionTransaction(requestBody, violations); validateSubscriptionTransaction(requestBody, violations);
validateCancellationTransaction(requestBody, violations); validateCancellationTransaction(requestBody, violations);
validateshareCount(requestBody, violations); validateshareCount(requestBody, violations);
if (violations.size() > 0) { MultiValidationException.throwInvalid(violations);
throw new ValidationException("[" + join(", ", violations) + "]");
}
} }
private static void validateSubscriptionTransaction( private static void validateSubscriptionTransaction(

View File

@ -0,0 +1,46 @@
package net.hostsharing.hsadminng.hs.validation;
import lombok.Setter;
import net.hostsharing.hsadminng.mapper.Array;
import java.util.AbstractMap;
import java.util.ArrayList;
import java.util.Map;
import java.util.Objects;
@Setter
public class BooleanProperty extends ValidatableProperty<Boolean> {
private static final String[] KEY_ORDER = Array.join(ValidatableProperty.KEY_ORDER_HEAD, ValidatableProperty.KEY_ORDER_TAIL);
private Map.Entry<String, String> falseIf;
private BooleanProperty(final String propertyName) {
super(Boolean.class, propertyName, KEY_ORDER);
}
public static BooleanProperty booleanProperty(final String propertyName) {
return new BooleanProperty(propertyName);
}
public ValidatableProperty<Boolean> falseIf(final String refPropertyName, final String refPropertyValue) {
this.falseIf = new AbstractMap.SimpleImmutableEntry<>(refPropertyName, refPropertyValue);
return this;
}
@Override
protected void validate(final ArrayList<String> result, final Boolean propValue, final Map<String, Object> props) {
if (falseIf != null && propValue) {
final Object referencedValue = props.get(falseIf.getKey());
if (Objects.equals(referencedValue, falseIf.getValue())) {
result.add(propertyName + " is expected to be false because " +
falseIf.getKey() + "=" + referencedValue + " but is " + propValue);
}
}
}
@Override
protected String simpleTypeName() {
return "boolean";
}
}

View File

@ -1,42 +0,0 @@
package net.hostsharing.hsadminng.hs.validation;
import lombok.Setter;
import java.util.AbstractMap;
import java.util.ArrayList;
import java.util.Map;
import java.util.Objects;
@Setter
public class BooleanPropertyValidator extends HsPropertyValidator<Boolean> {
private Map.Entry<String, String> falseIf;
private BooleanPropertyValidator(final String propertyName) {
super(Boolean.class, propertyName);
}
public static BooleanPropertyValidator booleanProperty(final String propertyName) {
return new BooleanPropertyValidator(propertyName);
}
public HsPropertyValidator<Boolean> falseIf(final String refPropertyName, final String refPropertyValue) {
this.falseIf = new AbstractMap.SimpleImmutableEntry<>(refPropertyName, refPropertyValue);
return this;
}
@Override
protected void validate(final ArrayList<String> result, final String propertiesName, final Boolean propValue, final Map<String, Object> props) {
if (falseIf != null && !Objects.equals(props.get(falseIf.getKey()), falseIf.getValue())) {
if (propValue) {
result.add("'"+propertiesName+"." + propertyName + "' is expected to be false because " +
propertiesName+"." + falseIf.getKey()+ "=" + falseIf.getValue() + " but is " + propValue);
}
}
}
@Override
protected String simpleTypeName() {
return "boolean";
}
}

View File

@ -0,0 +1,44 @@
package net.hostsharing.hsadminng.hs.validation;
import lombok.Setter;
import net.hostsharing.hsadminng.mapper.Array;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Map;
@Setter
public class EnumerationProperty extends ValidatableProperty<String> {
private static final String[] KEY_ORDER = Array.join(
ValidatableProperty.KEY_ORDER_HEAD,
Array.of("values"),
ValidatableProperty.KEY_ORDER_TAIL);
private String[] values;
private EnumerationProperty(final String propertyName) {
super(String.class, propertyName, KEY_ORDER);
}
public static EnumerationProperty enumerationProperty(final String propertyName) {
return new EnumerationProperty(propertyName);
}
public ValidatableProperty<String> values(final String... values) {
this.values = values;
return this;
}
@Override
protected void validate(final ArrayList<String> result, final String propValue, final Map<String, Object> props) {
if (Arrays.stream(values).noneMatch(v -> v.equals(propValue))) {
result.add(propertyName + " is expected to be one of " + Arrays.toString(values) + " but is '" + propValue + "'");
}
}
@Override
protected String simpleTypeName() {
return "enumeration";
}
}

View File

@ -1,38 +0,0 @@
package net.hostsharing.hsadminng.hs.validation;
import lombok.Setter;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Map;
@Setter
public class EnumerationPropertyValidator extends HsPropertyValidator<String> {
private String[] values;
private EnumerationPropertyValidator(final String propertyName) {
super(String.class, propertyName);
}
public static EnumerationPropertyValidator enumerationProperty(final String propertyName) {
return new EnumerationPropertyValidator(propertyName);
}
public HsPropertyValidator<String> values(final String... values) {
this.values = values;
return this;
}
@Override
protected void validate(final ArrayList<String> result, final String propertiesName, final String propValue, final Map<String, Object> props) {
if (Arrays.stream(values).noneMatch(v -> v.equals(propValue))) {
result.add("'"+propertiesName+"." + propertyName + "' is expected to be one of " + Arrays.toString(values) + " but is '" + propValue + "'");
}
}
@Override
protected String simpleTypeName() {
return "enumeration";
}
}

View File

@ -1,49 +1,83 @@
package net.hostsharing.hsadminng.hs.validation; package net.hostsharing.hsadminng.hs.validation;
import com.fasterxml.jackson.annotation.JsonAutoDetect; import net.hostsharing.hsadminng.hs.booking.item.HsBookingItemEntity;
import com.fasterxml.jackson.annotation.PropertyAccessor; import net.hostsharing.hsadminng.hs.booking.item.validators.HsBookingItemEntityValidator;
import com.fasterxml.jackson.databind.ObjectMapper;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Arrays; import java.util.Arrays;
import java.util.List; import java.util.List;
import java.util.Map; import java.util.Map;
import java.util.function.Supplier;
import static java.util.Arrays.stream; import static java.util.Arrays.stream;
import static java.util.Collections.emptyList;
public class HsEntityValidator<E extends Validatable<E, T>, T extends Enum<T>> { public abstract class HsEntityValidator<E> {
public final HsPropertyValidator<?>[] propertyValidators; public final ValidatableProperty<?>[] propertyValidators;
public HsEntityValidator(final HsPropertyValidator<?>... validators) { public HsEntityValidator(final ValidatableProperty<?>... validators) {
propertyValidators = validators; propertyValidators = validators;
} }
public List<String> validate(final E assetEntity) { protected static List<String> enrich(final String prefix, final List<String> messages) {
return messages.stream()
.map(message -> prefix + "." + message)
.toList();
}
protected static String prefix(final String... parts) {
return String.join(".", parts);
}
public abstract List<String> validate(final E entity);
public final List<Map<String, Object>> properties() {
return Arrays.stream(propertyValidators)
.map(ValidatableProperty::toOrderedMap)
.toList();
}
protected ArrayList<String> validateProperties(final Map<String, Object> properties) {
final var result = new ArrayList<String>(); final var result = new ArrayList<String>();
assetEntity.getProperties().keySet().forEach( givenPropName -> { properties.keySet().forEach( givenPropName -> {
if (stream(propertyValidators).map(pv -> pv.propertyName).noneMatch(propName -> propName.equals(givenPropName))) { if (stream(propertyValidators).map(pv -> pv.propertyName).noneMatch(propName -> propName.equals(givenPropName))) {
result.add("'"+assetEntity.getPropertiesName()+"." + givenPropName + "' is not expected but is set to '" +assetEntity.getProperties().get(givenPropName) + "'"); result.add(givenPropName + " is not expected but is set to '" + properties.get(givenPropName) + "'");
} }
}); });
stream(propertyValidators).forEach(pv -> { stream(propertyValidators).forEach(pv -> {
result.addAll(pv.validate(assetEntity.getPropertiesName(), assetEntity.getProperties())); result.addAll(pv.validate(properties));
}); });
return result; return result;
} }
public List<Map<String, Object>> properties() { protected List<String> validateParentEntities(final HsBookingItemEntity bookingItem) {
final var mapper = new ObjectMapper(); return bookingItem.getParentItem() != null
mapper.setVisibility(PropertyAccessor.FIELD, JsonAutoDetect.Visibility.ANY); ? HsBookingItemEntityValidator.doValidate(bookingItem.getParentItem())
return Arrays.stream(propertyValidators) : emptyList();
.map(propertyValidator -> propertyValidator.toMap(mapper))
.map(HsEntityValidator::asKeyValueMap)
.toList();
} }
@SuppressWarnings({ "unchecked", "rawtypes" }) @SafeVarargs
private static Map<String, Object> asKeyValueMap(final Map map) { protected static List<String> sequentiallyValidate(final Supplier<List<String>>... validators) {
return (Map<String, Object>) map; return new ArrayList<>(stream(validators)
.map(Supplier::get)
.filter(violations -> !violations.isEmpty())
.findFirst()
.orElse(emptyList()));
} }
protected static Integer getNonNullIntegerValue(final ValidatableProperty<?> prop, final Map<String, Object> propValues) {
final var value = prop.getValue(propValues);
if (value instanceof Integer) {
return (Integer) value;
}
throw new IllegalArgumentException(prop.propertyName + " Integer value expected, but got " + value);
}
protected static Integer toNonNullInteger(final Object value) {
if (value instanceof Integer) {
return (Integer) value;
}
throw new IllegalArgumentException("Integer value expected, but got " + value);
}
} }

View File

@ -1,67 +0,0 @@
package net.hostsharing.hsadminng.hs.validation;
import com.fasterxml.jackson.databind.ObjectMapper;
import lombok.RequiredArgsConstructor;
import java.util.AbstractMap.SimpleImmutableEntry;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
@RequiredArgsConstructor
public abstract class HsPropertyValidator<T> {
final Class<T> type;
final String propertyName;
private Boolean required;
public static <K, V> Map.Entry<K, V> defType(K k, V v) {
return new SimpleImmutableEntry<>(k, v);
}
public HsPropertyValidator<T> required() {
required = Boolean.TRUE;
return this;
}
public HsPropertyValidator<T> optional() {
required = Boolean.FALSE;
return this;
}
public final List<String> validate(final String propertiesName, final Map<String, Object> props) {
final var result = new ArrayList<String>();
final var propValue = props.get(propertyName);
if (propValue == null) {
if (required) {
result.add("'"+propertiesName+"." + propertyName + "' is required but missing");
}
}
if (propValue != null){
if ( type.isInstance(propValue)) {
//noinspection unchecked
validate(result, propertiesName, (T) propValue, props);
} else {
result.add("'"+propertiesName+"." + propertyName + "' is expected to be of type " + type + ", " +
"but is of type '" + propValue.getClass().getSimpleName() + "'");
}
}
return result;
}
protected abstract void validate(final ArrayList<String> result, final String propertiesName, final T propValue, final Map<String, Object> props);
public void verifyConsistency(final Map.Entry<? extends Enum<?>, ?> typeDef) {
if (required == null ) {
throw new IllegalStateException(typeDef.getKey() + "[" + propertyName + "] not fully initialized, please call either .required() or .optional()" );
}
}
public Map<String, Object> toMap(final ObjectMapper mapper) {
final Map<String, Object> map = mapper.convertValue(this, Map.class);
map.put("type", simpleTypeName());
return map;
}
protected abstract String simpleTypeName();
}

View File

@ -0,0 +1,56 @@
package net.hostsharing.hsadminng.hs.validation;
import lombok.Setter;
import net.hostsharing.hsadminng.mapper.Array;
import java.util.ArrayList;
import java.util.Map;
@Setter
public class IntegerProperty extends ValidatableProperty<Integer> {
private final static String[] KEY_ORDER = Array.join(
ValidatableProperty.KEY_ORDER_HEAD,
Array.of("unit", "min", "max", "step"),
ValidatableProperty.KEY_ORDER_TAIL);
private String unit;
private Integer min;
private Integer max;
private Integer step;
public static IntegerProperty integerProperty(final String propertyName) {
return new IntegerProperty(propertyName);
}
private IntegerProperty(final String propertyName) {
super(Integer.class, propertyName, KEY_ORDER);
}
@Override
public String unit() {
return unit;
}
public Integer max() {
return max;
}
@Override
protected void validate(final ArrayList<String> result, final Integer propValue, final Map<String, Object> props) {
if (min != null && propValue < min) {
result.add(propertyName + " is expected to be >= " + min + " but is " + propValue);
}
if (max != null && propValue > max) {
result.add(propertyName + " is expected to be <= " + max + " but is " + propValue);
}
if (step != null && propValue % step != 0) {
result.add(propertyName + " is expected to be multiple of " + step + " but is " + propValue);
}
}
@Override
protected String simpleTypeName() {
return "integer";
}
}

View File

@ -1,42 +0,0 @@
package net.hostsharing.hsadminng.hs.validation;
import lombok.Setter;
import java.util.ArrayList;
import java.util.Map;
@Setter
public class IntegerPropertyValidator extends HsPropertyValidator<Integer> {
private String unit;
private Integer min;
private Integer max;
private Integer step;
public static IntegerPropertyValidator integerProperty(final String propertyName) {
return new IntegerPropertyValidator(propertyName);
}
private IntegerPropertyValidator(final String propertyName) {
super(Integer.class, propertyName);
}
@Override
protected void validate(final ArrayList<String> result, final String propertiesName, final Integer propValue, final Map<String, Object> props) {
if (min != null && propValue < min) {
result.add("'"+propertiesName+"." + propertyName + "' is expected to be >= " + min + " but is " + propValue);
}
if (max != null && propValue > max) {
result.add("'"+propertiesName+"." + propertyName + "' is expected to be <= " + max + " but is " + propValue);
}
if (step != null && propValue % step != 0) {
result.add("'"+propertiesName+"." + propertyName + "' is expected to be multiple of " + step + " but is " + propValue);
}
}
@Override
protected String simpleTypeName() {
return "integer";
}
}

View File

@ -0,0 +1,19 @@
package net.hostsharing.hsadminng.hs.validation;
import jakarta.validation.ValidationException;
import java.util.List;
import static java.lang.String.join;
public class MultiValidationException extends ValidationException {
private MultiValidationException(final List<String> violations) {
super("[\n" + join(",\n", violations) + "\n]");
}
public static void throwInvalid(final List<String> violations) {
if (!violations.isEmpty()) {
throw new MultiValidationException(violations);
}
}
}

View File

@ -1,13 +0,0 @@
package net.hostsharing.hsadminng.hs.validation;
import java.util.Map;
public interface Validatable<E, T extends Enum<T>> {
Enum<T> getType();
String getPropertiesName();
Map<String, Object> getProperties();
}

View File

@ -0,0 +1,172 @@
package net.hostsharing.hsadminng.hs.validation;
import com.fasterxml.jackson.annotation.JsonIgnore;
import lombok.RequiredArgsConstructor;
import lombok.SneakyThrows;
import net.hostsharing.hsadminng.hs.booking.item.HsBookingItemEntity;
import net.hostsharing.hsadminng.mapper.Array;
import org.apache.commons.lang3.function.TriFunction;
import java.util.ArrayList;
import java.util.Collection;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.Optional;
import java.util.function.Function;
import static java.lang.Boolean.FALSE;
import static java.lang.Boolean.TRUE;
import static java.util.Collections.emptyList;
@RequiredArgsConstructor
public abstract class ValidatableProperty<T> {
protected static final String[] KEY_ORDER_HEAD = Array.of("propertyName");
protected static final String[] KEY_ORDER_TAIL = Array.of("required", "defaultValue", "isTotalsValidator", "thresholdPercentage");
final Class<T> type;
final String propertyName;
private final String[] keyOrder;
private Boolean required;
private T defaultValue;
private boolean isTotalsValidator = false;
@JsonIgnore
private List<Function<HsBookingItemEntity, List<String>>> asTotalLimitValidators; // TODO.impl: move to BookingItemIntegerProperty
private Integer thresholdPercentage; // TODO.impl: move to IntegerProperty
public String unit() {
return null;
}
public ValidatableProperty<T> required() {
required = TRUE;
return this;
}
public ValidatableProperty<T> optional() {
required = FALSE;
return this;
}
public ValidatableProperty<T> withDefault(final T value) {
defaultValue = value;
required = FALSE;
return this;
}
public ValidatableProperty<T> asTotalLimit() {
isTotalsValidator = true;
return this;
}
public String propertyName() {
return propertyName;
}
public boolean isTotalsValidator() {
return isTotalsValidator || asTotalLimitValidators != null;
}
public Integer thresholdPercentage() {
return thresholdPercentage;
}
public ValidatableProperty<T> eachComprising(final int factor, final TriFunction<HsBookingItemEntity, IntegerProperty, Integer, List<String>> validator) {
if (asTotalLimitValidators == null) {
asTotalLimitValidators = new ArrayList<>();
}
asTotalLimitValidators.add((final HsBookingItemEntity entity) -> validator.apply(entity, (IntegerProperty)this, factor));
return this;
}
public ValidatableProperty<?> withThreshold(final Integer percentage) {
this.thresholdPercentage = percentage;
return this;
}
public final List<String> validate(final Map<String, Object> props) {
final var result = new ArrayList<String>();
final var propValue = props.get(propertyName);
if (propValue == null) {
if (required) {
result.add(propertyName + " is required but missing");
}
}
if (propValue != null){
if ( type.isInstance(propValue)) {
//noinspection unchecked
validate(result, (T) propValue, props);
} else {
result.add(propertyName + " is expected to be of type " + type + ", " +
"but is of type '" + propValue.getClass().getSimpleName() + "'");
}
}
return result;
}
protected abstract void validate(final ArrayList<String> result, final T propValue, final Map<String, Object> props);
public void verifyConsistency(final Map.Entry<? extends Enum<?>, ?> typeDef) {
if (required == null ) {
throw new IllegalStateException(typeDef.getKey() + "[" + propertyName + "] not fully initialized, please call either .required() or .optional()" );
}
}
@SuppressWarnings("unchecked")
public T getValue(final Map<String, Object> propValues) {
return (T) Optional.ofNullable(propValues.get(propertyName)).orElse(defaultValue);
}
protected abstract String simpleTypeName();
public Map<String, Object> toOrderedMap() {
Map<String, Object> sortedMap = new LinkedHashMap<>();
sortedMap.put("type", simpleTypeName());
// Add entries according to the given order
for (String key : keyOrder) {
final Optional<Object> propValue = getPropertyValue(key);
propValue.ifPresent(o -> sortedMap.put(key, o));
}
return sortedMap;
}
@SneakyThrows
private Optional<Object> getPropertyValue(final String key) {
try {
final var field = getClass().getDeclaredField(key);
field.setAccessible(true);
return Optional.ofNullable(arrayToList(field.get(this)));
} catch (final NoSuchFieldException e1) {
try {
final var field = getClass().getSuperclass().getDeclaredField(key);
field.setAccessible(true);
return Optional.ofNullable(arrayToList(field.get(this)));
} catch (final NoSuchFieldException e2) {
return Optional.empty();
}
}
}
private Object arrayToList(final Object value) {
if ( value instanceof String[]) {
return List.of((String[])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)
.toList();
}
}

View File

@ -1,4 +1,4 @@
package net.hostsharing.hsadminng.rbac.test; package net.hostsharing.hsadminng.mapper;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Arrays; import java.util.Arrays;
@ -37,4 +37,10 @@ public class Array {
return resultList.toArray(String[]::new); return resultList.toArray(String[]::new);
} }
public static String[] join(final String[]... parts) {
final String[] joined = Arrays.stream(parts)
.flatMap(Arrays::stream)
.toArray(String[]::new);
return joined;
}
} }

View File

@ -62,6 +62,8 @@ public class RbacGrantsDiagramService {
@PersistenceContext @PersistenceContext
private EntityManager em; private EntityManager em;
private Map<UUID, List<RawRbacGrantEntity>> descendantsByUuid = new HashMap<>();
public String allGrantsToCurrentUser(final EnumSet<Include> includes) { public String allGrantsToCurrentUser(final EnumSet<Include> includes) {
final var graph = new LimitedHashSet<RawRbacGrantEntity>(); final var graph = new LimitedHashSet<RawRbacGrantEntity>();
for ( UUID subjectUuid: context.currentSubjectsUuids() ) { for ( UUID subjectUuid: context.currentSubjectsUuids() ) {
@ -102,7 +104,7 @@ public class RbacGrantsDiagramService {
} }
private void traverseGrantsFrom(final Set<RawRbacGrantEntity> graph, final UUID refUuid, final EnumSet<Include> option) { private void traverseGrantsFrom(final Set<RawRbacGrantEntity> graph, final UUID refUuid, final EnumSet<Include> option) {
final var grants = rawGrantRepo.findByDescendantUuid(refUuid); final var grants = findDescendantsByUuid(refUuid);
grants.forEach(g -> { grants.forEach(g -> {
if (!option.contains(USERS) && g.getAscendantIdName().startsWith("user:")) { if (!option.contains(USERS) && g.getAscendantIdName().startsWith("user:")) {
return; return;
@ -114,6 +116,11 @@ public class RbacGrantsDiagramService {
}); });
} }
private List<RawRbacGrantEntity> findDescendantsByUuid(final UUID refUuid) {
// TODO.impl: if that UUID already got processed, do we need to return anything at all?
return descendantsByUuid.computeIfAbsent(refUuid, uuid -> rawGrantRepo.findByDescendantUuid(uuid));
}
private String toMermaidFlowchart(final HashSet<RawRbacGrantEntity> graph, final EnumSet<Include> includes) { private String toMermaidFlowchart(final HashSet<RawRbacGrantEntity> graph, final EnumSet<Include> includes) {
final var entities = final var entities =
includes.contains(DETAILS) includes.contains(DETAILS)

View File

@ -33,13 +33,13 @@ begin
managedServerUuid := uuid_generate_v4(); managedServerUuid := uuid_generate_v4();
insert insert
into hs_booking_item (uuid, projectuuid, type, parentitemuuid, caption, validity, resources) into hs_booking_item (uuid, projectuuid, type, parentitemuuid, caption, validity, resources)
values (privateCloudUuid, relatedProject.uuid, 'PRIVATE_CLOUD', null, 'some PrivateCloud', daterange('20240401', null, '[]'), '{ "CPUs": 10, "SDD": 10240, "HDD": 10240, "Traffic": 42 }'::jsonb), values (privateCloudUuid, relatedProject.uuid, 'PRIVATE_CLOUD', null, 'some PrivateCloud', daterange('20240401', null, '[]'), '{ "CPUs": 10, "RAM": 32, "SSD": 4000, "HDD": 10000, "Traffic": 2000 }'::jsonb),
(uuid_generate_v4(), null, 'MANAGED_SERVER', privateCloudUuid, 'some ManagedServer', daterange('20230115', '20240415', '[)'), '{ "CPUs": 2, "RAM": 4, "HDD": 1024, "Traffic": 42 }'::jsonb), (uuid_generate_v4(), null, 'MANAGED_SERVER', privateCloudUuid, 'some ManagedServer', daterange('20230115', '20240415', '[)'), '{ "CPUs": 2, "RAM": 4, "SSD": 500, "Traffic": 500 }'::jsonb),
(uuid_generate_v4(), null, 'CLOUD_SERVER', privateCloudUuid, 'test CloudServer', daterange('20230115', '20240415', '[)'), '{ "CPUs": 2, "RAM": 4, "HDD": 1024, "Traffic": 42 }'::jsonb), (uuid_generate_v4(), null, 'CLOUD_SERVER', privateCloudUuid, 'test CloudServer', daterange('20230115', '20240415', '[)'), '{ "CPUs": 2, "RAM": 4, "SSD": 750, "Traffic": 500 }'::jsonb),
(uuid_generate_v4(), null, 'CLOUD_SERVER', privateCloudUuid, 'prod CloudServer', daterange('20230115', '20240415', '[)'), '{ "CPUs": 4, "RAM": 16, "HDD": 2924, "Traffic": 420 }'::jsonb), (uuid_generate_v4(), null, 'CLOUD_SERVER', privateCloudUuid, 'prod CloudServer', daterange('20230115', '20240415', '[)'), '{ "CPUs": 4, "RAM": 16, "SSD": 1000, "Traffic": 500 }'::jsonb),
(managedServerUuid, relatedProject.uuid, 'MANAGED_SERVER', null, 'separate ManagedServer', daterange('20221001', null, '[]'), '{ "CPUs": 2, "RAM": 8, "SDD": 512, "Traffic": 42 }'::jsonb), (managedServerUuid, relatedProject.uuid, 'MANAGED_SERVER', null, 'separate ManagedServer', daterange('20221001', null, '[]'), '{ "CPUs": 2, "RAM": 8, "SSD": 500, "Traffic": 500 }'::jsonb),
(uuid_generate_v4(), null, 'MANAGED_WEBSPACE', managedServerUuid, 'some ManagedWebspace', daterange('20221001', null, '[]'), '{ "SDD": 512, "Traffic": 12, "Daemons": 2, "Multi": 4 }'::jsonb), (uuid_generate_v4(), null, 'MANAGED_WEBSPACE', managedServerUuid, 'some ManagedWebspace', daterange('20221001', null, '[]'), '{ "SSD": 50, "Traffic": 20, "Daemons": 2, "Multi": 4 }'::jsonb),
(uuid_generate_v4(), relatedProject.uuid, 'MANAGED_WEBSPACE', null, 'some ManagedWebspace', daterange('20221001', null, '[]'), '{ "SDD": 512, "Traffic": 12, "Daemons": 2, "Multi": 4 }'::jsonb); (uuid_generate_v4(), relatedProject.uuid, 'MANAGED_WEBSPACE', null, 'separate ManagedWebspace', daterange('20221001', null, '[]'), '{ "SSD": 100, "Traffic": 50, "Daemons": 0, "Multi": 1 }'::jsonb);
end; $$; end; $$;
--// --//

View File

@ -75,10 +75,10 @@ begin
end); end);
if expectedParentType is not null and actualParentType is null then if expectedParentType is not null and actualParentType is null then
raise exception '[400] % must have % as parent, but got <NULL>', raise exception '[400] HostingAsset % must have % as parent, but got <NULL>',
NEW.type, expectedParentType; NEW.type, expectedParentType;
elsif expectedParentType is not null and actualParentType <> expectedParentType then elsif expectedParentType is not null and actualParentType <> expectedParentType then
raise exception '[400] % must have % as parent, but got %s', raise exception '[400] HostingAsset % must have % as parent, but got %s',
NEW.type, expectedParentType, actualParentType; NEW.type, expectedParentType, actualParentType;
end if; end if;
return NEW; return NEW;
@ -100,27 +100,23 @@ create or replace function hs_hosting_asset_booking_item_hierarchy_check_tf()
language plpgsql as $$ language plpgsql as $$
declare declare
actualBookingItemType HsBookingItemType; actualBookingItemType HsBookingItemType;
expectedBookingItemTypes HsBookingItemType[]; expectedBookingItemType HsBookingItemType;
begin begin
actualBookingItemType := (select type actualBookingItemType := (select type
from hs_booking_item from hs_booking_item
where NEW.bookingItemUuid = uuid); where NEW.bookingItemUuid = uuid);
if NEW.type = 'CLOUD_SERVER' then if NEW.type = 'CLOUD_SERVER' then
expectedBookingItemTypes := ARRAY['PRIVATE_CLOUD', 'CLOUD_SERVER']; expectedBookingItemType := 'CLOUD_SERVER';
elsif NEW.type = 'MANAGED_SERVER' then elsif NEW.type = 'MANAGED_SERVER' then
expectedBookingItemTypes := ARRAY['PRIVATE_CLOUD', 'MANAGED_SERVER']; expectedBookingItemType := 'MANAGED_SERVER';
elsif NEW.type = 'MANAGED_WEBSPACE' then elsif NEW.type = 'MANAGED_WEBSPACE' then
if NEW.parentAssetUuid is null then expectedBookingItemType := 'MANAGED_WEBSPACE';
expectedBookingItemTypes := ARRAY['MANAGED_WEBSPACE'];
else
expectedBookingItemTypes := ARRAY['PRIVATE_CLOUD', 'MANAGED_SERVER'];
end if;
end if; end if;
if not actualBookingItemType = any(expectedBookingItemTypes) then if not actualBookingItemType = expectedBookingItemType then
raise exception '[400] % % must have any of % as booking-item, but got %', raise exception '[400] HostingAsset % % must have % as booking-item, but got %',
NEW.type, NEW.identifier, expectedBookingItemTypes, actualBookingItemType; NEW.type, NEW.identifier, expectedBookingItemType, actualBookingItemType;
end if; end if;
return NEW; return NEW;
end; $$; end; $$;

View File

@ -16,6 +16,8 @@ declare
relatedDebitor hs_office_debitor; relatedDebitor hs_office_debitor;
relatedPrivateCloudBookingItem hs_booking_item; relatedPrivateCloudBookingItem hs_booking_item;
relatedManagedServerBookingItem hs_booking_item; relatedManagedServerBookingItem hs_booking_item;
relatedCloudServerBookingItem hs_booking_item;
relatedManagedWebspaceBookingItem hs_booking_item;
debitorNumberSuffix varchar; debitorNumberSuffix varchar;
defaultPrefix varchar; defaultPrefix varchar;
managedServerUuid uuid; managedServerUuid uuid;
@ -48,6 +50,18 @@ begin
and item.type = 'MANAGED_SERVER'; and item.type = 'MANAGED_SERVER';
assert relatedManagedServerBookingItem.uuid is not null, 'relatedManagedServerBookingItem for "' || givenProjectCaption|| '" must not be null'; assert relatedManagedServerBookingItem.uuid is not null, 'relatedManagedServerBookingItem for "' || givenProjectCaption|| '" must not be null';
select item.* into relatedCloudServerBookingItem
from hs_booking_item item
where item.parentItemuuid = relatedPrivateCloudBookingItem.uuid
and item.type = 'CLOUD_SERVER';
assert relatedCloudServerBookingItem.uuid is not null, 'relatedCloudServerBookingItem for "' || givenProjectCaption|| '" must not be null';
select item.* into relatedManagedWebspaceBookingItem
from hs_booking_item item
where item.projectUuid = relatedProject.uuid
and item.type = 'MANAGED_WEBSPACE';
assert relatedManagedWebspaceBookingItem.uuid is not null, 'relatedManagedWebspaceBookingItem for "' || givenProjectCaption|| '" must not be null';
select uuid_generate_v4() into managedServerUuid; select uuid_generate_v4() into managedServerUuid;
select uuid_generate_v4() into managedWebspaceUuid; select uuid_generate_v4() into managedWebspaceUuid;
select uuid_generate_v4() into webUnixUserUuid; select uuid_generate_v4() into webUnixUserUuid;
@ -56,11 +70,11 @@ begin
insert into hs_hosting_asset insert into hs_hosting_asset
(uuid, bookingitemuuid, type, parentAssetUuid, assignedToAssetUuid, identifier, caption, config) (uuid, bookingitemuuid, type, parentAssetUuid, assignedToAssetUuid, identifier, caption, config)
values (managedServerUuid, relatedPrivateCloudBookingItem.uuid, 'MANAGED_SERVER', null, null, 'vm10' || debitorNumberSuffix, 'some ManagedServer', '{ "extra": 42 }'::jsonb), values (managedServerUuid, relatedManagedServerBookingItem.uuid, 'MANAGED_SERVER', null, null, 'vm10' || debitorNumberSuffix, 'some ManagedServer', '{ "monit_max_cpu_usage": 90, "monit_max_ram_usage": 80, "monit_max_ssd_usage": 70 }'::jsonb),
(uuid_generate_v4(), relatedPrivateCloudBookingItem.uuid, 'CLOUD_SERVER', null, null, 'vm20' || debitorNumberSuffix, 'another CloudServer', '{ "extra": 42 }'::jsonb), (uuid_generate_v4(), relatedCloudServerBookingItem.uuid, 'CLOUD_SERVER', null, null, 'vm20' || debitorNumberSuffix, 'another CloudServer', '{}'::jsonb),
(managedWebspaceUuid, relatedManagedServerBookingItem.uuid, 'MANAGED_WEBSPACE', managedServerUuid, null, defaultPrefix || '01', 'some Webspace', '{ "extra": 42 }'::jsonb), (managedWebspaceUuid, relatedManagedWebspaceBookingItem.uuid, 'MANAGED_WEBSPACE', managedServerUuid, null, defaultPrefix || '01', 'some Webspace', '{}'::jsonb),
(webUnixUserUuid, null, 'UNIX_USER', managedWebspaceUuid, null, defaultPrefix || '01-web', 'some UnixUser for Website', '{ "SSD-soft-quota": "128", "SSD-hard-quota": "256", "HDD-soft-quota": "512", "HDD-hard-quota": "1024", "extra": 42 }'::jsonb), (webUnixUserUuid, null, 'UNIX_USER', managedWebspaceUuid, null, defaultPrefix || '01-web', 'some UnixUser for Website', '{ "SSD-soft-quota": "128", "SSD-hard-quota": "256", "HDD-soft-quota": "512", "HDD-hard-quota": "1024"}'::jsonb),
(uuid_generate_v4(), null, 'DOMAIN_HTTP_SETUP', managedWebspaceUuid, webUnixUserUuid, defaultPrefix || '.example.org', 'some Domain-HTTP-Setup', '{ "option-htdocsfallback": true, "use-fcgiphpbin": "/usr/lib/cgi-bin/php", "validsubdomainnames": "*", "extra": 42 }'::jsonb); (uuid_generate_v4(), null, 'DOMAIN_HTTP_SETUP', managedWebspaceUuid, webUnixUserUuid, defaultPrefix || '.example.org', 'some Domain-HTTP-Setup', '{ "option-htdocsfallback": true, "use-fcgiphpbin": "/usr/lib/cgi-bin/php", "validsubdomainnames": "*"}'::jsonb);
end; $$; end; $$;
--// --//

View File

@ -77,14 +77,14 @@ class HsBookingItemControllerAcceptanceTest extends ContextBasedTestWithCleanup
[ [
{ {
"type": "MANAGED_WEBSPACE", "type": "MANAGED_WEBSPACE",
"caption": "some ManagedWebspace", "caption": "separate ManagedWebspace",
"validFrom": "2022-10-01", "validFrom": "2022-10-01",
"validTo": null, "validTo": null,
"resources": { "resources": {
"SDD": 512, "SSD": 100,
"Multi": 4, "Multi": 1,
"Daemons": 2, "Daemons": 0,
"Traffic": 12 "Traffic": 50
} }
}, },
{ {
@ -94,9 +94,9 @@ class HsBookingItemControllerAcceptanceTest extends ContextBasedTestWithCleanup
"validTo": null, "validTo": null,
"resources": { "resources": {
"RAM": 8, "RAM": 8,
"SDD": 512, "SSD": 500,
"CPUs": 2, "CPUs": 2,
"Traffic": 42 "Traffic": 500
} }
}, },
{ {
@ -105,10 +105,11 @@ class HsBookingItemControllerAcceptanceTest extends ContextBasedTestWithCleanup
"validFrom": "2024-04-01", "validFrom": "2024-04-01",
"validTo": null, "validTo": null,
"resources": { "resources": {
"HDD": 10240, "HDD": 10000,
"SDD": 10240, "RAM": 32,
"SSD": 4000,
"CPUs": 10, "CPUs": 10,
"Traffic": 42 "Traffic": 2000
} }
} }
] ]
@ -174,7 +175,7 @@ class HsBookingItemControllerAcceptanceTest extends ContextBasedTestWithCleanup
@Test @Test
void globalAdmin_canGetArbitraryBookingItem() { void globalAdmin_canGetArbitraryBookingItem() {
context.define("superuser-alex@hostsharing.net"); context.define("superuser-alex@hostsharing.net");
final var givenBookingItemUuid = bookingItemRepo.findByCaption("some ManagedWebspace").stream() final var givenBookingItemUuid = bookingItemRepo.findByCaption("separate ManagedWebspace").stream()
.filter(bi -> belongsToDebitorWithDefaultPrefix(bi, "fir")) .filter(bi -> belongsToDebitorWithDefaultPrefix(bi, "fir"))
.map(HsBookingItemEntity::getUuid) .map(HsBookingItemEntity::getUuid)
.findAny().orElseThrow(); .findAny().orElseThrow();
@ -191,14 +192,14 @@ class HsBookingItemControllerAcceptanceTest extends ContextBasedTestWithCleanup
.body("", lenientlyEquals(""" .body("", lenientlyEquals("""
{ {
"type": "MANAGED_WEBSPACE", "type": "MANAGED_WEBSPACE",
"caption": "some ManagedWebspace", "caption": "separate ManagedWebspace",
"validFrom": "2022-10-01", "validFrom": "2022-10-01",
"validTo": null, "validTo": null,
"resources": { "resources": {
"SDD": 512, "SSD": 100,
"Multi": 4, "Multi": 1,
"Daemons": 2, "Daemons": 0,
"Traffic": 12 "Traffic": 50
} }
} }
""")); // @formatter:on """)); // @formatter:on
@ -227,14 +228,16 @@ class HsBookingItemControllerAcceptanceTest extends ContextBasedTestWithCleanup
void projectAdmin_canGetRelatedBookingItem() { void projectAdmin_canGetRelatedBookingItem() {
context.define("superuser-alex@hostsharing.net"); context.define("superuser-alex@hostsharing.net");
final var givenBookingItemUuid = bookingItemRepo.findByCaption("separate ManagedServer").stream() final var givenBookingItemUuid = bookingItemRepo.findByCaption("separate ManagedServer").stream()
.filter(bi -> belongsToDebitorWithDefaultPrefix(bi, "thi")) .filter(bi -> belongsToDebitorWithDefaultPrefix(bi, "sec"))
.map(HsBookingItemEntity::getUuid) .map(HsBookingItemEntity::getUuid)
.findAny().orElseThrow(); .findAny().orElseThrow();
generateRbacDiagramForObjectPermission(givenBookingItemUuid, "SELECT", "select");
RestAssured // @formatter:off RestAssured // @formatter:off
.given() .given()
.header("current-user", "superuser-alex@hostsharing.net") .header("current-user", "superuser-alex@hostsharing.net")
.header("assumed-roles", "hs_booking_project#D-1000313-D-1000313defaultproject:ADMIN") .header("assumed-roles", "hs_booking_project#D-1000212-D-1000212defaultproject:ADMIN")
.port(port) .port(port)
.when() .when()
.get("http://localhost/api/hs/booking/items/" + givenBookingItemUuid) .get("http://localhost/api/hs/booking/items/" + givenBookingItemUuid)
@ -249,9 +252,9 @@ class HsBookingItemControllerAcceptanceTest extends ContextBasedTestWithCleanup
"validTo": null, "validTo": null,
"resources": { "resources": {
"RAM": 8, "RAM": 8,
"SDD": 512, "SSD": 500,
"CPUs": 2, "CPUs": 2,
"Traffic": 42 "Traffic": 500
} }
} }
""")); // @formatter:on """)); // @formatter:on
@ -261,7 +264,7 @@ class HsBookingItemControllerAcceptanceTest extends ContextBasedTestWithCleanup
return ofNullable(bi) return ofNullable(bi)
.map(HsBookingItemEntity::getProject) .map(HsBookingItemEntity::getProject)
.map(HsBookingProjectEntity::getDebitor) .map(HsBookingProjectEntity::getDebitor)
.map(bd -> bd.getDefaultPrefix().equals(defaultPrefix)) .filter(bd -> bd.getDefaultPrefix().equals(defaultPrefix))
.isPresent(); .isPresent();
} }
} }

View File

@ -44,11 +44,11 @@ class HsBookingItemEntityPatcherUnitTest extends PatchUnitTestBase<
private static final Map<String, Object> PATCH_RESOURCES = patchMap( private static final Map<String, Object> PATCH_RESOURCES = patchMap(
entry("CPU", 2), entry("CPU", 2),
entry("HDD", null), entry("HDD", null),
entry("SDD", 256) entry("SSD", 256)
); );
private static final Map<String, Object> PATCHED_RESOURCES = patchMap( private static final Map<String, Object> PATCHED_RESOURCES = patchMap(
entry("CPU", 2), entry("CPU", 2),
entry("SDD", 256), entry("SSD", 256),
entry("MEM", 64) entry("MEM", 64)
); );

View File

@ -6,7 +6,7 @@ import net.hostsharing.hsadminng.hs.booking.project.HsBookingProjectRepository;
import net.hostsharing.hsadminng.hs.office.debitor.HsOfficeDebitorRepository; import net.hostsharing.hsadminng.hs.office.debitor.HsOfficeDebitorRepository;
import net.hostsharing.hsadminng.rbac.rbacgrant.RawRbacGrantRepository; import net.hostsharing.hsadminng.rbac.rbacgrant.RawRbacGrantRepository;
import net.hostsharing.hsadminng.rbac.rbacrole.RawRbacRoleRepository; import net.hostsharing.hsadminng.rbac.rbacrole.RawRbacRoleRepository;
import net.hostsharing.hsadminng.rbac.test.Array; import net.hostsharing.hsadminng.mapper.Array;
import net.hostsharing.hsadminng.rbac.test.ContextBasedTestWithCleanup; import net.hostsharing.hsadminng.rbac.test.ContextBasedTestWithCleanup;
import net.hostsharing.hsadminng.rbac.test.JpaAttempt; import net.hostsharing.hsadminng.rbac.test.JpaAttempt;
import org.junit.jupiter.api.Nested; import org.junit.jupiter.api.Nested;
@ -30,7 +30,7 @@ import static net.hostsharing.hsadminng.hs.booking.item.HsBookingItemType.MANAGE
import static net.hostsharing.hsadminng.hs.booking.item.HsBookingItemType.MANAGED_WEBSPACE; import static net.hostsharing.hsadminng.hs.booking.item.HsBookingItemType.MANAGED_WEBSPACE;
import static net.hostsharing.hsadminng.rbac.rbacgrant.RawRbacGrantEntity.distinctGrantDisplaysOf; import static net.hostsharing.hsadminng.rbac.rbacgrant.RawRbacGrantEntity.distinctGrantDisplaysOf;
import static net.hostsharing.hsadminng.rbac.rbacrole.RawRbacRoleEntity.distinctRoleNamesOf; import static net.hostsharing.hsadminng.rbac.rbacrole.RawRbacRoleEntity.distinctRoleNamesOf;
import static net.hostsharing.hsadminng.rbac.test.Array.fromFormatted; import static net.hostsharing.hsadminng.mapper.Array.fromFormatted;
import static net.hostsharing.hsadminng.rbac.test.JpaAttempt.attempt; import static net.hostsharing.hsadminng.rbac.test.JpaAttempt.attempt;
import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThat;
@ -174,9 +174,9 @@ class HsBookingItemRepositoryIntegrationTest extends ContextBasedTestWithCleanup
// then // then
allTheseBookingItemsAreReturned( allTheseBookingItemsAreReturned(
result, result,
"HsBookingItemEntity(D-1000212:D-1000212 default project, MANAGED_WEBSPACE, [2022-10-01,), some ManagedWebspace, { Daemons: 2, Multi: 4, SDD: 512, Traffic: 12 })", "HsBookingItemEntity(D-1000212:D-1000212 default project, MANAGED_WEBSPACE, [2022-10-01,), separate ManagedWebspace, { Daemons: 0, Multi: 1, SSD: 100, Traffic: 50 })",
"HsBookingItemEntity(D-1000212:D-1000212 default project, MANAGED_SERVER, [2022-10-01,), separate ManagedServer, { CPUs: 2, RAM: 8, SDD: 512, Traffic: 42 })", "HsBookingItemEntity(D-1000212:D-1000212 default project, MANAGED_SERVER, [2022-10-01,), separate ManagedServer, { CPUs: 2, RAM: 8, SSD: 500, Traffic: 500 })",
"HsBookingItemEntity(D-1000212:D-1000212 default project, PRIVATE_CLOUD, [2024-04-01,), some PrivateCloud, { CPUs: 10, HDD: 10240, SDD: 10240, Traffic: 42 })"); "HsBookingItemEntity(D-1000212:D-1000212 default project, PRIVATE_CLOUD, [2024-04-01,), some PrivateCloud, { CPUs: 10, HDD: 10000, RAM: 32, SSD: 4000, Traffic: 2000 })");
} }
@Test @Test
@ -194,9 +194,9 @@ class HsBookingItemRepositoryIntegrationTest extends ContextBasedTestWithCleanup
// then: // then:
exactlyTheseBookingItemsAreReturned( exactlyTheseBookingItemsAreReturned(
result, result,
"HsBookingItemEntity(D-1000111:D-1000111 default project, MANAGED_SERVER, [2022-10-01,), separate ManagedServer, { CPUs: 2, RAM: 8, SDD: 512, Traffic: 42 })", "HsBookingItemEntity(D-1000111:D-1000111 default project, MANAGED_SERVER, [2022-10-01,), separate ManagedServer, { CPUs: 2, RAM: 8, SSD: 500, Traffic: 500 })",
"HsBookingItemEntity(D-1000111:D-1000111 default project, MANAGED_WEBSPACE, [2022-10-01,), some ManagedWebspace, { Daemons: 2, Multi: 4, SDD: 512, Traffic: 12 })", "HsBookingItemEntity(D-1000111:D-1000111 default project, MANAGED_WEBSPACE, [2022-10-01,), separate ManagedWebspace, { Daemons: 0, Multi: 1, SSD: 100, Traffic: 50 })",
"HsBookingItemEntity(D-1000111:D-1000111 default project, PRIVATE_CLOUD, [2024-04-01,), some PrivateCloud, { CPUs: 10, HDD: 10240, SDD: 10240, Traffic: 42 })"); "HsBookingItemEntity(D-1000111:D-1000111 default project, PRIVATE_CLOUD, [2024-04-01,), some PrivateCloud, { CPUs: 10, HDD: 10000, RAM: 32, SSD: 4000, Traffic: 2000 })");
} }
} }

View File

@ -0,0 +1,56 @@
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.project.HsBookingProjectEntity;
import org.junit.jupiter.api.Test;
import jakarta.validation.ValidationException;
import static net.hostsharing.hsadminng.hs.booking.item.HsBookingItemType.PRIVATE_CLOUD;
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_WEBSPACE;
import static net.hostsharing.hsadminng.hs.booking.item.validators.HsBookingItemEntityValidator.validated;
import static org.assertj.core.api.Assertions.assertThat;
import static org.assertj.core.api.Assertions.catchThrowable;
class HsBookingItemEntityValidatorUnitTest {
final HsBookingDebitorEntity debitor = HsBookingDebitorEntity.builder()
.debitorNumber(12345)
.build();
final HsBookingProjectEntity project = HsBookingProjectEntity.builder()
.debitor(debitor)
.caption("test project")
.build();
@Test
void validThrowsException() {
// given
final var cloudServerBookingItemEntity = HsBookingItemEntity.builder()
.type(CLOUD_SERVER)
.project(project)
.caption("Test-Server")
.build();
// when
final var result = catchThrowable( ()-> validated(cloudServerBookingItemEntity) );
// then
assertThat(result).isInstanceOf(ValidationException.class)
.hasMessageContaining(
"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.SSD is required but missing",
"D-12345:test project:Test-Server.resources.Traffic is required but missing");
}
@Test
void listsTypes() {
// when
final var result = HsBookingItemEntityValidator.types();
// then
assertThat(result).containsExactlyInAnyOrder(PRIVATE_CLOUD, CLOUD_SERVER, MANAGED_SERVER, MANAGED_WEBSPACE);
}
}

View File

@ -1,44 +0,0 @@
package net.hostsharing.hsadminng.hs.booking.item.validators;
import net.hostsharing.hsadminng.hs.booking.item.HsBookingItemEntity;
import org.junit.jupiter.api.Test;
import jakarta.validation.ValidationException;
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.validators.HsBookingItemEntityValidators.valid;
import static net.hostsharing.hsadminng.hs.booking.item.HsBookingItemType.CLOUD_SERVER;
import static org.assertj.core.api.Assertions.assertThat;
import static org.assertj.core.api.Assertions.catchThrowable;
class HsBookingItemEntityValidatorsUnitTest {
@Test
void validThrowsException() {
// given
final var cloudServerBookingItemEntity = HsBookingItemEntity.builder()
.type(CLOUD_SERVER)
.build();
// when
final var result = catchThrowable( ()-> valid(cloudServerBookingItemEntity) );
// then
assertThat(result).isInstanceOf(ValidationException.class)
.hasMessageContaining(
"'resources.CPUs' is required but missing",
"'resources.RAM' is required but missing",
"'resources.SSD' is required but missing",
"'resources.Traffic' is required but missing");
}
@Test
void listsTypes() {
// when
final var result = HsBookingItemEntityValidators.types();
// then
assertThat(result).containsExactlyInAnyOrder(CLOUD_SERVER, MANAGED_SERVER, MANAGED_WEBSPACE);
}
}

View File

@ -1,23 +1,37 @@
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 java.util.Map; import java.util.Map;
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 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.validators.HsBookingItemEntityValidators.forType; import static net.hostsharing.hsadminng.hs.booking.item.HsBookingItemType.MANAGED_SERVER;
import static net.hostsharing.hsadminng.hs.booking.item.HsBookingItemType.PRIVATE_CLOUD;
import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThat;
class HsCloudServerBookingItemValidatorUnitTest { class HsCloudServerBookingItemValidatorUnitTest {
final HsBookingDebitorEntity debitor = HsBookingDebitorEntity.builder()
.debitorNumber(12345)
.build();
final HsBookingProjectEntity project = HsBookingProjectEntity.builder()
.debitor(debitor)
.caption("Test-Project")
.build();
@Test @Test
void validatesProperties() { void validatesProperties() {
// given // given
final var validator = HsBookingItemEntityValidators.forType(CLOUD_SERVER);
final var cloudServerBookingItemEntity = HsBookingItemEntity.builder() final var cloudServerBookingItemEntity = HsBookingItemEntity.builder()
.type(CLOUD_SERVER) .type(CLOUD_SERVER)
.project(project)
.caption("Test-Server")
.resources(Map.ofEntries( .resources(Map.ofEntries(
entry("CPUs", 2), entry("CPUs", 2),
entry("RAM", 25), entry("RAM", 25),
@ -28,24 +42,77 @@ class HsCloudServerBookingItemValidatorUnitTest {
.build(); .build();
// when // when
final var result = validator.validate(cloudServerBookingItemEntity); final var result = HsBookingItemEntityValidator.doValidate(cloudServerBookingItemEntity);
// then // then
assertThat(result).containsExactly("'resources.SLA-EMail' is not expected but is set to 'true'"); assertThat(result).containsExactly("D-12345:Test-Project:Test-Server.resources.SLA-EMail is not expected but is set to 'true'");
} }
@Test @Test
void containsAllValidations() { void containsAllValidations() {
// when // when
final var validator = forType(CLOUD_SERVER); final var validator = HsBookingItemEntityValidatorRegistry.forType(CLOUD_SERVER);
// then // then
assertThat(validator.properties()).map(Map::toString).containsExactlyInAnyOrder( assertThat(validator.properties()).map(Map::toString).containsExactlyInAnyOrder(
"{type=integer, propertyName=CPUs, required=true, unit=null, min=1, max=32, step=null}", "{type=integer, propertyName=CPUs, min=1, max=32, required=true, isTotalsValidator=false}",
"{type=integer, propertyName=RAM, required=true, unit=GB, min=1, max=128, step=null}", "{type=integer, propertyName=RAM, unit=GB, min=1, max=128, required=true, isTotalsValidator=false}",
"{type=integer, propertyName=SSD, required=true, unit=GB, min=25, max=1000, step=25}", "{type=integer, propertyName=SSD, unit=GB, min=25, max=1000, step=25, required=true, isTotalsValidator=false}",
"{type=integer, propertyName=HDD, required=false, unit=GB, min=0, max=4000, step=250}", "{type=integer, propertyName=HDD, unit=GB, min=0, max=4000, step=250, required=false, defaultValue=0, isTotalsValidator=false}",
"{type=integer, propertyName=Traffic, required=true, unit=GB, min=250, max=10000, step=250}", "{type=integer, propertyName=Traffic, unit=GB, min=250, max=10000, step=250, required=true, isTotalsValidator=false}",
"{type=enumeration, propertyName=SLA-Infrastructure, required=false, values=[BASIC, EXT8H, EXT4H, EXT2H]}"); "{type=enumeration, propertyName=SLA-Infrastructure, values=[BASIC, EXT8H, EXT4H, EXT2H], required=false, isTotalsValidator=false}");
}
@Test
void validatesExceedingPropertyTotals() {
// given
final var subCloudServerBookingItemEntity = HsBookingItemEntity.builder()
.type(CLOUD_SERVER)
.caption("Test Cloud-Server")
.resources(ofEntries(
entry("CPUs", 2),
entry("RAM", 10),
entry("SSD", 50),
entry("Traffic", 2500)
))
.build();
final HsBookingItemEntity subManagedServerBookingItemEntity = HsBookingItemEntity.builder()
.type(MANAGED_SERVER)
.caption("Test Managed-Server")
.resources(ofEntries(
entry("CPUs", 3),
entry("RAM", 20),
entry("SSD", 100),
entry("Traffic", 3000)
))
.build();
final var privateCloudBookingItemEntity = HsBookingItemEntity.builder()
.type(PRIVATE_CLOUD)
.project(project)
.caption("Test Cloud")
.resources(ofEntries(
entry("CPUs", 4),
entry("RAM", 20),
entry("SSD", 100),
entry("Traffic", 5000)
))
.subBookingItems(of(
subManagedServerBookingItemEntity,
subCloudServerBookingItemEntity
))
.build();
subManagedServerBookingItemEntity.setParentItem(privateCloudBookingItemEntity);
subCloudServerBookingItemEntity.setParentItem(privateCloudBookingItemEntity);
// when
final var result = HsBookingItemEntityValidator.doValidate(subCloudServerBookingItemEntity);
// then
assertThat(result).containsExactlyInAnyOrder(
"D-12345:Test-Project:Test Cloud-Server.parentItem.total CPUs is 5 exceeds max total CPUs 4",
"D-12345:Test-Project:Test Cloud-Server.parentItem.total RAM is 30 GB exceeds max total RAM 20 GB",
"D-12345:Test-Project:Test Cloud-Server.parentItem.total SSD is 150 GB exceeds max total SSD 100 GB",
"D-12345:Test-Project:Test Cloud-Server.parentItem.total Traffic is 5500 GB exceeds max total Traffic 5000 GB"
);
} }
} }

View File

@ -1,56 +1,227 @@
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 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.Map.entry; import static java.util.Map.entry;
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.MANAGED_SERVER; import static net.hostsharing.hsadminng.hs.booking.item.HsBookingItemType.MANAGED_SERVER;
import static net.hostsharing.hsadminng.hs.booking.item.validators.HsBookingItemEntityValidators.forType; import static net.hostsharing.hsadminng.hs.booking.item.HsBookingItemType.MANAGED_WEBSPACE;
import static net.hostsharing.hsadminng.hs.booking.item.HsBookingItemType.PRIVATE_CLOUD;
import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThat;
class HsManagedServerBookingItemValidatorUnitTest { class HsManagedServerBookingItemValidatorUnitTest {
final HsBookingDebitorEntity debitor = HsBookingDebitorEntity.builder()
.debitorNumber(12345)
.build();
final HsBookingProjectEntity project = HsBookingProjectEntity.builder()
.debitor(debitor)
.caption("Test-Project")
.build();
@Test @Test
void validatesProperties() { void validatesProperties() {
// given // given
final var validator = HsBookingItemEntityValidators.forType(MANAGED_SERVER);
final var mangedServerBookingItemEntity = HsBookingItemEntity.builder() final var mangedServerBookingItemEntity = HsBookingItemEntity.builder()
.type(MANAGED_SERVER) .type(MANAGED_SERVER)
.project(project)
.resources(Map.ofEntries( .resources(Map.ofEntries(
entry("CPUs", 2), entry("CPUs", 2),
entry("RAM", 25), entry("RAM", 25),
entry("SSD", 25), entry("SSD", 25),
entry("Traffic", 250), entry("Traffic", 250),
entry("SLA-Platform", "BASIC"),
entry("SLA-EMail", true) entry("SLA-EMail", true)
)) ))
.build(); .build();
// when // when
final var result = validator.validate(mangedServerBookingItemEntity); final var result = HsBookingItemEntityValidator.doValidate(mangedServerBookingItemEntity);
// then // then
assertThat(result).containsExactly("'resources.SLA-EMail' is expected to be false because resources.SLA-Platform=BASIC but is true"); assertThat(result).containsExactly("D-12345:Test-Project:null.resources.SLA-EMail is expected to be false because SLA-Platform=BASIC but is true");
} }
@Test @Test
void containsAllValidations() { void containsAllValidations() {
// when // when
final var validator = forType(MANAGED_SERVER); final var validator = HsBookingItemEntityValidatorRegistry.forType(MANAGED_SERVER);
// then // then
assertThat(validator.properties()).map(Map::toString).containsExactlyInAnyOrder( assertThat(validator.properties()).map(Map::toString).containsExactlyInAnyOrder(
"{type=integer, propertyName=CPUs, required=true, unit=null, min=1, max=32, step=null}", "{type=integer, propertyName=CPUs, min=1, max=32, required=true, isTotalsValidator=false}",
"{type=integer, propertyName=RAM, required=true, unit=GB, min=1, max=128, step=null}", "{type=integer, propertyName=RAM, unit=GB, min=1, max=128, required=true, isTotalsValidator=false}",
"{type=integer, propertyName=SSD, required=true, unit=GB, min=25, max=1000, step=25}", "{type=integer, propertyName=SSD, unit=GB, min=25, max=1000, step=25, required=true, isTotalsValidator=true, thresholdPercentage=200}",
"{type=integer, propertyName=HDD, required=false, unit=GB, min=0, max=4000, step=250}", "{type=integer, propertyName=HDD, unit=GB, min=0, max=4000, step=250, required=false, defaultValue=0, isTotalsValidator=true, thresholdPercentage=200}",
"{type=integer, propertyName=Traffic, required=true, unit=GB, min=250, max=10000, step=250}", "{type=integer, propertyName=Traffic, unit=GB, min=250, max=10000, step=250, required=true, isTotalsValidator=true, thresholdPercentage=200}",
"{type=enumeration, propertyName=SLA-Platform, required=false, values=[BASIC, EXT8H, EXT4H, EXT2H]}", "{type=enumeration, propertyName=SLA-Platform, values=[BASIC, EXT8H, EXT4H, EXT2H], required=false, defaultValue=BASIC, isTotalsValidator=false}",
"{type=boolean, propertyName=SLA-EMail, required=false, falseIf={SLA-Platform=BASIC}}", "{type=boolean, propertyName=SLA-EMail, required=false, defaultValue=false, isTotalsValidator=false}",
"{type=boolean, propertyName=SLA-Maria, required=false, falseIf={SLA-Platform=BASIC}}", "{type=boolean, propertyName=SLA-Maria, required=false, isTotalsValidator=false}",
"{type=boolean, propertyName=SLA-PgSQL, required=false, falseIf={SLA-Platform=BASIC}}", "{type=boolean, propertyName=SLA-PgSQL, required=false, isTotalsValidator=false}",
"{type=boolean, propertyName=SLA-Office, required=false, falseIf={SLA-Platform=BASIC}}", "{type=boolean, propertyName=SLA-Office, required=false, isTotalsValidator=false}",
"{type=boolean, propertyName=SLA-Web, required=false, falseIf={SLA-Platform=BASIC}}"); "{type=boolean, propertyName=SLA-Web, required=false, isTotalsValidator=false}");
} }
@Test
void validatesExceedingPropertyTotals() {
// given
final var subCloudServerBookingItemEntity = HsBookingItemEntity.builder()
.type(CLOUD_SERVER)
.resources(ofEntries(
entry("CPUs", 2),
entry("RAM", 10),
entry("SSD", 50),
entry("Traffic", 2500)
))
.build();
final HsBookingItemEntity subManagedServerBookingItemEntity = HsBookingItemEntity.builder()
.type(MANAGED_SERVER)
.resources(ofEntries(
entry("CPUs", 3),
entry("RAM", 20),
entry("SSD", 100),
entry("Traffic", 3000)
))
.build();
final var privateCloudBookingItemEntity = HsBookingItemEntity.builder()
.type(PRIVATE_CLOUD)
.project(project)
.resources(ofEntries(
entry("CPUs", 4),
entry("RAM", 20),
entry("SSD", 100),
entry("Traffic", 5000)
))
.subBookingItems(of(
subManagedServerBookingItemEntity,
subCloudServerBookingItemEntity
))
.build();
subManagedServerBookingItemEntity.setParentItem(privateCloudBookingItemEntity);
subCloudServerBookingItemEntity.setParentItem(privateCloudBookingItemEntity);
// when
final var result = HsBookingItemEntityValidator.doValidate(subManagedServerBookingItemEntity);
// then
assertThat(result).containsExactlyInAnyOrder(
"D-12345:Test-Project:null.parentItem.total CPUs is 5 exceeds max total CPUs 4",
"D-12345:Test-Project:null.parentItem.total RAM is 30 GB exceeds max total RAM 20 GB",
"D-12345:Test-Project:null.parentItem.total SSD is 150 GB exceeds max total SSD 100 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("Multi", 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",
2, HsHostingAssetType.MARIADB_DATABASE
),
generateDomainEmailSetupsWithEMailAddresses(26, HsHostingAssetType.DOMAIN_EMAIL_SETUP,
"%c%c.example.com",
10, HsHostingAssetType.EMAIL_ADDRESS
)
))
.build()
))
.build();
// when
final var result = HsBookingItemEntityValidator.doValidate(managedWebspaceBookingItem);
// then
assertThat(result).containsExactlyInAnyOrder(
"Multi=1 allows at maximum 25 unix users, but 26 found",
"Multi=1 allows at maximum 5 database users, but 6 found",
"Multi=1 allows at maximum 5 databases, but 9 found",
"Multi=1 allows at maximum 250 databases, but 260 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, "%c%c.example.com".formatted((n/'a')+'a', (n%'a')+'a'))
)
.build())
.toList();
}
private List<HsHostingAssetEntity> generateDomainEmailSetupsWithEMailAddresses(
final int domainCount,
final HsHostingAssetType directAssetType,
final String directAssetIdentifierFormat,
final int emailAddressCount,
final HsHostingAssetType subAssetType) {
return IntStream.range(0, domainCount)
.mapToObj(n -> HsHostingAssetEntity.builder()
.type(directAssetType)
.identifier(directAssetIdentifierFormat.formatted((n/'a')+'a', (n%'a')+'a'))
.subHostingAssets(
generate(emailAddressCount, subAssetType, "xyz00_%c%c%%c%%c".formatted((n/'a')+'a', (n%'a')+'a'))
)
.build())
.toList();
}
} }

View File

@ -1,54 +1,66 @@
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 java.util.Map; import java.util.Map;
import static java.util.Map.entry; import static java.util.Map.entry;
import static net.hostsharing.hsadminng.hs.booking.item.HsBookingItemType.MANAGED_WEBSPACE; import static net.hostsharing.hsadminng.hs.booking.item.HsBookingItemType.MANAGED_WEBSPACE;
import static net.hostsharing.hsadminng.hs.booking.item.validators.HsBookingItemEntityValidators.forType;
import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThat;
class HsManagedWebspaceBookingItemValidatorUnitTest { class HsManagedWebspaceBookingItemValidatorUnitTest {
final HsBookingDebitorEntity debitor = HsBookingDebitorEntity.builder()
.debitorNumber(12345)
.build();
final HsBookingProjectEntity project = HsBookingProjectEntity.builder()
.debitor(debitor)
.caption("Test-Project")
.build();
@Test @Test
void validatesProperties() { void validatesProperties() {
// given // given
final var mangedServerBookingItemEntity = HsBookingItemEntity.builder() final var mangedServerBookingItemEntity = HsBookingItemEntity.builder()
.type(MANAGED_WEBSPACE) .type(MANAGED_WEBSPACE)
.project(project)
.caption("Test Managed-Webspace")
.resources(Map.ofEntries( .resources(Map.ofEntries(
entry("CPUs", 2), entry("CPUs", 2),
entry("RAM", 25), entry("RAM", 25),
entry("SSD", 25),
entry("Traffic", 250), entry("Traffic", 250),
entry("SLA-EMail", true) entry("SLA-EMail", true)
)) ))
.build(); .build();
final var validator = forType(mangedServerBookingItemEntity.getType());
// when // when
final var result = validator.validate(mangedServerBookingItemEntity); final var result = HsBookingItemEntityValidator.doValidate(mangedServerBookingItemEntity);
// then // then
assertThat(result).containsExactlyInAnyOrder( assertThat(result).containsExactlyInAnyOrder(
"'resources.CPUs' is not expected but is set to '2'", "D-12345:Test-Project:Test Managed-Webspace.resources.CPUs is not expected but is set to '2'",
"'resources.SLA-EMail' is not expected but is set to 'true'", "D-12345:Test-Project:Test Managed-Webspace.resources.RAM is not expected but is set to '25'",
"'resources.RAM' is not expected but is set to '25'"); "D-12345:Test-Project:Test Managed-Webspace.resources.SSD is required but missing",
"D-12345:Test-Project:Test Managed-Webspace.resources.SLA-EMail is not expected but is set to 'true'"
);
} }
@Test @Test
void containsAllValidations() { void containsAllValidations() {
// when // when
final var validator = forType(MANAGED_WEBSPACE); final var validator = HsBookingItemEntityValidatorRegistry.forType(MANAGED_WEBSPACE);
// then // then
assertThat(validator.properties()).map(Map::toString).containsExactlyInAnyOrder( assertThat(validator.properties()).map(Map::toString).containsExactlyInAnyOrder(
"{type=integer, propertyName=SSD, required=true, unit=GB, min=1, max=100, step=1}", "{type=integer, propertyName=SSD, unit=GB, min=1, max=100, step=1, required=true, isTotalsValidator=false}",
"{type=integer, propertyName=HDD, required=false, unit=GB, min=0, max=250, step=10}", "{type=integer, propertyName=HDD, unit=GB, min=0, max=250, step=10, required=false, isTotalsValidator=false}",
"{type=integer, propertyName=Traffic, required=true, unit=GB, min=10, max=1000, step=10}", "{type=integer, propertyName=Traffic, unit=GB, min=10, max=1000, step=10, required=true, isTotalsValidator=false}",
"{type=enumeration, propertyName=SLA-Platform, required=false, values=[BASIC, EXT24H]}", "{type=integer, propertyName=Multi, min=1, max=100, step=1, required=false, defaultValue=1, isTotalsValidator=false}",
"{type=integer, propertyName=Daemons, required=false, unit=null, min=0, max=10, step=null}", "{type=integer, propertyName=Daemons, min=0, max=10, required=false, defaultValue=0, isTotalsValidator=false}",
"{type=boolean, propertyName=Online Office Server, required=false, falseIf=null}"); "{type=boolean, propertyName=Online Office Server, required=false, isTotalsValidator=false}",
"{type=enumeration, propertyName=SLA-Platform, values=[BASIC, EXT24H], required=false, defaultValue=BASIC, isTotalsValidator=false}");
} }
} }

View File

@ -0,0 +1,102 @@
package net.hostsharing.hsadminng.hs.booking.item.validators;
import net.hostsharing.hsadminng.hs.booking.item.HsBookingItemEntity;
import org.junit.jupiter.api.Test;
import static java.util.List.of;
import static java.util.Map.entry;
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.MANAGED_SERVER;
import static net.hostsharing.hsadminng.hs.booking.item.HsBookingItemType.PRIVATE_CLOUD;
import static org.assertj.core.api.Assertions.assertThat;
class HsPrivateCloudBookingItemValidatorTest {
@Test
void validatesPropertyTotals() {
// given
final var privateCloudBookingItemEntity = HsBookingItemEntity.builder()
.type(PRIVATE_CLOUD)
.resources(ofEntries(
entry("CPUs", 4),
entry("RAM", 20),
entry("SSD", 100),
entry("Traffic", 5000)
))
.subBookingItems(of(
HsBookingItemEntity.builder()
.type(MANAGED_SERVER)
.resources(ofEntries(
entry("CPUs", 2),
entry("RAM", 10),
entry("SSD", 50),
entry("Traffic", 2500)
))
.build(),
HsBookingItemEntity.builder()
.type(CLOUD_SERVER)
.resources(ofEntries(
entry("CPUs", 2),
entry("RAM", 10),
entry("SSD", 50),
entry("Traffic", 2500)
))
.build()
))
.build();
// when
final var result = HsBookingItemEntityValidator.doValidate(privateCloudBookingItemEntity);
// then
assertThat(result).isEmpty();
}
@Test
void validatesExceedingPropertyTotals() {
// given
final var privateCloudBookingItemEntity = HsBookingItemEntity.builder()
.type(PRIVATE_CLOUD)
.resources(ofEntries(
entry("CPUs", 4),
entry("RAM", 20),
entry("SSD", 100),
entry("Traffic", 5000)
))
.subBookingItems(of(
HsBookingItemEntity.builder()
.type(MANAGED_SERVER)
.resources(ofEntries(
entry("CPUs", 3),
entry("RAM", 20),
entry("SSD", 100),
entry("Traffic", 3000)
))
.build(),
HsBookingItemEntity.builder()
.type(CLOUD_SERVER)
.resources(ofEntries(
entry("CPUs", 2),
entry("RAM", 10),
entry("SSD", 50),
entry("Traffic", 2500)
))
.build()
))
.build();
// when
final var result = HsBookingItemEntityValidator.doValidate(privateCloudBookingItemEntity);
// then
assertThat(result).containsExactlyInAnyOrder(
"total CPUs is 5 exceeds max total CPUs 4",
"total RAM is 30 GB exceeds max total RAM 20 GB",
"total SSD is 150 GB exceeds max total SSD 100 GB",
"total Traffic is 5500 GB exceeds max total Traffic 5000 GB"
);
}
}

View File

@ -4,7 +4,7 @@ import net.hostsharing.hsadminng.context.Context;
import net.hostsharing.hsadminng.hs.booking.debitor.HsBookingDebitorRepository; import net.hostsharing.hsadminng.hs.booking.debitor.HsBookingDebitorRepository;
import net.hostsharing.hsadminng.rbac.rbacgrant.RawRbacGrantRepository; import net.hostsharing.hsadminng.rbac.rbacgrant.RawRbacGrantRepository;
import net.hostsharing.hsadminng.rbac.rbacrole.RawRbacRoleRepository; import net.hostsharing.hsadminng.rbac.rbacrole.RawRbacRoleRepository;
import net.hostsharing.hsadminng.rbac.test.Array; import net.hostsharing.hsadminng.mapper.Array;
import net.hostsharing.hsadminng.rbac.test.ContextBasedTestWithCleanup; import net.hostsharing.hsadminng.rbac.test.ContextBasedTestWithCleanup;
import net.hostsharing.hsadminng.rbac.test.JpaAttempt; import net.hostsharing.hsadminng.rbac.test.JpaAttempt;
import org.junit.jupiter.api.Nested; import org.junit.jupiter.api.Nested;
@ -23,7 +23,7 @@ import java.util.List;
import static net.hostsharing.hsadminng.rbac.rbacgrant.RawRbacGrantEntity.distinctGrantDisplaysOf; import static net.hostsharing.hsadminng.rbac.rbacgrant.RawRbacGrantEntity.distinctGrantDisplaysOf;
import static net.hostsharing.hsadminng.rbac.rbacrole.RawRbacRoleEntity.distinctRoleNamesOf; import static net.hostsharing.hsadminng.rbac.rbacrole.RawRbacRoleEntity.distinctRoleNamesOf;
import static net.hostsharing.hsadminng.rbac.test.Array.fromFormatted; import static net.hostsharing.hsadminng.mapper.Array.fromFormatted;
import static net.hostsharing.hsadminng.rbac.test.JpaAttempt.attempt; import static net.hostsharing.hsadminng.rbac.test.JpaAttempt.attempt;
import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThat;

View File

@ -5,6 +5,7 @@ import io.restassured.http.ContentType;
import net.hostsharing.hsadminng.HsadminNgApplication; import net.hostsharing.hsadminng.HsadminNgApplication;
import net.hostsharing.hsadminng.hs.booking.item.HsBookingItemEntity; import net.hostsharing.hsadminng.hs.booking.item.HsBookingItemEntity;
import net.hostsharing.hsadminng.hs.booking.item.HsBookingItemRepository; import net.hostsharing.hsadminng.hs.booking.item.HsBookingItemRepository;
import net.hostsharing.hsadminng.hs.booking.item.HsBookingItemType;
import net.hostsharing.hsadminng.hs.booking.project.HsBookingProjectRepository; import net.hostsharing.hsadminng.hs.booking.project.HsBookingProjectRepository;
import net.hostsharing.hsadminng.hs.office.debitor.HsOfficeDebitorRepository; import net.hostsharing.hsadminng.hs.office.debitor.HsOfficeDebitorRepository;
import net.hostsharing.hsadminng.rbac.test.ContextBasedTestWithCleanup; import net.hostsharing.hsadminng.rbac.test.ContextBasedTestWithCleanup;
@ -20,8 +21,9 @@ import java.util.Map;
import java.util.UUID; import java.util.UUID;
import static java.util.Map.entry; import static java.util.Map.entry;
import static net.hostsharing.hsadminng.hs.hosting.asset.HsHostingAssetType.CLOUD_SERVER;
import static net.hostsharing.hsadminng.hs.hosting.asset.HsHostingAssetType.MANAGED_SERVER; import static net.hostsharing.hsadminng.hs.hosting.asset.HsHostingAssetType.MANAGED_SERVER;
import static net.hostsharing.hsadminng.hs.hosting.asset.HsHostingAssetType.MANAGED_WEBSPACE;
import static net.hostsharing.hsadminng.hs.hosting.asset.HsHostingAssetType.UNIX_USER;
import static net.hostsharing.hsadminng.rbac.test.JsonMatcher.lenientlyEquals; import static net.hostsharing.hsadminng.rbac.test.JsonMatcher.lenientlyEquals;
import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThat;
import static org.hamcrest.Matchers.matchesRegex; import static org.hamcrest.Matchers.matchesRegex;
@ -77,25 +79,19 @@ class HsHostingAssetControllerAcceptanceTest extends ContextBasedTestWithCleanup
"type": "MANAGED_WEBSPACE", "type": "MANAGED_WEBSPACE",
"identifier": "sec01", "identifier": "sec01",
"caption": "some Webspace", "caption": "some Webspace",
"config": { "config": {}
"extra": 42
}
}, },
{ {
"type": "MANAGED_WEBSPACE", "type": "MANAGED_WEBSPACE",
"identifier": "fir01", "identifier": "fir01",
"caption": "some Webspace", "caption": "some Webspace",
"config": { "config": {}
"extra": 42
}
}, },
{ {
"type": "MANAGED_WEBSPACE", "type": "MANAGED_WEBSPACE",
"identifier": "thi01", "identifier": "thi01",
"caption": "some Webspace", "caption": "some Webspace",
"config": { "config": {}
"extra": 42
}
} }
] ]
""")); """));
@ -113,7 +109,7 @@ class HsHostingAssetControllerAcceptanceTest extends ContextBasedTestWithCleanup
.header("current-user", "superuser-alex@hostsharing.net") .header("current-user", "superuser-alex@hostsharing.net")
.port(port) .port(port)
.when() .when()
.get("http://localhost/api/hs/hosting/assets?type=" + MANAGED_SERVER) . get("http://localhost/api/hs/hosting/assets?type=" + MANAGED_SERVER)
.then().log().all().assertThat() .then().log().all().assertThat()
.statusCode(200) .statusCode(200)
.contentType("application/json") .contentType("application/json")
@ -124,7 +120,9 @@ class HsHostingAssetControllerAcceptanceTest extends ContextBasedTestWithCleanup
"identifier": "vm1011", "identifier": "vm1011",
"caption": "some ManagedServer", "caption": "some ManagedServer",
"config": { "config": {
"extra": 42 "monit_max_cpu_usage": 90,
"monit_max_ram_usage": 80,
"monit_max_ssd_usage": 70
} }
}, },
{ {
@ -132,7 +130,9 @@ class HsHostingAssetControllerAcceptanceTest extends ContextBasedTestWithCleanup
"identifier": "vm1012", "identifier": "vm1012",
"caption": "some ManagedServer", "caption": "some ManagedServer",
"config": { "config": {
"extra": 42 "monit_max_cpu_usage": 90,
"monit_max_ram_usage": 80,
"monit_max_ssd_usage": 70
} }
}, },
{ {
@ -140,7 +140,9 @@ class HsHostingAssetControllerAcceptanceTest extends ContextBasedTestWithCleanup
"identifier": "vm1013", "identifier": "vm1013",
"caption": "some ManagedServer", "caption": "some ManagedServer",
"config": { "config": {
"extra": 42 "monit_max_cpu_usage": 90,
"monit_max_ram_usage": 80,
"monit_max_ssd_usage": 70
} }
} }
] ]
@ -156,7 +158,14 @@ class HsHostingAssetControllerAcceptanceTest extends ContextBasedTestWithCleanup
void globalAdmin_canAddBookedAsset() { void globalAdmin_canAddBookedAsset() {
context.define("superuser-alex@hostsharing.net"); context.define("superuser-alex@hostsharing.net");
final var givenBookingItem = givenBookingItem("D-1000111 default project", "some PrivateCloud"); final var givenBookingItem = newBookingItem("D-1000111 default project",
HsBookingItemType.MANAGED_WEBSPACE, "separate ManagedWebspace BI",
Map.ofEntries(
entry("SSD", 50),
entry("Traffic", 50)
)
);
final var givenParentAsset = givenParentAsset(MANAGED_SERVER, "vm1011");
final var location = RestAssured // @formatter:off final var location = RestAssured // @formatter:off
.given() .given()
@ -165,12 +174,13 @@ class HsHostingAssetControllerAcceptanceTest extends ContextBasedTestWithCleanup
.body(""" .body("""
{ {
"bookingItemUuid": "%s", "bookingItemUuid": "%s",
"type": "MANAGED_SERVER", "type": "MANAGED_WEBSPACE",
"identifier": "vm1400", "identifier": "fir10",
"caption": "some new ManagedServer", "parentAssetUuid": "%s",
"config": { "monit_max_ssd_usage": 80, "monit_max_cpu_usage": 90, "monit_max_ram_usage": 70 } "caption": "some separate ManagedWebspace HA",
"config": {}
} }
""".formatted(givenBookingItem.getUuid())) """.formatted(givenBookingItem.getUuid(), givenParentAsset.getUuid()))
.port(port) .port(port)
.when() .when()
.post("http://localhost/api/hs/hosting/assets") .post("http://localhost/api/hs/hosting/assets")
@ -179,10 +189,10 @@ class HsHostingAssetControllerAcceptanceTest extends ContextBasedTestWithCleanup
.contentType(ContentType.JSON) .contentType(ContentType.JSON)
.body("", lenientlyEquals(""" .body("", lenientlyEquals("""
{ {
"type": "MANAGED_SERVER", "type": "MANAGED_WEBSPACE",
"identifier": "vm1400", "identifier": "fir10",
"caption": "some new ManagedServer", "caption": "some separate ManagedWebspace HA",
"config": { "monit_max_ssd_usage": 80, "monit_max_cpu_usage": 90, "monit_max_ram_usage": 70 } "config": {}
} }
""")) """))
.header("Location", matchesRegex("http://localhost:[1-9][0-9]*/api/hs/hosting/assets/[^/]*")) .header("Location", matchesRegex("http://localhost:[1-9][0-9]*/api/hs/hosting/assets/[^/]*"))
@ -192,6 +202,7 @@ class HsHostingAssetControllerAcceptanceTest extends ContextBasedTestWithCleanup
final var newUserUuid = UUID.fromString( final var newUserUuid = UUID.fromString(
location.substring(location.lastIndexOf('/') + 1)); location.substring(location.lastIndexOf('/') + 1));
assertThat(newUserUuid).isNotNull(); assertThat(newUserUuid).isNotNull();
toCleanup(HsHostingAssetEntity.class, newUserUuid);
} }
@Test @Test
@ -240,7 +251,7 @@ class HsHostingAssetControllerAcceptanceTest extends ContextBasedTestWithCleanup
} }
@Test @Test
void additionalValidationsArePerformend_whenAddingAsset() { void propertyValidationsArePerformend_whenAddingAsset() {
context.define("superuser-alex@hostsharing.net"); context.define("superuser-alex@hostsharing.net");
final var givenBookingItem = givenBookingItem("D-1000111 default project", "some PrivateCloud"); final var givenBookingItem = givenBookingItem("D-1000111 default project", "some PrivateCloud");
@ -267,9 +278,66 @@ class HsHostingAssetControllerAcceptanceTest extends ContextBasedTestWithCleanup
.body("", lenientlyEquals(""" .body("", lenientlyEquals("""
{ {
"statusPhrase": "Bad Request", "statusPhrase": "Bad Request",
"message": "['config.extra' is not expected but is set to '42', 'config.monit_max_ssd_usage' is expected to be >= 10 but is 0, 'config.monit_max_cpu_usage' is expected to be <= 100 but is 101, 'config.monit_max_ram_usage' is required but missing]" "message": "[
<<<MANAGED_SERVER:vm1400.config.extra is not expected but is set to '42',
<<<MANAGED_SERVER:vm1400.config.monit_max_ssd_usage is expected to be >= 10 but is 0,
<<<MANAGED_SERVER:vm1400.config.monit_max_cpu_usage is expected to be <= 100 but is 101,
<<<MANAGED_SERVER:vm1400.config.monit_max_ram_usage is required but missing
<<<]"
} }
""")); // @formatter:on """.replaceAll(" +<<<", ""))); // @formatter:on
}
@Test
void totalsLimitValidationsArePerformend_whenAddingAsset() {
context.define("superuser-alex@hostsharing.net");
final var givenHostingAsset = givenHostingAsset(MANAGED_WEBSPACE, "fir01");
assertThat(givenHostingAsset.getBookingItem().getResources().get("Multi"))
.as("precondition failed")
.isEqualTo(1);
jpaAttempt.transacted(() -> {
context.define("superuser-alex@hostsharing.net");
for (int n = 0; n < 25; ++n ) {
toCleanup(assetRepo.save(
HsHostingAssetEntity.builder()
.type(UNIX_USER)
.parentAsset(givenHostingAsset)
.identifier("fir01-%2d".formatted(n))
.caption("Test UnixUser fir01-%2d".formatted(n))
.build()));
}
}).assertSuccessful();
RestAssured // @formatter:off
.given()
.header("current-user", "superuser-alex@hostsharing.net")
.contentType(ContentType.JSON)
.body("""
{
"parentAssetUuid": "%s",
"type": "UNIX_USER",
"identifier": "fir01-extra",
"caption": "some extra UnixUser",
"config": { }
}
""".formatted(givenHostingAsset.getUuid()))
.port(port)
.when()
.post("http://localhost/api/hs/hosting/assets")
.then().log().all().assertThat()
.statusCode(400)
.contentType(ContentType.JSON)
.body("", lenientlyEquals("""
{
"statusPhrase": "Bad Request",
"message": "[
<<<UNIX_USER:fir01-extra.parentAsset.MANAGED_WEBSPACE:fir01.bookingItem.Multi=1 allows at maximum 25 unix users, but 26 found
<<<]"
}
""".replaceAll(" +<<<", ""))); // @formatter:on
} }
} }
@ -295,9 +363,7 @@ class HsHostingAssetControllerAcceptanceTest extends ContextBasedTestWithCleanup
.body("", lenientlyEquals(""" .body("", lenientlyEquals("""
{ {
"caption": "some ManagedServer", "caption": "some ManagedServer",
"config": { "config": {}
"extra": 42
}
} }
""")); // @formatter:on """)); // @formatter:on
} }
@ -340,9 +406,7 @@ class HsHostingAssetControllerAcceptanceTest extends ContextBasedTestWithCleanup
{ {
"identifier": "vm1013", "identifier": "vm1013",
"caption": "some ManagedServer", "caption": "some ManagedServer",
"config": { "config": {}
"extra": 42
}
} }
""")); // @formatter:on """)); // @formatter:on
} }
@ -443,6 +507,29 @@ class HsHostingAssetControllerAcceptanceTest extends ContextBasedTestWithCleanup
} }
} }
HsHostingAssetEntity givenHostingAsset(final HsHostingAssetType type, final String identifier) {
return assetRepo.findByIdentifier(identifier).stream()
.filter(ha -> ha.getType()==type)
.findAny().orElseThrow();
}
HsBookingItemEntity newBookingItem(
final String projectCaption,
final HsBookingItemType type, final String bookingItemCaption, final Map<String, Object> resources) {
return jpaAttempt.transacted(() -> {
context.define("superuser-alex@hostsharing.net");
final var project = projectRepo.findByCaption(projectCaption).stream()
.findAny().orElseThrow();
final var bookingItem = HsBookingItemEntity.builder()
.project(project)
.type(type)
.caption(bookingItemCaption)
.resources(resources)
.build();
return toCleanup(bookingItemRepo.save(bookingItem));
}).assertSuccessful().returnedValue();
}
HsBookingItemEntity givenBookingItem(final String projectCaption, final String bookingItemCaption) { HsBookingItemEntity givenBookingItem(final String projectCaption, final String bookingItemCaption) {
return bookingItemRepo.findByCaption(bookingItemCaption).stream() return bookingItemRepo.findByCaption(bookingItemCaption).stream()
.filter(bi -> bi.getRelatedProject().getCaption().contains(projectCaption)) .filter(bi -> bi.getRelatedProject().getCaption().contains(projectCaption))

View File

@ -40,11 +40,11 @@ class HsHostingAssetEntityPatcherUnitTest extends PatchUnitTestBase<
private static final Map<String, Object> PATCH_CONFIG = patchMap( private static final Map<String, Object> PATCH_CONFIG = patchMap(
entry("CPU", 2), entry("CPU", 2),
entry("HDD", null), entry("HDD", null),
entry("SDD", 256) entry("SSD", 256)
); );
private static final Map<String, Object> PATCHED_CONFIG = patchMap( private static final Map<String, Object> PATCHED_CONFIG = patchMap(
entry("CPU", 2), entry("CPU", 2),
entry("SDD", 256), entry("SSD", 256),
entry("MEM", 64) entry("MEM", 64)
); );

View File

@ -33,7 +33,8 @@ class HsHostingAssetPropsControllerAcceptanceTest {
[ [
"MANAGED_SERVER", "MANAGED_SERVER",
"MANAGED_WEBSPACE", "MANAGED_WEBSPACE",
"CLOUD_SERVER" "CLOUD_SERVER",
"UNIX_USER"
] ]
""")); """));
// @formatter:on // @formatter:on
@ -55,56 +56,54 @@ class HsHostingAssetPropsControllerAcceptanceTest {
{ {
"type": "integer", "type": "integer",
"propertyName": "monit_min_free_ssd", "propertyName": "monit_min_free_ssd",
"required": false,
"unit": null,
"min": 1, "min": 1,
"max": 1000, "max": 1000,
"step": null "required": false,
"isTotalsValidator": false
}, },
{ {
"type": "integer", "type": "integer",
"propertyName": "monit_min_free_hdd", "propertyName": "monit_min_free_hdd",
"required": false,
"unit": null,
"min": 1, "min": 1,
"max": 4000, "max": 4000,
"step": null "required": false,
"isTotalsValidator": false
}, },
{ {
"type": "integer", "type": "integer",
"propertyName": "monit_max_ssd_usage", "propertyName": "monit_max_ssd_usage",
"required": true,
"unit": "%", "unit": "%",
"min": 10, "min": 10,
"max": 100, "max": 100,
"step": null "required": true,
"isTotalsValidator": false
}, },
{ {
"type": "integer", "type": "integer",
"propertyName": "monit_max_hdd_usage", "propertyName": "monit_max_hdd_usage",
"required": false,
"unit": "%", "unit": "%",
"min": 10, "min": 10,
"max": 100, "max": 100,
"step": null "required": false,
"isTotalsValidator": false
}, },
{ {
"type": "integer", "type": "integer",
"propertyName": "monit_max_cpu_usage", "propertyName": "monit_max_cpu_usage",
"required": true,
"unit": "%", "unit": "%",
"min": 10, "min": 10,
"max": 100, "max": 100,
"step": null "required": true,
"isTotalsValidator": false
}, },
{ {
"type": "integer", "type": "integer",
"propertyName": "monit_max_ram_usage", "propertyName": "monit_max_ram_usage",
"required": true,
"unit": "%", "unit": "%",
"min": 10, "min": 10,
"max": 100, "max": 100,
"step": null "required": true,
"isTotalsValidator": false
} }
] ]
""")); """));

View File

@ -3,10 +3,11 @@ package net.hostsharing.hsadminng.hs.hosting.asset;
import net.hostsharing.hsadminng.context.Context; import net.hostsharing.hsadminng.context.Context;
import net.hostsharing.hsadminng.hs.booking.item.HsBookingItemEntity; import net.hostsharing.hsadminng.hs.booking.item.HsBookingItemEntity;
import net.hostsharing.hsadminng.hs.booking.item.HsBookingItemRepository; import net.hostsharing.hsadminng.hs.booking.item.HsBookingItemRepository;
import net.hostsharing.hsadminng.hs.booking.item.HsBookingItemType;
import net.hostsharing.hsadminng.hs.booking.project.HsBookingProjectRepository; import net.hostsharing.hsadminng.hs.booking.project.HsBookingProjectRepository;
import net.hostsharing.hsadminng.rbac.rbacgrant.RawRbacGrantRepository; import net.hostsharing.hsadminng.rbac.rbacgrant.RawRbacGrantRepository;
import net.hostsharing.hsadminng.rbac.rbacrole.RawRbacRoleRepository; import net.hostsharing.hsadminng.rbac.rbacrole.RawRbacRoleRepository;
import net.hostsharing.hsadminng.rbac.test.Array; import net.hostsharing.hsadminng.mapper.Array;
import net.hostsharing.hsadminng.rbac.test.ContextBasedTestWithCleanup; import net.hostsharing.hsadminng.rbac.test.ContextBasedTestWithCleanup;
import net.hostsharing.hsadminng.rbac.test.JpaAttempt; import net.hostsharing.hsadminng.rbac.test.JpaAttempt;
import org.junit.jupiter.api.Nested; import org.junit.jupiter.api.Nested;
@ -30,7 +31,7 @@ import static net.hostsharing.hsadminng.hs.hosting.asset.HsHostingAssetType.MANA
import static net.hostsharing.hsadminng.hs.hosting.asset.HsHostingAssetType.MANAGED_WEBSPACE; import static net.hostsharing.hsadminng.hs.hosting.asset.HsHostingAssetType.MANAGED_WEBSPACE;
import static net.hostsharing.hsadminng.rbac.rbacgrant.RawRbacGrantEntity.distinctGrantDisplaysOf; import static net.hostsharing.hsadminng.rbac.rbacgrant.RawRbacGrantEntity.distinctGrantDisplaysOf;
import static net.hostsharing.hsadminng.rbac.rbacrole.RawRbacRoleEntity.distinctRoleNamesOf; import static net.hostsharing.hsadminng.rbac.rbacrole.RawRbacRoleEntity.distinctRoleNamesOf;
import static net.hostsharing.hsadminng.rbac.test.Array.fromFormatted; import static net.hostsharing.hsadminng.mapper.Array.fromFormatted;
import static net.hostsharing.hsadminng.rbac.test.JpaAttempt.attempt; import static net.hostsharing.hsadminng.rbac.test.JpaAttempt.attempt;
import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThat;
@ -70,12 +71,13 @@ class HsHostingAssetRepositoryIntegrationTest extends ContextBasedTestWithCleanu
// given // given
context("superuser-alex@hostsharing.net"); context("superuser-alex@hostsharing.net");
final var count = assetRepo.count(); final var count = assetRepo.count();
final var givenManagedServer = givenManagedServer("D-1000111 default project", MANAGED_SERVER); final var givenManagedServer = givenHostingAsset("D-1000111 default project", MANAGED_SERVER);
final var newWebspaceBookingItem = newBookingItem(givenManagedServer.getBookingItem(), HsBookingItemType.MANAGED_WEBSPACE, "fir01");
// when // when
final var result = attempt(em, () -> { final var result = attempt(em, () -> {
final var newAsset = HsHostingAssetEntity.builder() final var newAsset = HsHostingAssetEntity.builder()
.bookingItem(givenManagedServer.getBookingItem()) .bookingItem(newWebspaceBookingItem)
.parentAsset(givenManagedServer) .parentAsset(givenManagedServer)
.caption("some new managed webspace") .caption("some new managed webspace")
.type(MANAGED_WEBSPACE) .type(MANAGED_WEBSPACE)
@ -95,18 +97,19 @@ class HsHostingAssetRepositoryIntegrationTest extends ContextBasedTestWithCleanu
public void createsAndGrantsRoles() { public void createsAndGrantsRoles() {
// given // given
context("superuser-alex@hostsharing.net"); context("superuser-alex@hostsharing.net");
final var givenManagedServer = givenHostingAsset("D-1000111 default project", MANAGED_SERVER);
final var newWebspaceBookingItem = newBookingItem(givenManagedServer.getBookingItem(), HsBookingItemType.MANAGED_WEBSPACE, "fir01");
em.flush();
final var initialRoleNames = distinctRoleNamesOf(rawRoleRepo.findAll()); final var initialRoleNames = distinctRoleNamesOf(rawRoleRepo.findAll());
final var initialGrantNames = distinctGrantDisplaysOf(rawGrantRepo.findAll()).stream() final var initialGrantNames = distinctGrantDisplaysOf(rawGrantRepo.findAll());
.map(s -> s.replace("hs_office_", ""))
.toList();
final var givenBookingItem = givenBookingItem("D-1000111 default project", "some PrivateCloud");
// when // when
final var result = attempt(em, () -> { final var result = attempt(em, () -> {
final var newAsset = HsHostingAssetEntity.builder() final var newAsset = HsHostingAssetEntity.builder()
.bookingItem(givenBookingItem) .bookingItem(newWebspaceBookingItem)
.type(HsHostingAssetType.MANAGED_SERVER) .parentAsset(givenManagedServer)
.identifier("vm9000") .type(HsHostingAssetType.MANAGED_WEBSPACE)
.identifier("fir00")
.caption("some new managed webspace") .caption("some new managed webspace")
.build(); .build();
return toCleanup(assetRepo.save(newAsset)); return toCleanup(assetRepo.save(newAsset));
@ -117,29 +120,33 @@ class HsHostingAssetRepositoryIntegrationTest extends ContextBasedTestWithCleanu
final var all = rawRoleRepo.findAll(); final var all = rawRoleRepo.findAll();
assertThat(distinctRoleNamesOf(all)).containsExactlyInAnyOrder(Array.from( assertThat(distinctRoleNamesOf(all)).containsExactlyInAnyOrder(Array.from(
initialRoleNames, initialRoleNames,
"hs_hosting_asset#vm9000:OWNER", "hs_hosting_asset#fir00:ADMIN",
"hs_hosting_asset#vm9000:ADMIN", "hs_hosting_asset#fir00:AGENT",
"hs_hosting_asset#vm9000:AGENT", "hs_hosting_asset#fir00:OWNER",
"hs_hosting_asset#vm9000:TENANT")); "hs_hosting_asset#fir00:TENANT"));
assertThat(distinctGrantDisplaysOf(rawGrantRepo.findAll())) assertThat(distinctGrantDisplaysOf(rawGrantRepo.findAll()))
.map(s -> s.replace("hs_office_", ""))
.containsExactlyInAnyOrder(fromFormatted( .containsExactlyInAnyOrder(fromFormatted(
initialGrantNames, initialGrantNames,
// owner // owner
"{ grant role:hs_hosting_asset#vm9000:OWNER to role:hs_booking_item#somePrivateCloud:ADMIN by system and assume }", "{ grant role:hs_hosting_asset#fir00:OWNER to role:hs_booking_item#fir01:ADMIN by system and assume }",
"{ grant perm:hs_hosting_asset#vm9000:DELETE to role:hs_hosting_asset#vm9000:OWNER by system and assume }", "{ grant role:hs_hosting_asset#fir00:OWNER to role:hs_hosting_asset#vm1011:ADMIN by system and assume }",
"{ grant role:hs_hosting_asset#vm9000:ADMIN to role:hs_hosting_asset#vm9000:OWNER by system and assume }", "{ grant perm:hs_hosting_asset#fir00:DELETE to role:hs_hosting_asset#fir00:OWNER by system and assume }",
// admin // admin
"{ grant perm:hs_hosting_asset#vm9000:INSERT>hs_hosting_asset to role:hs_hosting_asset#vm9000:ADMIN by system and assume }", "{ grant role:hs_hosting_asset#fir00:ADMIN to role:hs_hosting_asset#fir00:OWNER by system and assume }",
"{ grant perm:hs_hosting_asset#vm9000:UPDATE to role:hs_hosting_asset#vm9000:ADMIN by system and assume }", "{ grant role:hs_hosting_asset#fir00:ADMIN to role:hs_booking_item#fir01:AGENT by system and assume }",
"{ grant role:hs_hosting_asset#vm9000:ADMIN to role:hs_booking_item#somePrivateCloud:AGENT by system and assume }", "{ grant perm:hs_hosting_asset#fir00:INSERT>hs_hosting_asset to role:hs_hosting_asset#fir00:ADMIN by system and assume }",
"{ grant role:hs_hosting_asset#vm9000:TENANT to role:hs_hosting_asset#vm9000:AGENT by system and assume }", "{ grant perm:hs_hosting_asset#fir00:UPDATE to role:hs_hosting_asset#fir00:ADMIN by system and assume }",
"{ grant role:hs_hosting_asset#vm9000:AGENT to role:hs_hosting_asset#vm9000:ADMIN by system and assume }",
// agent
"{ grant role:hs_hosting_asset#fir00:ADMIN to role:hs_hosting_asset#vm1011:AGENT by system and assume }",
"{ grant role:hs_hosting_asset#fir00:AGENT to role:hs_hosting_asset#fir00:ADMIN by system and assume }",
// tenant // tenant
"{ grant role:hs_booking_item#somePrivateCloud:TENANT to role:hs_hosting_asset#vm9000:TENANT by system and assume }", "{ grant role:hs_booking_item#fir01:TENANT to role:hs_hosting_asset#fir00:TENANT by system and assume }",
"{ grant role:hs_hosting_asset#fir00:TENANT to role:hs_hosting_asset#fir00:AGENT by system and assume }",
"{ grant role:hs_hosting_asset#vm1011:TENANT to role:hs_hosting_asset#fir00:TENANT by system and assume }",
null)); null));
} }
@ -164,9 +171,9 @@ class HsHostingAssetRepositoryIntegrationTest extends ContextBasedTestWithCleanu
// then // then
allTheseServersAreReturned( allTheseServersAreReturned(
result, result,
"HsHostingAssetEntity(MANAGED_WEBSPACE, sec01, some Webspace, MANAGED_SERVER:vm1012, D-1000212:D-1000212 default project:separate ManagedServer, { extra: 42 })", "HsHostingAssetEntity(MANAGED_WEBSPACE, sec01, some Webspace, MANAGED_SERVER:vm1012, D-1000212:D-1000212 default project:separate ManagedWebspace)",
"HsHostingAssetEntity(MANAGED_WEBSPACE, thi01, some Webspace, MANAGED_SERVER:vm1013, D-1000313:D-1000313 default project:separate ManagedServer, { extra: 42 })", "HsHostingAssetEntity(MANAGED_WEBSPACE, fir01, some Webspace, MANAGED_SERVER:vm1011, D-1000111:D-1000111 default project:separate ManagedWebspace)",
"HsHostingAssetEntity(MANAGED_WEBSPACE, fir01, some Webspace, MANAGED_SERVER:vm1011, D-1000111:D-1000111 default project:separate ManagedServer, { extra: 42 })"); "HsHostingAssetEntity(MANAGED_WEBSPACE, thi01, some Webspace, MANAGED_SERVER:vm1013, D-1000313:D-1000313 default project:separate ManagedWebspace)");
} }
@Test @Test
@ -182,9 +189,8 @@ class HsHostingAssetRepositoryIntegrationTest extends ContextBasedTestWithCleanu
// then: // then:
exactlyTheseAssetsAreReturned( exactlyTheseAssetsAreReturned(
result, result,
"HsHostingAssetEntity(MANAGED_WEBSPACE, fir01, some Webspace, MANAGED_SERVER:vm1011, D-1000111:D-1000111 default project:separate ManagedServer, { extra: 42 })", "HsHostingAssetEntity(MANAGED_WEBSPACE, fir01, some Webspace, MANAGED_SERVER:vm1011, D-1000111:D-1000111 default project:separate ManagedWebspace)",
"HsHostingAssetEntity(MANAGED_SERVER, vm1011, some ManagedServer, D-1000111:D-1000111 default project:some PrivateCloud, { extra: 42 })", "HsHostingAssetEntity(MANAGED_SERVER, vm1011, some ManagedServer, D-1000111:D-1000111 default project:separate ManagedServer, { monit_max_cpu_usage: 90, monit_max_ram_usage: 80, monit_max_ssd_usage: 70 })");
"HsHostingAssetEntity(CLOUD_SERVER, vm2011, another CloudServer, D-1000111:D-1000111 default project:some PrivateCloud, { extra: 42 })");
} }
@Test @Test
@ -200,7 +206,7 @@ class HsHostingAssetRepositoryIntegrationTest extends ContextBasedTestWithCleanu
// then // then
allTheseServersAreReturned( allTheseServersAreReturned(
result, result,
"HsHostingAssetEntity(MANAGED_WEBSPACE, thi01, some Webspace, MANAGED_SERVER:vm1013, D-1000313:D-1000313 default project:separate ManagedServer, { extra: 42 })"); "HsHostingAssetEntity(MANAGED_WEBSPACE, sec01, some Webspace, MANAGED_SERVER:vm1012, D-1000212:D-1000212 default project:separate ManagedWebspace)");
} }
} }
@ -351,7 +357,7 @@ class HsHostingAssetRepositoryIntegrationTest extends ContextBasedTestWithCleanu
private HsHostingAssetEntity givenSomeTemporaryAsset(final String projectCaption, final String identifier) { private HsHostingAssetEntity givenSomeTemporaryAsset(final String projectCaption, final String identifier) {
return jpaAttempt.transacted(() -> { return jpaAttempt.transacted(() -> {
context("superuser-alex@hostsharing.net"); context("superuser-alex@hostsharing.net");
final var givenBookingItem = givenBookingItem("D-1000111 default project", "some PrivateCloud"); final var givenBookingItem = givenBookingItem("D-1000111 default project", "test CloudServer");
final var newAsset = HsHostingAssetEntity.builder() final var newAsset = HsHostingAssetEntity.builder()
.bookingItem(givenBookingItem) .bookingItem(givenBookingItem)
.type(CLOUD_SERVER) .type(CLOUD_SERVER)
@ -367,20 +373,30 @@ class HsHostingAssetRepositoryIntegrationTest extends ContextBasedTestWithCleanu
} }
HsBookingItemEntity givenBookingItem(final String projectCaption, final String bookingItemCaption) { HsBookingItemEntity givenBookingItem(final String projectCaption, final String bookingItemCaption) {
final var givenProject = projectRepo.findByCaption(projectCaption).stream() return bookingItemRepo.findByCaption(bookingItemCaption).stream()
.findAny().orElseThrow(); .filter(i -> i.getRelatedProject().getCaption().equals(projectCaption))
return bookingItemRepo.findAllByProjectUuid(givenProject.getUuid()).stream()
.filter(i -> i.getCaption().equals(bookingItemCaption))
.findAny().orElseThrow(); .findAny().orElseThrow();
} }
HsHostingAssetEntity givenManagedServer(final String projectCaption, final HsHostingAssetType type) { HsHostingAssetEntity givenHostingAsset(final String projectCaption, final HsHostingAssetType type) {
final var givenProject = projectRepo.findByCaption(projectCaption).stream() final var givenProject = projectRepo.findByCaption(projectCaption).stream()
.findAny().orElseThrow(); .findAny().orElseThrow();
return assetRepo.findAllByCriteria(givenProject.getUuid(), null, type).stream() return assetRepo.findAllByCriteria(givenProject.getUuid(), null, type).stream()
.findAny().orElseThrow(); .findAny().orElseThrow();
} }
HsBookingItemEntity newBookingItem(
final HsBookingItemEntity parentBookingItem,
final HsBookingItemType type,
final String caption) {
final var newBookingItem = HsBookingItemEntity.builder()
.parentItem(parentBookingItem)
.type(type)
.caption(caption)
.build();
return toCleanup(bookingItemRepo.save(newBookingItem));
}
void exactlyTheseAssetsAreReturned( void exactlyTheseAssetsAreReturned(
final List<HsHostingAssetEntity> actualResult, final List<HsHostingAssetEntity> actualResult,
final String... serverNames) { final String... serverNames) {

View File

@ -7,7 +7,7 @@ import java.util.Map;
import static java.util.Map.entry; import static java.util.Map.entry;
import static net.hostsharing.hsadminng.hs.hosting.asset.HsHostingAssetType.CLOUD_SERVER; import static net.hostsharing.hsadminng.hs.hosting.asset.HsHostingAssetType.CLOUD_SERVER;
import static net.hostsharing.hsadminng.hs.hosting.asset.validators.HsHostingAssetEntityValidators.forType; import static net.hostsharing.hsadminng.hs.hosting.asset.validators.HsHostingAssetEntityValidator.forType;
import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThat;
class HsCloudServerHostingAssetValidatorUnitTest { class HsCloudServerHostingAssetValidatorUnitTest {
@ -17,6 +17,7 @@ class HsCloudServerHostingAssetValidatorUnitTest {
// given // given
final var cloudServerHostingAssetEntity = HsHostingAssetEntity.builder() final var cloudServerHostingAssetEntity = HsHostingAssetEntity.builder()
.type(CLOUD_SERVER) .type(CLOUD_SERVER)
.identifier("vm1234")
.config(Map.ofEntries( .config(Map.ofEntries(
entry("RAM", 2000) entry("RAM", 2000)
)) ))
@ -28,7 +29,7 @@ class HsCloudServerHostingAssetValidatorUnitTest {
final var result = validator.validate(cloudServerHostingAssetEntity); final var result = validator.validate(cloudServerHostingAssetEntity);
// then // then
assertThat(result).containsExactly("'config.RAM' is not expected but is set to '2000'"); assertThat(result).containsExactly("CLOUD_SERVER:vm1234.config.RAM is not expected but is set to '2000'");
} }
@Test @Test

View File

@ -6,27 +6,28 @@ import org.junit.jupiter.api.Test;
import jakarta.validation.ValidationException; import jakarta.validation.ValidationException;
import static net.hostsharing.hsadminng.hs.hosting.asset.HsHostingAssetType.MANAGED_SERVER; import static net.hostsharing.hsadminng.hs.hosting.asset.HsHostingAssetType.MANAGED_SERVER;
import static net.hostsharing.hsadminng.hs.hosting.asset.validators.HsHostingAssetEntityValidators.valid; import static net.hostsharing.hsadminng.hs.hosting.asset.validators.HsHostingAssetEntityValidator.validated;
import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThat;
import static org.assertj.core.api.Assertions.catchThrowable; import static org.assertj.core.api.Assertions.catchThrowable;
class HsHostingAssetEntityValidatorsUnitTest { class HsHostingAssetEntityValidatorUnitTest {
@Test @Test
void validThrowsException() { void validThrowsException() {
// given // given
final var managedServerHostingAssetEntity = HsHostingAssetEntity.builder() final var managedServerHostingAssetEntity = HsHostingAssetEntity.builder()
.type(MANAGED_SERVER) .type(MANAGED_SERVER)
.identifier("vm1234")
.build(); .build();
// when // when
final var result = catchThrowable( ()-> valid(managedServerHostingAssetEntity) ); final var result = catchThrowable( ()-> validated(managedServerHostingAssetEntity) );
// then // then
assertThat(result).isInstanceOf(ValidationException.class) assertThat(result).isInstanceOf(ValidationException.class)
.hasMessageContaining( .hasMessageContaining(
"'config.monit_max_ssd_usage' is required but missing", "MANAGED_SERVER:vm1234.config.monit_max_ssd_usage is required but missing",
"'config.monit_max_cpu_usage' is required but missing", "MANAGED_SERVER:vm1234.config.monit_max_cpu_usage is required but missing",
"'config.monit_max_ram_usage' is required but missing"); "MANAGED_SERVER:vm1234.config.monit_max_ram_usage is required but missing");
} }
} }

View File

@ -7,7 +7,7 @@ import java.util.Map;
import static java.util.Map.entry; import static java.util.Map.entry;
import static net.hostsharing.hsadminng.hs.hosting.asset.HsHostingAssetType.MANAGED_SERVER; import static net.hostsharing.hsadminng.hs.hosting.asset.HsHostingAssetType.MANAGED_SERVER;
import static net.hostsharing.hsadminng.hs.hosting.asset.validators.HsHostingAssetEntityValidators.forType; import static net.hostsharing.hsadminng.hs.hosting.asset.validators.HsHostingAssetEntityValidator.forType;
import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThat;
class HsManagedServerHostingAssetValidatorUnitTest { class HsManagedServerHostingAssetValidatorUnitTest {
@ -17,6 +17,7 @@ class HsManagedServerHostingAssetValidatorUnitTest {
// given // given
final var mangedWebspaceHostingAssetEntity = HsHostingAssetEntity.builder() final var mangedWebspaceHostingAssetEntity = HsHostingAssetEntity.builder()
.type(MANAGED_SERVER) .type(MANAGED_SERVER)
.identifier("vm1234")
.config(Map.ofEntries( .config(Map.ofEntries(
entry("monit_max_hdd_usage", "90"), entry("monit_max_hdd_usage", "90"),
entry("monit_max_cpu_usage", 2), entry("monit_max_cpu_usage", 2),
@ -30,9 +31,9 @@ class HsManagedServerHostingAssetValidatorUnitTest {
// then // then
assertThat(result).containsExactlyInAnyOrder( assertThat(result).containsExactlyInAnyOrder(
"'config.monit_max_ssd_usage' is required but missing", "MANAGED_SERVER:vm1234.config.monit_max_ssd_usage is required but missing",
"'config.monit_max_hdd_usage' is expected to be of type class java.lang.Integer, but is of type 'String'", "MANAGED_SERVER:vm1234.config.monit_max_hdd_usage is expected to be of type class java.lang.Integer, but is of type 'String'",
"'config.monit_max_cpu_usage' is expected to be >= 10 but is 2", "MANAGED_SERVER:vm1234.config.monit_max_cpu_usage is expected to be >= 10 but is 2",
"'config.monit_max_ram_usage' is expected to be <= 100 but is 101"); "MANAGED_SERVER:vm1234.config.monit_max_ram_usage is expected to be <= 100 but is 101");
} }
} }

View File

@ -1,13 +1,14 @@
package net.hostsharing.hsadminng.hs.hosting.asset.validators; package net.hostsharing.hsadminng.hs.hosting.asset.validators;
import net.hostsharing.hsadminng.hs.booking.item.HsBookingItemEntity; import net.hostsharing.hsadminng.hs.booking.item.HsBookingItemEntity;
import net.hostsharing.hsadminng.hs.booking.item.HsBookingItemType;
import net.hostsharing.hsadminng.hs.hosting.asset.HsHostingAssetEntity; 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.Map; import java.util.Map;
import static java.util.Map.entry; import static java.util.Map.entry;
import static net.hostsharing.hsadminng.hs.hosting.asset.HsHostingAssetType.MANAGED_SERVER;
import static net.hostsharing.hsadminng.hs.hosting.asset.HsHostingAssetType.MANAGED_WEBSPACE; import static net.hostsharing.hsadminng.hs.hosting.asset.HsHostingAssetType.MANAGED_WEBSPACE;
import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThat;
import static net.hostsharing.hsadminng.hs.booking.project.TestHsBookingProject.TEST_PROJECT; import static net.hostsharing.hsadminng.hs.booking.project.TestHsBookingProject.TEST_PROJECT;
@ -16,21 +17,32 @@ class HsManagedWebspaceHostingAssetValidatorUnitTest {
final HsBookingItemEntity managedServerBookingItem = HsBookingItemEntity.builder() final HsBookingItemEntity managedServerBookingItem = HsBookingItemEntity.builder()
.project(TEST_PROJECT) .project(TEST_PROJECT)
.type(HsBookingItemType.MANAGED_SERVER)
.caption("Test Managed-Server")
.resources(Map.ofEntries(
entry("CPUs", 2),
entry("RAM", 25),
entry("SSD", 25),
entry("Traffic", 250),
entry("SLA-Platform", "EXT4H"),
entry("SLA-EMail", true)
))
.build(); .build();
final HsHostingAssetEntity mangedServerAssetEntity = HsHostingAssetEntity.builder() final HsHostingAssetEntity mangedServerAssetEntity = HsHostingAssetEntity.builder()
.type(MANAGED_SERVER) .type(HsHostingAssetType.MANAGED_SERVER)
.bookingItem(managedServerBookingItem) .bookingItem(managedServerBookingItem)
.identifier("vm1234")
.config(Map.ofEntries( .config(Map.ofEntries(
entry("HDD", 0), entry("monit_max_ssd_usage", 70),
entry("SSD", 1), entry("monit_max_cpu_usage", 80),
entry("Traffic", 10) entry("monit_max_ram_usage", 90)
)) ))
.build(); .build();
@Test @Test
void validatesIdentifier() { void validatesIdentifier() {
// given // given
final var validator = HsHostingAssetEntityValidators.forType(MANAGED_WEBSPACE); final var validator = HsHostingAssetEntityValidator.forType(MANAGED_WEBSPACE);
final var mangedWebspaceHostingAssetEntity = HsHostingAssetEntity.builder() final var mangedWebspaceHostingAssetEntity = HsHostingAssetEntity.builder()
.type(MANAGED_WEBSPACE) .type(MANAGED_WEBSPACE)
.parentAsset(mangedServerAssetEntity) .parentAsset(mangedServerAssetEntity)
@ -47,7 +59,7 @@ class HsManagedWebspaceHostingAssetValidatorUnitTest {
@Test @Test
void validatesUnknownProperties() { void validatesUnknownProperties() {
// given // given
final var validator = HsHostingAssetEntityValidators.forType(MANAGED_WEBSPACE); final var validator = HsHostingAssetEntityValidator.forType(MANAGED_WEBSPACE);
final var mangedWebspaceHostingAssetEntity = HsHostingAssetEntity.builder() final var mangedWebspaceHostingAssetEntity = HsHostingAssetEntity.builder()
.type(MANAGED_WEBSPACE) .type(MANAGED_WEBSPACE)
.parentAsset(mangedServerAssetEntity) .parentAsset(mangedServerAssetEntity)
@ -61,13 +73,13 @@ class HsManagedWebspaceHostingAssetValidatorUnitTest {
final var result = validator.validate(mangedWebspaceHostingAssetEntity); final var result = validator.validate(mangedWebspaceHostingAssetEntity);
// then // then
assertThat(result).containsExactly("'config.unknown' is not expected but is set to 'some value'"); assertThat(result).containsExactly("MANAGED_WEBSPACE:abc00.config.unknown is not expected but is set to 'some value'");
} }
@Test @Test
void validatesValidEntity() { void validatesValidEntity() {
// given // given
final var validator = HsHostingAssetEntityValidators.forType(MANAGED_WEBSPACE); final var validator = HsHostingAssetEntityValidator.forType(MANAGED_WEBSPACE);
final var mangedWebspaceHostingAssetEntity = HsHostingAssetEntity.builder() final var mangedWebspaceHostingAssetEntity = HsHostingAssetEntity.builder()
.type(MANAGED_WEBSPACE) .type(MANAGED_WEBSPACE)
.parentAsset(mangedServerAssetEntity) .parentAsset(mangedServerAssetEntity)

View File

@ -4,7 +4,7 @@ import net.hostsharing.hsadminng.context.Context;
import net.hostsharing.hsadminng.rbac.test.ContextBasedTestWithCleanup; import net.hostsharing.hsadminng.rbac.test.ContextBasedTestWithCleanup;
import net.hostsharing.hsadminng.rbac.rbacgrant.RawRbacGrantRepository; import net.hostsharing.hsadminng.rbac.rbacgrant.RawRbacGrantRepository;
import net.hostsharing.hsadminng.rbac.rbacrole.RawRbacRoleRepository; import net.hostsharing.hsadminng.rbac.rbacrole.RawRbacRoleRepository;
import net.hostsharing.hsadminng.rbac.test.Array; import net.hostsharing.hsadminng.mapper.Array;
import net.hostsharing.hsadminng.rbac.test.JpaAttempt; import net.hostsharing.hsadminng.rbac.test.JpaAttempt;
import org.apache.commons.lang3.RandomStringUtils; import org.apache.commons.lang3.RandomStringUtils;
import org.junit.jupiter.api.Nested; import org.junit.jupiter.api.Nested;

View File

@ -4,7 +4,7 @@ import net.hostsharing.hsadminng.context.Context;
import net.hostsharing.hsadminng.rbac.test.ContextBasedTestWithCleanup; import net.hostsharing.hsadminng.rbac.test.ContextBasedTestWithCleanup;
import net.hostsharing.hsadminng.rbac.rbacgrant.RawRbacGrantRepository; import net.hostsharing.hsadminng.rbac.rbacgrant.RawRbacGrantRepository;
import net.hostsharing.hsadminng.rbac.rbacrole.RawRbacRoleRepository; import net.hostsharing.hsadminng.rbac.rbacrole.RawRbacRoleRepository;
import net.hostsharing.hsadminng.rbac.test.Array; import net.hostsharing.hsadminng.mapper.Array;
import net.hostsharing.hsadminng.rbac.test.JpaAttempt; import net.hostsharing.hsadminng.rbac.test.JpaAttempt;
import org.apache.commons.lang3.RandomStringUtils; import org.apache.commons.lang3.RandomStringUtils;
import org.junit.jupiter.api.Nested; import org.junit.jupiter.api.Nested;

View File

@ -5,7 +5,7 @@ import net.hostsharing.hsadminng.hs.office.membership.HsOfficeMembershipReposito
import net.hostsharing.hsadminng.rbac.test.ContextBasedTestWithCleanup; import net.hostsharing.hsadminng.rbac.test.ContextBasedTestWithCleanup;
import net.hostsharing.hsadminng.rbac.rbacgrant.RawRbacGrantRepository; import net.hostsharing.hsadminng.rbac.rbacgrant.RawRbacGrantRepository;
import net.hostsharing.hsadminng.rbac.rbacrole.RawRbacRoleRepository; import net.hostsharing.hsadminng.rbac.rbacrole.RawRbacRoleRepository;
import net.hostsharing.hsadminng.rbac.test.Array; import net.hostsharing.hsadminng.mapper.Array;
import net.hostsharing.hsadminng.rbac.test.JpaAttempt; import net.hostsharing.hsadminng.rbac.test.JpaAttempt;
import org.junit.jupiter.api.AfterEach; import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.BeforeEach;

View File

@ -5,7 +5,7 @@ import net.hostsharing.hsadminng.hs.office.membership.HsOfficeMembershipReposito
import net.hostsharing.hsadminng.rbac.test.ContextBasedTestWithCleanup; import net.hostsharing.hsadminng.rbac.test.ContextBasedTestWithCleanup;
import net.hostsharing.hsadminng.rbac.rbacgrant.RawRbacGrantRepository; import net.hostsharing.hsadminng.rbac.rbacgrant.RawRbacGrantRepository;
import net.hostsharing.hsadminng.rbac.rbacrole.RawRbacRoleRepository; import net.hostsharing.hsadminng.rbac.rbacrole.RawRbacRoleRepository;
import net.hostsharing.hsadminng.rbac.test.Array; import net.hostsharing.hsadminng.mapper.Array;
import net.hostsharing.hsadminng.rbac.test.JpaAttempt; import net.hostsharing.hsadminng.rbac.test.JpaAttempt;
import org.junit.jupiter.api.AfterEach; import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.BeforeEach;

View File

@ -11,7 +11,7 @@ import net.hostsharing.hsadminng.rbac.test.ContextBasedTestWithCleanup;
import net.hostsharing.hsadminng.rbac.rbacgrant.RawRbacGrantRepository; import net.hostsharing.hsadminng.rbac.rbacgrant.RawRbacGrantRepository;
import net.hostsharing.hsadminng.rbac.rbacgrant.RbacGrantsDiagramService; import net.hostsharing.hsadminng.rbac.rbacgrant.RbacGrantsDiagramService;
import net.hostsharing.hsadminng.rbac.rbacrole.RawRbacRoleRepository; import net.hostsharing.hsadminng.rbac.rbacrole.RawRbacRoleRepository;
import net.hostsharing.hsadminng.rbac.test.Array; import net.hostsharing.hsadminng.mapper.Array;
import net.hostsharing.hsadminng.rbac.test.JpaAttempt; import net.hostsharing.hsadminng.rbac.test.JpaAttempt;
import org.hibernate.Hibernate; import org.hibernate.Hibernate;
import org.junit.jupiter.api.Disabled; import org.junit.jupiter.api.Disabled;

View File

@ -7,7 +7,7 @@ import net.hostsharing.hsadminng.hs.office.partner.HsOfficePartnerRepository;
import net.hostsharing.hsadminng.rbac.test.ContextBasedTestWithCleanup; import net.hostsharing.hsadminng.rbac.test.ContextBasedTestWithCleanup;
import net.hostsharing.hsadminng.rbac.rbacgrant.RawRbacGrantRepository; import net.hostsharing.hsadminng.rbac.rbacgrant.RawRbacGrantRepository;
import net.hostsharing.hsadminng.rbac.rbacrole.RawRbacRoleRepository; import net.hostsharing.hsadminng.rbac.rbacrole.RawRbacRoleRepository;
import net.hostsharing.hsadminng.rbac.test.Array; import net.hostsharing.hsadminng.mapper.Array;
import net.hostsharing.hsadminng.rbac.test.JpaAttempt; import net.hostsharing.hsadminng.rbac.test.JpaAttempt;
import org.junit.jupiter.api.Nested; import org.junit.jupiter.api.Nested;
import org.junit.jupiter.api.Test; import org.junit.jupiter.api.Test;

View File

@ -30,7 +30,7 @@ import java.util.Objects;
import static net.hostsharing.hsadminng.rbac.rbacgrant.RawRbacGrantEntity.distinctGrantDisplaysOf; import static net.hostsharing.hsadminng.rbac.rbacgrant.RawRbacGrantEntity.distinctGrantDisplaysOf;
import static net.hostsharing.hsadminng.rbac.rbacrole.RawRbacObjectEntity.objectDisplaysOf; import static net.hostsharing.hsadminng.rbac.rbacrole.RawRbacObjectEntity.objectDisplaysOf;
import static net.hostsharing.hsadminng.rbac.rbacrole.RawRbacRoleEntity.distinctRoleNamesOf; import static net.hostsharing.hsadminng.rbac.rbacrole.RawRbacRoleEntity.distinctRoleNamesOf;
import static net.hostsharing.hsadminng.rbac.test.Array.from; import static net.hostsharing.hsadminng.mapper.Array.from;
import static net.hostsharing.hsadminng.rbac.test.JpaAttempt.attempt; import static net.hostsharing.hsadminng.rbac.test.JpaAttempt.attempt;
import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThat;

View File

@ -4,7 +4,7 @@ import net.hostsharing.hsadminng.context.Context;
import net.hostsharing.hsadminng.rbac.test.ContextBasedTestWithCleanup; import net.hostsharing.hsadminng.rbac.test.ContextBasedTestWithCleanup;
import net.hostsharing.hsadminng.rbac.rbacgrant.RawRbacGrantRepository; import net.hostsharing.hsadminng.rbac.rbacgrant.RawRbacGrantRepository;
import net.hostsharing.hsadminng.rbac.rbacrole.RawRbacRoleRepository; import net.hostsharing.hsadminng.rbac.rbacrole.RawRbacRoleRepository;
import net.hostsharing.hsadminng.rbac.test.Array; import net.hostsharing.hsadminng.mapper.Array;
import net.hostsharing.hsadminng.rbac.test.JpaAttempt; import net.hostsharing.hsadminng.rbac.test.JpaAttempt;
import org.apache.commons.lang3.RandomStringUtils; import org.apache.commons.lang3.RandomStringUtils;
import org.junit.jupiter.api.Nested; import org.junit.jupiter.api.Nested;

View File

@ -6,7 +6,7 @@ import net.hostsharing.hsadminng.hs.office.person.HsOfficePersonRepository;
import net.hostsharing.hsadminng.rbac.test.ContextBasedTestWithCleanup; import net.hostsharing.hsadminng.rbac.test.ContextBasedTestWithCleanup;
import net.hostsharing.hsadminng.rbac.rbacgrant.RawRbacGrantRepository; import net.hostsharing.hsadminng.rbac.rbacgrant.RawRbacGrantRepository;
import net.hostsharing.hsadminng.rbac.rbacrole.RawRbacRoleRepository; import net.hostsharing.hsadminng.rbac.rbacrole.RawRbacRoleRepository;
import net.hostsharing.hsadminng.rbac.test.Array; import net.hostsharing.hsadminng.mapper.Array;
import net.hostsharing.hsadminng.rbac.test.JpaAttempt; import net.hostsharing.hsadminng.rbac.test.JpaAttempt;
import org.junit.jupiter.api.Nested; import org.junit.jupiter.api.Nested;
import org.junit.jupiter.api.Test; import org.junit.jupiter.api.Test;

View File

@ -7,7 +7,7 @@ import net.hostsharing.hsadminng.hs.office.debitor.HsOfficeDebitorRepository;
import net.hostsharing.hsadminng.rbac.test.ContextBasedTestWithCleanup; import net.hostsharing.hsadminng.rbac.test.ContextBasedTestWithCleanup;
import net.hostsharing.hsadminng.rbac.rbacgrant.RawRbacGrantRepository; import net.hostsharing.hsadminng.rbac.rbacgrant.RawRbacGrantRepository;
import net.hostsharing.hsadminng.rbac.rbacrole.RawRbacRoleRepository; import net.hostsharing.hsadminng.rbac.rbacrole.RawRbacRoleRepository;
import net.hostsharing.hsadminng.rbac.test.Array; import net.hostsharing.hsadminng.mapper.Array;
import net.hostsharing.hsadminng.rbac.test.JpaAttempt; import net.hostsharing.hsadminng.rbac.test.JpaAttempt;
import org.junit.jupiter.api.Nested; import org.junit.jupiter.api.Nested;
import org.junit.jupiter.api.Test; import org.junit.jupiter.api.Test;
@ -26,7 +26,7 @@ import java.util.List;
import static net.hostsharing.hsadminng.rbac.rbacgrant.RawRbacGrantEntity.distinctGrantDisplaysOf; import static net.hostsharing.hsadminng.rbac.rbacgrant.RawRbacGrantEntity.distinctGrantDisplaysOf;
import static net.hostsharing.hsadminng.rbac.rbacrole.RawRbacRoleEntity.distinctRoleNamesOf; import static net.hostsharing.hsadminng.rbac.rbacrole.RawRbacRoleEntity.distinctRoleNamesOf;
import static net.hostsharing.hsadminng.rbac.test.Array.fromFormatted; import static net.hostsharing.hsadminng.mapper.Array.fromFormatted;
import static net.hostsharing.hsadminng.rbac.test.JpaAttempt.attempt; import static net.hostsharing.hsadminng.rbac.test.JpaAttempt.attempt;
import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThat;

View File

@ -2,7 +2,7 @@ package net.hostsharing.hsadminng.rbac.context;
import net.hostsharing.hsadminng.context.Context; import net.hostsharing.hsadminng.context.Context;
import net.hostsharing.hsadminng.mapper.Mapper; import net.hostsharing.hsadminng.mapper.Mapper;
import net.hostsharing.hsadminng.rbac.test.Array; import net.hostsharing.hsadminng.mapper.Array;
import net.hostsharing.hsadminng.rbac.test.JpaAttempt; import net.hostsharing.hsadminng.rbac.test.JpaAttempt;
import org.junit.jupiter.api.Test; import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Autowired;

View File

@ -1,7 +1,7 @@
package net.hostsharing.hsadminng.rbac.rbacrole; package net.hostsharing.hsadminng.rbac.rbacrole;
import net.hostsharing.hsadminng.context.Context; import net.hostsharing.hsadminng.context.Context;
import net.hostsharing.hsadminng.rbac.test.Array; import net.hostsharing.hsadminng.mapper.Array;
import net.hostsharing.hsadminng.rbac.test.JpaAttempt; import net.hostsharing.hsadminng.rbac.test.JpaAttempt;
import org.junit.jupiter.api.Nested; import org.junit.jupiter.api.Nested;
import org.junit.jupiter.api.Test; import org.junit.jupiter.api.Test;

View File

@ -2,7 +2,7 @@ package net.hostsharing.hsadminng.rbac.rbacuser;
import net.hostsharing.hsadminng.context.Context; import net.hostsharing.hsadminng.context.Context;
import net.hostsharing.hsadminng.rbac.context.ContextBasedTest; import net.hostsharing.hsadminng.rbac.context.ContextBasedTest;
import net.hostsharing.hsadminng.rbac.test.Array; import net.hostsharing.hsadminng.mapper.Array;
import net.hostsharing.hsadminng.rbac.test.JpaAttempt; import net.hostsharing.hsadminng.rbac.test.JpaAttempt;
import org.junit.jupiter.api.Nested; import org.junit.jupiter.api.Nested;
import org.junit.jupiter.api.Test; import org.junit.jupiter.api.Test;