Unify target attributes and metadata (#2408)

* Unify target attributes and metadata

Currently, the target attributes are Map while the metadata,
which has the same concept is List.
This PR unifies them making the metadata also a Map

Signed-off-by: Avgustin Marinov <Avgustin.Marinov@bosch.com>
This commit is contained in:
Avgustin Marinov
2025-05-21 11:26:02 +03:00
committed by GitHub
parent 424520bb72
commit ceba4f5cfb
29 changed files with 490 additions and 1107 deletions

View File

@@ -27,7 +27,6 @@ import org.eclipse.hawkbit.repository.jpa.builder.JpaRolloutGroupBuilder;
import org.eclipse.hawkbit.repository.jpa.builder.JpaSoftwareModuleTypeBuilder;
import org.eclipse.hawkbit.repository.jpa.builder.JpaTagBuilder;
import org.eclipse.hawkbit.repository.jpa.model.JpaDistributionSetMetadata;
import org.eclipse.hawkbit.repository.jpa.model.JpaTargetMetadata;
import org.eclipse.hawkbit.repository.model.MetaData;
import org.springframework.validation.annotation.Validated;
@@ -77,11 +76,6 @@ public class JpaEntityFactory implements EntityFactory {
return new JpaDistributionSetMetadata(key, value == null ? null : value.strip());
}
@Override
public MetaData generateTargetMetadata(final String key, final String value) {
return new JpaTargetMetadata(key, value == null ? null : value.strip());
}
@Override
public SoftwareModuleMetadataBuilder softwareModuleMetadata() {
return softwareModuleMetadataBuilder;

View File

@@ -132,7 +132,6 @@ import org.eclipse.hawkbit.repository.jpa.repository.SoftwareModuleMetadataRepos
import org.eclipse.hawkbit.repository.jpa.repository.SoftwareModuleRepository;
import org.eclipse.hawkbit.repository.jpa.repository.SoftwareModuleTypeRepository;
import org.eclipse.hawkbit.repository.jpa.repository.TargetFilterQueryRepository;
import org.eclipse.hawkbit.repository.jpa.repository.TargetMetadataRepository;
import org.eclipse.hawkbit.repository.jpa.repository.TargetRepository;
import org.eclipse.hawkbit.repository.jpa.repository.TargetTagRepository;
import org.eclipse.hawkbit.repository.jpa.repository.TargetTypeRepository;
@@ -622,7 +621,7 @@ public class RepositoryApplicationConfiguration {
@Bean
@ConditionalOnMissingBean
TargetManagement targetManagement(final EntityManager entityManager, final QuotaManagement quotaManagement,
final TargetRepository targetRepository, final TargetMetadataRepository targetMetadataRepository,
final TargetRepository targetRepository,
final RolloutGroupRepository rolloutGroupRepository,
final TargetFilterQueryRepository targetFilterQueryRepository,
final TargetTypeRepository targetTypeRepository, final TargetTagRepository targetTagRepository,
@@ -630,7 +629,7 @@ public class RepositoryApplicationConfiguration {
final VirtualPropertyReplacer virtualPropertyReplacer,
final JpaProperties properties, final DistributionSetManagement distributionSetManagement) {
return new JpaTargetManagement(entityManager, distributionSetManagement, quotaManagement, targetRepository,
targetTypeRepository, targetMetadataRepository, rolloutGroupRepository, targetFilterQueryRepository,
targetTypeRepository, rolloutGroupRepository, targetFilterQueryRepository,
targetTagRepository, eventPublisherHolder, tenantAware, virtualPropertyReplacer,
properties.getDatabase());
}

View File

@@ -37,12 +37,7 @@ public class JpaTargetCreate extends AbstractTargetUpdateCreate<TargetCreate> im
@Override
public JpaTarget build() {
final JpaTarget target;
if (ObjectUtils.isEmpty(securityToken)) {
target = new JpaTarget(controllerId);
} else {
target = new JpaTarget(controllerId, securityToken);
}
final JpaTarget target = new JpaTarget(controllerId, securityToken);
if (!ObjectUtils.isEmpty(name)) {
target.setName(name);

View File

@@ -31,6 +31,7 @@ import jakarta.persistence.criteria.CriteriaBuilder;
import jakarta.persistence.criteria.CriteriaQuery;
import jakarta.persistence.criteria.MapJoin;
import jakarta.persistence.criteria.Root;
import jakarta.persistence.metamodel.MapAttribute;
import jakarta.validation.constraints.NotEmpty;
import org.apache.commons.collections4.ListUtils;
@@ -39,12 +40,10 @@ import org.eclipse.hawkbit.repository.FilterParams;
import org.eclipse.hawkbit.repository.QuotaManagement;
import org.eclipse.hawkbit.repository.TargetFields;
import org.eclipse.hawkbit.repository.TargetManagement;
import org.eclipse.hawkbit.repository.TargetMetadataFields;
import org.eclipse.hawkbit.repository.TimestampCalculator;
import org.eclipse.hawkbit.repository.builder.TargetCreate;
import org.eclipse.hawkbit.repository.builder.TargetUpdate;
import org.eclipse.hawkbit.repository.event.remote.TargetAttributesRequestedEvent;
import org.eclipse.hawkbit.repository.event.remote.entity.TargetUpdatedEvent;
import org.eclipse.hawkbit.repository.exception.EntityAlreadyExistsException;
import org.eclipse.hawkbit.repository.exception.EntityNotFoundException;
import org.eclipse.hawkbit.repository.jpa.JpaManagementHelper;
@@ -52,18 +51,13 @@ import org.eclipse.hawkbit.repository.jpa.acm.AccessController;
import org.eclipse.hawkbit.repository.jpa.builder.JpaTargetCreate;
import org.eclipse.hawkbit.repository.jpa.builder.JpaTargetUpdate;
import org.eclipse.hawkbit.repository.jpa.configuration.Constants;
import org.eclipse.hawkbit.repository.jpa.model.AbstractJpaBaseEntity_;
import org.eclipse.hawkbit.repository.jpa.model.JpaTarget;
import org.eclipse.hawkbit.repository.jpa.model.JpaTargetMetadata;
import org.eclipse.hawkbit.repository.jpa.model.JpaTargetMetadata_;
import org.eclipse.hawkbit.repository.jpa.model.JpaTargetTag;
import org.eclipse.hawkbit.repository.jpa.model.JpaTargetType;
import org.eclipse.hawkbit.repository.jpa.model.JpaTarget_;
import org.eclipse.hawkbit.repository.jpa.model.TargetMetadataCompositeKey;
import org.eclipse.hawkbit.repository.jpa.model.helper.AfterTransactionCommitExecutorHolder;
import org.eclipse.hawkbit.repository.jpa.repository.RolloutGroupRepository;
import org.eclipse.hawkbit.repository.jpa.repository.TargetFilterQueryRepository;
import org.eclipse.hawkbit.repository.jpa.repository.TargetMetadataRepository;
import org.eclipse.hawkbit.repository.jpa.repository.TargetRepository;
import org.eclipse.hawkbit.repository.jpa.repository.TargetTagRepository;
import org.eclipse.hawkbit.repository.jpa.repository.TargetTypeRepository;
@@ -73,11 +67,9 @@ import org.eclipse.hawkbit.repository.jpa.specifications.TargetSpecifications;
import org.eclipse.hawkbit.repository.jpa.utils.QuotaHelper;
import org.eclipse.hawkbit.repository.model.DistributionSet;
import org.eclipse.hawkbit.repository.model.DistributionSetType;
import org.eclipse.hawkbit.repository.model.MetaData;
import org.eclipse.hawkbit.repository.model.RolloutGroup;
import org.eclipse.hawkbit.repository.model.Target;
import org.eclipse.hawkbit.repository.model.TargetFilterQuery;
import org.eclipse.hawkbit.repository.model.TargetMetadata;
import org.eclipse.hawkbit.repository.model.TargetTag;
import org.eclipse.hawkbit.repository.model.TargetType;
import org.eclipse.hawkbit.repository.model.TargetTypeAssignmentResult;
@@ -109,7 +101,6 @@ public class JpaTargetManagement implements TargetManagement {
private final QuotaManagement quotaManagement;
private final TargetRepository targetRepository;
private final TargetTypeRepository targetTypeRepository;
private final TargetMetadataRepository targetMetadataRepository;
private final RolloutGroupRepository rolloutGroupRepository;
private final TargetFilterQueryRepository targetFilterQueryRepository;
private final TargetTagRepository targetTagRepository;
@@ -122,7 +113,6 @@ public class JpaTargetManagement implements TargetManagement {
public JpaTargetManagement(final EntityManager entityManager,
final DistributionSetManagement distributionSetManagement, final QuotaManagement quotaManagement,
final TargetRepository targetRepository, final TargetTypeRepository targetTypeRepository,
final TargetMetadataRepository targetMetadataRepository,
final RolloutGroupRepository rolloutGroupRepository,
final TargetFilterQueryRepository targetFilterQueryRepository,
final TargetTagRepository targetTagRepository, final EventPublisherHolder eventPublisherHolder,
@@ -133,7 +123,6 @@ public class JpaTargetManagement implements TargetManagement {
this.quotaManagement = quotaManagement;
this.targetRepository = targetRepository;
this.targetTypeRepository = targetTypeRepository;
this.targetMetadataRepository = targetMetadataRepository;
this.rolloutGroupRepository = rolloutGroupRepository;
this.targetFilterQueryRepository = targetFilterQueryRepository;
this.targetTagRepository = targetTagRepository;
@@ -597,7 +586,7 @@ public class JpaTargetManagement implements TargetManagement {
final JpaTarget target = getByControllerIdAndThrowIfNotFound(controllerId);
targetRepository.getAccessController().ifPresent(acm ->
acm.assertOperationAllowed(AccessController.Operation.UPDATE, target));
acm.assertOperationAllowed(AccessController.Operation.UPDATE, target));
final JpaTargetType targetType = getTargetTypeByIdAndThrowIfNotFound(targetTypeId);
target.setTargetType(targetType);
@@ -635,57 +624,6 @@ public class JpaTargetManagement implements TargetManagement {
return Collections.unmodifiableList(targetRepository.findAll(TargetSpecifications.hasIdIn(ids)));
}
@Override
public Map<String, String> getControllerAttributes(final String controllerId) {
getByControllerIdAndThrowIfNotFound(controllerId);
final CriteriaBuilder cb = entityManager.getCriteriaBuilder();
final CriteriaQuery<Object[]> query = cb.createQuery(Object[].class);
final Root<JpaTarget> targetRoot = query.from(JpaTarget.class);
query.where(cb.equal(targetRoot.get(JpaTarget_.controllerId), controllerId));
final MapJoin<JpaTarget, String, String> attributes = targetRoot.join(JpaTarget_.controllerAttributes);
query.multiselect(attributes.key(), attributes.value());
query.orderBy(cb.asc(attributes.key()));
final List<Object[]> attr = entityManager.createQuery(query).getResultList();
return attr.stream().collect(Collectors.toMap(entry -> (String) entry[0], entry -> (String) entry[1],
(v1, v2) -> v1, LinkedHashMap::new));
}
@Override
@Transactional
@Retryable(retryFor = { ConcurrencyFailureException.class }, maxAttempts = Constants.TX_RT_MAX,
backoff = @Backoff(delay = Constants.TX_RT_DELAY))
public void requestControllerAttributes(final String controllerId) {
final JpaTarget target = getByControllerIdAndThrowIfNotFound(controllerId);
targetRepository.getAccessController()
.ifPresent(acm -> acm.assertOperationAllowed(AccessController.Operation.UPDATE, target));
target.setRequestControllerAttributes(true);
AfterTransactionCommitExecutorHolder.getInstance().getAfterCommit().afterCommit(() ->
eventPublisherHolder.getEventPublisher()
.publishEvent(new TargetAttributesRequestedEvent(
tenantAware.getCurrentTenant(), target.getId(), target.getControllerId(),
target.getAddress() != null ? target.getAddress().toString() : null,
JpaTarget.class, eventPublisherHolder.getApplicationId())));
}
@Override
public boolean isControllerAttributesRequested(final String controllerId) {
final JpaTarget target = getByControllerIdAndThrowIfNotFound(controllerId);
return target.isRequestControllerAttributes();
}
@Override
public Page<Target> findByControllerAttributesRequested(final Pageable pageReq) {
return JpaManagementHelper.findAllWithCountBySpec(targetRepository, List.of(TargetSpecifications.hasRequestControllerAttributesTrue()),
pageReq
);
}
@Override
public boolean existsByControllerId(final String controllerId) {
return targetRepository.exists(TargetSpecifications.hasControllerId(controllerId));
@@ -710,115 +648,120 @@ public class JpaTargetManagement implements TargetManagement {
}
@Override
public Set<TargetTag> getTagsByControllerId(@NotEmpty String controllerId) {
public Set<TargetTag> getTags(@NotEmpty String controllerId) {
// the method has PreAuthorized by itself
return ((JpaTarget)getWithTags(controllerId)).getTags();
return ((JpaTarget) getWithTags(controllerId)).getTags();
}
@Override
public Map<String, String> getControllerAttributes(final String controllerId) {
return getMap(controllerId, JpaTarget_.controllerAttributes);
}
@Override
@Transactional
@Retryable(retryFor = { ConcurrencyFailureException.class }, maxAttempts = Constants.TX_RT_MAX,
backoff = @Backoff(delay = Constants.TX_RT_DELAY))
public List<TargetMetadata> createMetaData(final String controllerId, final Collection<MetaData> md) {
public void requestControllerAttributes(final String controllerId) {
final JpaTarget target = getByControllerIdAndThrowIfNotFound(controllerId);
md.forEach(meta -> checkAndThrowIfTargetMetadataAlreadyExists(
new TargetMetadataCompositeKey(target.getId(), meta.getKey())));
assertMetaDataQuota(target.getId(), md.size());
final JpaTarget updatedTarget = JpaManagementHelper.touch(entityManager, targetRepository, target);
final List<TargetMetadata> createdMetadata = md.stream()
.map(meta -> targetMetadataRepository.save(new JpaTargetMetadata(meta.getKey(), meta.getValue(), updatedTarget)))
.collect(Collectors.toUnmodifiableList());
// TargetUpdatedEvent is not sent within the touch() method due to the
// "lastModifiedAt" field being ignored in JpaTarget
eventPublisherHolder.getEventPublisher().publishEvent(new TargetUpdatedEvent(updatedTarget, eventPublisherHolder.getApplicationId()));
return createdMetadata;
}
@Override
@Transactional
@Retryable(retryFor = { ConcurrencyFailureException.class }, maxAttempts = Constants.TX_RT_MAX,
backoff = @Backoff(delay = Constants.TX_RT_DELAY))
public void deleteMetaData(final String controllerId, final String key) {
final JpaTargetMetadata metadata = (JpaTargetMetadata) getMetaDataByControllerId(controllerId, key)
.orElseThrow(() -> new EntityNotFoundException(TargetMetadata.class, controllerId, key));
final JpaTarget target = JpaManagementHelper.touch(
entityManager, targetRepository, getByControllerIdAndThrowIfNotFound(controllerId));
targetRepository.getAccessController()
.ifPresent(acm -> acm.assertOperationAllowed(AccessController.Operation.UPDATE, target));
targetMetadataRepository.deleteById(metadata.getId());
// target update event is set to ignore "lastModifiedAt" field, so it is
// not send automatically within the touch() method
eventPublisherHolder.getEventPublisher()
.publishEvent(new TargetUpdatedEvent(target, eventPublisherHolder.getApplicationId()));
target.setRequestControllerAttributes(true);
AfterTransactionCommitExecutorHolder.getInstance().getAfterCommit().afterCommit(() ->
eventPublisherHolder.getEventPublisher()
.publishEvent(new TargetAttributesRequestedEvent(
tenantAware.getCurrentTenant(), target.getId(), target.getControllerId(),
target.getAddress() != null ? target.getAddress().toString() : null,
JpaTarget.class, eventPublisherHolder.getApplicationId())));
}
@Override
public Page<TargetMetadata> findMetaDataByControllerId(final Pageable pageable, final String controllerId) {
final Long id = getByControllerIdAndThrowIfNotFound(controllerId).getId();
return JpaManagementHelper.findAllWithCountBySpec(targetMetadataRepository, Collections.singletonList(metadataByTargetIdSpec(id)),
pageable
);
public boolean isControllerAttributesRequested(final String controllerId) {
return getByControllerIdAndThrowIfNotFound(controllerId).isRequestControllerAttributes();
}
@Override
public long countMetaDataByControllerId(@NotEmpty final String controllerId) {
final Long targetId = getByControllerIdAndThrowIfNotFound(controllerId).getId();
return JpaManagementHelper.countBySpec(targetMetadataRepository,
Collections.singletonList(metadataByTargetIdSpec(targetId)));
}
@Override
public Page<TargetMetadata> findMetaDataByControllerIdAndRsql(final Pageable pageable, final String controllerId,
final String rsqlParam) {
final Long targetId = getByControllerIdAndThrowIfNotFound(controllerId).getId();
final List<Specification<JpaTargetMetadata>> specList = Arrays.asList(RSQLUtility
.buildRsqlSpecification(rsqlParam, TargetMetadataFields.class, virtualPropertyReplacer, database),
metadataByTargetIdSpec(targetId));
return JpaManagementHelper.findAllWithCountBySpec(targetMetadataRepository, specList, pageable);
}
@Override
public Optional<TargetMetadata> getMetaDataByControllerId(final String controllerId, final String key) {
final Long targetId = getByControllerIdAndThrowIfNotFound(controllerId).getId();
return targetMetadataRepository.findById(new TargetMetadataCompositeKey(targetId, key)).map(t -> t);
public Page<Target> findByControllerAttributesRequested(final Pageable pageReq) {
return JpaManagementHelper.findAllWithCountBySpec(
targetRepository, List.of(TargetSpecifications.hasRequestControllerAttributesTrue()), pageReq);
}
@Override
@Transactional
@Retryable(retryFor = { ConcurrencyFailureException.class }, maxAttempts = Constants.TX_RT_MAX,
backoff = @Backoff(delay = Constants.TX_RT_DELAY))
public TargetMetadata updateMetadata(final String controllerId, final MetaData md) {
public void createMetadata(final String controllerId, final Map<String, String> md) {
final JpaTarget target = getByControllerIdAndThrowIfNotFound(controllerId);
// check if exists otherwise throw entity not found exception
final JpaTargetMetadata updatedMetadata = (JpaTargetMetadata) getMetaDataByControllerId(controllerId,
md.getKey())
.orElseThrow(() -> new EntityNotFoundException(TargetMetadata.class, controllerId, md.getKey()));
updatedMetadata.setValue(md.getValue());
// touch it to update the lock revision because we are modifying the
// target indirectly
final JpaTarget target = JpaManagementHelper.touch(entityManager, targetRepository,
getByControllerIdAndThrowIfNotFound(controllerId));
// get the modifiable metadata map
final Map<String, String> metadata = target.getMetadata();
md.keySet().forEach(key -> {
if (metadata.containsKey(key)) {
throw new EntityAlreadyExistsException("Metadata entry with key '" + key + "' already exists");
}
});
metadata.putAll(md);
final JpaTargetMetadata metadata = targetMetadataRepository.save(updatedMetadata);
// target update event is set to ignore "lastModifiedAt" field, so it is
// not send automatically within the touch() method
eventPublisherHolder.getEventPublisher()
.publishEvent(new TargetUpdatedEvent(target, eventPublisherHolder.getApplicationId()));
return metadata;
assertMetadataQuota(target.getId(), metadata.size());
targetRepository.save(target);
}
@Override
public Map<String, String> getMetadata(final String controllerId) {
return getMap(controllerId, JpaTarget_.metadata);
}
@Override
@Transactional
@Retryable(retryFor = { ConcurrencyFailureException.class }, maxAttempts = Constants.TX_RT_MAX,
backoff = @Backoff(delay = Constants.TX_RT_DELAY))
public void updateMetadata(final String controllerId, final String key, final String value) {
final JpaTarget target = getByControllerIdAndThrowIfNotFound(controllerId);
// get the modifiable metadata map
final Map<String, String> metadata = target.getMetadata();
if (!metadata.containsKey(key)) {
throw new EntityNotFoundException("Target metadata", controllerId + ":" + key);
}
metadata.put(key, value);
targetRepository.save(target);
}
@Override
@Transactional
@Retryable(retryFor = { ConcurrencyFailureException.class }, maxAttempts = Constants.TX_RT_MAX,
backoff = @Backoff(delay = Constants.TX_RT_DELAY))
public void deleteMetadata(final String controllerId, final String key) {
final JpaTarget target = getByControllerIdAndThrowIfNotFound(controllerId);
// get the modifiable metadata map
final Map<String, String> metadata = target.getMetadata();
if (metadata.remove(key) == null) {
throw new EntityNotFoundException("Target metadata", controllerId + ":" + key);
}
targetRepository.save(target);
}
private Map<String, String> getMap(final String controllerId, final MapAttribute<JpaTarget, String, String> mapAttribute) {
getByControllerIdAndThrowIfNotFound(controllerId);
final CriteriaBuilder cb = entityManager.getCriteriaBuilder();
final CriteriaQuery<Object[]> query = cb.createQuery(Object[].class);
final Root<JpaTarget> targetRoot = query.from(JpaTarget.class);
query.where(cb.equal(targetRoot.get(JpaTarget_.controllerId), controllerId));
final MapJoin<JpaTarget, String, String> mapJoin = targetRoot.join(mapAttribute);
query.multiselect(mapJoin.key(), mapJoin.value());
query.orderBy(cb.asc(mapJoin.key()));
return entityManager
.createQuery(query)
.getResultList()
.stream()
.collect(Collectors.toMap(entry -> (String) entry[0], entry -> (String) entry[1], (v1, v2) -> v1, LinkedHashMap::new));
}
private static boolean hasTagsFilterActive(final FilterParams filterParams) {
@@ -851,20 +794,9 @@ public class JpaTargetManagement implements TargetManagement {
return targetTypeRepository.findById(id).orElseThrow(() -> new EntityNotFoundException(TargetType.class, id));
}
private void checkAndThrowIfTargetMetadataAlreadyExists(final TargetMetadataCompositeKey metadataId) {
if (targetMetadataRepository.existsById(metadataId)) {
throw new EntityAlreadyExistsException(
"Metadata entry with key '" + metadataId.getKey() + "' already exists");
}
}
private void assertMetaDataQuota(final Long targetId, final int requested) {
QuotaHelper.assertAssignmentQuota(targetId, requested, quotaManagement.getMaxMetaDataEntriesPerTarget(),
TargetMetadata.class, Target.class, targetMetadataRepository::countByTargetId);
}
private Specification<JpaTargetMetadata> metadataByTargetIdSpec(final Long targetId) {
return (root, query, cb) -> cb.equal(root.get(JpaTargetMetadata_.target).get(AbstractJpaBaseEntity_.id), targetId);
private void assertMetadataQuota(final Long targetId, final int requested) {
final int limit = quotaManagement.getMaxMetaDataEntriesPerTarget();
QuotaHelper.assertAssignmentQuota(targetId, requested, limit, "Metadata", Target.class.getSimpleName(), null);
}
private List<Specification<JpaTarget>> buildSpecificationList(final FilterParams filterParams) {

View File

@@ -59,7 +59,6 @@ import org.eclipse.hawkbit.repository.jpa.utils.MapAttributeConverter;
import org.eclipse.hawkbit.repository.model.Action;
import org.eclipse.hawkbit.repository.model.PollStatus;
import org.eclipse.hawkbit.repository.model.Target;
import org.eclipse.hawkbit.repository.model.TargetMetadata;
import org.eclipse.hawkbit.repository.model.TargetTag;
import org.eclipse.hawkbit.repository.model.TargetType;
import org.eclipse.hawkbit.repository.model.TargetUpdateStatus;
@@ -181,7 +180,7 @@ public class JpaTarget extends AbstractJpaNamedEntity implements Target, EventAw
})
private Set<TargetTag> tags;
// no cascade option on an ElementCollection, the target objects are always persisted, merged, removed with their parent.
// no cascade option on an ElementCollection, the target objects are always persisted, merged, removed with their parent
@Getter
@ElementCollection
@CollectionTable(
@@ -192,8 +191,16 @@ public class JpaTarget extends AbstractJpaNamedEntity implements Target, EventAw
@Column(name = "attribute_value", length = Target.CONTROLLER_ATTRIBUTE_VALUE_SIZE)
private Map<String, String> controllerAttributes;
@OneToMany(mappedBy = "target", fetch = FetchType.LAZY, cascade = { CascadeType.REMOVE }, targetEntity = JpaTargetMetadata.class)
private List<TargetMetadata> metadata;
// no cascade option on an ElementCollection, the target objects are always persisted, merged, removed with their parent
@Getter
@ElementCollection
@CollectionTable(
name = "sp_target_metadata",
joinColumns = { @JoinColumn(name = "target", nullable = false) },
foreignKey = @ForeignKey(value = ConstraintMode.CONSTRAINT, name = "fk_target_metadata_target"))
@MapKeyColumn(name = "meta_key", length = Target.CONTROLLER_METADATA_KEY_SIZE)
@Column(name = "meta_value", length = Target.CONTROLLER_METADATA_VALUE_SIZE)
private Map<String, String> metadata;
@OneToMany(mappedBy = "target", fetch = FetchType.LAZY, cascade = { CascadeType.REMOVE }, targetEntity = JpaAction.class)
private List<JpaAction> actions;
@@ -201,15 +208,11 @@ public class JpaTarget extends AbstractJpaNamedEntity implements Target, EventAw
@OneToMany(mappedBy = "target", fetch = FetchType.LAZY, cascade = { CascadeType.PERSIST, CascadeType.REMOVE })
private List<RolloutTargetGroup> rolloutTargetGroup;
public JpaTarget(final String controllerId) {
this(controllerId, SecurityTokenGeneratorHolder.getInstance().generateToken());
}
public JpaTarget(final String controllerId, final String securityToken) {
this.controllerId = controllerId;
// truncate controller ID to max name length (if needed)
// truncate controller ID to max name length (if too big)
setName(controllerId != null && controllerId.length() > NAME_MAX_SIZE ? controllerId.substring(0, NAME_MAX_SIZE) : controllerId);
this.securityToken = securityToken;
this.securityToken = ObjectUtils.isEmpty(securityToken) ? SecurityTokenGeneratorHolder.getInstance().generateToken() : securityToken;
}
@Override
@@ -287,10 +290,6 @@ public class JpaTarget extends AbstractJpaNamedEntity implements Target, EventAw
return tags == null ? Collections.emptySet() : Collections.unmodifiableSet(tags);
}
public List<TargetMetadata> getMetadata() {
return metadata == null ? Collections.emptyList() : Collections.unmodifiableList(metadata);
}
public void addAction(final Action action) {
if (actions == null) {
actions = new ArrayList<>();

View File

@@ -1,100 +0,0 @@
/**
* Copyright (c) 2018 Bosch Software Innovations GmbH and others
*
* This program and the accompanying materials are made
* available under the terms of the Eclipse Public License 2.0
* which is available at https://www.eclipse.org/legal/epl-2.0/
*
* SPDX-License-Identifier: EPL-2.0
*/
package org.eclipse.hawkbit.repository.jpa.model;
import java.io.Serial;
import jakarta.persistence.ConstraintMode;
import jakarta.persistence.Entity;
import jakarta.persistence.FetchType;
import jakarta.persistence.ForeignKey;
import jakarta.persistence.Id;
import jakarta.persistence.IdClass;
import jakarta.persistence.JoinColumn;
import jakarta.persistence.ManyToOne;
import jakarta.persistence.Table;
import lombok.Getter;
import lombok.NoArgsConstructor;
import lombok.Setter;
import org.eclipse.hawkbit.repository.model.Target;
import org.eclipse.hawkbit.repository.model.TargetMetadata;
/**
* Meta data for {@link Target}.
*/
@NoArgsConstructor // Default constructor for JPA
@Setter
@Getter
@IdClass(TargetMetadataCompositeKey.class)
@Entity
@Table(name = "sp_target_metadata")
public class JpaTargetMetadata extends AbstractJpaMetaData implements TargetMetadata {
@Serial
private static final long serialVersionUID = 1L;
@Id
@ManyToOne(fetch = FetchType.LAZY, optional = false)
@JoinColumn(
name = "target", nullable = false, updatable = false,
foreignKey = @ForeignKey(value = ConstraintMode.CONSTRAINT, name = "fk_target_metadata_target"))
private JpaTarget target;
/**
* Creates a single metadata entry with the given key and value.
*
* @param key of the meta-data entry
* @param value of the meta-data entry
*/
public JpaTargetMetadata(final String key, final String value) {
super(key, value);
}
/**
* Creates a single metadata entry with the given key and value for the
* given {@link Target}.
*
* @param key of the meta-data entry
* @param value of the meta-data entry
* @param target the meta-data entry is associated with
*/
public JpaTargetMetadata(final String key, final String value, final Target target) {
super(key, value);
this.target = (JpaTarget) target;
}
public TargetMetadataCompositeKey getId() {
return new TargetMetadataCompositeKey(target.getId(), getKey());
}
@Override
public int hashCode() {
final int prime = 31;
int result = super.hashCode();
result = prime * result + ((target == null) ? 0 : target.hashCode());
return result;
}
@Override
// exception squid:S2259 - obj is checked for null in super
@SuppressWarnings("squid:S2259")
public boolean equals(final Object obj) {
if (!super.equals(obj)) {
return false;
}
final JpaTargetMetadata other = (JpaTargetMetadata) obj;
if (target == null) {
return other.target == null;
} else {
return target.equals(other.target);
}
}
}

View File

@@ -1,39 +0,0 @@
/**
* Copyright (c) 2018 Bosch Software Innovations GmbH and others
*
* This program and the accompanying materials are made
* available under the terms of the Eclipse Public License 2.0
* which is available at https://www.eclipse.org/legal/epl-2.0/
*
* SPDX-License-Identifier: EPL-2.0
*/
package org.eclipse.hawkbit.repository.jpa.model;
import java.io.Serial;
import java.io.Serializable;
import lombok.Data;
import lombok.NoArgsConstructor;
/**
* The Target Metadata composite key which contains the meta-data key and the ID of the Target itself.
*/
@NoArgsConstructor // Default constructor for JPA
@Data
public final class TargetMetadataCompositeKey implements Serializable {
@Serial
private static final long serialVersionUID = 1L;
private String key;
private Long target;
/**
* @param target the target Id for this meta-data
* @param key the key of the meta-data
*/
public TargetMetadataCompositeKey(final Long target, final String key) {
this.target = target;
this.key = key;
}
}

View File

@@ -1,39 +0,0 @@
/**
* Copyright (c) 2015 Bosch Software Innovations GmbH and others
*
* This program and the accompanying materials are made
* available under the terms of the Eclipse Public License 2.0
* which is available at https://www.eclipse.org/legal/epl-2.0/
*
* SPDX-License-Identifier: EPL-2.0
*/
package org.eclipse.hawkbit.repository.jpa.repository;
import org.eclipse.hawkbit.repository.jpa.model.JpaTargetMetadata;
import org.eclipse.hawkbit.repository.jpa.model.TargetMetadataCompositeKey;
import org.eclipse.hawkbit.repository.model.TargetMetadata;
import org.springframework.data.jpa.repository.JpaSpecificationExecutor;
import org.springframework.data.repository.CrudRepository;
import org.springframework.data.repository.PagingAndSortingRepository;
import org.springframework.data.repository.query.Param;
import org.springframework.transaction.annotation.Transactional;
/**
* {@link TargetMetadata} repository.
*/
@Transactional(readOnly = true)
public interface TargetMetadataRepository
extends PagingAndSortingRepository<JpaTargetMetadata, TargetMetadataCompositeKey>,
CrudRepository<JpaTargetMetadata, TargetMetadataCompositeKey>,
JpaSpecificationExecutor<JpaTargetMetadata> {
/**
* Counts the meta data entries that match the given target ID.
* <p/>
* No access control applied
*
* @param id of the target.
* @return The number of matching meta data entries.
*/
long countByTargetId(@Param("id") Long id);
}

View File

@@ -13,35 +13,31 @@ import java.util.function.ToLongFunction;
import jakarta.validation.constraints.NotNull;
import lombok.NoArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.eclipse.hawkbit.repository.exception.AssignmentQuotaExceededException;
/**
* Helper class to check quotas.
*/
@NoArgsConstructor
@Slf4j
public final class QuotaHelper {
private static final String MAX_ASSIGNMENT_QUOTA_EXCEEDED = "Quota exceeded: Cannot assign %s entities at once. The maximum is %s.";
private QuotaHelper() {
// no need to instantiate this class
}
/**
* Asserts the specified assignment quota.
*
* @param requested The number of entities that shall be assigned to the parent
* entity.
* @param limit The maximum number of entities that may be assigned to the
* parent entity.
* @param requested The number of entities that shall be assigned to the parent entity.
* @param limit The maximum number of entities that may be assigned to the parent entity.
* @param type The type of the entities that shall be assigned.
* @param parentType The type of the parent entity.
* @throws AssignmentQuotaExceededException if the assignment operation would cause the quota to be
* exceeded
* @throws AssignmentQuotaExceededException if the assignment operation would cause the quota to be exceeded
*/
public static void assertAssignmentQuota(final long requested, final long limit, @NotNull final Class<?> type,
@NotNull final Class<?> parentType) {
public static void assertAssignmentQuota(
final long requested, final long limit,
@NotNull final Class<?> type, @NotNull final Class<?> parentType) {
assertAssignmentQuota(null, requested, limit, type.getSimpleName(), parentType.getSimpleName(), null);
}
@@ -49,19 +45,18 @@ public final class QuotaHelper {
* Asserts the specified assignment quota.
*
* @param parentId The ID of the parent entity.
* @param requested The number of entities that shall be assigned to the parent
* entity.
* @param limit The maximum number of entities that may be assigned to the
* parent entity.
* @param requested The number of entities that shall be assigned to the parent entity.
* @param limit The maximum number of entities that may be assigned to the parent entity.
* @param type The type of the entities that shall be assigned.
* @param parentType The type of the parent entity.
* @param countFct Function to count the entities that are currently assigned to
* the parent entity.
* @throws AssignmentQuotaExceededException if the assignment operation would cause the quota to be
* exceeded
* @param countFct Function to count the entities that are currently assigned to the parent entity.
* @throws AssignmentQuotaExceededException if the assignment operation would cause the quota to be exceeded
*/
public static <T> void assertAssignmentQuota(final T parentId, final long requested, final long limit,
@NotNull final Class<?> type, @NotNull final Class<?> parentType, final ToLongFunction<T> countFct) {
public static <T> void assertAssignmentQuota(
final T parentId,
final long requested, final long limit,
@NotNull final Class<?> type, @NotNull final Class<?> parentType,
final ToLongFunction<T> countFct) {
assertAssignmentQuota(parentId, requested, limit, type.getSimpleName(), parentType.getSimpleName(), countFct);
}
@@ -69,20 +64,18 @@ public final class QuotaHelper {
* Asserts the specified assignment quota.
*
* @param parentId The ID of the parent entity.
* @param requested The number of entities that shall be assigned to the parent
* entity.
* @param limit The maximum number of entities that may be assigned to the
* parent entity.
* @param requested The number of entities that shall be assigned to the parent entity.
* @param limit The maximum number of entities that may be assigned to the parent entity.
* @param type The type of the entities that shall be assigned.
* @param parentType The type of the parent entity.
* @param countFct Function to count the entities that are currently assigned to
* the parent entity.
* @throws AssignmentQuotaExceededException if the assignment operation would cause the quota to be
* exceeded
* @param countFct Function to count the entities that are currently assigned to the parent entity.
* @throws AssignmentQuotaExceededException if the assignment operation would cause the quota to be exceeded
*/
public static <T> void assertAssignmentQuota(final T parentId, final long requested, final long limit,
@NotNull final String type, @NotNull final String parentType, final ToLongFunction<T> countFct) {
public static <T> void assertAssignmentQuota(
final T parentId,
final long requested, final long limit,
@NotNull final String type, @NotNull final String parentType,
final ToLongFunction<T> countFct) {
// check if the quota is unlimited
if (limit <= 0) {
log.debug("Quota 'Max {} entities per {}' is unlimited.", type, parentType);
@@ -91,8 +84,9 @@ public final class QuotaHelper {
if (requested > limit) {
final String parentIdStr = parentId != null ? String.valueOf(parentId) : "<new>";
log.warn("Cannot assign {} {} entities to {} '{}' because of the configured quota limit {}.", requested,
type, parentType, parentIdStr, limit);
log.warn(
"Cannot assign {} {} entities to {} '{}' because of the configured quota limit {}.",
requested, type, parentType, parentIdStr, limit);
throw new AssignmentQuotaExceededException(type, parentType, parentId, requested, limit);
}
@@ -100,7 +94,8 @@ public final class QuotaHelper {
final long currentCount = countFct.applyAsLong(parentId);
if (currentCount + requested > limit) {
log.warn(
"Cannot assign {} {} entities to {} '{}' because of the configured quota limit {}. Currently, there are {} {} entities assigned.",
"Cannot assign {} {} entities to {} '{}' because of the configured quota limit {}. " +
"Currently, there are {} {} entities assigned.",
requested, type, parentType, parentId, limit, currentCount, type);
throw new AssignmentQuotaExceededException(type, parentType, parentId, requested, limit);
}
@@ -108,8 +103,7 @@ public final class QuotaHelper {
}
/**
* Assert that the number of assignments in a request does not exceed the
* limit.
* Assert that the number of assignments in a request does not exceed the limit.
*
* @param requested the number of assignments that are to be made
* @param limit the maximum number of assignments per request
@@ -121,4 +115,4 @@ public final class QuotaHelper {
throw new AssignmentQuotaExceededException(message);
}
}
}
}

View File

@@ -303,7 +303,7 @@ public abstract class AbstractJpaIntegrationTest extends AbstractIntegrationTest
}
protected Set<TargetTag> getTargetTags(final String controllerId) {
return targetManagement.getTagsByControllerId(controllerId);
return targetManagement.getTags(controllerId);
}
protected JpaRolloutGroup refresh(final RolloutGroup group) {

View File

@@ -68,7 +68,7 @@ class DistributionSetManagementSecurityTest
@Description("Tests ManagementAPI PreAuthorized method with correct and insufficient permissions.")
void createMetaDataPermissionsCheck() {
assertPermissions(
() -> distributionSetManagement.putMetaData(1L, List.of(entityFactory.generateTargetMetadata("key", "value"))),
() -> distributionSetManagement.putMetaData(1L, List.of(entityFactory.generateDsMetadata("key", "value"))),
List.of(SpPermission.UPDATE_REPOSITORY));
}

View File

@@ -10,6 +10,7 @@
package org.eclipse.hawkbit.repository.jpa.management;
import java.util.List;
import java.util.Map;
import io.qameta.allure.Description;
import io.qameta.allure.Feature;
@@ -339,6 +340,26 @@ class TargetManagementSecurityTest extends AbstractJpaIntegrationTest {
assertPermissions(() -> targetManagement.get(List.of(1L)), List.of(SpPermission.READ_TARGET));
}
@Test
@Description("Tests ManagementAPI PreAuthorized method with correct and insufficient permissions.")
void existsByControllerIdPermissionsCheck() {
assertPermissions(() -> targetManagement.existsByControllerId("controllerId"), List.of(SpPermission.READ_TARGET));
}
@Test
@Description("Tests ManagementAPI PreAuthorized method with correct and insufficient permissions.")
void isTargetMatchingQueryAndDSNotAssignedAndCompatibleAndUpdatablePermissionsCheck() {
assertPermissions(
() -> targetManagement.isTargetMatchingQueryAndDSNotAssignedAndCompatibleAndUpdatable("controllerId", 1L, "controllerId==id"),
List.of(SpPermission.READ_TARGET, SpPermission.READ_REPOSITORY));
}
@Test
@Description("Tests ManagementAPI PreAuthorized method with correct and insufficient permissions.")
void getTagsPermissionsCheck() {
assertPermissions(() -> targetManagement.getTags("controllerId"), List.of(SpPermission.READ_TARGET));
}
@Test
@Description("Tests ManagementAPI PreAuthorized method with correct and insufficient permissions.")
void getControllerAttributesPermissionsCheck() {
@@ -368,72 +389,38 @@ class TargetManagementSecurityTest extends AbstractJpaIntegrationTest {
@Test
@Description("Tests ManagementAPI PreAuthorized method with correct and insufficient permissions.")
void existsByControllerIdPermissionsCheck() {
assertPermissions(() -> targetManagement.existsByControllerId("controllerId"), List.of(SpPermission.READ_TARGET));
}
@Test
@Description("Tests ManagementAPI PreAuthorized method with correct and insufficient permissions.")
void isTargetMatchingQueryAndDSNotAssignedAndCompatibleAndUpdatablePermissionsCheck() {
void createMetadataPermissionsCheck() {
assertPermissions(
() -> targetManagement.isTargetMatchingQueryAndDSNotAssignedAndCompatibleAndUpdatable("controllerId", 1L, "controllerId==id"),
List.of(SpPermission.READ_TARGET, SpPermission.READ_REPOSITORY));
}
@Test
@Description("Tests ManagementAPI PreAuthorized method with correct and insufficient permissions.")
void getTagsByControllerIdPermissionsCheck() {
assertPermissions(() -> targetManagement.getTagsByControllerId("controllerId"), List.of(SpPermission.READ_TARGET));
}
@Test
@Description("Tests ManagementAPI PreAuthorized method with correct and insufficient permissions.")
void createMetaDataPermissionsCheck() {
assertPermissions(
() -> targetManagement.createMetaData("controllerId", List.of(entityFactory.generateTargetMetadata("key", "value"))),
() -> {
targetManagement.createMetadata("controllerId", Map.of("key", "value"));
return null;
},
List.of(SpPermission.UPDATE_REPOSITORY));
}
@Test
@Description("Tests ManagementAPI PreAuthorized method with correct and insufficient permissions.")
void deleteMetaDataPermissionsCheck() {
assertPermissions(() -> {
targetManagement.deleteMetaData("controllerId", "key");
return null;
}, List.of(SpPermission.UPDATE_REPOSITORY));
}
@Test
@Description("Tests ManagementAPI PreAuthorized method with correct and insufficient permissions.")
void countMetaDataByControllerIdPermissionsCheck() {
assertPermissions(() -> targetManagement.countMetaDataByControllerId("controllerId"), List.of(SpPermission.READ_REPOSITORY));
}
@Test
@Description("Tests ManagementAPI PreAuthorized method with correct and insufficient permissions.")
void findMetaDataByControllerIdAndRsqlPermissionsCheck() {
assertPermissions(() -> targetManagement.findMetaDataByControllerIdAndRsql(PAGE, "controllerId", "controllerId==id"),
List.of(SpPermission.READ_REPOSITORY));
}
@Test
@Description("Tests ManagementAPI PreAuthorized method with correct and insufficient permissions.")
void getMetaDataByControllerIdPermissionsCheck() {
assertPermissions(() -> targetManagement.getMetaDataByControllerId("controllerId", "key"), List.of(SpPermission.READ_REPOSITORY));
}
@Test
@Description("Tests ManagementAPI PreAuthorized method with correct and insufficient permissions.")
void findMetaDataByControllerIdPermissionsCheck() {
assertPermissions(() -> targetManagement.findMetaDataByControllerId(PAGE, "controllerId"), List.of(SpPermission.READ_REPOSITORY));
void getMetadataPermissionsCheck() {
assertPermissions(() -> targetManagement.getMetadata("controllerId"), List.of(SpPermission.READ_REPOSITORY));
}
@Test
@Description("Tests ManagementAPI PreAuthorized method with correct and insufficient permissions.")
@WithUser(principal = "user", authorities = { SpPermission.UPDATE_REPOSITORY })
void updateMetadataPermissionsCheck() {
assertPermissions(() -> targetManagement.updateMetadata("controllerId", entityFactory.generateTargetMetadata("key", "value")),
assertPermissions(() -> {
targetManagement.updateMetadata("controllerId", "key", "value");
return null;
},
List.of(SpPermission.UPDATE_REPOSITORY));
}
}
@Test
@Description("Tests ManagementAPI PreAuthorized method with correct and insufficient permissions.")
void deleteMetadataPermissionsCheck() {
assertPermissions(() -> {
targetManagement.deleteMetadata("controllerId", "key");
return null;
}, List.of(SpPermission.UPDATE_REPOSITORY));
}
}

View File

@@ -60,17 +60,14 @@ import org.eclipse.hawkbit.repository.exception.RSQLParameterUnsupportedFieldExc
import org.eclipse.hawkbit.repository.jpa.AbstractJpaIntegrationTest;
import org.eclipse.hawkbit.repository.jpa.model.JpaAction;
import org.eclipse.hawkbit.repository.jpa.model.JpaTarget;
import org.eclipse.hawkbit.repository.jpa.model.JpaTargetMetadata;
import org.eclipse.hawkbit.repository.model.Action;
import org.eclipse.hawkbit.repository.model.Action.Status;
import org.eclipse.hawkbit.repository.model.DistributionSet;
import org.eclipse.hawkbit.repository.model.DistributionSetAssignmentResult;
import org.eclipse.hawkbit.repository.model.MetaData;
import org.eclipse.hawkbit.repository.model.NamedEntity;
import org.eclipse.hawkbit.repository.model.Rollout;
import org.eclipse.hawkbit.repository.model.Tag;
import org.eclipse.hawkbit.repository.model.Target;
import org.eclipse.hawkbit.repository.model.TargetMetadata;
import org.eclipse.hawkbit.repository.model.TargetTag;
import org.eclipse.hawkbit.repository.model.TargetType;
import org.eclipse.hawkbit.repository.model.TargetTypeAssignmentResult;
@@ -81,7 +78,6 @@ import org.eclipse.hawkbit.repository.test.matcher.ExpectEvents;
import org.eclipse.hawkbit.repository.test.util.SecurityContextSwitch;
import org.eclipse.hawkbit.repository.test.util.WithUser;
import org.junit.jupiter.api.Test;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.PageRequest;
import org.springframework.data.domain.Slice;
@@ -99,7 +95,7 @@ class TargetManagementTest extends AbstractJpaIntegrationTest {
final Target target = testdataFactory.createTarget();
assertThat(targetManagement.getByControllerID(NOT_EXIST_ID)).isNotPresent();
assertThat(targetManagement.get(NOT_EXIST_IDL)).isNotPresent();
assertThat(targetManagement.getMetaDataByControllerId(target.getControllerId(), NOT_EXIST_ID)).isNotPresent();
assertThat(targetManagement.getMetadata(target.getControllerId()).get(NOT_EXIST_ID)).isNull();
}
@Test
@@ -113,20 +109,15 @@ class TargetManagementTest extends AbstractJpaIntegrationTest {
final Target target = testdataFactory.createTarget();
verifyThrownExceptionBy(
() -> targetManagement.assignTag(Collections.singletonList(target.getControllerId()), NOT_EXIST_IDL),
"TargetTag");
verifyThrownExceptionBy(() -> targetManagement.assignTag(Collections.singletonList(NOT_EXIST_ID), tag.getId()),
"Target");
() -> targetManagement.assignTag(Collections.singletonList(target.getControllerId()), NOT_EXIST_IDL), "TargetTag");
verifyThrownExceptionBy(() -> targetManagement.assignTag(Collections.singletonList(NOT_EXIST_ID), tag.getId()), "Target");
verifyThrownExceptionBy(() -> targetManagement.findByTag(PAGE, NOT_EXIST_IDL), "TargetTag");
verifyThrownExceptionBy(() -> targetManagement.findByRsqlAndTag(PAGE, "name==*", NOT_EXIST_IDL), "TargetTag");
verifyThrownExceptionBy(() -> targetManagement.countByAssignedDistributionSet(NOT_EXIST_IDL),
"DistributionSet");
verifyThrownExceptionBy(() -> targetManagement.countByInstalledDistributionSet(NOT_EXIST_IDL),
"DistributionSet");
verifyThrownExceptionBy(() -> targetManagement.existsByInstalledOrAssignedDistributionSet(NOT_EXIST_IDL),
"DistributionSet");
verifyThrownExceptionBy(() -> targetManagement.countByAssignedDistributionSet(NOT_EXIST_IDL), "DistributionSet");
verifyThrownExceptionBy(() -> targetManagement.countByInstalledDistributionSet(NOT_EXIST_IDL), "DistributionSet");
verifyThrownExceptionBy(() -> targetManagement.existsByInstalledOrAssignedDistributionSet(NOT_EXIST_IDL), "DistributionSet");
verifyThrownExceptionBy(() -> targetManagement.countByTargetFilterQuery(NOT_EXIST_IDL), "TargetFilterQuery");
verifyThrownExceptionBy(() -> targetManagement.countByRsqlAndNonDSAndCompatibleAndUpdatable(NOT_EXIST_IDL, "name==*"),
@@ -138,45 +129,29 @@ class TargetManagementTest extends AbstractJpaIntegrationTest {
verifyThrownExceptionBy(
() -> targetManagement.findByTargetFilterQueryAndNonDSAndCompatibleAndUpdatable(PAGE, NOT_EXIST_IDL, "name==*"),
"DistributionSet");
verifyThrownExceptionBy(() -> targetManagement.findByInRolloutGroupWithoutAction(PAGE, NOT_EXIST_IDL),
"RolloutGroup");
verifyThrownExceptionBy(() -> targetManagement.findByAssignedDistributionSet(PAGE, NOT_EXIST_IDL),
"DistributionSet");
verifyThrownExceptionBy(() -> targetManagement.findByInRolloutGroupWithoutAction(PAGE, NOT_EXIST_IDL), "RolloutGroup");
verifyThrownExceptionBy(() -> targetManagement.findByAssignedDistributionSet(PAGE, NOT_EXIST_IDL), "DistributionSet");
verifyThrownExceptionBy(
() -> targetManagement.findByAssignedDistributionSetAndRsql(PAGE, NOT_EXIST_IDL, "name==*"),
"DistributionSet");
() -> targetManagement.findByAssignedDistributionSetAndRsql(PAGE, NOT_EXIST_IDL, "name==*"), "DistributionSet");
verifyThrownExceptionBy(() -> targetManagement.findByInstalledDistributionSet(PAGE, NOT_EXIST_IDL),
"DistributionSet");
verifyThrownExceptionBy(() -> targetManagement.findByInstalledDistributionSet(PAGE, NOT_EXIST_IDL), "DistributionSet");
verifyThrownExceptionBy(
() -> targetManagement.findByInstalledDistributionSetAndRsql(PAGE, NOT_EXIST_IDL, "name==*"),
"DistributionSet");
() -> targetManagement.findByInstalledDistributionSetAndRsql(PAGE, NOT_EXIST_IDL, "name==*"), "DistributionSet");
verifyThrownExceptionBy(() -> targetManagement
.assignTag(Collections.singletonList(target.getControllerId()), Long.parseLong(NOT_EXIST_ID)), "TargetTag");
verifyThrownExceptionBy(
() -> targetManagement.assignTag(Collections.singletonList(NOT_EXIST_ID), tag.getId()),
"Target");
() -> targetManagement.assignTag(Collections.singletonList(NOT_EXIST_ID), tag.getId()), "Target");
verifyThrownExceptionBy(() -> targetManagement.unassignTag(List.of(NOT_EXIST_ID), tag.getId()), "Target");
verifyThrownExceptionBy(() -> targetManagement.unassignTag(List.of(target.getControllerId()), NOT_EXIST_IDL),
"TargetTag");
verifyThrownExceptionBy(() -> targetManagement.unassignTag(List.of(target.getControllerId()), NOT_EXIST_IDL), "TargetTag");
verifyThrownExceptionBy(() -> targetManagement.update(entityFactory.target().update(NOT_EXIST_ID)), "Target");
verifyThrownExceptionBy(() -> targetManagement.createMetaData(NOT_EXIST_ID,
Collections.singletonList(entityFactory.generateTargetMetadata("123", "123"))), "Target");
verifyThrownExceptionBy(() -> targetManagement.deleteMetaData(NOT_EXIST_ID, "xxx"), "Target");
verifyThrownExceptionBy(() -> targetManagement.deleteMetaData(target.getControllerId(), NOT_EXIST_ID),
"TargetMetadata");
verifyThrownExceptionBy(() -> targetManagement.getMetaDataByControllerId(NOT_EXIST_ID, "xxx"), "Target");
verifyThrownExceptionBy(() -> targetManagement.findMetaDataByControllerId(PAGE, NOT_EXIST_ID), "Target");
verifyThrownExceptionBy(() -> targetManagement.findMetaDataByControllerIdAndRsql(PAGE, NOT_EXIST_ID, "key==*"),
"Target");
verifyThrownExceptionBy(
() -> targetManagement.updateMetadata(NOT_EXIST_ID, entityFactory.generateTargetMetadata("xxx", "xxx")),
"Target");
verifyThrownExceptionBy(() -> targetManagement.updateMetadata(target.getControllerId(),
entityFactory.generateTargetMetadata(NOT_EXIST_ID, "xxx")), "TargetMetadata");
verifyThrownExceptionBy(() -> targetManagement.createMetadata(NOT_EXIST_ID, Map.of("123", "123")), "Target");
verifyThrownExceptionBy(() -> targetManagement.deleteMetadata(NOT_EXIST_ID, "xxx"), "Target");
verifyThrownExceptionBy(() -> targetManagement.deleteMetadata(target.getControllerId(), NOT_EXIST_ID), "Target");
verifyThrownExceptionBy(() -> targetManagement.getMetadata(NOT_EXIST_ID).get("xxx"), "Target");
verifyThrownExceptionBy(() -> targetManagement.updateMetadata(NOT_EXIST_ID, "xxx", "xxx"), "Target");
}
@Test
@@ -764,68 +739,75 @@ class TargetManagementTest extends AbstractJpaIntegrationTest {
@Test
@Description("Checks that metadata for a target can be created.")
void createTargetMetadata() {
final String knownKey = "targetMetaKnownKey";
final String knownValue = "targetMetaKnownValue";
final Target target = testdataFactory.createTarget("targetIdWithMetadata");
final JpaTargetMetadata createdMetadata = insertTargetMetadata(knownKey, knownValue, target);
assertThat(createdMetadata).isNotNull();
assertThat(createdMetadata.getId().getKey()).isEqualTo(knownKey);
assertThat(createdMetadata.getTarget().getControllerId()).isEqualTo(target.getControllerId());
assertThat(createdMetadata.getTarget().getId()).isEqualTo(target.getId());
assertThat(createdMetadata.getValue()).isEqualTo(knownValue);
void createMetadata() {
insertMetadata(
"targetMetaKnownKey", "targetMetaKnownValue",
testdataFactory.createTarget("targetIdWithMetadata")); // and validate
}
@Test
@Description("Verifies the enforcement of the metadata quota per target.")
void createTargetMetadataUntilQuotaIsExceeded() {
void createMetadataUntilQuotaIsExceeded() {
// add meta-data one by one
final Target target1 = testdataFactory.createTarget("target1");
final int maxMetaData = quotaManagement.getMaxMetaDataEntriesPerTarget();
for (int i = 0; i < maxMetaData; ++i) {
assertThat(insertTargetMetadata("k" + i, "v" + i, target1)).isNotNull();
insertMetadata("k" + i, "v" + i, target1);
}
// quota exceeded
assertThatExceptionOfType(AssignmentQuotaExceededException.class)
.isThrownBy(() -> insertTargetMetadata("k" + maxMetaData, "v" + maxMetaData, target1));
.isThrownBy(() -> insertMetadata("k" + maxMetaData, "v" + maxMetaData, target1));
// add multiple meta data entries at once
final Target target2 = testdataFactory.createTarget("target2");
final List<MetaData> metaData2 = new ArrayList<>();
for (int i = 0; i < maxMetaData + 1; ++i) {
metaData2.add(new JpaTargetMetadata("k" + i, "v" + i, target2));
final Map<String, String> metaData2 = new HashMap<>();
for (int i = 0; i < maxMetaData + 1; i++) {
metaData2.put("k" + i, "v" + i);
}
// verify quota is exceeded
final String target2ControllerId = target2.getControllerId();
assertThatExceptionOfType(AssignmentQuotaExceededException.class)
.isThrownBy(() -> targetManagement.createMetaData(target2ControllerId, metaData2));
.isThrownBy(() -> targetManagement.createMetadata(target2ControllerId, metaData2));
// add some meta data entries
final Target target3 = testdataFactory.createTarget("target3");
final int firstHalf = maxMetaData / 2;
for (int i = 0; i < firstHalf; ++i) {
insertTargetMetadata("k" + i, "v" + i, target3);
for (int i = 0; i < firstHalf; i++) {
insertMetadata("k" + i, "v" + i, target3);
}
// add too many data entries
final int secondHalf = maxMetaData - firstHalf;
final List<MetaData> metaData3 = new ArrayList<>();
for (int i = 0; i < secondHalf + 1; ++i) {
metaData3.add(new JpaTargetMetadata("kk" + i, "vv" + i, target3));
final Map<String, String> metaData3 = new HashMap<>();
for (int i = 0; i < secondHalf + 1; i++) {
metaData3.put("kk" + i, "vv" + i);
}
// verify quota is exceeded
final String target3ControllerId = target3.getControllerId();
assertThatExceptionOfType(AssignmentQuotaExceededException.class)
.isThrownBy(() -> targetManagement.createMetaData(target3ControllerId, metaData3));
.isThrownBy(() -> targetManagement.createMetadata(target3ControllerId, metaData3));
}
@Test
@Description("Queries and loads the metadata related to a given target.")
void getMetadata() {
// create targets
final Target target1 = createTargetWithMetadata("target1", 10);
final Target target2 = createTargetWithMetadata("target2", 8);
final Map<String, String> metadataOfTarget1 = targetManagement.getMetadata(target1.getControllerId());
final Map<String, String> metadataOfTarget2 = targetManagement.getMetadata(target2.getControllerId());
assertThat(metadataOfTarget1).hasSize(10);
assertThat(metadataOfTarget2).hasSize(8);
}
@Test
@WithUser(allSpPermissions = true)
@Description("Checks that metadata for a target can be updated.")
void updateTargetMetadata() {
void updateMetadata() {
final String knownKey = "myKnownKey";
final String knownValue = "myKnownValue";
final String knownUpdateValue = "myNewUpdatedValue";
@@ -836,26 +818,36 @@ class TargetManagementTest extends AbstractJpaIntegrationTest {
assertThat(target.getOptLockRevision()).isEqualTo(1);
// create target meta data entry
insertTargetMetadata(knownKey, knownValue, target);
insertMetadata(knownKey, knownValue, target);
Target changedLockRevisionTarget = targetManagement.get(target.getId()).orElseThrow(NoSuchElementException::new);
final Target changedLockRevisionTarget = targetManagement.get(target.getId()).orElseThrow(NoSuchElementException::new);
assertThat(changedLockRevisionTarget.getOptLockRevision()).isEqualTo(2);
// update the target metadata
final JpaTargetMetadata updated = (JpaTargetMetadata) targetManagement.updateMetadata(
target.getControllerId(), entityFactory.generateTargetMetadata(knownKey, knownUpdateValue));
// we are updating the target meta-data so also modifying the base
// software module so opt lock revision must be three
changedLockRevisionTarget = targetManagement.get(target.getId()).orElseThrow(NoSuchElementException::new);
assertThat(changedLockRevisionTarget.getOptLockRevision()).isEqualTo(3);
assertThat(changedLockRevisionTarget.getLastModifiedAt()).isPositive();
targetManagement.updateMetadata(target.getControllerId(), knownKey, knownUpdateValue);
// we are updating the target meta-data so also modifying the target so opt lock revision must be three
final Target changedLockRevisionTarget2 = targetManagement.get(target.getId()).orElseThrow(NoSuchElementException::new);
assertThat(changedLockRevisionTarget2.getOptLockRevision()).isEqualTo(3);
assertThat(changedLockRevisionTarget2.getLastModifiedAt()).isPositive();
// verify updated meta data contains the updated value
assertThat(updated).isNotNull();
assertThat(updated.getValue()).isEqualTo(knownUpdateValue);
assertThat(updated.getId().getKey()).isEqualTo(knownKey);
assertThat(updated.getTarget().getControllerId()).isEqualTo(target.getControllerId());
assertThat(updated.getTarget().getId()).isEqualTo(target.getId());
assertThat(targetManagement.getMetadata(target.getControllerId()).get(knownKey)).isEqualTo(knownUpdateValue);
}
@Test
@Description("Queries and loads the metadata related to a given target.")
void deleteMetadata() {
final String knownKey = "myKnownKey";
final String knownValue = "myKnownValue";
// create target
final String controllerId = createTargetWithMetadata("target1", 10).getControllerId();
targetManagement.createMetadata(controllerId, Map.of(knownKey, knownValue));
targetManagement.deleteMetadata(controllerId, knownKey);
// already removed
assertThatExceptionOfType(EntityNotFoundException.class)
.isThrownBy(() -> targetManagement.deleteMetadata(controllerId, knownKey));
}
@Test
@@ -963,26 +955,6 @@ class TargetManagementTest extends AbstractJpaIntegrationTest {
checkTargetsHaveType(typeATargets, typeB);
}
@Test
@Description("Queries and loads the metadata related to a given target.")
void findAllTargetMetadataByControllerId() {
// create targets
final Target target1 = createTargetWithMetadata("target1", 10);
final Target target2 = createTargetWithMetadata("target2", 8);
final Page<TargetMetadata> metadataOfTarget1 = targetManagement
.findMetaDataByControllerId(PageRequest.of(0, 100), target1.getControllerId());
final Page<TargetMetadata> metadataOfTarget2 = targetManagement
.findMetaDataByControllerId(PageRequest.of(0, 100), target2.getControllerId());
assertThat(metadataOfTarget1.getNumberOfElements()).isEqualTo(10);
assertThat(metadataOfTarget1.getTotalElements()).isEqualTo(10);
assertThat(metadataOfTarget2.getNumberOfElements()).isEqualTo(8);
assertThat(metadataOfTarget2.getTotalElements()).isEqualTo(8);
}
@Test
@WithUser(allSpPermissions = true)
@Description("Checks that target type is not assigned to target if invalid.")
@@ -1419,11 +1391,9 @@ class TargetManagementTest extends AbstractJpaIntegrationTest {
}
}
private JpaTargetMetadata insertTargetMetadata(final String knownKey, final String knownValue,
final Target target) {
final JpaTargetMetadata metadata = new JpaTargetMetadata(knownKey, knownValue, target);
return (JpaTargetMetadata) targetManagement
.createMetaData(target.getControllerId(), Collections.singletonList(metadata)).get(0);
private void insertMetadata(final String knownKey, final String knownValue, final Target target) {
targetManagement.createMetadata(target.getControllerId(), Map.of(knownKey, knownValue));
assertThat(targetManagement.getMetadata(target.getControllerId()).get(knownKey)).isEqualTo(knownValue);
}
private void checkTargetsHaveType(final List<Target> targets, final TargetType type) {
@@ -1440,7 +1410,7 @@ class TargetManagementTest extends AbstractJpaIntegrationTest {
final Target target = testdataFactory.createTarget(controllerId);
for (int index = 1; index <= count; index++) {
insertTargetMetadata("key" + index, controllerId + "-value" + index, target);
insertMetadata("key" + index, controllerId + "-value" + index, target);
}
return target;
@@ -1450,7 +1420,7 @@ class TargetManagementTest extends AbstractJpaIntegrationTest {
final Target target = testdataFactory.createTarget(controllerId, controllerId, targetTypeId);
for (int index = 1; index <= count; index++) {
insertTargetMetadata("key" + index, controllerId + "-value" + index, target);
insertMetadata("key" + index, controllerId + "-value" + index, target);
}
return target;

View File

@@ -79,7 +79,7 @@ class RSQLTargetFieldTest extends AbstractJpaIntegrationTest {
.description("targetDesc123"));
target = controllerManagement.updateControllerAttributes(target.getControllerId(), Map.of("revision", "1.1"), null);
target = controllerManagement.findOrRegisterTargetIfItDoesNotExist(target.getControllerId(), LOCALHOST);
createTargetMetadata(target.getControllerId(), entityFactory.generateTargetMetadata("metaKey", "metaValue"));
targetManagement.createMetadata(target.getControllerId(), Map.of("metaKey", "metaValue"));
assignDistributionSet(ds.getId(), target.getControllerId());
targetManagement.assignType(target.getControllerId(), targetType1.getId());
@@ -89,7 +89,7 @@ class RSQLTargetFieldTest extends AbstractJpaIntegrationTest {
.description("targetId1234"));
target2 = controllerManagement.updateControllerAttributes(target2.getControllerId(), Map.of("revision", "1.2"), null);
targetManagement.assignType(target2.getControllerId(), targetType2.getId());
createTargetMetadata(target2.getControllerId(), entityFactory.generateTargetMetadata("metaKey", "value"));
targetManagement.createMetadata(target2.getControllerId(), Map.of("metaKey", "value"));
target2 = controllerManagement.findOrRegisterTargetIfItDoesNotExist(target2.getControllerId(), LOCALHOST);
targetManagement.assignTag(Arrays.asList(target.getControllerId(), target2.getControllerId()), targetTag.getId());
@@ -270,7 +270,7 @@ class RSQLTargetFieldTest extends AbstractJpaIntegrationTest {
@Test
@Description("Test filter target by metadata")
void testFilterByMetadata() {
createTargetMetadata(testdataFactory.createTarget().getControllerId(), entityFactory.generateTargetMetadata("key.dot", "value.dot"));
targetManagement.createMetadata(testdataFactory.createTarget().getControllerId(), Map.of("key.dot", "value.dot"));
assertRSQLQuery(TargetFields.METADATA.name() + ".metaKey==metaValue", 1);
assertRSQLQuery(TargetFields.METADATA.name() + ".metaKey==null", 0); // "null" check

View File

@@ -1,81 +0,0 @@
/**
* Copyright (c) 2015 Bosch Software Innovations GmbH and others
*
* This program and the accompanying materials are made
* available under the terms of the Eclipse Public License 2.0
* which is available at https://www.eclipse.org/legal/epl-2.0/
*
* SPDX-License-Identifier: EPL-2.0
*/
package org.eclipse.hawkbit.repository.jpa.rsql;
import static org.assertj.core.api.Assertions.assertThat;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import io.qameta.allure.Description;
import io.qameta.allure.Feature;
import io.qameta.allure.Story;
import org.eclipse.hawkbit.repository.TargetMetadataFields;
import org.eclipse.hawkbit.repository.jpa.AbstractJpaIntegrationTest;
import org.eclipse.hawkbit.repository.model.MetaData;
import org.eclipse.hawkbit.repository.model.Target;
import org.eclipse.hawkbit.repository.model.TargetMetadata;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.PageRequest;
@Feature("Component Tests - Repository")
@Story("RSQL filter target metadata")
class RSQLTargetMetadataFieldsTest extends AbstractJpaIntegrationTest {
private String controllerId;
@BeforeEach
void setupBeforeTest() {
final Target target = testdataFactory.createTarget("target");
controllerId = target.getControllerId();
final List<MetaData> metadata = new ArrayList<>(5);
for (int i = 0; i < 5; i++) {
metadata.add(entityFactory.generateTargetMetadata("" + i, "" + i));
}
targetManagement.createMetaData(controllerId, metadata);
targetManagement.createMetaData(controllerId,
Arrays.asList(entityFactory.generateTargetMetadata("emptyValueTest", null)));
}
@Test
@Description("Test filter target metadata by key")
void testFilterByParameterKey() {
assertRSQLQuery(TargetMetadataFields.KEY.name() + "==1", 1);
assertRSQLQuery(TargetMetadataFields.KEY.name() + "!=1", 5);
assertRSQLQuery(TargetMetadataFields.KEY.name() + "=in=(1,2)", 2);
assertRSQLQuery(TargetMetadataFields.KEY.name() + "=out=(1,2)", 4);
}
@Test
@Description("Test filter target metadata by value")
void testFilterByParameterValue() {
assertRSQLQuery(TargetMetadataFields.VALUE.name() + "==''", 1);
assertRSQLQuery(TargetMetadataFields.VALUE.name() + "!=''", 5);
assertRSQLQuery(TargetMetadataFields.VALUE.name() + "==1", 1);
assertRSQLQuery(TargetMetadataFields.VALUE.name() + "!=1", 5);
assertRSQLQuery(TargetMetadataFields.VALUE.name() + "=in=(1,2)", 2);
assertRSQLQuery(TargetMetadataFields.VALUE.name() + "=out=(1,2)", 4);
}
private void assertRSQLQuery(final String rsqlParam, final long expectedEntities) {
final Page<TargetMetadata> findEnitity = targetManagement
.findMetaDataByControllerIdAndRsql(PageRequest.of(0, 100), controllerId, rsqlParam);
final long countAllEntities = findEnitity.getTotalElements();
assertThat(findEnitity).isNotNull();
assertThat(countAllEntities).isEqualTo(expectedEntities);
}
}

View File

@@ -12,10 +12,10 @@
logging.level.org.eclipse.persistence=ERROR
## Uncomment to see the debug of persistence, e.g. to see the generated SQLs
logging.level.org.eclipse.persistence=DEBUG
spring.jpa.properties.eclipselink.logging.level=FINE
spring.jpa.properties.eclipselink.logging.level.sql=FINE
spring.jpa.properties.eclipselink.logging.parameters=true
#logging.level.org.eclipse.persistence=DEBUG
#spring.jpa.properties.eclipselink.logging.level=FINE
#spring.jpa.properties.eclipselink.logging.level.sql=FINE
#spring.jpa.properties.eclipselink.logging.parameters=true
## Enable EclipseLink performance monitor (monitoring and profile)
#spring.jpa.properties.eclipselink.profiler=PerformanceMonitor