SystemManagement getTenantMetadata - fetch details, added method getTenantMetadataWithoutDetails (#2194)

Signed-off-by: Avgustin Marinov <Avgustin.Marinov@bosch.com>
This commit is contained in:
Avgustin Marinov
2025-01-10 15:37:57 +02:00
committed by GitHub
parent 0e4efe0987
commit b294798ae5
19 changed files with 117 additions and 131 deletions

View File

@@ -623,7 +623,7 @@ public class JpaDistributionSetManagement implements DistributionSetManagement {
return distributionSetRepository.countAutoAssignmentsForDistributionSet(id);
}
// check if shall implicitly lock a distribution set
// check if it shall implicitly lock a distribution set
boolean isImplicitLockApplicable(final DistributionSet distributionSet) {
final JpaDistributionSet jpaDistributionSet = (JpaDistributionSet) distributionSet;
if (jpaDistributionSet.isLocked()) {
@@ -631,7 +631,7 @@ public class JpaDistributionSetManagement implements DistributionSetManagement {
return false;
}
if (!tenantConfigHelper.getConfigValue(IMPLICIT_LOCK_ENABLED, Boolean.class)) {
if (Boolean.FALSE.equals(tenantConfigHelper.getConfigValue(IMPLICIT_LOCK_ENABLED, Boolean.class))) {
// implicit lock disabled
return false;
}

View File

@@ -173,8 +173,7 @@ public class JpaSystemManagement implements CurrentTenantCacheKeyGenerator, Syst
@Override
@Transactional(propagation = Propagation.NOT_SUPPORTED)
// Exception squid:S2229 - calling findTenants without transaction is
// intended in this case
// Exception squid:S2229 - calling findTenants without transaction is intended in this case
@SuppressWarnings("squid:S2229")
public void forEachTenant(final Consumer<String> consumer) {
Page<String> tenants;
@@ -207,7 +206,6 @@ public class JpaSystemManagement implements CurrentTenantCacheKeyGenerator, Syst
@Override
public SystemUsageReport getSystemUsageStatistics() {
final Number count = (Number) entityManager.createNativeQuery(countSoftwareModulesQuery).getSingleResult();
long sumOfArtifacts = 0;
@@ -232,22 +230,12 @@ public class JpaSystemManagement implements CurrentTenantCacheKeyGenerator, Syst
@Override
public TenantMetaData getTenantMetadata() {
final String tenant = tenantAware.getCurrentTenant();
if (tenant == null) {
throw new IllegalStateException("Tenant not set");
}
return getTenantMetadata0(true);
}
final TenantMetaData metaData = tenantMetaDataRepository.findByTenantIgnoreCase(tenant);
if (metaData == null) {
if (repositoryProperties.isImplicitTenantCreateAllowed()) {
log.info("Tenant {} doesn't exist create metadata", tenant, new Exception("Thread dump"));
return createTenantMetadata0(tenant);
} else {
throw new EntityNotFoundException(TenantMetaData.class, tenant);
}
} else {
return metaData;
}
@Override
public TenantMetaData getTenantMetadataWithoutDetails() {
return getTenantMetadata0(false);
}
@Override
@@ -265,11 +253,9 @@ public class JpaSystemManagement implements CurrentTenantCacheKeyGenerator, Syst
@Retryable(retryFor = { ConcurrencyFailureException.class }, maxAttempts = Constants.TX_RT_MAX,
backoff = @Backoff(delay = Constants.TX_RT_DELAY))
public TenantMetaData updateTenantMetadata(final long defaultDsType) {
final JpaTenantMetaData data = (JpaTenantMetaData) getTenantMetadata();
final JpaTenantMetaData data = (JpaTenantMetaData) getTenantMetadataWithoutDetails();
data.setDefaultDsType(distributionSetTypeRepository.findById(defaultDsType)
.orElseThrow(() -> new EntityNotFoundException(DistributionSetType.class, defaultDsType)));
return tenantMetaDataRepository.save(data);
}
@@ -283,6 +269,28 @@ public class JpaSystemManagement implements CurrentTenantCacheKeyGenerator, Syst
return Database.POSTGRESQL == properties.getDatabase();
}
private TenantMetaData getTenantMetadata0(final boolean withDetails) {
final String tenant = tenantAware.getCurrentTenant();
if (tenant == null) {
throw new IllegalStateException("Tenant not set");
}
final TenantMetaData metaData =
withDetails ?
tenantMetaDataRepository.findWitDetailsByTenantIgnoreCase(tenant) :
tenantMetaDataRepository.findByTenantIgnoreCase(tenant);
if (metaData == null) {
if (repositoryProperties.isImplicitTenantCreateAllowed()) {
log.info("Tenant {} doesn't exist create metadata", tenant, new Exception("Thread dump"));
return createTenantMetadata0(tenant);
} else {
throw new EntityNotFoundException(TenantMetaData.class, tenant);
}
} else {
return metaData;
}
}
private void usageStatsPerTenant(final SystemUsageReportWithTenants report) {
forEachTenant(tenant -> report.addTenantData(systemStatsManagement.getStatsOfTenant()));
}

View File

@@ -35,7 +35,6 @@ import org.eclipse.hawkbit.repository.model.SoftwareModuleType;
* {@link SoftwareModuleType} elements.
*/
@NoArgsConstructor // Default constructor for JPA
@Getter
@Entity
@Table(name = "sp_ds_type_element")
public class DistributionSetTypeElement implements Serializable {
@@ -47,16 +46,22 @@ public class DistributionSetTypeElement implements Serializable {
private DistributionSetTypeElementCompositeKey key;
@MapsId("dsType")
@ManyToOne(optional = false, fetch = FetchType.LAZY)
@JoinColumn(name = "distribution_set_type", nullable = false, updatable = false, foreignKey = @ForeignKey(value = ConstraintMode.CONSTRAINT, name = "fk_ds_type_element_element"))
@ManyToOne(optional = false)
@JoinColumn(
name = "distribution_set_type", nullable = false, updatable = false,
foreignKey = @ForeignKey(value = ConstraintMode.CONSTRAINT, name = "fk_ds_type_element_element"))
private JpaDistributionSetType dsType;
@Getter
@MapsId("smType")
@ManyToOne(optional = false, fetch = FetchType.LAZY)
@JoinColumn(name = "software_module_type", nullable = false, updatable = false, foreignKey = @ForeignKey(value = ConstraintMode.CONSTRAINT, name = "fk_ds_type_element_smtype"))
@ManyToOne(optional = false)
@JoinColumn(
name = "software_module_type", nullable = false, updatable = false,
foreignKey = @ForeignKey(value = ConstraintMode.CONSTRAINT, name = "fk_ds_type_element_smtype"))
private JpaSoftwareModuleType smType;
@Setter
@Getter
@Column(name = "mandatory")
private boolean mandatory;

View File

@@ -60,6 +60,7 @@ public class JpaDistributionSetType extends AbstractJpaTypeEntity implements Dis
@OneToMany(
mappedBy = "dsType", targetEntity = DistributionSetTypeElement.class,
fetch = FetchType.EAGER,
cascade = { CascadeType.PERSIST, CascadeType.MERGE, CascadeType.REMOVE }, orphanRemoval = true)
private Set<DistributionSetTypeElement> elements = new HashSet<>();
@@ -118,10 +119,6 @@ public class JpaDistributionSetType extends AbstractJpaTypeEntity implements Dis
return this;
}
public Set<DistributionSetTypeElement> getElements() {
return Collections.unmodifiableSet(elements);
}
@Override
public String toString() {
return "DistributionSetType [key=" + getKey() + ", isDeleted()=" + isDeleted() + ", getId()=" + getId() + "]";

View File

@@ -19,6 +19,8 @@ import jakarta.persistence.FetchType;
import jakarta.persistence.ForeignKey;
import jakarta.persistence.Index;
import jakarta.persistence.JoinColumn;
import jakarta.persistence.NamedAttributeNode;
import jakarta.persistence.NamedEntityGraph;
import jakarta.persistence.OneToOne;
import jakarta.persistence.Table;
import jakarta.persistence.UniqueConstraint;
@@ -45,6 +47,7 @@ import org.eclipse.hawkbit.repository.model.TenantMetaData;
@Table(name = "sp_tenant", indexes = {
@Index(name = "sp_idx_tenant_prim", columnList = "tenant,id") }, uniqueConstraints = {
@UniqueConstraint(columnNames = { "tenant" }, name = "uk_tenantmd_tenant") })
@NamedEntityGraph(name = "TenantMetaData.withDetails", attributeNodes = { @NamedAttributeNode("defaultDsType") })
@Entity
// exception squid:S2160 - BaseEntity equals/hashcode is handling correctly for sub entities
@SuppressWarnings("squid:S2160")

View File

@@ -13,6 +13,7 @@ import org.eclipse.hawkbit.repository.jpa.model.JpaTenantMetaData;
import org.eclipse.hawkbit.repository.model.TenantMetaData;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.Pageable;
import org.springframework.data.jpa.repository.EntityGraph;
import org.springframework.data.jpa.repository.Modifying;
import org.springframework.data.jpa.repository.Query;
import org.springframework.data.repository.CrudRepository;
@@ -26,21 +27,16 @@ import org.springframework.transaction.annotation.Transactional;
@Transactional(readOnly = true)
public interface TenantMetaDataRepository extends PagingAndSortingRepository<JpaTenantMetaData, Long>, CrudRepository<JpaTenantMetaData, Long> {
/**
* Search {@link TenantMetaData} by tenant name.
*
* @param tenant to search for
* @return found {@link TenantMetaData} or <code>null</code>
*/
// find without details
TenantMetaData findByTenantIgnoreCase(String tenant);
@Transactional
// find with details
@EntityGraph(value = "TenantMetaData.withDetails", type = EntityGraph.EntityGraphType.LOAD)
TenantMetaData findWitDetailsByTenantIgnoreCase(String tenant);
@Query("SELECT t.tenant FROM JpaTenantMetaData t")
Page<String> findTenants(final Pageable pageable);
/**
* @param tenant
*/
@Transactional
@Modifying
@Query("DELETE FROM JpaTenantMetaData t WHERE UPPER(t.tenant) = UPPER(:tenant)")

View File

@@ -212,7 +212,8 @@ class DistributionSetManagementTest extends AbstractJpaIntegrationTest {
final DistributionSet set = distributionSetManagement
.create(entityFactory.distributionSet().create().name("newtypesoft").version("1"));
assertThat(set.getType()).as("Type should be equal to default type of tenant")
assertThat(set.getType())
.as("Type should be equal to default type of tenant")
.isEqualTo(systemManagement.getTenantMetadata().getDefaultDsType());
}
@@ -705,13 +706,12 @@ class DistributionSetManagementTest extends AbstractJpaIntegrationTest {
@Test
@Description("Test implicit locks for a DS and skip tags.")
void isImplicitLockApplicableForDistributionSet() {
final JpaDistributionSetManagement distributionSetManagement =
(JpaDistributionSetManagement) this.distributionSetManagement;
final JpaDistributionSetManagement distributionSetManagement = (JpaDistributionSetManagement) this.distributionSetManagement;
final DistributionSet distributionSet = testdataFactory.createDistributionSet("ds-non-skip");
// assert that implicit lock is applicable for non skip tags
assertThat(distributionSetManagement.isImplicitLockApplicable(distributionSet)).isTrue();
assertThat(repositoryProperties.getSkipImplicitLockForTags().size()).isNotEqualTo(0);
assertThat(repositoryProperties.getSkipImplicitLockForTags().size()).isNotZero();
final List<DistributionSetTag> skipTags = distributionSetTagManagement.create(
repositoryProperties.getSkipImplicitLockForTags().stream()
.map(String::toLowerCase)
@@ -733,13 +733,12 @@ class DistributionSetManagementTest extends AbstractJpaIntegrationTest {
@Test
@Description("Locks an incomplete DS. Expected behaviour is to throw an exception and to do not lock it.")
void lockIncompleteDistributionSetFails() {
final DistributionSet incompleteDistributionSet = testdataFactory.createIncompleteDistributionSet();
final long incompleteDistributionSetId = testdataFactory.createIncompleteDistributionSet().getId();
assertThatExceptionOfType(IncompleteDistributionSetException.class)
.as("Locking an incomplete distribution set should throw an exception")
.isThrownBy(() -> distributionSetManagement.lock(incompleteDistributionSet.getId()));
.isThrownBy(() -> distributionSetManagement.lock(incompleteDistributionSetId));
assertThat(
distributionSetManagement.get(incompleteDistributionSet.getId()).map(DistributionSet::isLocked)
.orElse(true))
distributionSetManagement.get(incompleteDistributionSetId).map(DistributionSet::isLocked).orElse(true))
.isFalse();
}

View File

@@ -69,6 +69,15 @@ public class SystemManagementSecurityTest extends AbstractJpaIntegrationTest {
assertPermissions(() -> systemManagement.getTenantMetadata(), List.of(SpPermission.SpringEvalExpressions.CONTROLLER_ROLE), List.of(SpPermission.CREATE_REPOSITORY));
}
@Test
@Description("Tests ManagementAPI PreAuthorized method with correct and insufficient permissions.")
void getTenantMetadataWithoutDetailsPermissionsCheck() {
assertPermissions(() -> systemManagement.getTenantMetadataWithoutDetails(), List.of(SpPermission.READ_REPOSITORY), List.of(SpPermission.CREATE_REPOSITORY));
assertPermissions(() -> systemManagement.getTenantMetadataWithoutDetails(), List.of(SpPermission.READ_TARGET), List.of(SpPermission.CREATE_REPOSITORY));
assertPermissions(() -> systemManagement.getTenantMetadataWithoutDetails(), List.of(SpPermission.READ_TENANT_CONFIGURATION), List.of(SpPermission.CREATE_REPOSITORY));
assertPermissions(() -> systemManagement.getTenantMetadataWithoutDetails(), List.of(SpPermission.SpringEvalExpressions.CONTROLLER_ROLE), List.of(SpPermission.CREATE_REPOSITORY));
}
@Test
@Description("Tests ManagementAPI PreAuthorized method with correct and insufficient permissions.")
void getTenantMetadataByTenantPermissionsCheck() {

View File

@@ -130,7 +130,7 @@ class SystemManagementTest extends AbstractJpaIntegrationTest {
final String tenantname = "TENANT" + i;
SecurityContextSwitch.runAs(SecurityContextSwitch.withUserAndTenant("bumlux", tenantname, true, true, false,
SpringEvalExpressions.SYSTEM_ROLE), () -> {
systemManagement.getTenantMetadata();
systemManagement.getTenantMetadataWithoutDetails();
if (artifactSize > 0) {
createTestArtifact(random);
createDeletedTestArtifact(random);

View File

@@ -112,11 +112,10 @@ public class MultiTenancyEntityTest extends AbstractJpaIntegrationTest {
assertThat(distributionSetTypeManagement.findAll(PAGE)).isNotEmpty();
// check that the cache is not getting in the way, i.e. "bumlux" results
// in bumlux and not
// mytenant
assertThat(SecurityContextSwitch.runAs(SecurityContextSwitch.withUserAndTenantAllSpPermissions("user", "bumlux"),
() -> systemManagement.getTenantMetadata().getTenant().toUpperCase()))
// check that the cache is not getting in the way, i.e. "bumlux" results in bumlux and not mytenant
assertThat(SecurityContextSwitch.runAs(
SecurityContextSwitch.withUserAndTenantAllSpPermissions("user", "bumlux"),
() -> systemManagement.getTenantMetadataWithoutDetails().getTenant().toUpperCase()))
.isEqualTo("bumlux".toUpperCase());
}