From 537a942021df5d263b7f9fdf470216fcdf812a63 Mon Sep 17 00:00:00 2001 From: Avgustin Marinov Date: Tue, 30 Jan 2024 15:46:39 +0200 Subject: [PATCH] Made implicit tenant meta data creation configurable (#1575) In hawkBit up to 0.4.1 it was true - getTenantMetadate created implicitly a tenant metadata. It was disable in latest commits - but now it is made optional - disabled by default Signed-off-by: Marinov Avgustin --- .../amqp/AmqpMessageDispatcherService.java | 6 +- .../repository/RepositoryProperties.java | 11 ++- .../jpa/management/JpaSystemManagement.java | 89 +++++++++++-------- 3 files changed, 67 insertions(+), 39 deletions(-) diff --git a/hawkbit-dmf/hawkbit-dmf-amqp/src/main/java/org/eclipse/hawkbit/amqp/AmqpMessageDispatcherService.java b/hawkbit-dmf/hawkbit-dmf-amqp/src/main/java/org/eclipse/hawkbit/amqp/AmqpMessageDispatcherService.java index 24e6c0ce1..5224c45a7 100644 --- a/hawkbit-dmf/hawkbit-dmf-amqp/src/main/java/org/eclipse/hawkbit/amqp/AmqpMessageDispatcherService.java +++ b/hawkbit-dmf/hawkbit-dmf-amqp/src/main/java/org/eclipse/hawkbit/amqp/AmqpMessageDispatcherService.java @@ -62,6 +62,7 @@ import org.eclipse.hawkbit.repository.model.DistributionSet; import org.eclipse.hawkbit.repository.model.SoftwareModule; import org.eclipse.hawkbit.repository.model.SoftwareModuleMetadata; import org.eclipse.hawkbit.repository.model.Target; +import org.eclipse.hawkbit.repository.model.TenantMetaData; import org.eclipse.hawkbit.security.SystemSecurityContext; import org.eclipse.hawkbit.util.IpUtil; import org.slf4j.Logger; @@ -557,9 +558,10 @@ public class AmqpMessageDispatcherService extends BaseAmqpService { private DmfArtifact convertArtifact(final Target target, final Artifact localArtifact) { final DmfArtifact artifact = new DmfArtifact(); + final TenantMetaData metaData = systemManagement.getTenantMetadata(); artifact.setUrls(artifactUrlHandler - .getUrls(new URLPlaceholder(systemManagement.getTenantMetadata().getTenant(), - systemManagement.getTenantMetadata().getId(), target.getControllerId(), target.getId(), + .getUrls(new URLPlaceholder(metaData.getTenant(), + metaData.getId(), target.getControllerId(), target.getId(), new SoftwareData(localArtifact.getSoftwareModule().getId(), localArtifact.getFilename(), localArtifact.getId(), localArtifact.getSha1Hash())), ApiType.DMF) diff --git a/hawkbit-repository/hawkbit-repository-api/src/main/java/org/eclipse/hawkbit/repository/RepositoryProperties.java b/hawkbit-repository/hawkbit-repository-api/src/main/java/org/eclipse/hawkbit/repository/RepositoryProperties.java index c67549fae..587e6d7ba 100644 --- a/hawkbit-repository/hawkbit-repository-api/src/main/java/org/eclipse/hawkbit/repository/RepositoryProperties.java +++ b/hawkbit-repository/hawkbit-repository-api/src/main/java/org/eclipse/hawkbit/repository/RepositoryProperties.java @@ -56,7 +56,7 @@ public class RepositoryProperties { private boolean eagerPollPersistence; /** - * If an {@link Action} has a weight of null this value is used as weight. + * If an {@link org.eclipse.hawkbit.repository.model.Action} has a weight of null this value is used as weight. */ private int actionWeightIfAbsent = 1000; @@ -66,6 +66,8 @@ public class RepositoryProperties { */ private long dsInvalidationLockTimeout = 5; + private boolean implicitTenantCreateAllowed; + public boolean isEagerPollPersistence() { return eagerPollPersistence; } @@ -122,4 +124,11 @@ public class RepositoryProperties { this.dsInvalidationLockTimeout = dsInvalidationLockTimeout; } + public boolean isImplicitTenantCreateAllowed() { + return implicitTenantCreateAllowed; + } + + public void setImplicitTenantCreateAllowed(final boolean implicitTenantCreateAllowed) { + this.implicitTenantCreateAllowed = implicitTenantCreateAllowed; + } } diff --git a/hawkbit-repository/hawkbit-repository-jpa/src/main/java/org/eclipse/hawkbit/repository/jpa/management/JpaSystemManagement.java b/hawkbit-repository/hawkbit-repository-jpa/src/main/java/org/eclipse/hawkbit/repository/jpa/management/JpaSystemManagement.java index 327d2e0f5..d960f2f3e 100644 --- a/hawkbit-repository/hawkbit-repository-jpa/src/main/java/org/eclipse/hawkbit/repository/jpa/management/JpaSystemManagement.java +++ b/hawkbit-repository/hawkbit-repository-jpa/src/main/java/org/eclipse/hawkbit/repository/jpa/management/JpaSystemManagement.java @@ -9,15 +9,13 @@ */ package org.eclipse.hawkbit.repository.jpa.management; -import java.util.Collections; -import java.util.Objects; import java.util.function.Consumer; -import java.util.stream.Collectors; import jakarta.persistence.EntityManager; import org.eclipse.hawkbit.artifact.repository.ArtifactRepository; import org.eclipse.hawkbit.cache.TenancyCacheManager; +import org.eclipse.hawkbit.repository.RepositoryProperties; import org.eclipse.hawkbit.repository.RolloutStatusCache; import org.eclipse.hawkbit.repository.SystemManagement; import org.eclipse.hawkbit.repository.TenantStatsManagement; @@ -58,7 +56,6 @@ import org.springframework.cache.annotation.Cacheable; import org.springframework.cache.interceptor.KeyGenerator; import org.springframework.dao.ConcurrencyFailureException; import org.springframework.data.domain.Page; -import org.springframework.data.domain.PageImpl; import org.springframework.data.domain.PageRequest; import org.springframework.data.domain.Pageable; import org.springframework.orm.jpa.vendor.Database; @@ -144,6 +141,9 @@ public class JpaSystemManagement implements CurrentTenantCacheKeyGenerator, Syst @Autowired private ArtifactRepository artifactRepository; + @Autowired + private RepositoryProperties repositoryProperties; + private final String countArtifactQuery; private final String countSoftwareModulesQuery; @@ -215,38 +215,11 @@ public class JpaSystemManagement implements CurrentTenantCacheKeyGenerator, Syst final TenantMetaData result = tenantMetaDataRepository.findByTenantIgnoreCase(tenant); // Create if it does not exist if (result == null) { - try { - currentTenantCacheKeyGenerator.setTenantInCreation(tenant); - return createInitialTenantMetaData(tenant); - } finally { - currentTenantCacheKeyGenerator.removeTenantInCreation(); - } + return createTenantMetadata0(tenant); } return result; } - /** - * Creating the initial tenant meta-data in a new transaction. Due the - * {@link MultiTenantJpaTransactionManager} is using the current tenant to - * set the necessary tenant discriminator to the query. This is not working - * if we don't have a current tenant set. Due the - * {@link #createTenantMetadata(String)} is maybe called without having a - * current tenant we need to re-open a new transaction so the - * {@link MultiTenantJpaTransactionManager} is called again and set the - * tenant for this transaction. - * - * @param tenant - * the tenant to be created - * @return the initial created {@link TenantMetaData} - */ - private TenantMetaData createInitialTenantMetaData(final String tenant) { - return systemSecurityContext.runAsSystemAsTenant( - () -> DeploymentHelper.runInNewTransaction(txManager, "initial-tenant-creation", status -> { - final DistributionSetType defaultDsType = createStandardSoftwareDataSetup(); - return tenantMetaDataRepository.save(new JpaTenantMetaData(defaultDsType, tenant)); - }), tenant); - } - @Override public Page findTenants(final Pageable pageable) { return tenantMetaDataRepository.findTenants(pageable); @@ -281,12 +254,22 @@ public class JpaSystemManagement implements CurrentTenantCacheKeyGenerator, Syst @Override public TenantMetaData getTenantMetadata() { - if (tenantAware.getCurrentTenant() == null) { + final String tenant = tenantAware.getCurrentTenant(); + if (tenant == null) { throw new IllegalStateException("Tenant not set"); } - return Objects.requireNonNull( - tenantMetaDataRepository.findByTenantIgnoreCase(tenantAware.getCurrentTenant()), - "No such tenant!"); + + final TenantMetaData metaData = tenantMetaDataRepository.findByTenantIgnoreCase(tenant); + if (metaData == null) { + if (repositoryProperties.isImplicitTenantCreateAllowed()) { + LOGGER.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 @@ -371,4 +354,38 @@ public class JpaSystemManagement implements CurrentTenantCacheKeyGenerator, Syst } while ((query = tenants.nextPageable()) != Pageable.unpaged()); } + + private TenantMetaData createTenantMetadata0(final String tenant) { + try { + currentTenantCacheKeyGenerator.setTenantInCreation(tenant); + return createInitialTenantMetaData(tenant); + } catch (final Throwable t) { + LOGGER.error("Failed to create tenant: {}", tenant, t); + return null; + } finally { + currentTenantCacheKeyGenerator.removeTenantInCreation(); + } + } + + /** + * Creating the initial tenant meta-data in a new transaction. Due the + * {@link MultiTenantJpaTransactionManager} is using the current tenant to + * set the necessary tenant discriminator to the query. This is not working + * if we don't have a current tenant set. Due the + * {@link #createTenantMetadata(String)} is maybe called without having a + * current tenant we need to re-open a new transaction so the + * {@link MultiTenantJpaTransactionManager} is called again and set the + * tenant for this transaction. + * + * @param tenant + * the tenant to be created + * @return the initial created {@link TenantMetaData} + */ + private TenantMetaData createInitialTenantMetaData(final String tenant) { + return systemSecurityContext.runAsSystemAsTenant( + () -> DeploymentHelper.runInNewTransaction(txManager, "initial-tenant-creation", status -> { + final DistributionSetType defaultDsType = createStandardSoftwareDataSetup(); + return tenantMetaDataRepository.save(new JpaTenantMetaData(defaultDsType, tenant)); + }), tenant); + } }