Compare commits
11 Commits
master
...
idea-bug-r
Author | SHA1 | Date | |
---|---|---|---|
|
e3bcf65b9f | ||
|
4898a8753b | ||
|
42d484fdc6 | ||
|
5d15968e3c | ||
|
5cae64af4c | ||
|
0045c62a27 | ||
|
5f28f12676 | ||
|
6455f89e5b | ||
|
7b63d867e0 | ||
|
1d2a65ac22 | ||
|
eb98ab99be |
@ -9,7 +9,7 @@ import org.springframework.web.context.request.WebRequest;
|
||||
import java.time.LocalDateTime;
|
||||
|
||||
@Getter
|
||||
class CustomErrorResponse {
|
||||
public class CustomErrorResponse {
|
||||
|
||||
static ResponseEntity<CustomErrorResponse> errorResponse(
|
||||
final WebRequest request,
|
||||
|
@ -1,5 +1,6 @@
|
||||
package net.hostsharing.hsadminng.errors;
|
||||
|
||||
import net.hostsharing.hsadminng.hs.validation.MultiValidationException;
|
||||
import org.iban4j.Iban4jException;
|
||||
import org.springframework.core.NestedExceptionUtils;
|
||||
import org.springframework.dao.DataIntegrityViolationException;
|
||||
@ -75,7 +76,8 @@ public class RestResponseEntityExceptionHandler
|
||||
@ExceptionHandler({ Iban4jException.class, ValidationException.class })
|
||||
protected ResponseEntity<CustomErrorResponse> handleIbanAndBicExceptions(
|
||||
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);
|
||||
}
|
||||
|
||||
|
@ -13,11 +13,13 @@ import org.springframework.transaction.annotation.Transactional;
|
||||
import org.springframework.web.bind.annotation.RestController;
|
||||
import org.springframework.web.servlet.mvc.method.annotation.MvcUriComponentsBuilder;
|
||||
|
||||
import jakarta.persistence.EntityManager;
|
||||
import jakarta.persistence.PersistenceContext;
|
||||
import java.util.List;
|
||||
import java.util.UUID;
|
||||
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;
|
||||
|
||||
@RestController
|
||||
@ -32,6 +34,9 @@ public class HsBookingItemController implements HsBookingItemsApi {
|
||||
@Autowired
|
||||
private HsBookingItemRepository bookingItemRepo;
|
||||
|
||||
@PersistenceContext
|
||||
private EntityManager em;
|
||||
|
||||
@Override
|
||||
@Transactional(readOnly = true)
|
||||
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 saved = bookingItemRepo.save(valid(entityToSave));
|
||||
final var saved = validated(bookingItemRepo.save(entityToSave));
|
||||
|
||||
final var uri =
|
||||
MvcUriComponentsBuilder.fromController(getClass())
|
||||
@ -78,6 +83,7 @@ public class HsBookingItemController implements HsBookingItemsApi {
|
||||
context.define(currentUser, assumedRoles);
|
||||
|
||||
final var result = bookingItemRepo.findByUuid(bookingItemUuid);
|
||||
result.ifPresent(entity -> em.detach(entity));
|
||||
return result
|
||||
.map(bookingItemEntity -> ResponseEntity.ok(
|
||||
mapper.map(bookingItemEntity, HsBookingItemResource.class, ENTITY_TO_RESOURCE_POSTMAPPER)))
|
||||
@ -112,7 +118,7 @@ public class HsBookingItemController implements HsBookingItemsApi {
|
||||
|
||||
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);
|
||||
return ResponseEntity.ok(mapped);
|
||||
}
|
||||
|
@ -10,7 +10,7 @@ import lombok.Getter;
|
||||
import lombok.NoArgsConstructor;
|
||||
import lombok.Setter;
|
||||
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.rbac.rbacdef.RbacView;
|
||||
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 org.hibernate.annotations.Type;
|
||||
|
||||
import jakarta.persistence.CascadeType;
|
||||
import jakarta.persistence.Column;
|
||||
import jakarta.persistence.Entity;
|
||||
import jakarta.persistence.EnumType;
|
||||
@ -27,12 +28,14 @@ import jakarta.persistence.GeneratedValue;
|
||||
import jakarta.persistence.Id;
|
||||
import jakarta.persistence.JoinColumn;
|
||||
import jakarta.persistence.ManyToOne;
|
||||
import jakarta.persistence.OneToMany;
|
||||
import jakarta.persistence.Table;
|
||||
import jakarta.persistence.Transient;
|
||||
import jakarta.persistence.Version;
|
||||
import java.io.IOException;
|
||||
import java.time.LocalDate;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.UUID;
|
||||
|
||||
@ -62,7 +65,7 @@ import static net.hostsharing.hsadminng.stringify.Stringify.stringify;
|
||||
@Setter
|
||||
@NoArgsConstructor
|
||||
@AllArgsConstructor
|
||||
public class HsBookingItemEntity implements Stringifyable, RbacObject, Validatable<HsBookingItemEntity, HsBookingItemType> {
|
||||
public class HsBookingItemEntity implements Stringifyable, RbacObject {
|
||||
|
||||
private static Stringify<HsBookingItemEntity> stringify = stringify(HsBookingItemEntity.class)
|
||||
.withProp(HsBookingItemEntity::getProject)
|
||||
@ -85,6 +88,7 @@ public class HsBookingItemEntity implements Stringifyable, RbacObject, Validatab
|
||||
|
||||
@ManyToOne
|
||||
@JoinColumn(name = "parentitemuuid")
|
||||
// @Transient
|
||||
private HsBookingItemEntity parentItem;
|
||||
|
||||
@Column(name = "type")
|
||||
@ -105,6 +109,14 @@ public class HsBookingItemEntity implements Stringifyable, RbacObject, Validatab
|
||||
@Column(columnDefinition = "resources")
|
||||
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
|
||||
private PatchableMapWrapper<Object> resourcesWrapper;
|
||||
|
||||
@ -150,16 +162,6 @@ public class HsBookingItemEntity implements Stringifyable, RbacObject, Validatab
|
||||
return parentItem == null ? null : parentItem.relatedProject();
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getPropertiesName() {
|
||||
return "resources";
|
||||
}
|
||||
|
||||
@Override
|
||||
public Map<String, Object> getProperties() {
|
||||
return resources;
|
||||
}
|
||||
|
||||
public HsBookingProjectEntity getRelatedProject() {
|
||||
return project != null ? project : parentItem.getRelatedProject();
|
||||
}
|
||||
|
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
@ -1,50 +1,61 @@
|
||||
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.HsBookingItemType;
|
||||
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.List;
|
||||
import java.util.Map;
|
||||
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;
|
||||
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.HsBookingItemType.PRIVATE_CLOUD;
|
||||
|
||||
@UtilityClass
|
||||
public class HsBookingItemEntityValidators {
|
||||
public class HsBookingItemEntityValidatorRegistry {
|
||||
|
||||
private static final Map<Enum<HsBookingItemType>, HsEntityValidator<HsBookingItemEntity, HsBookingItemType>> validators = new HashMap<>();
|
||||
private static final Map<Enum<HsBookingItemType>, HsEntityValidator<HsBookingItemEntity>> validators = new HashMap<>();
|
||||
static {
|
||||
register(PRIVATE_CLOUD, new HsPrivateCloudBookingItemValidator());
|
||||
register(CLOUD_SERVER, new HsCloudServerBookingItemValidator());
|
||||
register(MANAGED_SERVER, new HsManagedServerBookingItemValidator());
|
||||
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 -> {
|
||||
entry.verifyConsistency(Map.entry(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);
|
||||
}
|
||||
throw new IllegalArgumentException("no validator found for type " + type);
|
||||
}
|
||||
|
||||
public static Set<Enum<HsBookingItemType>> types() {
|
||||
return validators.keySet();
|
||||
}
|
||||
|
||||
public static HsBookingItemEntity valid(final HsBookingItemEntity entityToSave) {
|
||||
final var violations = HsBookingItemEntityValidators.forType(entityToSave.getType()).validate(entityToSave);
|
||||
if (!violations.isEmpty()) {
|
||||
throw new ValidationException(violations.toString());
|
||||
public static List<String> doValidate(final HsBookingItemEntity bookingItem) {
|
||||
return HsBookingItemEntityValidatorRegistry.forType(bookingItem.getType()).validate(bookingItem);
|
||||
}
|
||||
|
||||
public static HsBookingItemEntity validated(final HsBookingItemEntity entityToSave) {
|
||||
MultiValidationException.throwInvalid(doValidate(entityToSave));
|
||||
return entityToSave;
|
||||
}
|
||||
}
|
@ -1,20 +1,20 @@
|
||||
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.EnumerationPropertyValidator.enumerationProperty;
|
||||
import static net.hostsharing.hsadminng.hs.validation.IntegerPropertyValidator.integerProperty;
|
||||
import java.util.List;
|
||||
|
||||
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() {
|
||||
super(
|
||||
integerProperty("CPUs").min(1).max(32).required(),
|
||||
integerProperty("RAM").unit("GB").min(1).max(128).required(),
|
||||
integerProperty("SSD").unit("GB").min(25).max(1000).step(25).required(),
|
||||
integerProperty("HDD").unit("GB").min(0).max(4000).step(250).optional(),
|
||||
integerProperty("HDD").unit("GB").min(0).max(4000).step(250).withDefault(0),
|
||||
integerProperty("Traffic").unit("GB").min(250).max(10000).step(250).required(),
|
||||
enumerationProperty("SLA-Infrastructure").values("BASIC", "EXT8H", "EXT4H", "EXT2H").optional()
|
||||
);
|
||||
|
@ -1,24 +1,22 @@
|
||||
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() {
|
||||
super(
|
||||
integerProperty("CPUs").min(1).max(32).required(),
|
||||
integerProperty("RAM").unit("GB").min(1).max(128).required(),
|
||||
integerProperty("SSD").unit("GB").min(25).max(1000).step(25).required(),
|
||||
integerProperty("HDD").unit("GB").min(0).max(4000).step(250).optional(),
|
||||
integerProperty("Traffic").unit("GB").min(250).max(10000).step(250).required(),
|
||||
enumerationProperty("SLA-Platform").values("BASIC", "EXT8H", "EXT4H", "EXT2H").optional(),
|
||||
booleanProperty("SLA-EMail").falseIf("SLA-Platform", "BASIC").optional(),
|
||||
integerProperty("SSD").unit("GB").min(25).max(1000).step(25).required().asTotalLimit().withThreshold(200),
|
||||
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().asTotalLimit().withThreshold(200),
|
||||
enumerationProperty("SLA-Platform").values("BASIC", "EXT8H", "EXT4H", "EXT2H").withDefault("BASIC"),
|
||||
booleanProperty("SLA-EMail").falseIf("SLA-Platform", "BASIC").withDefault(false),
|
||||
booleanProperty("SLA-Maria").falseIf("SLA-Platform", "BASIC").optional(),
|
||||
booleanProperty("SLA-PgSQL").falseIf("SLA-Platform", "BASIC").optional(),
|
||||
booleanProperty("SLA-Office").falseIf("SLA-Platform", "BASIC").optional(),
|
||||
|
@ -1,24 +1,98 @@
|
||||
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.IntegerProperty;
|
||||
import org.apache.commons.lang3.function.TriFunction;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
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;
|
||||
import static java.util.Collections.emptyList;
|
||||
import static net.hostsharing.hsadminng.hs.hosting.asset.HsHostingAssetType.DOMAIN_EMAIL_SETUP;
|
||||
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() {
|
||||
super(
|
||||
integerProperty("SSD").unit("GB").min(1).max(100).step(1).required(),
|
||||
integerProperty("HDD").unit("GB").min(0).max(250).step(10).optional(),
|
||||
integerProperty("Traffic").unit("GB").min(10).max(1000).step(10).required(),
|
||||
enumerationProperty("SLA-Platform").values("BASIC", "EXT24H").optional(),
|
||||
integerProperty("Daemons").min(0).max(10).optional(),
|
||||
booleanProperty("Online Office Server").optional()
|
||||
integerProperty("Multi").min(1).max(100).step(1).withDefault(1)
|
||||
.eachComprising( 25, unixUsers())
|
||||
.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();
|
||||
};
|
||||
}
|
||||
}
|
||||
|
@ -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")
|
||||
);
|
||||
}
|
||||
}
|
@ -20,7 +20,7 @@ import java.util.List;
|
||||
import java.util.UUID;
|
||||
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
|
||||
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 saved = assetRepo.save(valid(entityToSave));
|
||||
final var saved = validated(assetRepo.save(entityToSave));
|
||||
|
||||
final var uri =
|
||||
MvcUriComponentsBuilder.fromController(getClass())
|
||||
@ -117,7 +117,7 @@ public class HsHostingAssetController implements HsHostingAssetsApi {
|
||||
|
||||
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);
|
||||
return ResponseEntity.ok(mapped);
|
||||
}
|
||||
|
@ -8,7 +8,6 @@ import lombok.Getter;
|
||||
import lombok.NoArgsConstructor;
|
||||
import lombok.Setter;
|
||||
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.rbac.rbacdef.RbacView;
|
||||
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 org.hibernate.annotations.Type;
|
||||
|
||||
import jakarta.persistence.CascadeType;
|
||||
import jakarta.persistence.Column;
|
||||
import jakarta.persistence.Entity;
|
||||
import jakarta.persistence.EnumType;
|
||||
@ -25,11 +25,13 @@ import jakarta.persistence.GeneratedValue;
|
||||
import jakarta.persistence.Id;
|
||||
import jakarta.persistence.JoinColumn;
|
||||
import jakarta.persistence.ManyToOne;
|
||||
import jakarta.persistence.OneToMany;
|
||||
import jakarta.persistence.Table;
|
||||
import jakarta.persistence.Transient;
|
||||
import jakarta.persistence.Version;
|
||||
import java.io.IOException;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.UUID;
|
||||
|
||||
@ -56,7 +58,7 @@ import static net.hostsharing.hsadminng.stringify.Stringify.stringify;
|
||||
@Setter
|
||||
@NoArgsConstructor
|
||||
@AllArgsConstructor
|
||||
public class HsHostingAssetEntity implements Stringifyable, RbacObject, Validatable<HsHostingAssetEntity, HsHostingAssetType> {
|
||||
public class HsHostingAssetEntity implements Stringifyable, RbacObject {
|
||||
|
||||
private static Stringify<HsHostingAssetEntity> stringify = stringify(HsHostingAssetEntity.class)
|
||||
.withProp(HsHostingAssetEntity::getType)
|
||||
@ -91,6 +93,10 @@ public class HsHostingAssetEntity implements Stringifyable, RbacObject, Validata
|
||||
@Enumerated(EnumType.STRING)
|
||||
private HsHostingAssetType type;
|
||||
|
||||
@OneToMany(cascade = CascadeType.REFRESH, orphanRemoval = true)
|
||||
@JoinColumn(name="parentassetuuid", referencedColumnName="uuid")
|
||||
private List<HsHostingAssetEntity> subHostingAssets;
|
||||
|
||||
@Column(name = "identifier")
|
||||
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);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getPropertiesName() {
|
||||
return "config";
|
||||
}
|
||||
|
||||
@Override
|
||||
public Map<String, Object> getProperties() {
|
||||
return config;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return stringify.apply(this);
|
||||
|
@ -1,6 +1,6 @@
|
||||
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.model.HsHostingAssetTypeResource;
|
||||
import org.springframework.http.ResponseEntity;
|
||||
@ -15,7 +15,7 @@ public class HsHostingAssetPropsController implements HsHostingAssetPropsApi {
|
||||
|
||||
@Override
|
||||
public ResponseEntity<List<String>> listAssetTypes() {
|
||||
final var resource = HsHostingAssetEntityValidators.types().stream()
|
||||
final var resource = HsHostingAssetEntityValidator.types().stream()
|
||||
.map(Enum::name)
|
||||
.toList();
|
||||
return ResponseEntity.ok(resource);
|
||||
@ -25,7 +25,7 @@ public class HsHostingAssetPropsController implements HsHostingAssetPropsApi {
|
||||
public ResponseEntity<List<Object>> listAssetTypeProps(
|
||||
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();
|
||||
return ResponseEntity.ok(toListOfObjects(resource));
|
||||
}
|
||||
|
@ -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;
|
||||
}
|
||||
}
|
@ -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;
|
||||
}
|
||||
}
|
@ -1,12 +1,8 @@
|
||||
package net.hostsharing.hsadminng.hs.hosting.asset.validators;
|
||||
|
||||
import net.hostsharing.hsadminng.hs.hosting.asset.HsHostingAssetEntity;
|
||||
import net.hostsharing.hsadminng.hs.hosting.asset.HsHostingAssetType;
|
||||
import net.hostsharing.hsadminng.hs.validation.HsEntityValidator;
|
||||
import static net.hostsharing.hsadminng.hs.validation.IntegerProperty.integerProperty;
|
||||
|
||||
import static net.hostsharing.hsadminng.hs.validation.IntegerPropertyValidator.integerProperty;
|
||||
|
||||
class HsManagedServerHostingAssetValidator extends HsEntityValidator<HsHostingAssetEntity, HsHostingAssetType> {
|
||||
class HsManagedServerHostingAssetValidator extends HsHostingAssetEntityValidator {
|
||||
|
||||
public HsManagedServerHostingAssetValidator() {
|
||||
super(
|
||||
|
@ -1,28 +1,29 @@
|
||||
package net.hostsharing.hsadminng.hs.hosting.asset.validators;
|
||||
|
||||
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.stream.Collectors;
|
||||
|
||||
|
||||
class HsManagedWebspaceHostingAssetValidator extends HsEntityValidator<HsHostingAssetEntity, HsHostingAssetType> {
|
||||
class HsManagedWebspaceHostingAssetValidator extends HsHostingAssetEntityValidator {
|
||||
public HsManagedWebspaceHostingAssetValidator() {
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<String> validate(final HsHostingAssetEntity assetEntity) {
|
||||
final var result = super.validate(assetEntity);
|
||||
validateIdentifierPattern(result, assetEntity);
|
||||
|
||||
return result;
|
||||
return Stream.of(validateIdentifierPattern(assetEntity), super.validate(assetEntity))
|
||||
.flatMap(Collection::stream)
|
||||
.collect(Collectors.toList());
|
||||
}
|
||||
|
||||
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]$";
|
||||
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();
|
||||
}
|
||||
}
|
||||
|
@ -3,6 +3,7 @@ package net.hostsharing.hsadminng.hs.office.coopassets;
|
||||
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.model.*;
|
||||
import net.hostsharing.hsadminng.hs.validation.MultiValidationException;
|
||||
import net.hostsharing.hsadminng.mapper.Mapper;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.format.annotation.DateTimeFormat;
|
||||
@ -97,9 +98,7 @@ public class HsOfficeCoopAssetsTransactionController implements HsOfficeCoopAsse
|
||||
validateDebitTransaction(requestBody, violations);
|
||||
validateCreditTransaction(requestBody, violations);
|
||||
validateAssetValue(requestBody, violations);
|
||||
if (violations.size() > 0) {
|
||||
throw new ValidationException("[" + join(", ", violations) + "]");
|
||||
}
|
||||
MultiValidationException.throwInvalid(violations);
|
||||
}
|
||||
|
||||
private static void validateDebitTransaction(
|
||||
|
@ -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.model.HsOfficeCoopSharesTransactionInsertResource;
|
||||
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 org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.format.annotation.DateTimeFormat;
|
||||
@ -99,9 +100,7 @@ public class HsOfficeCoopSharesTransactionController implements HsOfficeCoopShar
|
||||
validateSubscriptionTransaction(requestBody, violations);
|
||||
validateCancellationTransaction(requestBody, violations);
|
||||
validateshareCount(requestBody, violations);
|
||||
if (violations.size() > 0) {
|
||||
throw new ValidationException("[" + join(", ", violations) + "]");
|
||||
}
|
||||
MultiValidationException.throwInvalid(violations);
|
||||
}
|
||||
|
||||
private static void validateSubscriptionTransaction(
|
||||
|
@ -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";
|
||||
}
|
||||
}
|
@ -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";
|
||||
}
|
||||
}
|
@ -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";
|
||||
}
|
||||
}
|
@ -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";
|
||||
}
|
||||
}
|
@ -1,49 +1,83 @@
|
||||
package net.hostsharing.hsadminng.hs.validation;
|
||||
|
||||
import com.fasterxml.jackson.annotation.JsonAutoDetect;
|
||||
import com.fasterxml.jackson.annotation.PropertyAccessor;
|
||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||
import net.hostsharing.hsadminng.hs.booking.item.HsBookingItemEntity;
|
||||
import net.hostsharing.hsadminng.hs.booking.item.validators.HsBookingItemEntityValidator;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.function.Supplier;
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
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>();
|
||||
assetEntity.getProperties().keySet().forEach( givenPropName -> {
|
||||
properties.keySet().forEach( 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 -> {
|
||||
result.addAll(pv.validate(assetEntity.getPropertiesName(), assetEntity.getProperties()));
|
||||
result.addAll(pv.validate(properties));
|
||||
});
|
||||
return result;
|
||||
}
|
||||
|
||||
public List<Map<String, Object>> properties() {
|
||||
final var mapper = new ObjectMapper();
|
||||
mapper.setVisibility(PropertyAccessor.FIELD, JsonAutoDetect.Visibility.ANY);
|
||||
return Arrays.stream(propertyValidators)
|
||||
.map(propertyValidator -> propertyValidator.toMap(mapper))
|
||||
.map(HsEntityValidator::asKeyValueMap)
|
||||
.toList();
|
||||
protected List<String> validateParentEntities(final HsBookingItemEntity bookingItem) {
|
||||
return bookingItem.getParentItem() != null
|
||||
? HsBookingItemEntityValidator.doValidate(bookingItem.getParentItem())
|
||||
: emptyList();
|
||||
}
|
||||
|
||||
@SuppressWarnings({ "unchecked", "rawtypes" })
|
||||
private static Map<String, Object> asKeyValueMap(final Map map) {
|
||||
return (Map<String, Object>) map;
|
||||
@SafeVarargs
|
||||
protected static List<String> sequentiallyValidate(final Supplier<List<String>>... validators) {
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
@ -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();
|
||||
}
|
@ -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";
|
||||
}
|
||||
}
|
@ -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";
|
||||
}
|
||||
}
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
@ -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();
|
||||
}
|
@ -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();
|
||||
}
|
||||
}
|
@ -1,4 +1,4 @@
|
||||
package net.hostsharing.hsadminng.rbac.test;
|
||||
package net.hostsharing.hsadminng.mapper;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
@ -37,4 +37,10 @@ public class Array {
|
||||
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;
|
||||
}
|
||||
}
|
@ -62,6 +62,8 @@ public class RbacGrantsDiagramService {
|
||||
@PersistenceContext
|
||||
private EntityManager em;
|
||||
|
||||
private Map<UUID, List<RawRbacGrantEntity>> descendantsByUuid = new HashMap<>();
|
||||
|
||||
public String allGrantsToCurrentUser(final EnumSet<Include> includes) {
|
||||
final var graph = new LimitedHashSet<RawRbacGrantEntity>();
|
||||
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) {
|
||||
final var grants = rawGrantRepo.findByDescendantUuid(refUuid);
|
||||
final var grants = findDescendantsByUuid(refUuid);
|
||||
grants.forEach(g -> {
|
||||
if (!option.contains(USERS) && g.getAscendantIdName().startsWith("user:")) {
|
||||
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) {
|
||||
final var entities =
|
||||
includes.contains(DETAILS)
|
||||
|
@ -33,13 +33,13 @@ begin
|
||||
managedServerUuid := uuid_generate_v4();
|
||||
insert
|
||||
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),
|
||||
(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, 'CLOUD_SERVER', privateCloudUuid, 'test CloudServer', daterange('20230115', '20240415', '[)'), '{ "CPUs": 2, "RAM": 4, "HDD": 1024, "Traffic": 42 }'::jsonb),
|
||||
(uuid_generate_v4(), null, 'CLOUD_SERVER', privateCloudUuid, 'prod CloudServer', daterange('20230115', '20240415', '[)'), '{ "CPUs": 4, "RAM": 16, "HDD": 2924, "Traffic": 420 }'::jsonb),
|
||||
(managedServerUuid, relatedProject.uuid, 'MANAGED_SERVER', null, 'separate ManagedServer', daterange('20221001', null, '[]'), '{ "CPUs": 2, "RAM": 8, "SDD": 512, "Traffic": 42 }'::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(), relatedProject.uuid, 'MANAGED_WEBSPACE', null, 'some ManagedWebspace', daterange('20221001', null, '[]'), '{ "SDD": 512, "Traffic": 12, "Daemons": 2, "Multi": 4 }'::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, "SSD": 500, "Traffic": 500 }'::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, "SSD": 1000, "Traffic": 500 }'::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, '[]'), '{ "SSD": 50, "Traffic": 20, "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; $$;
|
||||
--//
|
||||
|
||||
|
@ -75,10 +75,10 @@ begin
|
||||
end);
|
||||
|
||||
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;
|
||||
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;
|
||||
end if;
|
||||
return NEW;
|
||||
@ -100,27 +100,23 @@ create or replace function hs_hosting_asset_booking_item_hierarchy_check_tf()
|
||||
language plpgsql as $$
|
||||
declare
|
||||
actualBookingItemType HsBookingItemType;
|
||||
expectedBookingItemTypes HsBookingItemType[];
|
||||
expectedBookingItemType HsBookingItemType;
|
||||
begin
|
||||
actualBookingItemType := (select type
|
||||
from hs_booking_item
|
||||
where NEW.bookingItemUuid = uuid);
|
||||
|
||||
if NEW.type = 'CLOUD_SERVER' then
|
||||
expectedBookingItemTypes := ARRAY['PRIVATE_CLOUD', 'CLOUD_SERVER'];
|
||||
expectedBookingItemType := 'CLOUD_SERVER';
|
||||
elsif NEW.type = 'MANAGED_SERVER' then
|
||||
expectedBookingItemTypes := ARRAY['PRIVATE_CLOUD', 'MANAGED_SERVER'];
|
||||
expectedBookingItemType := 'MANAGED_SERVER';
|
||||
elsif NEW.type = 'MANAGED_WEBSPACE' then
|
||||
if NEW.parentAssetUuid is null then
|
||||
expectedBookingItemTypes := ARRAY['MANAGED_WEBSPACE'];
|
||||
else
|
||||
expectedBookingItemTypes := ARRAY['PRIVATE_CLOUD', 'MANAGED_SERVER'];
|
||||
end if;
|
||||
expectedBookingItemType := 'MANAGED_WEBSPACE';
|
||||
end if;
|
||||
|
||||
if not actualBookingItemType = any(expectedBookingItemTypes) then
|
||||
raise exception '[400] % % must have any of % as booking-item, but got %',
|
||||
NEW.type, NEW.identifier, expectedBookingItemTypes, actualBookingItemType;
|
||||
if not actualBookingItemType = expectedBookingItemType then
|
||||
raise exception '[400] HostingAsset % % must have % as booking-item, but got %',
|
||||
NEW.type, NEW.identifier, expectedBookingItemType, actualBookingItemType;
|
||||
end if;
|
||||
return NEW;
|
||||
end; $$;
|
||||
|
@ -16,6 +16,8 @@ declare
|
||||
relatedDebitor hs_office_debitor;
|
||||
relatedPrivateCloudBookingItem hs_booking_item;
|
||||
relatedManagedServerBookingItem hs_booking_item;
|
||||
relatedCloudServerBookingItem hs_booking_item;
|
||||
relatedManagedWebspaceBookingItem hs_booking_item;
|
||||
debitorNumberSuffix varchar;
|
||||
defaultPrefix varchar;
|
||||
managedServerUuid uuid;
|
||||
@ -48,6 +50,18 @@ begin
|
||||
and item.type = 'MANAGED_SERVER';
|
||||
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 managedWebspaceUuid;
|
||||
select uuid_generate_v4() into webUnixUserUuid;
|
||||
@ -56,11 +70,11 @@ begin
|
||||
|
||||
insert into hs_hosting_asset
|
||||
(uuid, bookingitemuuid, type, parentAssetUuid, assignedToAssetUuid, identifier, caption, config)
|
||||
values (managedServerUuid, relatedPrivateCloudBookingItem.uuid, 'MANAGED_SERVER', null, null, 'vm10' || debitorNumberSuffix, 'some ManagedServer', '{ "extra": 42 }'::jsonb),
|
||||
(uuid_generate_v4(), relatedPrivateCloudBookingItem.uuid, 'CLOUD_SERVER', null, null, 'vm20' || debitorNumberSuffix, 'another CloudServer', '{ "extra": 42 }'::jsonb),
|
||||
(managedWebspaceUuid, relatedManagedServerBookingItem.uuid, 'MANAGED_WEBSPACE', managedServerUuid, null, defaultPrefix || '01', 'some Webspace', '{ "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", "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": "*", "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(), relatedCloudServerBookingItem.uuid, 'CLOUD_SERVER', null, null, 'vm20' || debitorNumberSuffix, 'another CloudServer', '{}'::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"}'::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; $$;
|
||||
--//
|
||||
|
||||
|
@ -77,14 +77,14 @@ class HsBookingItemControllerAcceptanceTest extends ContextBasedTestWithCleanup
|
||||
[
|
||||
{
|
||||
"type": "MANAGED_WEBSPACE",
|
||||
"caption": "some ManagedWebspace",
|
||||
"caption": "separate ManagedWebspace",
|
||||
"validFrom": "2022-10-01",
|
||||
"validTo": null,
|
||||
"resources": {
|
||||
"SDD": 512,
|
||||
"Multi": 4,
|
||||
"Daemons": 2,
|
||||
"Traffic": 12
|
||||
"SSD": 100,
|
||||
"Multi": 1,
|
||||
"Daemons": 0,
|
||||
"Traffic": 50
|
||||
}
|
||||
},
|
||||
{
|
||||
@ -94,9 +94,9 @@ class HsBookingItemControllerAcceptanceTest extends ContextBasedTestWithCleanup
|
||||
"validTo": null,
|
||||
"resources": {
|
||||
"RAM": 8,
|
||||
"SDD": 512,
|
||||
"SSD": 500,
|
||||
"CPUs": 2,
|
||||
"Traffic": 42
|
||||
"Traffic": 500
|
||||
}
|
||||
},
|
||||
{
|
||||
@ -105,10 +105,11 @@ class HsBookingItemControllerAcceptanceTest extends ContextBasedTestWithCleanup
|
||||
"validFrom": "2024-04-01",
|
||||
"validTo": null,
|
||||
"resources": {
|
||||
"HDD": 10240,
|
||||
"SDD": 10240,
|
||||
"HDD": 10000,
|
||||
"RAM": 32,
|
||||
"SSD": 4000,
|
||||
"CPUs": 10,
|
||||
"Traffic": 42
|
||||
"Traffic": 2000
|
||||
}
|
||||
}
|
||||
]
|
||||
@ -174,7 +175,7 @@ class HsBookingItemControllerAcceptanceTest extends ContextBasedTestWithCleanup
|
||||
@Test
|
||||
void globalAdmin_canGetArbitraryBookingItem() {
|
||||
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"))
|
||||
.map(HsBookingItemEntity::getUuid)
|
||||
.findAny().orElseThrow();
|
||||
@ -191,14 +192,14 @@ class HsBookingItemControllerAcceptanceTest extends ContextBasedTestWithCleanup
|
||||
.body("", lenientlyEquals("""
|
||||
{
|
||||
"type": "MANAGED_WEBSPACE",
|
||||
"caption": "some ManagedWebspace",
|
||||
"caption": "separate ManagedWebspace",
|
||||
"validFrom": "2022-10-01",
|
||||
"validTo": null,
|
||||
"resources": {
|
||||
"SDD": 512,
|
||||
"Multi": 4,
|
||||
"Daemons": 2,
|
||||
"Traffic": 12
|
||||
"SSD": 100,
|
||||
"Multi": 1,
|
||||
"Daemons": 0,
|
||||
"Traffic": 50
|
||||
}
|
||||
}
|
||||
""")); // @formatter:on
|
||||
@ -227,14 +228,16 @@ class HsBookingItemControllerAcceptanceTest extends ContextBasedTestWithCleanup
|
||||
void projectAdmin_canGetRelatedBookingItem() {
|
||||
context.define("superuser-alex@hostsharing.net");
|
||||
final var givenBookingItemUuid = bookingItemRepo.findByCaption("separate ManagedServer").stream()
|
||||
.filter(bi -> belongsToDebitorWithDefaultPrefix(bi, "thi"))
|
||||
.filter(bi -> belongsToDebitorWithDefaultPrefix(bi, "sec"))
|
||||
.map(HsBookingItemEntity::getUuid)
|
||||
.findAny().orElseThrow();
|
||||
|
||||
generateRbacDiagramForObjectPermission(givenBookingItemUuid, "SELECT", "select");
|
||||
|
||||
RestAssured // @formatter:off
|
||||
.given()
|
||||
.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)
|
||||
.when()
|
||||
.get("http://localhost/api/hs/booking/items/" + givenBookingItemUuid)
|
||||
@ -249,9 +252,9 @@ class HsBookingItemControllerAcceptanceTest extends ContextBasedTestWithCleanup
|
||||
"validTo": null,
|
||||
"resources": {
|
||||
"RAM": 8,
|
||||
"SDD": 512,
|
||||
"SSD": 500,
|
||||
"CPUs": 2,
|
||||
"Traffic": 42
|
||||
"Traffic": 500
|
||||
}
|
||||
}
|
||||
""")); // @formatter:on
|
||||
@ -261,7 +264,7 @@ class HsBookingItemControllerAcceptanceTest extends ContextBasedTestWithCleanup
|
||||
return ofNullable(bi)
|
||||
.map(HsBookingItemEntity::getProject)
|
||||
.map(HsBookingProjectEntity::getDebitor)
|
||||
.map(bd -> bd.getDefaultPrefix().equals(defaultPrefix))
|
||||
.filter(bd -> bd.getDefaultPrefix().equals(defaultPrefix))
|
||||
.isPresent();
|
||||
}
|
||||
}
|
||||
|
@ -44,11 +44,11 @@ class HsBookingItemEntityPatcherUnitTest extends PatchUnitTestBase<
|
||||
private static final Map<String, Object> PATCH_RESOURCES = patchMap(
|
||||
entry("CPU", 2),
|
||||
entry("HDD", null),
|
||||
entry("SDD", 256)
|
||||
entry("SSD", 256)
|
||||
);
|
||||
private static final Map<String, Object> PATCHED_RESOURCES = patchMap(
|
||||
entry("CPU", 2),
|
||||
entry("SDD", 256),
|
||||
entry("SSD", 256),
|
||||
entry("MEM", 64)
|
||||
);
|
||||
|
||||
|
@ -6,7 +6,7 @@ import net.hostsharing.hsadminng.hs.booking.project.HsBookingProjectRepository;
|
||||
import net.hostsharing.hsadminng.hs.office.debitor.HsOfficeDebitorRepository;
|
||||
import net.hostsharing.hsadminng.rbac.rbacgrant.RawRbacGrantRepository;
|
||||
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.JpaAttempt;
|
||||
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.rbac.rbacgrant.RawRbacGrantEntity.distinctGrantDisplaysOf;
|
||||
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 org.assertj.core.api.Assertions.assertThat;
|
||||
|
||||
@ -174,9 +174,9 @@ class HsBookingItemRepositoryIntegrationTest extends ContextBasedTestWithCleanup
|
||||
// then
|
||||
allTheseBookingItemsAreReturned(
|
||||
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_SERVER, [2022-10-01,), separate ManagedServer, { CPUs: 2, RAM: 8, SDD: 512, Traffic: 42 })",
|
||||
"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, 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, SSD: 500, Traffic: 500 })",
|
||||
"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
|
||||
@ -194,9 +194,9 @@ class HsBookingItemRepositoryIntegrationTest extends ContextBasedTestWithCleanup
|
||||
// then:
|
||||
exactlyTheseBookingItemsAreReturned(
|
||||
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_WEBSPACE, [2022-10-01,), some ManagedWebspace, { Daemons: 2, Multi: 4, SDD: 512, Traffic: 12 })",
|
||||
"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, 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,), 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: 10000, RAM: 32, SSD: 4000, Traffic: 2000 })");
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -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);
|
||||
}
|
||||
}
|
@ -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);
|
||||
}
|
||||
}
|
@ -1,23 +1,37 @@
|
||||
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 java.util.Map;
|
||||
|
||||
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.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;
|
||||
|
||||
class HsCloudServerBookingItemValidatorUnitTest {
|
||||
|
||||
final HsBookingDebitorEntity debitor = HsBookingDebitorEntity.builder()
|
||||
.debitorNumber(12345)
|
||||
.build();
|
||||
final HsBookingProjectEntity project = HsBookingProjectEntity.builder()
|
||||
.debitor(debitor)
|
||||
.caption("Test-Project")
|
||||
.build();
|
||||
|
||||
@Test
|
||||
void validatesProperties() {
|
||||
// given
|
||||
final var validator = HsBookingItemEntityValidators.forType(CLOUD_SERVER);
|
||||
final var cloudServerBookingItemEntity = HsBookingItemEntity.builder()
|
||||
.type(CLOUD_SERVER)
|
||||
.project(project)
|
||||
.caption("Test-Server")
|
||||
.resources(Map.ofEntries(
|
||||
entry("CPUs", 2),
|
||||
entry("RAM", 25),
|
||||
@ -28,24 +42,77 @@ class HsCloudServerBookingItemValidatorUnitTest {
|
||||
.build();
|
||||
|
||||
// when
|
||||
final var result = validator.validate(cloudServerBookingItemEntity);
|
||||
final var result = HsBookingItemEntityValidator.doValidate(cloudServerBookingItemEntity);
|
||||
|
||||
// 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
|
||||
void containsAllValidations() {
|
||||
// when
|
||||
final var validator = forType(CLOUD_SERVER);
|
||||
final var validator = HsBookingItemEntityValidatorRegistry.forType(CLOUD_SERVER);
|
||||
|
||||
// then
|
||||
assertThat(validator.properties()).map(Map::toString).containsExactlyInAnyOrder(
|
||||
"{type=integer, propertyName=CPUs, required=true, unit=null, min=1, max=32, step=null}",
|
||||
"{type=integer, propertyName=RAM, required=true, unit=GB, min=1, max=128, step=null}",
|
||||
"{type=integer, propertyName=SSD, required=true, unit=GB, min=25, max=1000, step=25}",
|
||||
"{type=integer, propertyName=HDD, required=false, unit=GB, min=0, max=4000, step=250}",
|
||||
"{type=integer, propertyName=Traffic, required=true, unit=GB, min=250, max=10000, step=250}",
|
||||
"{type=enumeration, propertyName=SLA-Infrastructure, required=false, values=[BASIC, EXT8H, EXT4H, EXT2H]}");
|
||||
"{type=integer, propertyName=CPUs, min=1, max=32, required=true, isTotalsValidator=false}",
|
||||
"{type=integer, propertyName=RAM, unit=GB, min=1, max=128, required=true, isTotalsValidator=false}",
|
||||
"{type=integer, propertyName=SSD, unit=GB, min=25, max=1000, step=25, required=true, isTotalsValidator=false}",
|
||||
"{type=integer, propertyName=HDD, unit=GB, min=0, max=4000, step=250, required=false, defaultValue=0, isTotalsValidator=false}",
|
||||
"{type=integer, propertyName=Traffic, unit=GB, min=250, max=10000, step=250, required=true, isTotalsValidator=false}",
|
||||
"{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"
|
||||
);
|
||||
}
|
||||
}
|
||||
|
@ -1,56 +1,227 @@
|
||||
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 net.hostsharing.hsadminng.hs.hosting.asset.HsHostingAssetEntity;
|
||||
import net.hostsharing.hsadminng.hs.hosting.asset.HsHostingAssetType;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import java.util.Collection;
|
||||
import java.util.List;
|
||||
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.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.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;
|
||||
|
||||
class HsManagedServerBookingItemValidatorUnitTest {
|
||||
|
||||
final HsBookingDebitorEntity debitor = HsBookingDebitorEntity.builder()
|
||||
.debitorNumber(12345)
|
||||
.build();
|
||||
final HsBookingProjectEntity project = HsBookingProjectEntity.builder()
|
||||
.debitor(debitor)
|
||||
.caption("Test-Project")
|
||||
.build();
|
||||
|
||||
@Test
|
||||
void validatesProperties() {
|
||||
// given
|
||||
final var validator = HsBookingItemEntityValidators.forType(MANAGED_SERVER);
|
||||
final var mangedServerBookingItemEntity = HsBookingItemEntity.builder()
|
||||
.type(MANAGED_SERVER)
|
||||
.project(project)
|
||||
.resources(Map.ofEntries(
|
||||
entry("CPUs", 2),
|
||||
entry("RAM", 25),
|
||||
entry("SSD", 25),
|
||||
entry("Traffic", 250),
|
||||
entry("SLA-Platform", "BASIC"),
|
||||
entry("SLA-EMail", true)
|
||||
))
|
||||
.build();
|
||||
|
||||
// when
|
||||
final var result = validator.validate(mangedServerBookingItemEntity);
|
||||
final var result = HsBookingItemEntityValidator.doValidate(mangedServerBookingItemEntity);
|
||||
|
||||
// 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
|
||||
void containsAllValidations() {
|
||||
// when
|
||||
final var validator = forType(MANAGED_SERVER);
|
||||
final var validator = HsBookingItemEntityValidatorRegistry.forType(MANAGED_SERVER);
|
||||
|
||||
// then
|
||||
assertThat(validator.properties()).map(Map::toString).containsExactlyInAnyOrder(
|
||||
"{type=integer, propertyName=CPUs, required=true, unit=null, min=1, max=32, step=null}",
|
||||
"{type=integer, propertyName=RAM, required=true, unit=GB, min=1, max=128, step=null}",
|
||||
"{type=integer, propertyName=SSD, required=true, unit=GB, min=25, max=1000, step=25}",
|
||||
"{type=integer, propertyName=HDD, required=false, unit=GB, min=0, max=4000, step=250}",
|
||||
"{type=integer, propertyName=Traffic, required=true, unit=GB, min=250, max=10000, step=250}",
|
||||
"{type=enumeration, propertyName=SLA-Platform, required=false, values=[BASIC, EXT8H, EXT4H, EXT2H]}",
|
||||
"{type=boolean, propertyName=SLA-EMail, required=false, falseIf={SLA-Platform=BASIC}}",
|
||||
"{type=boolean, propertyName=SLA-Maria, required=false, falseIf={SLA-Platform=BASIC}}",
|
||||
"{type=boolean, propertyName=SLA-PgSQL, required=false, falseIf={SLA-Platform=BASIC}}",
|
||||
"{type=boolean, propertyName=SLA-Office, required=false, falseIf={SLA-Platform=BASIC}}",
|
||||
"{type=boolean, propertyName=SLA-Web, required=false, falseIf={SLA-Platform=BASIC}}");
|
||||
"{type=integer, propertyName=CPUs, min=1, max=32, required=true, isTotalsValidator=false}",
|
||||
"{type=integer, propertyName=RAM, unit=GB, min=1, max=128, required=true, isTotalsValidator=false}",
|
||||
"{type=integer, propertyName=SSD, unit=GB, min=25, max=1000, step=25, required=true, isTotalsValidator=true, thresholdPercentage=200}",
|
||||
"{type=integer, propertyName=HDD, unit=GB, min=0, max=4000, step=250, required=false, defaultValue=0, isTotalsValidator=true, thresholdPercentage=200}",
|
||||
"{type=integer, propertyName=Traffic, unit=GB, min=250, max=10000, step=250, required=true, isTotalsValidator=true, thresholdPercentage=200}",
|
||||
"{type=enumeration, propertyName=SLA-Platform, values=[BASIC, EXT8H, EXT4H, EXT2H], required=false, defaultValue=BASIC, isTotalsValidator=false}",
|
||||
"{type=boolean, propertyName=SLA-EMail, required=false, defaultValue=false, isTotalsValidator=false}",
|
||||
"{type=boolean, propertyName=SLA-Maria, required=false, isTotalsValidator=false}",
|
||||
"{type=boolean, propertyName=SLA-PgSQL, required=false, isTotalsValidator=false}",
|
||||
"{type=boolean, propertyName=SLA-Office, required=false, isTotalsValidator=false}",
|
||||
"{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();
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -1,54 +1,66 @@
|
||||
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 java.util.Map;
|
||||
|
||||
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.validators.HsBookingItemEntityValidators.forType;
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
|
||||
class HsManagedWebspaceBookingItemValidatorUnitTest {
|
||||
|
||||
final HsBookingDebitorEntity debitor = HsBookingDebitorEntity.builder()
|
||||
.debitorNumber(12345)
|
||||
.build();
|
||||
final HsBookingProjectEntity project = HsBookingProjectEntity.builder()
|
||||
.debitor(debitor)
|
||||
.caption("Test-Project")
|
||||
.build();
|
||||
|
||||
@Test
|
||||
void validatesProperties() {
|
||||
// given
|
||||
final var mangedServerBookingItemEntity = HsBookingItemEntity.builder()
|
||||
.type(MANAGED_WEBSPACE)
|
||||
.project(project)
|
||||
.caption("Test Managed-Webspace")
|
||||
.resources(Map.ofEntries(
|
||||
entry("CPUs", 2),
|
||||
entry("RAM", 25),
|
||||
entry("SSD", 25),
|
||||
entry("Traffic", 250),
|
||||
entry("SLA-EMail", true)
|
||||
))
|
||||
.build();
|
||||
final var validator = forType(mangedServerBookingItemEntity.getType());
|
||||
|
||||
// when
|
||||
final var result = validator.validate(mangedServerBookingItemEntity);
|
||||
final var result = HsBookingItemEntityValidator.doValidate(mangedServerBookingItemEntity);
|
||||
|
||||
// then
|
||||
assertThat(result).containsExactlyInAnyOrder(
|
||||
"'resources.CPUs' is not expected but is set to '2'",
|
||||
"'resources.SLA-EMail' is not expected but is set to 'true'",
|
||||
"'resources.RAM' is not expected but is set to '25'");
|
||||
"D-12345:Test-Project:Test Managed-Webspace.resources.CPUs is not expected but is set to '2'",
|
||||
"D-12345:Test-Project:Test Managed-Webspace.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
|
||||
void containsAllValidations() {
|
||||
// when
|
||||
final var validator = forType(MANAGED_WEBSPACE);
|
||||
final var validator = HsBookingItemEntityValidatorRegistry.forType(MANAGED_WEBSPACE);
|
||||
|
||||
// then
|
||||
assertThat(validator.properties()).map(Map::toString).containsExactlyInAnyOrder(
|
||||
"{type=integer, propertyName=SSD, required=true, unit=GB, min=1, max=100, step=1}",
|
||||
"{type=integer, propertyName=HDD, required=false, unit=GB, min=0, max=250, step=10}",
|
||||
"{type=integer, propertyName=Traffic, required=true, unit=GB, min=10, max=1000, step=10}",
|
||||
"{type=enumeration, propertyName=SLA-Platform, required=false, values=[BASIC, EXT24H]}",
|
||||
"{type=integer, propertyName=Daemons, required=false, unit=null, min=0, max=10, step=null}",
|
||||
"{type=boolean, propertyName=Online Office Server, required=false, falseIf=null}");
|
||||
"{type=integer, propertyName=SSD, unit=GB, min=1, max=100, step=1, required=true, isTotalsValidator=false}",
|
||||
"{type=integer, propertyName=HDD, unit=GB, min=0, max=250, step=10, required=false, isTotalsValidator=false}",
|
||||
"{type=integer, propertyName=Traffic, unit=GB, min=10, max=1000, step=10, required=true, isTotalsValidator=false}",
|
||||
"{type=integer, propertyName=Multi, min=1, max=100, step=1, required=false, defaultValue=1, isTotalsValidator=false}",
|
||||
"{type=integer, propertyName=Daemons, min=0, max=10, required=false, defaultValue=0, isTotalsValidator=false}",
|
||||
"{type=boolean, propertyName=Online Office Server, required=false, isTotalsValidator=false}",
|
||||
"{type=enumeration, propertyName=SLA-Platform, values=[BASIC, EXT24H], required=false, defaultValue=BASIC, isTotalsValidator=false}");
|
||||
}
|
||||
}
|
||||
|
@ -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"
|
||||
);
|
||||
}
|
||||
|
||||
}
|
@ -4,7 +4,7 @@ import net.hostsharing.hsadminng.context.Context;
|
||||
import net.hostsharing.hsadminng.hs.booking.debitor.HsBookingDebitorRepository;
|
||||
import net.hostsharing.hsadminng.rbac.rbacgrant.RawRbacGrantRepository;
|
||||
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.JpaAttempt;
|
||||
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.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 org.assertj.core.api.Assertions.assertThat;
|
||||
|
||||
|
@ -5,6 +5,7 @@ import io.restassured.http.ContentType;
|
||||
import net.hostsharing.hsadminng.HsadminNgApplication;
|
||||
import net.hostsharing.hsadminng.hs.booking.item.HsBookingItemEntity;
|
||||
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.office.debitor.HsOfficeDebitorRepository;
|
||||
import net.hostsharing.hsadminng.rbac.test.ContextBasedTestWithCleanup;
|
||||
@ -20,8 +21,9 @@ import java.util.Map;
|
||||
import java.util.UUID;
|
||||
|
||||
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_WEBSPACE;
|
||||
import static net.hostsharing.hsadminng.hs.hosting.asset.HsHostingAssetType.UNIX_USER;
|
||||
import static net.hostsharing.hsadminng.rbac.test.JsonMatcher.lenientlyEquals;
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
import static org.hamcrest.Matchers.matchesRegex;
|
||||
@ -77,25 +79,19 @@ class HsHostingAssetControllerAcceptanceTest extends ContextBasedTestWithCleanup
|
||||
"type": "MANAGED_WEBSPACE",
|
||||
"identifier": "sec01",
|
||||
"caption": "some Webspace",
|
||||
"config": {
|
||||
"extra": 42
|
||||
}
|
||||
"config": {}
|
||||
},
|
||||
{
|
||||
"type": "MANAGED_WEBSPACE",
|
||||
"identifier": "fir01",
|
||||
"caption": "some Webspace",
|
||||
"config": {
|
||||
"extra": 42
|
||||
}
|
||||
"config": {}
|
||||
},
|
||||
{
|
||||
"type": "MANAGED_WEBSPACE",
|
||||
"identifier": "thi01",
|
||||
"caption": "some Webspace",
|
||||
"config": {
|
||||
"extra": 42
|
||||
}
|
||||
"config": {}
|
||||
}
|
||||
]
|
||||
"""));
|
||||
@ -124,7 +120,9 @@ class HsHostingAssetControllerAcceptanceTest extends ContextBasedTestWithCleanup
|
||||
"identifier": "vm1011",
|
||||
"caption": "some ManagedServer",
|
||||
"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",
|
||||
"caption": "some ManagedServer",
|
||||
"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",
|
||||
"caption": "some ManagedServer",
|
||||
"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() {
|
||||
|
||||
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
|
||||
.given()
|
||||
@ -165,12 +174,13 @@ class HsHostingAssetControllerAcceptanceTest extends ContextBasedTestWithCleanup
|
||||
.body("""
|
||||
{
|
||||
"bookingItemUuid": "%s",
|
||||
"type": "MANAGED_SERVER",
|
||||
"identifier": "vm1400",
|
||||
"caption": "some new ManagedServer",
|
||||
"config": { "monit_max_ssd_usage": 80, "monit_max_cpu_usage": 90, "monit_max_ram_usage": 70 }
|
||||
"type": "MANAGED_WEBSPACE",
|
||||
"identifier": "fir10",
|
||||
"parentAssetUuid": "%s",
|
||||
"caption": "some separate ManagedWebspace HA",
|
||||
"config": {}
|
||||
}
|
||||
""".formatted(givenBookingItem.getUuid()))
|
||||
""".formatted(givenBookingItem.getUuid(), givenParentAsset.getUuid()))
|
||||
.port(port)
|
||||
.when()
|
||||
.post("http://localhost/api/hs/hosting/assets")
|
||||
@ -179,10 +189,10 @@ class HsHostingAssetControllerAcceptanceTest extends ContextBasedTestWithCleanup
|
||||
.contentType(ContentType.JSON)
|
||||
.body("", lenientlyEquals("""
|
||||
{
|
||||
"type": "MANAGED_SERVER",
|
||||
"identifier": "vm1400",
|
||||
"caption": "some new ManagedServer",
|
||||
"config": { "monit_max_ssd_usage": 80, "monit_max_cpu_usage": 90, "monit_max_ram_usage": 70 }
|
||||
"type": "MANAGED_WEBSPACE",
|
||||
"identifier": "fir10",
|
||||
"caption": "some separate ManagedWebspace HA",
|
||||
"config": {}
|
||||
}
|
||||
"""))
|
||||
.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(
|
||||
location.substring(location.lastIndexOf('/') + 1));
|
||||
assertThat(newUserUuid).isNotNull();
|
||||
toCleanup(HsHostingAssetEntity.class, newUserUuid);
|
||||
}
|
||||
|
||||
@Test
|
||||
@ -240,7 +251,7 @@ class HsHostingAssetControllerAcceptanceTest extends ContextBasedTestWithCleanup
|
||||
}
|
||||
|
||||
@Test
|
||||
void additionalValidationsArePerformend_whenAddingAsset() {
|
||||
void propertyValidationsArePerformend_whenAddingAsset() {
|
||||
|
||||
context.define("superuser-alex@hostsharing.net");
|
||||
final var givenBookingItem = givenBookingItem("D-1000111 default project", "some PrivateCloud");
|
||||
@ -267,9 +278,66 @@ class HsHostingAssetControllerAcceptanceTest extends ContextBasedTestWithCleanup
|
||||
.body("", lenientlyEquals("""
|
||||
{
|
||||
"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("""
|
||||
{
|
||||
"caption": "some ManagedServer",
|
||||
"config": {
|
||||
"extra": 42
|
||||
}
|
||||
"config": {}
|
||||
}
|
||||
""")); // @formatter:on
|
||||
}
|
||||
@ -340,9 +406,7 @@ class HsHostingAssetControllerAcceptanceTest extends ContextBasedTestWithCleanup
|
||||
{
|
||||
"identifier": "vm1013",
|
||||
"caption": "some ManagedServer",
|
||||
"config": {
|
||||
"extra": 42
|
||||
}
|
||||
"config": {}
|
||||
}
|
||||
""")); // @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) {
|
||||
return bookingItemRepo.findByCaption(bookingItemCaption).stream()
|
||||
.filter(bi -> bi.getRelatedProject().getCaption().contains(projectCaption))
|
||||
|
@ -40,11 +40,11 @@ class HsHostingAssetEntityPatcherUnitTest extends PatchUnitTestBase<
|
||||
private static final Map<String, Object> PATCH_CONFIG = patchMap(
|
||||
entry("CPU", 2),
|
||||
entry("HDD", null),
|
||||
entry("SDD", 256)
|
||||
entry("SSD", 256)
|
||||
);
|
||||
private static final Map<String, Object> PATCHED_CONFIG = patchMap(
|
||||
entry("CPU", 2),
|
||||
entry("SDD", 256),
|
||||
entry("SSD", 256),
|
||||
entry("MEM", 64)
|
||||
);
|
||||
|
||||
|
@ -33,7 +33,8 @@ class HsHostingAssetPropsControllerAcceptanceTest {
|
||||
[
|
||||
"MANAGED_SERVER",
|
||||
"MANAGED_WEBSPACE",
|
||||
"CLOUD_SERVER"
|
||||
"CLOUD_SERVER",
|
||||
"UNIX_USER"
|
||||
]
|
||||
"""));
|
||||
// @formatter:on
|
||||
@ -55,56 +56,54 @@ class HsHostingAssetPropsControllerAcceptanceTest {
|
||||
{
|
||||
"type": "integer",
|
||||
"propertyName": "monit_min_free_ssd",
|
||||
"required": false,
|
||||
"unit": null,
|
||||
"min": 1,
|
||||
"max": 1000,
|
||||
"step": null
|
||||
"required": false,
|
||||
"isTotalsValidator": false
|
||||
},
|
||||
{
|
||||
"type": "integer",
|
||||
"propertyName": "monit_min_free_hdd",
|
||||
"required": false,
|
||||
"unit": null,
|
||||
"min": 1,
|
||||
"max": 4000,
|
||||
"step": null
|
||||
"required": false,
|
||||
"isTotalsValidator": false
|
||||
},
|
||||
{
|
||||
"type": "integer",
|
||||
"propertyName": "monit_max_ssd_usage",
|
||||
"required": true,
|
||||
"unit": "%",
|
||||
"min": 10,
|
||||
"max": 100,
|
||||
"step": null
|
||||
"required": true,
|
||||
"isTotalsValidator": false
|
||||
},
|
||||
{
|
||||
"type": "integer",
|
||||
"propertyName": "monit_max_hdd_usage",
|
||||
"required": false,
|
||||
"unit": "%",
|
||||
"min": 10,
|
||||
"max": 100,
|
||||
"step": null
|
||||
"required": false,
|
||||
"isTotalsValidator": false
|
||||
},
|
||||
{
|
||||
"type": "integer",
|
||||
"propertyName": "monit_max_cpu_usage",
|
||||
"required": true,
|
||||
"unit": "%",
|
||||
"min": 10,
|
||||
"max": 100,
|
||||
"step": null
|
||||
"required": true,
|
||||
"isTotalsValidator": false
|
||||
},
|
||||
{
|
||||
"type": "integer",
|
||||
"propertyName": "monit_max_ram_usage",
|
||||
"required": true,
|
||||
"unit": "%",
|
||||
"min": 10,
|
||||
"max": 100,
|
||||
"step": null
|
||||
"required": true,
|
||||
"isTotalsValidator": false
|
||||
}
|
||||
]
|
||||
"""));
|
||||
|
@ -3,10 +3,11 @@ package net.hostsharing.hsadminng.hs.hosting.asset;
|
||||
import net.hostsharing.hsadminng.context.Context;
|
||||
import net.hostsharing.hsadminng.hs.booking.item.HsBookingItemEntity;
|
||||
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.rbac.rbacgrant.RawRbacGrantRepository;
|
||||
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.JpaAttempt;
|
||||
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.rbac.rbacgrant.RawRbacGrantEntity.distinctGrantDisplaysOf;
|
||||
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 org.assertj.core.api.Assertions.assertThat;
|
||||
|
||||
@ -70,12 +71,13 @@ class HsHostingAssetRepositoryIntegrationTest extends ContextBasedTestWithCleanu
|
||||
// given
|
||||
context("superuser-alex@hostsharing.net");
|
||||
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
|
||||
final var result = attempt(em, () -> {
|
||||
final var newAsset = HsHostingAssetEntity.builder()
|
||||
.bookingItem(givenManagedServer.getBookingItem())
|
||||
.bookingItem(newWebspaceBookingItem)
|
||||
.parentAsset(givenManagedServer)
|
||||
.caption("some new managed webspace")
|
||||
.type(MANAGED_WEBSPACE)
|
||||
@ -95,18 +97,19 @@ class HsHostingAssetRepositoryIntegrationTest extends ContextBasedTestWithCleanu
|
||||
public void createsAndGrantsRoles() {
|
||||
// given
|
||||
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 initialGrantNames = distinctGrantDisplaysOf(rawGrantRepo.findAll()).stream()
|
||||
.map(s -> s.replace("hs_office_", ""))
|
||||
.toList();
|
||||
final var givenBookingItem = givenBookingItem("D-1000111 default project", "some PrivateCloud");
|
||||
final var initialGrantNames = distinctGrantDisplaysOf(rawGrantRepo.findAll());
|
||||
|
||||
// when
|
||||
final var result = attempt(em, () -> {
|
||||
final var newAsset = HsHostingAssetEntity.builder()
|
||||
.bookingItem(givenBookingItem)
|
||||
.type(HsHostingAssetType.MANAGED_SERVER)
|
||||
.identifier("vm9000")
|
||||
.bookingItem(newWebspaceBookingItem)
|
||||
.parentAsset(givenManagedServer)
|
||||
.type(HsHostingAssetType.MANAGED_WEBSPACE)
|
||||
.identifier("fir00")
|
||||
.caption("some new managed webspace")
|
||||
.build();
|
||||
return toCleanup(assetRepo.save(newAsset));
|
||||
@ -117,29 +120,33 @@ class HsHostingAssetRepositoryIntegrationTest extends ContextBasedTestWithCleanu
|
||||
final var all = rawRoleRepo.findAll();
|
||||
assertThat(distinctRoleNamesOf(all)).containsExactlyInAnyOrder(Array.from(
|
||||
initialRoleNames,
|
||||
"hs_hosting_asset#vm9000:OWNER",
|
||||
"hs_hosting_asset#vm9000:ADMIN",
|
||||
"hs_hosting_asset#vm9000:AGENT",
|
||||
"hs_hosting_asset#vm9000:TENANT"));
|
||||
"hs_hosting_asset#fir00:ADMIN",
|
||||
"hs_hosting_asset#fir00:AGENT",
|
||||
"hs_hosting_asset#fir00:OWNER",
|
||||
"hs_hosting_asset#fir00:TENANT"));
|
||||
assertThat(distinctGrantDisplaysOf(rawGrantRepo.findAll()))
|
||||
.map(s -> s.replace("hs_office_", ""))
|
||||
.containsExactlyInAnyOrder(fromFormatted(
|
||||
initialGrantNames,
|
||||
|
||||
// owner
|
||||
"{ grant role:hs_hosting_asset#vm9000:OWNER to role:hs_booking_item#somePrivateCloud: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#vm9000:ADMIN to role:hs_hosting_asset#vm9000:OWNER by system and assume }",
|
||||
"{ grant role:hs_hosting_asset#fir00:OWNER to role:hs_booking_item#fir01:ADMIN by system and assume }",
|
||||
"{ grant role:hs_hosting_asset#fir00:OWNER to role:hs_hosting_asset#vm1011:ADMIN by system and assume }",
|
||||
"{ grant perm:hs_hosting_asset#fir00:DELETE to role:hs_hosting_asset#fir00:OWNER by system and assume }",
|
||||
|
||||
// admin
|
||||
"{ grant perm:hs_hosting_asset#vm9000:INSERT>hs_hosting_asset to role:hs_hosting_asset#vm9000:ADMIN 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#vm9000:ADMIN to role:hs_booking_item#somePrivateCloud:AGENT by system and assume }",
|
||||
"{ grant role:hs_hosting_asset#vm9000:TENANT to role:hs_hosting_asset#vm9000:AGENT by system and assume }",
|
||||
"{ grant role:hs_hosting_asset#vm9000:AGENT 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 role:hs_hosting_asset#fir00:ADMIN to role:hs_booking_item#fir01: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 perm:hs_hosting_asset#fir00:UPDATE to role:hs_hosting_asset#fir00: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
|
||||
"{ 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));
|
||||
}
|
||||
@ -164,9 +171,9 @@ class HsHostingAssetRepositoryIntegrationTest extends ContextBasedTestWithCleanu
|
||||
// then
|
||||
allTheseServersAreReturned(
|
||||
result,
|
||||
"HsHostingAssetEntity(MANAGED_WEBSPACE, sec01, some Webspace, MANAGED_SERVER:vm1012, D-1000212:D-1000212 default project:separate ManagedServer, { extra: 42 })",
|
||||
"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 ManagedServer, { extra: 42 })");
|
||||
"HsHostingAssetEntity(MANAGED_WEBSPACE, sec01, some Webspace, MANAGED_SERVER:vm1012, D-1000212:D-1000212 default project:separate ManagedWebspace)",
|
||||
"HsHostingAssetEntity(MANAGED_WEBSPACE, fir01, some Webspace, MANAGED_SERVER:vm1011, D-1000111:D-1000111 default project:separate ManagedWebspace)",
|
||||
"HsHostingAssetEntity(MANAGED_WEBSPACE, thi01, some Webspace, MANAGED_SERVER:vm1013, D-1000313:D-1000313 default project:separate ManagedWebspace)");
|
||||
}
|
||||
|
||||
@Test
|
||||
@ -182,9 +189,8 @@ class HsHostingAssetRepositoryIntegrationTest extends ContextBasedTestWithCleanu
|
||||
// then:
|
||||
exactlyTheseAssetsAreReturned(
|
||||
result,
|
||||
"HsHostingAssetEntity(MANAGED_WEBSPACE, fir01, some Webspace, MANAGED_SERVER:vm1011, D-1000111:D-1000111 default project:separate ManagedServer, { extra: 42 })",
|
||||
"HsHostingAssetEntity(MANAGED_SERVER, vm1011, some ManagedServer, D-1000111:D-1000111 default project:some PrivateCloud, { extra: 42 })",
|
||||
"HsHostingAssetEntity(CLOUD_SERVER, vm2011, another CloudServer, D-1000111:D-1000111 default project:some PrivateCloud, { 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:separate ManagedServer, { monit_max_cpu_usage: 90, monit_max_ram_usage: 80, monit_max_ssd_usage: 70 })");
|
||||
}
|
||||
|
||||
@Test
|
||||
@ -200,7 +206,7 @@ class HsHostingAssetRepositoryIntegrationTest extends ContextBasedTestWithCleanu
|
||||
// then
|
||||
allTheseServersAreReturned(
|
||||
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) {
|
||||
return jpaAttempt.transacted(() -> {
|
||||
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()
|
||||
.bookingItem(givenBookingItem)
|
||||
.type(CLOUD_SERVER)
|
||||
@ -367,20 +373,30 @@ class HsHostingAssetRepositoryIntegrationTest extends ContextBasedTestWithCleanu
|
||||
}
|
||||
|
||||
HsBookingItemEntity givenBookingItem(final String projectCaption, final String bookingItemCaption) {
|
||||
final var givenProject = projectRepo.findByCaption(projectCaption).stream()
|
||||
.findAny().orElseThrow();
|
||||
return bookingItemRepo.findAllByProjectUuid(givenProject.getUuid()).stream()
|
||||
.filter(i -> i.getCaption().equals(bookingItemCaption))
|
||||
return bookingItemRepo.findByCaption(bookingItemCaption).stream()
|
||||
.filter(i -> i.getRelatedProject().getCaption().equals(projectCaption))
|
||||
.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()
|
||||
.findAny().orElseThrow();
|
||||
return assetRepo.findAllByCriteria(givenProject.getUuid(), null, type).stream()
|
||||
.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(
|
||||
final List<HsHostingAssetEntity> actualResult,
|
||||
final String... serverNames) {
|
||||
|
@ -7,7 +7,7 @@ import java.util.Map;
|
||||
|
||||
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.validators.HsHostingAssetEntityValidators.forType;
|
||||
import static net.hostsharing.hsadminng.hs.hosting.asset.validators.HsHostingAssetEntityValidator.forType;
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
|
||||
class HsCloudServerHostingAssetValidatorUnitTest {
|
||||
@ -17,6 +17,7 @@ class HsCloudServerHostingAssetValidatorUnitTest {
|
||||
// given
|
||||
final var cloudServerHostingAssetEntity = HsHostingAssetEntity.builder()
|
||||
.type(CLOUD_SERVER)
|
||||
.identifier("vm1234")
|
||||
.config(Map.ofEntries(
|
||||
entry("RAM", 2000)
|
||||
))
|
||||
@ -28,7 +29,7 @@ class HsCloudServerHostingAssetValidatorUnitTest {
|
||||
final var result = validator.validate(cloudServerHostingAssetEntity);
|
||||
|
||||
// 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
|
||||
|
@ -6,27 +6,28 @@ import org.junit.jupiter.api.Test;
|
||||
import jakarta.validation.ValidationException;
|
||||
|
||||
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.catchThrowable;
|
||||
|
||||
class HsHostingAssetEntityValidatorsUnitTest {
|
||||
class HsHostingAssetEntityValidatorUnitTest {
|
||||
|
||||
@Test
|
||||
void validThrowsException() {
|
||||
// given
|
||||
final var managedServerHostingAssetEntity = HsHostingAssetEntity.builder()
|
||||
.type(MANAGED_SERVER)
|
||||
.identifier("vm1234")
|
||||
.build();
|
||||
|
||||
// when
|
||||
final var result = catchThrowable( ()-> valid(managedServerHostingAssetEntity) );
|
||||
final var result = catchThrowable( ()-> validated(managedServerHostingAssetEntity) );
|
||||
|
||||
// then
|
||||
assertThat(result).isInstanceOf(ValidationException.class)
|
||||
.hasMessageContaining(
|
||||
"'config.monit_max_ssd_usage' is required but missing",
|
||||
"'config.monit_max_cpu_usage' is required but missing",
|
||||
"'config.monit_max_ram_usage' is required but missing");
|
||||
"MANAGED_SERVER:vm1234.config.monit_max_ssd_usage is required but missing",
|
||||
"MANAGED_SERVER:vm1234.config.monit_max_cpu_usage is required but missing",
|
||||
"MANAGED_SERVER:vm1234.config.monit_max_ram_usage is required but missing");
|
||||
}
|
||||
}
|
@ -7,7 +7,7 @@ import java.util.Map;
|
||||
|
||||
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.validators.HsHostingAssetEntityValidators.forType;
|
||||
import static net.hostsharing.hsadminng.hs.hosting.asset.validators.HsHostingAssetEntityValidator.forType;
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
|
||||
class HsManagedServerHostingAssetValidatorUnitTest {
|
||||
@ -17,6 +17,7 @@ class HsManagedServerHostingAssetValidatorUnitTest {
|
||||
// given
|
||||
final var mangedWebspaceHostingAssetEntity = HsHostingAssetEntity.builder()
|
||||
.type(MANAGED_SERVER)
|
||||
.identifier("vm1234")
|
||||
.config(Map.ofEntries(
|
||||
entry("monit_max_hdd_usage", "90"),
|
||||
entry("monit_max_cpu_usage", 2),
|
||||
@ -30,9 +31,9 @@ class HsManagedServerHostingAssetValidatorUnitTest {
|
||||
|
||||
// then
|
||||
assertThat(result).containsExactlyInAnyOrder(
|
||||
"'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'",
|
||||
"'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_ssd_usage is required but missing",
|
||||
"MANAGED_SERVER:vm1234.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_cpu_usage is expected to be >= 10 but is 2",
|
||||
"MANAGED_SERVER:vm1234.config.monit_max_ram_usage is expected to be <= 100 but is 101");
|
||||
}
|
||||
}
|
||||
|
@ -1,13 +1,14 @@
|
||||
package net.hostsharing.hsadminng.hs.hosting.asset.validators;
|
||||
|
||||
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.HsHostingAssetType;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import java.util.Map;
|
||||
|
||||
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 org.assertj.core.api.Assertions.assertThat;
|
||||
import static net.hostsharing.hsadminng.hs.booking.project.TestHsBookingProject.TEST_PROJECT;
|
||||
@ -16,21 +17,32 @@ class HsManagedWebspaceHostingAssetValidatorUnitTest {
|
||||
|
||||
final HsBookingItemEntity managedServerBookingItem = HsBookingItemEntity.builder()
|
||||
.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();
|
||||
final HsHostingAssetEntity mangedServerAssetEntity = HsHostingAssetEntity.builder()
|
||||
.type(MANAGED_SERVER)
|
||||
.type(HsHostingAssetType.MANAGED_SERVER)
|
||||
.bookingItem(managedServerBookingItem)
|
||||
.identifier("vm1234")
|
||||
.config(Map.ofEntries(
|
||||
entry("HDD", 0),
|
||||
entry("SSD", 1),
|
||||
entry("Traffic", 10)
|
||||
entry("monit_max_ssd_usage", 70),
|
||||
entry("monit_max_cpu_usage", 80),
|
||||
entry("monit_max_ram_usage", 90)
|
||||
))
|
||||
.build();
|
||||
|
||||
@Test
|
||||
void validatesIdentifier() {
|
||||
// given
|
||||
final var validator = HsHostingAssetEntityValidators.forType(MANAGED_WEBSPACE);
|
||||
final var validator = HsHostingAssetEntityValidator.forType(MANAGED_WEBSPACE);
|
||||
final var mangedWebspaceHostingAssetEntity = HsHostingAssetEntity.builder()
|
||||
.type(MANAGED_WEBSPACE)
|
||||
.parentAsset(mangedServerAssetEntity)
|
||||
@ -47,7 +59,7 @@ class HsManagedWebspaceHostingAssetValidatorUnitTest {
|
||||
@Test
|
||||
void validatesUnknownProperties() {
|
||||
// given
|
||||
final var validator = HsHostingAssetEntityValidators.forType(MANAGED_WEBSPACE);
|
||||
final var validator = HsHostingAssetEntityValidator.forType(MANAGED_WEBSPACE);
|
||||
final var mangedWebspaceHostingAssetEntity = HsHostingAssetEntity.builder()
|
||||
.type(MANAGED_WEBSPACE)
|
||||
.parentAsset(mangedServerAssetEntity)
|
||||
@ -61,13 +73,13 @@ class HsManagedWebspaceHostingAssetValidatorUnitTest {
|
||||
final var result = validator.validate(mangedWebspaceHostingAssetEntity);
|
||||
|
||||
// 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
|
||||
void validatesValidEntity() {
|
||||
// given
|
||||
final var validator = HsHostingAssetEntityValidators.forType(MANAGED_WEBSPACE);
|
||||
final var validator = HsHostingAssetEntityValidator.forType(MANAGED_WEBSPACE);
|
||||
final var mangedWebspaceHostingAssetEntity = HsHostingAssetEntity.builder()
|
||||
.type(MANAGED_WEBSPACE)
|
||||
.parentAsset(mangedServerAssetEntity)
|
||||
|
@ -4,7 +4,7 @@ import net.hostsharing.hsadminng.context.Context;
|
||||
import net.hostsharing.hsadminng.rbac.test.ContextBasedTestWithCleanup;
|
||||
import net.hostsharing.hsadminng.rbac.rbacgrant.RawRbacGrantRepository;
|
||||
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 org.apache.commons.lang3.RandomStringUtils;
|
||||
import org.junit.jupiter.api.Nested;
|
||||
|
@ -4,7 +4,7 @@ import net.hostsharing.hsadminng.context.Context;
|
||||
import net.hostsharing.hsadminng.rbac.test.ContextBasedTestWithCleanup;
|
||||
import net.hostsharing.hsadminng.rbac.rbacgrant.RawRbacGrantRepository;
|
||||
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 org.apache.commons.lang3.RandomStringUtils;
|
||||
import org.junit.jupiter.api.Nested;
|
||||
|
@ -5,7 +5,7 @@ import net.hostsharing.hsadminng.hs.office.membership.HsOfficeMembershipReposito
|
||||
import net.hostsharing.hsadminng.rbac.test.ContextBasedTestWithCleanup;
|
||||
import net.hostsharing.hsadminng.rbac.rbacgrant.RawRbacGrantRepository;
|
||||
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 org.junit.jupiter.api.AfterEach;
|
||||
import org.junit.jupiter.api.BeforeEach;
|
||||
|
@ -5,7 +5,7 @@ import net.hostsharing.hsadminng.hs.office.membership.HsOfficeMembershipReposito
|
||||
import net.hostsharing.hsadminng.rbac.test.ContextBasedTestWithCleanup;
|
||||
import net.hostsharing.hsadminng.rbac.rbacgrant.RawRbacGrantRepository;
|
||||
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 org.junit.jupiter.api.AfterEach;
|
||||
import org.junit.jupiter.api.BeforeEach;
|
||||
|
@ -11,7 +11,7 @@ import net.hostsharing.hsadminng.rbac.test.ContextBasedTestWithCleanup;
|
||||
import net.hostsharing.hsadminng.rbac.rbacgrant.RawRbacGrantRepository;
|
||||
import net.hostsharing.hsadminng.rbac.rbacgrant.RbacGrantsDiagramService;
|
||||
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 org.hibernate.Hibernate;
|
||||
import org.junit.jupiter.api.Disabled;
|
||||
|
@ -7,7 +7,7 @@ import net.hostsharing.hsadminng.hs.office.partner.HsOfficePartnerRepository;
|
||||
import net.hostsharing.hsadminng.rbac.test.ContextBasedTestWithCleanup;
|
||||
import net.hostsharing.hsadminng.rbac.rbacgrant.RawRbacGrantRepository;
|
||||
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 org.junit.jupiter.api.Nested;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
@ -30,7 +30,7 @@ import java.util.Objects;
|
||||
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.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 org.assertj.core.api.Assertions.assertThat;
|
||||
|
||||
|
@ -4,7 +4,7 @@ import net.hostsharing.hsadminng.context.Context;
|
||||
import net.hostsharing.hsadminng.rbac.test.ContextBasedTestWithCleanup;
|
||||
import net.hostsharing.hsadminng.rbac.rbacgrant.RawRbacGrantRepository;
|
||||
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 org.apache.commons.lang3.RandomStringUtils;
|
||||
import org.junit.jupiter.api.Nested;
|
||||
|
@ -6,7 +6,7 @@ import net.hostsharing.hsadminng.hs.office.person.HsOfficePersonRepository;
|
||||
import net.hostsharing.hsadminng.rbac.test.ContextBasedTestWithCleanup;
|
||||
import net.hostsharing.hsadminng.rbac.rbacgrant.RawRbacGrantRepository;
|
||||
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 org.junit.jupiter.api.Nested;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
@ -7,7 +7,7 @@ import net.hostsharing.hsadminng.hs.office.debitor.HsOfficeDebitorRepository;
|
||||
import net.hostsharing.hsadminng.rbac.test.ContextBasedTestWithCleanup;
|
||||
import net.hostsharing.hsadminng.rbac.rbacgrant.RawRbacGrantRepository;
|
||||
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 org.junit.jupiter.api.Nested;
|
||||
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.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 org.assertj.core.api.Assertions.assertThat;
|
||||
|
||||
|
@ -2,7 +2,7 @@ package net.hostsharing.hsadminng.rbac.context;
|
||||
|
||||
import net.hostsharing.hsadminng.context.Context;
|
||||
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 org.junit.jupiter.api.Test;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
|
@ -1,7 +1,7 @@
|
||||
package net.hostsharing.hsadminng.rbac.rbacrole;
|
||||
|
||||
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 org.junit.jupiter.api.Nested;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
@ -2,7 +2,7 @@ package net.hostsharing.hsadminng.rbac.rbacuser;
|
||||
|
||||
import net.hostsharing.hsadminng.context.Context;
|
||||
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 org.junit.jupiter.api.Nested;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
Loading…
Reference in New Issue
Block a user