Feature target metadata (#757)
* Defined the model for target matadata and the corresponding repository layer/management * Added target metadata quotas incl enforcement * Extended Target Mgmt REST API to allow for metadata CRUD operations * Added migration scripts for each database * Added back reference to target metadata in JpaTarget * Added tests for target management, Mgmt REST API, target metadata RSQL, and REST documentation * Updated asciidocs for target rest documentation * Fix Allure imports and annotations * Fix review findings Signed-off-by: Bogdan Bondar <Bogdan.Bondar@bosch-si.com> Signed-off-by: Stefan Behl <stefan.behl@bosch-si.com>
This commit is contained in:
committed by
Stefan Behl
parent
1cbde47370
commit
0cf4f8e8b9
@@ -43,7 +43,8 @@ public interface EntityFactory {
|
||||
DistributionSetBuilder distributionSet();
|
||||
|
||||
/**
|
||||
* Generates an {@link MetaData} element without persisting it.
|
||||
* Generates an {@link MetaData} element for distribution set without
|
||||
* persisting it.
|
||||
*
|
||||
* @param key
|
||||
* {@link MetaData#getKey()}
|
||||
@@ -52,7 +53,20 @@ public interface EntityFactory {
|
||||
*
|
||||
* @return {@link MetaData} object
|
||||
*/
|
||||
MetaData generateMetadata(@Size(min = 1, max = MetaData.KEY_MAX_SIZE) @NotNull String key,
|
||||
MetaData generateDsMetadata(@Size(min = 1, max = MetaData.KEY_MAX_SIZE) @NotNull String key,
|
||||
@Size(max = MetaData.VALUE_MAX_SIZE) String value);
|
||||
|
||||
/**
|
||||
* Generates an {@link MetaData} element for target without persisting it.
|
||||
*
|
||||
* @param key
|
||||
* {@link MetaData#getKey()}
|
||||
* @param value
|
||||
* {@link MetaData#getValue()}
|
||||
*
|
||||
* @return {@link MetaData} object
|
||||
*/
|
||||
MetaData generateTargetMetadata(@Size(min = 1, max = MetaData.KEY_MAX_SIZE) @NotNull String key,
|
||||
@Size(max = MetaData.VALUE_MAX_SIZE) String value);
|
||||
|
||||
/**
|
||||
|
||||
@@ -53,6 +53,11 @@ public interface QuotaManagement {
|
||||
*/
|
||||
int getMaxMetaDataEntriesPerDistributionSet();
|
||||
|
||||
/**
|
||||
* @return maximum number of meta data entries per target
|
||||
*/
|
||||
int getMaxMetaDataEntriesPerTarget();
|
||||
|
||||
/**
|
||||
* @return maximum number of software modules per distribution set
|
||||
*/
|
||||
|
||||
@@ -23,13 +23,16 @@ import org.eclipse.hawkbit.repository.builder.TargetCreate;
|
||||
import org.eclipse.hawkbit.repository.builder.TargetUpdate;
|
||||
import org.eclipse.hawkbit.repository.exception.EntityAlreadyExistsException;
|
||||
import org.eclipse.hawkbit.repository.exception.EntityNotFoundException;
|
||||
import org.eclipse.hawkbit.repository.exception.QuotaExceededException;
|
||||
import org.eclipse.hawkbit.repository.exception.RSQLParameterSyntaxException;
|
||||
import org.eclipse.hawkbit.repository.exception.RSQLParameterUnsupportedFieldException;
|
||||
import org.eclipse.hawkbit.repository.model.DistributionSet;
|
||||
import org.eclipse.hawkbit.repository.model.MetaData;
|
||||
import org.eclipse.hawkbit.repository.model.RolloutGroup;
|
||||
import org.eclipse.hawkbit.repository.model.Tag;
|
||||
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.TargetTagAssignmentResult;
|
||||
import org.eclipse.hawkbit.repository.model.TargetUpdateStatus;
|
||||
@@ -650,4 +653,116 @@ public interface TargetManagement {
|
||||
* {@code false}: update of controller attributes not requested.
|
||||
*/
|
||||
boolean isControllerAttributesRequested(@NotEmpty String controllerId);
|
||||
|
||||
/**
|
||||
* Creates a list of target meta data entries.
|
||||
*
|
||||
* @param controllerId
|
||||
* {@link Target} controller id the metadata has to be created
|
||||
* for
|
||||
* @param metadata
|
||||
* the meta data entries to create or update
|
||||
* @return the updated or created target meta data entries
|
||||
*
|
||||
* @throws EntityNotFoundException
|
||||
* if given target does not exist
|
||||
*
|
||||
* @throws EntityAlreadyExistsException
|
||||
* in case one of the meta data entry already exists for the
|
||||
* specific key
|
||||
*
|
||||
* @throws QuotaExceededException
|
||||
* if the maximum number of {@link MetaData} entries is exceeded
|
||||
* for the addressed {@link Target}
|
||||
*/
|
||||
@PreAuthorize(SpringEvalExpressions.HAS_AUTH_UPDATE_REPOSITORY)
|
||||
List<TargetMetadata> createMetaData(@NotEmpty String controllerId, @NotEmpty Collection<MetaData> metadata);
|
||||
|
||||
/**
|
||||
* Deletes a target meta data entry.
|
||||
*
|
||||
* @param controllerId
|
||||
* where meta data has to be deleted
|
||||
* @param key
|
||||
* of the meta data element
|
||||
*
|
||||
* @throws EntityNotFoundException
|
||||
* if given target does not exist
|
||||
*/
|
||||
@PreAuthorize(SpringEvalExpressions.HAS_AUTH_UPDATE_REPOSITORY)
|
||||
void deleteMetaData(@NotEmpty String controllerId, @NotEmpty String key);
|
||||
|
||||
/**
|
||||
* Finds all meta data by the given target id.
|
||||
*
|
||||
* @param pageable
|
||||
* the page request to page the result
|
||||
* @param controllerId
|
||||
* the controller id to retrieve the meta data from
|
||||
*
|
||||
* @return a paged result of all meta data entries for a given target id
|
||||
*
|
||||
* @throws EntityNotFoundException
|
||||
* if target with given ID does not exist
|
||||
*/
|
||||
@PreAuthorize(SpringEvalExpressions.HAS_AUTH_READ_REPOSITORY)
|
||||
Page<TargetMetadata> findMetaDataByControllerId(@NotNull Pageable pageable, @NotEmpty String controllerId);
|
||||
|
||||
/**
|
||||
* Finds all meta data by the given target id and query.
|
||||
*
|
||||
* @param pageable
|
||||
* the page request to page the result
|
||||
* @param controllerId
|
||||
* the controller id to retrieve the meta data from
|
||||
* @param rsqlParam
|
||||
* rsql query string
|
||||
*
|
||||
* @return a paged result of all meta data entries for a given target id
|
||||
*
|
||||
* @throws RSQLParameterUnsupportedFieldException
|
||||
* if a field in the RSQL string is used but not provided by the
|
||||
* given {@code fieldNameProvider}
|
||||
*
|
||||
* @throws RSQLParameterSyntaxException
|
||||
* if the RSQL syntax is wrong
|
||||
*
|
||||
* @throws EntityNotFoundException
|
||||
* if target with given ID does not exist
|
||||
*/
|
||||
@PreAuthorize(SpringEvalExpressions.HAS_AUTH_READ_REPOSITORY)
|
||||
Page<TargetMetadata> findMetaDataByControllerIdAndRsql(@NotNull Pageable pageable, @NotEmpty String controllerId,
|
||||
@NotNull String rsqlParam);
|
||||
|
||||
/**
|
||||
* Finds a single target meta data by its id.
|
||||
*
|
||||
* @param controllerId
|
||||
* of the {@link Target}
|
||||
* @param key
|
||||
* of the meta data element
|
||||
* @return the found TargetMetadata
|
||||
*
|
||||
* @throws EntityNotFoundException
|
||||
* if target with given ID does not exist
|
||||
*/
|
||||
@PreAuthorize(SpringEvalExpressions.HAS_AUTH_READ_REPOSITORY)
|
||||
Optional<TargetMetadata> getMetaDataByControllerId(@NotEmpty String controllerId, @NotEmpty String key);
|
||||
|
||||
/**
|
||||
* Updates a target meta data value if corresponding entry exists.
|
||||
*
|
||||
* @param controllerId
|
||||
* {@link Target} controller id of the meta data entry to be
|
||||
* updated
|
||||
* @param metadata
|
||||
* meta data entry to be updated
|
||||
* @return the updated meta data entry
|
||||
*
|
||||
* @throws EntityNotFoundException
|
||||
* in case the meta data entry does not exists and cannot be
|
||||
* updated
|
||||
*/
|
||||
@PreAuthorize(SpringEvalExpressions.HAS_AUTH_UPDATE_REPOSITORY)
|
||||
TargetMetadata updateMetaData(@NotEmpty String controllerId, @NotNull MetaData metadata);
|
||||
}
|
||||
|
||||
@@ -70,11 +70,25 @@ public class EntityNotFoundException extends AbstractServerRtException {
|
||||
* @param type
|
||||
* of the entity that was not found
|
||||
*
|
||||
* @param enityId
|
||||
* @param entityId
|
||||
* of the {@link BaseEntity}
|
||||
*/
|
||||
public EntityNotFoundException(final Class<? extends BaseEntity> type, final Object enityId) {
|
||||
this(type.getSimpleName() + " with given identifier {" + enityId + "} does not exist.");
|
||||
public EntityNotFoundException(final Class<? extends BaseEntity> type, final Object entityId) {
|
||||
this(type.getSimpleName() + " with given identifier {" + entityId + "} does not exist.");
|
||||
}
|
||||
|
||||
/**
|
||||
* Parameterized constructor for {@link MetaData} not found.
|
||||
*
|
||||
* @param type
|
||||
* of the entity that was not found
|
||||
* @param entityId
|
||||
* of the {@link BaseEntity} the {@link MetaData} was for
|
||||
* @param key
|
||||
* for the {@link MetaData} entry
|
||||
*/
|
||||
public EntityNotFoundException(final Class<? extends MetaData> type, final Long entityId, final String key) {
|
||||
this(type.getSimpleName() + " for given entity {" + entityId + "} and with key {" + key + "} does not exist.");
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -87,7 +101,7 @@ public class EntityNotFoundException extends AbstractServerRtException {
|
||||
* @param key
|
||||
* for the {@link MetaData} entry
|
||||
*/
|
||||
public EntityNotFoundException(final Class<? extends MetaData> type, final Long enityId, final String key) {
|
||||
public EntityNotFoundException(final Class<? extends MetaData> type, final String enityId, final String key) {
|
||||
this(type.getSimpleName() + " for given entity {" + enityId + "} and with key {" + key + "} does not exist.");
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,26 @@
|
||||
/**
|
||||
* Copyright (c) 2018 Bosch Software Innovations GmbH and others.
|
||||
*
|
||||
* All rights reserved. This program and the accompanying materials
|
||||
* are made available under the terms of the Eclipse Public License v1.0
|
||||
* which accompanies this distribution, and is available at
|
||||
* http://www.eclipse.org/legal/epl-v10.html
|
||||
*/
|
||||
package org.eclipse.hawkbit.repository.model;
|
||||
|
||||
/**
|
||||
* {@link MetaData} of a {@link Target}.
|
||||
*
|
||||
*/
|
||||
public interface TargetMetadata extends MetaData {
|
||||
|
||||
/**
|
||||
* @return {@link Target} of this {@link MetaData} entry.
|
||||
*/
|
||||
Target getTarget();
|
||||
|
||||
@Override
|
||||
default Long getEntityId() {
|
||||
return getTarget().getId();
|
||||
}
|
||||
}
|
||||
@@ -58,6 +58,11 @@ public class PropertiesQuotaManagement implements QuotaManagement {
|
||||
return securityProperties.getDos().getMaxMetaDataEntriesPerDistributionSet();
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getMaxMetaDataEntriesPerTarget() {
|
||||
return securityProperties.getDos().getMaxMetaDataEntriesPerTarget();
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getMaxSoftwareModulesPerDistributionSet() {
|
||||
return securityProperties.getDos().getMaxSoftwareModulesPerDistributionSet();
|
||||
|
||||
@@ -457,7 +457,7 @@ public class JpaDistributionSetManagement implements DistributionSetManagement {
|
||||
ConcurrencyFailureException.class }, maxAttempts = Constants.TX_RT_MAX, backoff = @Backoff(delay = Constants.TX_RT_DELAY))
|
||||
public List<DistributionSetMetadata> createMetaData(final long dsId, final Collection<MetaData> md) {
|
||||
|
||||
md.forEach(meta -> checkAndThrowAlreadyIfDistributionSetMetadataExists(
|
||||
md.forEach(meta -> checkAndThrowIfDistributionSetMetadataAlreadyExists(
|
||||
new DsMetadataCompositeKey(dsId, meta.getKey())));
|
||||
|
||||
assertMetaDataQuota(dsId, md.size());
|
||||
@@ -478,8 +478,7 @@ public class JpaDistributionSetManagement implements DistributionSetManagement {
|
||||
|
||||
private void assertSoftwareModuleQuota(final Long id, final int requested) {
|
||||
QuotaHelper.assertAssignmentQuota(id, requested, quotaManagement.getMaxSoftwareModulesPerDistributionSet(),
|
||||
SoftwareModule.class, DistributionSet.class,
|
||||
softwareModuleRepository::countByAssignedToId);
|
||||
SoftwareModule.class, DistributionSet.class, softwareModuleRepository::countByAssignedToId);
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -676,7 +675,7 @@ public class JpaDistributionSetManagement implements DistributionSetManagement {
|
||||
return distributionSetRepository.findAll(SpecificationsBuilder.combineWithAnd(specList), pageable);
|
||||
}
|
||||
|
||||
private void checkAndThrowAlreadyIfDistributionSetMetadataExists(final DsMetadataCompositeKey metadataId) {
|
||||
private void checkAndThrowIfDistributionSetMetadataAlreadyExists(final DsMetadataCompositeKey metadataId) {
|
||||
if (distributionSetMetadataRepository.exists(metadataId)) {
|
||||
throw new EntityAlreadyExistsException(
|
||||
"Metadata entry with key '" + metadataId.getKey() + "' already exists");
|
||||
|
||||
@@ -26,6 +26,7 @@ import org.eclipse.hawkbit.repository.jpa.builder.JpaSoftwareModuleTypeBuilder;
|
||||
import org.eclipse.hawkbit.repository.jpa.builder.JpaTagBuilder;
|
||||
import org.eclipse.hawkbit.repository.jpa.builder.JpaTargetBuilder;
|
||||
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.beans.factory.annotation.Autowired;
|
||||
import org.springframework.util.StringUtils;
|
||||
@@ -57,10 +58,15 @@ public class JpaEntityFactory implements EntityFactory {
|
||||
private SoftwareModuleMetadataBuilder softwareModuleMetadataBuilder;
|
||||
|
||||
@Override
|
||||
public MetaData generateMetadata(final String key, final String value) {
|
||||
public MetaData generateDsMetadata(final String key, final String value) {
|
||||
return new JpaDistributionSetMetadata(key, StringUtils.trimWhitespace(value));
|
||||
}
|
||||
|
||||
@Override
|
||||
public MetaData generateTargetMetadata(final String key, final String value) {
|
||||
return new JpaTargetMetadata(key, StringUtils.trimWhitespace(value));
|
||||
}
|
||||
|
||||
@Override
|
||||
public DistributionSetTypeBuilder distributionSetType() {
|
||||
return distributionSetTypeBuilder;
|
||||
|
||||
@@ -26,13 +26,16 @@ import javax.persistence.criteria.Predicate;
|
||||
import javax.persistence.criteria.Root;
|
||||
|
||||
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.TargetDeletedEvent;
|
||||
import org.eclipse.hawkbit.repository.event.remote.TargetAttributesRequestedEvent;
|
||||
import org.eclipse.hawkbit.repository.event.remote.TargetDeletedEvent;
|
||||
import org.eclipse.hawkbit.repository.exception.EntityAlreadyExistsException;
|
||||
import org.eclipse.hawkbit.repository.exception.EntityNotFoundException;
|
||||
import org.eclipse.hawkbit.repository.jpa.builder.JpaTargetCreate;
|
||||
import org.eclipse.hawkbit.repository.jpa.builder.JpaTargetUpdate;
|
||||
@@ -40,15 +43,21 @@ import org.eclipse.hawkbit.repository.jpa.configuration.Constants;
|
||||
import org.eclipse.hawkbit.repository.jpa.executor.AfterTransactionCommitExecutor;
|
||||
import org.eclipse.hawkbit.repository.jpa.model.JpaDistributionSet_;
|
||||
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.JpaTarget_;
|
||||
import org.eclipse.hawkbit.repository.jpa.model.TargetMetadataCompositeKey;
|
||||
import org.eclipse.hawkbit.repository.jpa.rsql.RSQLUtility;
|
||||
import org.eclipse.hawkbit.repository.jpa.specifications.SpecificationsBuilder;
|
||||
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.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.TargetTagAssignmentResult;
|
||||
import org.eclipse.hawkbit.repository.model.TargetUpdateStatus;
|
||||
@@ -83,8 +92,12 @@ public class JpaTargetManagement implements TargetManagement {
|
||||
|
||||
private final EntityManager entityManager;
|
||||
|
||||
private final QuotaManagement quotaManagement;
|
||||
|
||||
private final TargetRepository targetRepository;
|
||||
|
||||
private final TargetMetadataRepository targetMetadataRepository;
|
||||
|
||||
private final RolloutGroupRepository rolloutGroupRepository;
|
||||
|
||||
private final DistributionSetRepository distributionSetRepository;
|
||||
@@ -107,7 +120,8 @@ public class JpaTargetManagement implements TargetManagement {
|
||||
|
||||
private final Database database;
|
||||
|
||||
JpaTargetManagement(final EntityManager entityManager, final TargetRepository targetRepository,
|
||||
JpaTargetManagement(final EntityManager entityManager, final QuotaManagement quotaManagement,
|
||||
final TargetRepository targetRepository, final TargetMetadataRepository targetMetadataRepository,
|
||||
final RolloutGroupRepository rolloutGroupRepository,
|
||||
final DistributionSetRepository distributionSetRepository,
|
||||
final TargetFilterQueryRepository targetFilterQueryRepository,
|
||||
@@ -116,7 +130,9 @@ public class JpaTargetManagement implements TargetManagement {
|
||||
final TenantAware tenantAware, final AfterTransactionCommitExecutor afterCommit,
|
||||
final VirtualPropertyReplacer virtualPropertyReplacer, final Database database) {
|
||||
this.entityManager = entityManager;
|
||||
this.quotaManagement = quotaManagement;
|
||||
this.targetRepository = targetRepository;
|
||||
this.targetMetadataRepository = targetMetadataRepository;
|
||||
this.rolloutGroupRepository = rolloutGroupRepository;
|
||||
this.distributionSetRepository = distributionSetRepository;
|
||||
this.targetFilterQueryRepository = targetFilterQueryRepository;
|
||||
@@ -135,6 +151,11 @@ public class JpaTargetManagement implements TargetManagement {
|
||||
return targetRepository.findByControllerId(controllerId);
|
||||
}
|
||||
|
||||
private JpaTarget getByControllerIdAndThrowIfNotFound(final String controllerId) {
|
||||
return targetRepository.findByControllerId(controllerId).map(JpaTarget.class::cast)
|
||||
.orElseThrow(() -> new EntityNotFoundException(Target.class, controllerId));
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<Target> getByControllerID(final Collection<String> controllerIDs) {
|
||||
return Collections.unmodifiableList(
|
||||
@@ -146,6 +167,121 @@ public class JpaTargetManagement implements TargetManagement {
|
||||
return targetRepository.count();
|
||||
}
|
||||
|
||||
@Override
|
||||
@Transactional
|
||||
@Retryable(include = {
|
||||
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) {
|
||||
|
||||
final JpaTarget target = getByControllerIdAndThrowIfNotFound(controllerId);
|
||||
|
||||
md.forEach(meta -> checkAndThrowIfTargetMetadataAlreadyExists(
|
||||
new TargetMetadataCompositeKey(target.getId(), meta.getKey())));
|
||||
|
||||
assertMetaDataQuota(target.getId(), md.size());
|
||||
|
||||
final JpaTarget updatedTarget = touch(target);
|
||||
|
||||
return Collections.unmodifiableList(md.stream()
|
||||
.map(meta -> targetMetadataRepository
|
||||
.save(new JpaTargetMetadata(meta.getKey(), meta.getValue(), updatedTarget)))
|
||||
.collect(Collectors.toList()));
|
||||
}
|
||||
|
||||
private void checkAndThrowIfTargetMetadataAlreadyExists(final TargetMetadataCompositeKey metadataId) {
|
||||
if (targetMetadataRepository.exists(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 JpaTarget touch(final JpaTarget target) {
|
||||
|
||||
// merge base target so optLockRevision gets updated and audit
|
||||
// log written because modifying metadata is modifying the base
|
||||
// target itself for auditing purposes.
|
||||
final JpaTarget result = entityManager.merge(target);
|
||||
result.setLastModifiedAt(0L);
|
||||
|
||||
return targetRepository.save(result);
|
||||
}
|
||||
|
||||
private JpaTarget touch(final String controllerId) {
|
||||
return touch(getByControllerIdAndThrowIfNotFound(controllerId));
|
||||
}
|
||||
|
||||
@Override
|
||||
@Transactional
|
||||
@Retryable(include = {
|
||||
ConcurrencyFailureException.class }, maxAttempts = Constants.TX_RT_MAX, backoff = @Backoff(delay = Constants.TX_RT_DELAY))
|
||||
public TargetMetadata updateMetaData(final String controllerId, final MetaData md) {
|
||||
|
||||
// check if exists otherwise throw entity not found exception
|
||||
final JpaTargetMetadata toUpdate = (JpaTargetMetadata) getMetaDataByControllerId(controllerId, md.getKey())
|
||||
.orElseThrow(() -> new EntityNotFoundException(TargetMetadata.class, controllerId, md.getKey()));
|
||||
toUpdate.setValue(md.getValue());
|
||||
// touch it to update the lock revision because we are modifying the
|
||||
// target indirectly
|
||||
touch(controllerId);
|
||||
return targetMetadataRepository.save(toUpdate);
|
||||
}
|
||||
|
||||
@Override
|
||||
@Transactional
|
||||
@Retryable(include = {
|
||||
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));
|
||||
|
||||
touch(controllerId);
|
||||
targetMetadataRepository.delete(metadata.getId());
|
||||
}
|
||||
|
||||
@Override
|
||||
public Page<TargetMetadata> findMetaDataByControllerId(final Pageable pageable, final String controllerId) {
|
||||
final Long targetId = getByControllerIdAndThrowIfNotFound(controllerId).getId();
|
||||
|
||||
return convertMdPage(
|
||||
targetMetadataRepository
|
||||
.findAll(
|
||||
(Specification<JpaTargetMetadata>) (root, query, cb) -> cb
|
||||
.equal(root.get(JpaTargetMetadata_.target).get(JpaTarget_.id), targetId),
|
||||
pageable),
|
||||
pageable);
|
||||
}
|
||||
|
||||
private static Page<TargetMetadata> convertMdPage(final Page<JpaTargetMetadata> findAll, final Pageable pageable) {
|
||||
return new PageImpl<>(Collections.unmodifiableList(findAll.getContent()), pageable, findAll.getTotalElements());
|
||||
}
|
||||
|
||||
@Override
|
||||
public Page<TargetMetadata> findMetaDataByControllerIdAndRsql(final Pageable pageable, final String controllerId,
|
||||
final String rsqlParam) {
|
||||
|
||||
final Long targetId = getByControllerIdAndThrowIfNotFound(controllerId).getId();
|
||||
|
||||
final Specification<JpaTargetMetadata> spec = RSQLUtility.parse(rsqlParam, TargetMetadataFields.class,
|
||||
virtualPropertyReplacer, database);
|
||||
|
||||
return convertMdPage(targetMetadataRepository.findAll((Specification<JpaTargetMetadata>) (root, query, cb) -> cb
|
||||
.and(cb.equal(root.get(JpaTargetMetadata_.target).get(JpaTarget_.id), targetId),
|
||||
spec.toPredicate(root, query, cb)),
|
||||
pageable), pageable);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Optional<TargetMetadata> getMetaDataByControllerId(final String controllerId, final String key) {
|
||||
final Long targetId = getByControllerIdAndThrowIfNotFound(controllerId).getId();
|
||||
|
||||
return Optional.ofNullable(targetMetadataRepository.findOne(new TargetMetadataCompositeKey(targetId, key)));
|
||||
}
|
||||
|
||||
@Override
|
||||
public Slice<Target> findAll(final Pageable pageable) {
|
||||
return convertPage(criteriaNoCountDao.findAll(pageable, JpaTarget.class), pageable);
|
||||
@@ -178,8 +314,7 @@ public class JpaTargetManagement implements TargetManagement {
|
||||
public Target update(final TargetUpdate u) {
|
||||
final JpaTargetUpdate update = (JpaTargetUpdate) u;
|
||||
|
||||
final JpaTarget target = (JpaTarget) targetRepository.findByControllerId(update.getControllerId())
|
||||
.orElseThrow(() -> new EntityNotFoundException(Target.class, update.getControllerId()));
|
||||
final JpaTarget target = getByControllerIdAndThrowIfNotFound(update.getControllerId());
|
||||
|
||||
update.getName().ifPresent(target::setName);
|
||||
update.getDescription().ifPresent(target::setDescription);
|
||||
@@ -214,8 +349,7 @@ public class JpaTargetManagement implements TargetManagement {
|
||||
@Retryable(include = {
|
||||
ConcurrencyFailureException.class }, maxAttempts = Constants.TX_RT_MAX, backoff = @Backoff(delay = Constants.TX_RT_DELAY))
|
||||
public void deleteByControllerID(final String controllerID) {
|
||||
final Target target = targetRepository.findByControllerId(controllerID)
|
||||
.orElseThrow(() -> new EntityNotFoundException(Target.class, controllerID));
|
||||
final Target target = getByControllerIdAndThrowIfNotFound(controllerID);
|
||||
|
||||
targetRepository.delete(target.getId());
|
||||
}
|
||||
@@ -418,8 +552,7 @@ public class JpaTargetManagement implements TargetManagement {
|
||||
@Retryable(include = {
|
||||
ConcurrencyFailureException.class }, maxAttempts = Constants.TX_RT_MAX, backoff = @Backoff(delay = Constants.TX_RT_DELAY))
|
||||
public Target unAssignTag(final String controllerID, final long targetTagId) {
|
||||
final JpaTarget target = (JpaTarget) targetRepository.findByControllerId(controllerID)
|
||||
.orElseThrow(() -> new EntityNotFoundException(Target.class, controllerID));
|
||||
final JpaTarget target = getByControllerIdAndThrowIfNotFound(controllerID);
|
||||
|
||||
final TargetTag tag = targetTagRepository.findById(targetTagId)
|
||||
.orElseThrow(() -> new EntityNotFoundException(TargetTag.class, targetTagId));
|
||||
@@ -635,16 +768,14 @@ public class JpaTargetManagement implements TargetManagement {
|
||||
|
||||
@Override
|
||||
public Map<String, String> getControllerAttributes(final String controllerId) {
|
||||
final JpaTarget target = (JpaTarget) getByControllerID(controllerId)
|
||||
.orElseThrow(() -> new EntityNotFoundException(Target.class, controllerId));
|
||||
final JpaTarget target = getByControllerIdAndThrowIfNotFound(controllerId);
|
||||
|
||||
return target.getControllerAttributes();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void requestControllerAttributes(final String controllerId) {
|
||||
final JpaTarget target = (JpaTarget) getByControllerID(controllerId)
|
||||
.orElseThrow(() -> new EntityNotFoundException(Target.class, controllerId));
|
||||
final JpaTarget target = getByControllerIdAndThrowIfNotFound(controllerId);
|
||||
|
||||
target.setRequestControllerAttributes(true);
|
||||
|
||||
@@ -655,8 +786,7 @@ public class JpaTargetManagement implements TargetManagement {
|
||||
|
||||
@Override
|
||||
public boolean isControllerAttributesRequested(final String controllerId) {
|
||||
final JpaTarget target = (JpaTarget) getByControllerID(controllerId)
|
||||
.orElseThrow(() -> new EntityNotFoundException(Target.class, controllerId));
|
||||
final JpaTarget target = getByControllerIdAndThrowIfNotFound(controllerId);
|
||||
|
||||
return target.isRequestControllerAttributes();
|
||||
}
|
||||
|
||||
@@ -449,7 +449,8 @@ public class RepositoryApplicationConfiguration extends JpaBaseConfiguration {
|
||||
*/
|
||||
@Bean
|
||||
@ConditionalOnMissingBean
|
||||
TargetManagement targetManagement(final EntityManager entityManager, final TargetRepository targetRepository,
|
||||
TargetManagement targetManagement(final EntityManager entityManager, final QuotaManagement quotaManagement,
|
||||
final TargetRepository targetRepository, final TargetMetadataRepository targetMetadataRepository,
|
||||
final RolloutGroupRepository rolloutGroupRepository,
|
||||
final DistributionSetRepository distributionSetRepository,
|
||||
final TargetFilterQueryRepository targetFilterQueryRepository,
|
||||
@@ -457,10 +458,10 @@ public class RepositoryApplicationConfiguration extends JpaBaseConfiguration {
|
||||
final ApplicationEventPublisher eventPublisher, final ApplicationContext applicationContext,
|
||||
final TenantAware tenantAware, final AfterTransactionCommitExecutor afterCommit,
|
||||
final VirtualPropertyReplacer virtualPropertyReplacer, final JpaProperties properties) {
|
||||
return new JpaTargetManagement(entityManager, targetRepository, rolloutGroupRepository,
|
||||
distributionSetRepository, targetFilterQueryRepository, targetTagRepository, criteriaNoCountDao,
|
||||
eventPublisher, applicationContext, tenantAware, afterCommit, virtualPropertyReplacer,
|
||||
properties.getDatabase());
|
||||
return new JpaTargetManagement(entityManager, quotaManagement, targetRepository, targetMetadataRepository,
|
||||
rolloutGroupRepository, distributionSetRepository, targetFilterQueryRepository, targetTagRepository,
|
||||
criteriaNoCountDao, eventPublisher, applicationContext, tenantAware, afterCommit,
|
||||
virtualPropertyReplacer, properties.getDatabase());
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -0,0 +1,36 @@
|
||||
/**
|
||||
* Copyright (c) 2015 Bosch Software Innovations GmbH and others.
|
||||
*
|
||||
* All rights reserved. This program and the accompanying materials
|
||||
* are made available under the terms of the Eclipse Public License v1.0
|
||||
* which accompanies this distribution, and is available at
|
||||
* http://www.eclipse.org/legal/epl-v10.html
|
||||
*/
|
||||
package org.eclipse.hawkbit.repository.jpa;
|
||||
|
||||
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.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>,
|
||||
JpaSpecificationExecutor<JpaTargetMetadata> {
|
||||
|
||||
/**
|
||||
* Counts the meta data entries that match the given target ID.
|
||||
*
|
||||
* @param id
|
||||
* of the target.
|
||||
*
|
||||
* @return The number of matching meta data entries.
|
||||
*/
|
||||
long countByTargetId(@Param("id") Long id);
|
||||
}
|
||||
@@ -54,6 +54,7 @@ import org.eclipse.hawkbit.repository.model.Action;
|
||||
import org.eclipse.hawkbit.repository.model.DistributionSet;
|
||||
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.TargetUpdateStatus;
|
||||
import org.eclipse.hawkbit.repository.model.helper.EventPublisherHolder;
|
||||
@@ -167,6 +168,10 @@ public class JpaTarget extends AbstractJpaNamedEntity implements Target, EventAw
|
||||
@Column(name = "request_controller_attributes", nullable = false)
|
||||
private boolean requestControllerAttributes = true;
|
||||
|
||||
@CascadeOnDelete
|
||||
@OneToMany(mappedBy = "target", fetch = FetchType.LAZY, targetEntity = JpaTargetMetadata.class)
|
||||
private List<TargetMetadata> metadata;
|
||||
|
||||
/**
|
||||
* Constructor.
|
||||
*
|
||||
@@ -351,6 +356,14 @@ public class JpaTarget extends AbstractJpaNamedEntity implements Target, EventAw
|
||||
return requestControllerAttributes;
|
||||
}
|
||||
|
||||
public List<TargetMetadata> getMetadata() {
|
||||
if (metadata == null) {
|
||||
return Collections.emptyList();
|
||||
}
|
||||
|
||||
return Collections.unmodifiableList(metadata);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "JpaTarget [controllerId=" + controllerId + ", revision=" + getOptLockRevision() + ", id=" + getId()
|
||||
|
||||
@@ -0,0 +1,109 @@
|
||||
/**
|
||||
* Copyright (c) 2018 Bosch Software Innovations GmbH and others.
|
||||
*
|
||||
* All rights reserved. This program and the accompanying materials
|
||||
* are made available under the terms of the Eclipse Public License v1.0
|
||||
* which accompanies this distribution, and is available at
|
||||
* http://www.eclipse.org/legal/epl-v10.html
|
||||
*/
|
||||
package org.eclipse.hawkbit.repository.jpa.model;
|
||||
|
||||
import javax.persistence.ConstraintMode;
|
||||
import javax.persistence.Entity;
|
||||
import javax.persistence.FetchType;
|
||||
import javax.persistence.ForeignKey;
|
||||
import javax.persistence.Id;
|
||||
import javax.persistence.IdClass;
|
||||
import javax.persistence.JoinColumn;
|
||||
import javax.persistence.ManyToOne;
|
||||
import javax.persistence.Table;
|
||||
|
||||
import org.eclipse.hawkbit.repository.model.Target;
|
||||
import org.eclipse.hawkbit.repository.model.TargetMetadata;
|
||||
|
||||
/**
|
||||
* Meta data for {@link Target}.
|
||||
*
|
||||
*/
|
||||
@IdClass(TargetMetadataCompositeKey.class)
|
||||
@Entity
|
||||
@Table(name = "sp_target_metadata")
|
||||
public class JpaTargetMetadata extends AbstractJpaMetaData implements TargetMetadata {
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
@Id
|
||||
@ManyToOne(fetch = FetchType.LAZY, optional = false)
|
||||
@JoinColumn(name = "target_id", nullable = false, updatable = false, foreignKey = @ForeignKey(value = ConstraintMode.CONSTRAINT, name = "fk_metadata_target"))
|
||||
private JpaTarget target;
|
||||
|
||||
public JpaTargetMetadata() {
|
||||
// default public constructor for JPA
|
||||
}
|
||||
|
||||
/**
|
||||
* 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());
|
||||
}
|
||||
|
||||
public void setTarget(final Target target) {
|
||||
this.target = (JpaTarget) target;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Target getTarget() {
|
||||
return target;
|
||||
}
|
||||
|
||||
@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) {
|
||||
if (other.target != null) {
|
||||
return false;
|
||||
}
|
||||
} else if (!target.equals(other.target)) {
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,90 @@
|
||||
/**
|
||||
* Copyright (c) 2018 Bosch Software Innovations GmbH and others.
|
||||
*
|
||||
* All rights reserved. This program and the accompanying materials
|
||||
* are made available under the terms of the Eclipse Public License v1.0
|
||||
* which accompanies this distribution, and is available at
|
||||
* http://www.eclipse.org/legal/epl-v10.html
|
||||
*/
|
||||
package org.eclipse.hawkbit.repository.jpa.model;
|
||||
|
||||
import java.io.Serializable;
|
||||
import java.util.Objects;
|
||||
|
||||
/**
|
||||
* The Target Metadata composite key which contains the meta data key and the ID
|
||||
* of the Target itself.
|
||||
*
|
||||
*/
|
||||
public final class TargetMetadataCompositeKey implements Serializable {
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
private String key;
|
||||
|
||||
private Long target;
|
||||
|
||||
public TargetMetadataCompositeKey() {
|
||||
// Default constructor for JPA.
|
||||
}
|
||||
|
||||
/**
|
||||
* @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;
|
||||
}
|
||||
|
||||
public String getKey() {
|
||||
return key;
|
||||
}
|
||||
|
||||
public void setKey(final String key) {
|
||||
this.key = key;
|
||||
}
|
||||
|
||||
public Long getTargetId() {
|
||||
return target;
|
||||
}
|
||||
|
||||
public void setTargetId(final Long target) {
|
||||
this.target = target;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
return Objects.hash(target, key);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(final Object obj) {
|
||||
if (this == obj) {
|
||||
return true;
|
||||
}
|
||||
if (obj == null) {
|
||||
return false;
|
||||
}
|
||||
if (getClass() != obj.getClass()) {
|
||||
return false;
|
||||
}
|
||||
final TargetMetadataCompositeKey other = (TargetMetadataCompositeKey) obj;
|
||||
if (target == null) {
|
||||
if (other.target != null) {
|
||||
return false;
|
||||
}
|
||||
} else if (!target.equals(other.target)) {
|
||||
return false;
|
||||
}
|
||||
if (key == null) {
|
||||
if (other.key != null) {
|
||||
return false;
|
||||
}
|
||||
} else if (!key.equals(other.key)) {
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,9 @@
|
||||
CREATE TABLE sp_target_metadata
|
||||
(
|
||||
meta_key VARCHAR(128) NOT NULL,
|
||||
meta_value VARCHAR(4000),
|
||||
target_id BIGINT NOT NULL,
|
||||
PRIMARY KEY (meta_key, target_id)
|
||||
);
|
||||
|
||||
ALTER TABLE sp_target_metadata ADD CONSTRAINT fk_metadata_target FOREIGN KEY (target_id) REFERENCES sp_target (id) ON DELETE CASCADE;
|
||||
@@ -0,0 +1,12 @@
|
||||
create table sp_target_metadata (
|
||||
meta_key varchar(128) not null,
|
||||
meta_value varchar(4000),
|
||||
target_id bigint not null,
|
||||
primary key (target_id, meta_key)
|
||||
);
|
||||
|
||||
alter table sp_target_metadata
|
||||
add constraint fk_metadata_target
|
||||
foreign key (target_id)
|
||||
references sp_target
|
||||
on delete cascade;
|
||||
@@ -0,0 +1,12 @@
|
||||
create table sp_target_metadata (
|
||||
meta_key varchar(128) not null,
|
||||
meta_value varchar(4000),
|
||||
target_id bigint not null,
|
||||
primary key (target_id, meta_key)
|
||||
);
|
||||
|
||||
alter table sp_target_metadata
|
||||
add constraint fk_metadata_target
|
||||
foreign key (target_id)
|
||||
references sp_target (id)
|
||||
on delete cascade;
|
||||
@@ -0,0 +1,9 @@
|
||||
CREATE TABLE sp_target_metadata
|
||||
(
|
||||
meta_key VARCHAR(128) NOT NULL,
|
||||
meta_value VARCHAR(4000) NULL,
|
||||
target_id NUMERIC(19) NOT NULL,
|
||||
PRIMARY KEY (meta_key, target_id)
|
||||
);
|
||||
|
||||
ALTER TABLE sp_target_metadata ADD CONSTRAINT fk_metadata_target FOREIGN KEY (target_id) REFERENCES sp_target (id) ON DELETE CASCADE;
|
||||
@@ -136,7 +136,7 @@ public class DistributionSetManagementTest extends AbstractJpaIntegrationTest {
|
||||
"DistributionSetType");
|
||||
|
||||
verifyThrownExceptionBy(() -> distributionSetManagement.createMetaData(NOT_EXIST_IDL,
|
||||
Arrays.asList(entityFactory.generateMetadata("123", "123"))), "DistributionSet");
|
||||
Arrays.asList(entityFactory.generateDsMetadata("123", "123"))), "DistributionSet");
|
||||
|
||||
verifyThrownExceptionBy(() -> distributionSetManagement.delete(Arrays.asList(NOT_EXIST_IDL)),
|
||||
"DistributionSet");
|
||||
@@ -167,10 +167,10 @@ public class DistributionSetManagementTest extends AbstractJpaIntegrationTest {
|
||||
"DistributionSet");
|
||||
|
||||
verifyThrownExceptionBy(() -> distributionSetManagement.updateMetaData(NOT_EXIST_IDL,
|
||||
entityFactory.generateMetadata("xxx", "xxx")), "DistributionSet");
|
||||
entityFactory.generateDsMetadata("xxx", "xxx")), "DistributionSet");
|
||||
|
||||
verifyThrownExceptionBy(() -> distributionSetManagement.updateMetaData(set.getId(),
|
||||
entityFactory.generateMetadata(NOT_EXIST_ID, "xxx")), "DistributionSetMetadata");
|
||||
entityFactory.generateDsMetadata(NOT_EXIST_ID, "xxx")), "DistributionSetMetadata");
|
||||
}
|
||||
|
||||
@Test
|
||||
@@ -553,7 +553,7 @@ public class DistributionSetManagementTest extends AbstractJpaIntegrationTest {
|
||||
|
||||
// update the DS metadata
|
||||
final JpaDistributionSetMetadata updated = (JpaDistributionSetMetadata) distributionSetManagement
|
||||
.updateMetaData(ds.getId(), entityFactory.generateMetadata(knownKey, knownUpdateValue));
|
||||
.updateMetaData(ds.getId(), entityFactory.generateDsMetadata(knownKey, knownUpdateValue));
|
||||
// we are updating the sw meta data so also modifying the base software
|
||||
// module so opt lock
|
||||
// revision must be three
|
||||
|
||||
@@ -41,13 +41,17 @@ import org.eclipse.hawkbit.repository.event.remote.entity.TargetTagCreatedEvent;
|
||||
import org.eclipse.hawkbit.repository.event.remote.entity.TargetUpdatedEvent;
|
||||
import org.eclipse.hawkbit.repository.exception.EntityAlreadyExistsException;
|
||||
import org.eclipse.hawkbit.repository.exception.InvalidTargetAddressException;
|
||||
import org.eclipse.hawkbit.repository.exception.QuotaExceededException;
|
||||
import org.eclipse.hawkbit.repository.exception.TenantNotExistException;
|
||||
import org.eclipse.hawkbit.repository.jpa.model.JpaTarget;
|
||||
import org.eclipse.hawkbit.repository.jpa.model.JpaTargetMetadata;
|
||||
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.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.test.matcher.Expect;
|
||||
import org.eclipse.hawkbit.repository.test.matcher.ExpectEvents;
|
||||
@@ -73,10 +77,12 @@ public class TargetManagementTest extends AbstractJpaIntegrationTest {
|
||||
@Test
|
||||
@Description("Verifies that management get access react as specified on calls for non existing entities by means "
|
||||
+ "of Optional not present.")
|
||||
@ExpectEvents({ @Expect(type = TargetCreatedEvent.class, count = 0) })
|
||||
@ExpectEvents({ @Expect(type = TargetCreatedEvent.class, count = 1) })
|
||||
public void nonExistingEntityAccessReturnsNotPresent() {
|
||||
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();
|
||||
}
|
||||
|
||||
@Test
|
||||
@@ -89,8 +95,10 @@ public 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");
|
||||
@@ -123,16 +131,31 @@ public class TargetManagementTest extends AbstractJpaIntegrationTest {
|
||||
() -> targetManagement.findByInstalledDistributionSetAndRsql(PAGE, NOT_EXIST_IDL, "name==*"),
|
||||
"DistributionSet");
|
||||
|
||||
verifyThrownExceptionBy(() -> targetManagement
|
||||
.toggleTagAssignment(Collections.singletonList(target.getControllerId()), NOT_EXIST_ID), "TargetTag");
|
||||
verifyThrownExceptionBy(
|
||||
() -> targetManagement.toggleTagAssignment(Collections.singletonList(target.getControllerId()), NOT_EXIST_ID),
|
||||
"TargetTag");
|
||||
verifyThrownExceptionBy(() -> targetManagement.toggleTagAssignment(Collections.singletonList(NOT_EXIST_ID), tag.getName()),
|
||||
() -> targetManagement.toggleTagAssignment(Collections.singletonList(NOT_EXIST_ID), tag.getName()),
|
||||
"Target");
|
||||
|
||||
verifyThrownExceptionBy(() -> targetManagement.unAssignTag(NOT_EXIST_ID, tag.getId()), "Target");
|
||||
verifyThrownExceptionBy(() -> targetManagement.unAssignTag(target.getControllerId(), NOT_EXIST_IDL),
|
||||
"TargetTag");
|
||||
verifyThrownExceptionBy(() -> targetManagement.update(entityFactory.target().update(NOT_EXIST_ID)), "Target");
|
||||
|
||||
verifyThrownExceptionBy(() -> targetManagement.createMetaData(NOT_EXIST_ID,
|
||||
Arrays.asList(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, "name==*"),
|
||||
"Target");
|
||||
verifyThrownExceptionBy(
|
||||
() -> targetManagement.updateMetaData(NOT_EXIST_ID, entityFactory.generateTargetMetadata("xxx", "xxx")),
|
||||
"Target");
|
||||
verifyThrownExceptionBy(() -> targetManagement.updateMetaData(target.getControllerId(),
|
||||
entityFactory.generateTargetMetadata(NOT_EXIST_ID, "xxx")), "TargetMetadata");
|
||||
}
|
||||
|
||||
@Test
|
||||
@@ -143,8 +166,9 @@ public class TargetManagementTest extends AbstractJpaIntegrationTest {
|
||||
.create(entityFactory.target().create().controllerId("targetWithSecurityToken").securityToken("token"));
|
||||
|
||||
// retrieve security token only with READ_TARGET_SEC_TOKEN permission
|
||||
final String securityTokenWithReadPermission = securityRule.runAs(WithSpringAuthorityRule
|
||||
.withUser("OnlyTargetReadPermission", false, SpPermission.READ_TARGET_SEC_TOKEN), createdTarget::getSecurityToken);
|
||||
final String securityTokenWithReadPermission = securityRule.runAs(
|
||||
WithSpringAuthorityRule.withUser("OnlyTargetReadPermission", false, SpPermission.READ_TARGET_SEC_TOKEN),
|
||||
createdTarget::getSecurityToken);
|
||||
|
||||
// retrieve security token as system code execution
|
||||
final String securityTokenAsSystemCode = systemSecurityContext.runAsSystem(createdTarget::getSecurityToken);
|
||||
@@ -889,4 +913,138 @@ public class TargetManagementTest extends AbstractJpaIntegrationTest {
|
||||
assertThat(targetManagement.isControllerAttributesRequested(knownTargetId)).isTrue();
|
||||
|
||||
}
|
||||
|
||||
@Test
|
||||
@Description("Checks that metadata for a target can be created.")
|
||||
public void createTargetMetadata() {
|
||||
final String knownKey = "targetMetaKnownKey";
|
||||
final String knownValue = "targetMetaKnownValue";
|
||||
|
||||
final Target target = testdataFactory.createTarget("targetIdWithMetadata");
|
||||
final JpaTargetMetadata createdMetadata = insertTargetMetadata(knownKey, target, knownValue);
|
||||
|
||||
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);
|
||||
}
|
||||
|
||||
private JpaTargetMetadata insertTargetMetadata(final String knownKey, final Target target,
|
||||
final String knownValue) {
|
||||
final JpaTargetMetadata metadata = new JpaTargetMetadata(knownKey, knownValue, target);
|
||||
return (JpaTargetMetadata) targetManagement
|
||||
.createMetaData(target.getControllerId(), Collections.singletonList(metadata)).get(0);
|
||||
}
|
||||
|
||||
@Test
|
||||
@Description("Verifies the enforcement of the metadata quota per target.")
|
||||
public void createTargetMetadataUntilQuotaIsExceeded() {
|
||||
|
||||
// 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, target1, "v" + i)).isNotNull();
|
||||
}
|
||||
|
||||
// quota exceeded
|
||||
assertThatExceptionOfType(QuotaExceededException.class)
|
||||
.isThrownBy(() -> insertTargetMetadata("k" + maxMetaData, target1, "v" + maxMetaData));
|
||||
|
||||
// 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));
|
||||
}
|
||||
// verify quota is exceeded
|
||||
assertThatExceptionOfType(QuotaExceededException.class)
|
||||
.isThrownBy(() -> targetManagement.createMetaData(target2.getControllerId(), metaData2));
|
||||
|
||||
// add some meta data entries
|
||||
final Target target3 = testdataFactory.createTarget("target3");
|
||||
final int firstHalf = Math.round(maxMetaData / 2);
|
||||
for (int i = 0; i < firstHalf; ++i) {
|
||||
insertTargetMetadata("k" + i, target3, "v" + i);
|
||||
}
|
||||
// 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));
|
||||
}
|
||||
// verify quota is exceeded
|
||||
assertThatExceptionOfType(QuotaExceededException.class)
|
||||
.isThrownBy(() -> targetManagement.createMetaData(target3.getControllerId(), metaData3));
|
||||
|
||||
}
|
||||
|
||||
@Test
|
||||
@WithUser(allSpPermissions = true)
|
||||
@Description("Checks that metadata for a target can be updated.")
|
||||
public void updateTargetMetadata() throws InterruptedException {
|
||||
final String knownKey = "myKnownKey";
|
||||
final String knownValue = "myKnownValue";
|
||||
final String knownUpdateValue = "myNewUpdatedValue";
|
||||
|
||||
// create a target
|
||||
final Target target = testdataFactory.createTarget("target1");
|
||||
// initial opt lock revision must be zero
|
||||
assertThat(target.getOptLockRevision()).isEqualTo(1);
|
||||
|
||||
// create target meta data entry
|
||||
insertTargetMetadata(knownKey, target, knownValue);
|
||||
|
||||
Target changedLockRevisionTarget = targetManagement.get(target.getId()).get();
|
||||
assertThat(changedLockRevisionTarget.getOptLockRevision()).isEqualTo(2);
|
||||
|
||||
Thread.sleep(100);
|
||||
|
||||
// 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()).get();
|
||||
assertThat(changedLockRevisionTarget.getOptLockRevision()).isEqualTo(3);
|
||||
assertThat(changedLockRevisionTarget.getLastModifiedAt()).isGreaterThan(0L);
|
||||
|
||||
// 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());
|
||||
}
|
||||
|
||||
@Test
|
||||
@Description("Queries and loads the metadata related to a given target.")
|
||||
public void findAllTargetMetadataByControllerId() {
|
||||
// create targets
|
||||
final Target target1 = testdataFactory.createTarget("target1");
|
||||
final Target target2 = testdataFactory.createTarget("target2");
|
||||
|
||||
for (int index = 0; index < 10; index++) {
|
||||
insertTargetMetadata("key" + index, target1, "value" + index);
|
||||
}
|
||||
|
||||
for (int index = 0; index < 8; index++) {
|
||||
insertTargetMetadata("key" + index, target2, "value" + index);
|
||||
}
|
||||
|
||||
final Page<TargetMetadata> metadataOfTarget1 = targetManagement
|
||||
.findMetaDataByControllerId(new PageRequest(0, 100), target1.getControllerId());
|
||||
|
||||
final Page<TargetMetadata> metadataOfTarget2 = targetManagement
|
||||
.findMetaDataByControllerId(new PageRequest(0, 100), target2.getControllerId());
|
||||
|
||||
assertThat(metadataOfTarget1.getNumberOfElements()).isEqualTo(10);
|
||||
assertThat(metadataOfTarget1.getTotalElements()).isEqualTo(10);
|
||||
|
||||
assertThat(metadataOfTarget2.getNumberOfElements()).isEqualTo(8);
|
||||
assertThat(metadataOfTarget2.getTotalElements()).isEqualTo(8);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -37,12 +37,12 @@ public class RSQLDistributionSetFieldTest extends AbstractJpaIntegrationTest {
|
||||
|
||||
DistributionSet ds = testdataFactory.createDistributionSet("DS");
|
||||
ds = distributionSetManagement.update(entityFactory.distributionSet().update(ds.getId()).description("DS"));
|
||||
createDistributionSetMetadata(ds.getId(), entityFactory.generateMetadata("metaKey", "metaValue"));
|
||||
createDistributionSetMetadata(ds.getId(), entityFactory.generateDsMetadata("metaKey", "metaValue"));
|
||||
|
||||
DistributionSet ds2 = testdataFactory.createDistributionSets("NewDS", 3).get(0);
|
||||
|
||||
ds2 = distributionSetManagement.update(entityFactory.distributionSet().update(ds2.getId()).description("DS%"));
|
||||
createDistributionSetMetadata(ds2.getId(), entityFactory.generateMetadata("metaKey", "value"));
|
||||
createDistributionSetMetadata(ds2.getId(), entityFactory.generateDsMetadata("metaKey", "value"));
|
||||
|
||||
final DistributionSetTag targetTag = distributionSetTagManagement
|
||||
.create(entityFactory.tag().create().name("Tag1"));
|
||||
|
||||
@@ -41,13 +41,13 @@ public class RSQLDistributionSetMetadataFieldsTest extends AbstractJpaIntegratio
|
||||
|
||||
final List<MetaData> metadata = new ArrayList<>(5);
|
||||
for (int i = 0; i < 5; i++) {
|
||||
metadata.add(entityFactory.generateMetadata("" + i, "" + i));
|
||||
metadata.add(entityFactory.generateDsMetadata("" + i, "" + i));
|
||||
}
|
||||
|
||||
distributionSetManagement.createMetaData(distributionSetId, metadata);
|
||||
|
||||
distributionSetManagement.createMetaData(distributionSetId,
|
||||
Arrays.asList(entityFactory.generateMetadata("emptyValueTest", null)));
|
||||
Arrays.asList(entityFactory.generateDsMetadata("emptyValueTest", null)));
|
||||
}
|
||||
|
||||
@Test
|
||||
|
||||
@@ -0,0 +1,80 @@
|
||||
/**
|
||||
* Copyright (c) 2015 Bosch Software Innovations GmbH and others.
|
||||
*
|
||||
* All rights reserved. This program and the accompanying materials
|
||||
* are made available under the terms of the Eclipse Public License v1.0
|
||||
* which accompanies this distribution, and is available at
|
||||
* http://www.eclipse.org/legal/epl-v10.html
|
||||
*/
|
||||
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 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.Before;
|
||||
import org.junit.Test;
|
||||
import org.springframework.data.domain.Page;
|
||||
import org.springframework.data.domain.PageRequest;
|
||||
|
||||
import io.qameta.allure.Description;
|
||||
import io.qameta.allure.Feature;
|
||||
import io.qameta.allure.Story;
|
||||
|
||||
@Feature("Component Tests - Repository")
|
||||
@Story("RSQL filter target metadata")
|
||||
public class RSQLTargetMetadataFieldsTest extends AbstractJpaIntegrationTest {
|
||||
private String controllerId;
|
||||
|
||||
@Before
|
||||
public 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")
|
||||
public 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")
|
||||
public void testFilterByParameterValue() {
|
||||
assertRSQLQuery(TargetMetadataFields.VALUE.name() + "==''", 1);
|
||||
assertRSQLQuery(TargetMetadataFields.VALUE.name() + "!=''", 5);
|
||||
assertRSQLQuery(TargetMetadataFields.VALUE.name() + "==1", 1);
|
||||
assertRSQLQuery(TargetMetadataFields.VALUE.name() + "!=1", 4);
|
||||
assertRSQLQuery(TargetMetadataFields.VALUE.name() + "=in=(1,2)", 2);
|
||||
assertRSQLQuery(TargetMetadataFields.VALUE.name() + "=out=(1,2)", 3);
|
||||
}
|
||||
|
||||
private void assertRSQLQuery(final String rsqlParam, final long expectedEntities) {
|
||||
|
||||
final Page<TargetMetadata> findEnitity = targetManagement
|
||||
.findMetaDataByControllerIdAndRsql(new PageRequest(0, 100), controllerId, rsqlParam);
|
||||
final long countAllEntities = findEnitity.getTotalElements();
|
||||
assertThat(findEnitity).isNotNull();
|
||||
assertThat(countAllEntities).isEqualTo(expectedEntities);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user