diff --git a/persistence-service-v1/persistence-service-processor-v1/src/main/java/com/iqser/red/service/persistence/management/v1/processor/multitenancy/persistence/TenantPersistenceConfig.java b/persistence-service-v1/persistence-service-processor-v1/src/main/java/com/iqser/red/service/persistence/management/v1/processor/multitenancy/persistence/TenantPersistenceConfig.java index c04cec843..11b0822cb 100644 --- a/persistence-service-v1/persistence-service-processor-v1/src/main/java/com/iqser/red/service/persistence/management/v1/processor/multitenancy/persistence/TenantPersistenceConfig.java +++ b/persistence-service-v1/persistence-service-processor-v1/src/main/java/com/iqser/red/service/persistence/management/v1/processor/multitenancy/persistence/TenantPersistenceConfig.java @@ -28,12 +28,13 @@ import com.iqser.red.service.persistence.management.v1.processor.service.persist import com.iqser.red.service.persistence.management.v1.processor.service.persistence.mulitenancy.DynamicDataSourceBasedMultiTenantConnectionProvider; import com.iqser.red.service.persistence.management.v1.processor.service.persistence.mulitenancy.repository.TenantRepository; import com.iqser.red.service.persistence.management.v1.processor.service.persistence.repository.ColorsRepository; +import com.iqser.red.service.persistence.management.v1.processor.settings.TenantHikariSettings; import lombok.RequiredArgsConstructor; @Configuration @EnableJpaRepositories(basePackageClasses = ColorsRepository.class, entityManagerFactoryRef = "tenantEntityManagerFactory", transactionManagerRef = "tenantTransactionManager") -@EnableConfigurationProperties(JpaProperties.class) +@EnableConfigurationProperties({JpaProperties.class, TenantHikariSettings.class}) @RequiredArgsConstructor public class TenantPersistenceConfig { diff --git a/persistence-service-v1/persistence-service-processor-v1/src/main/java/com/iqser/red/service/persistence/management/v1/processor/service/TenantManagementService.java b/persistence-service-v1/persistence-service-processor-v1/src/main/java/com/iqser/red/service/persistence/management/v1/processor/service/TenantManagementService.java index 04d1106ea..f3a1a2633 100644 --- a/persistence-service-v1/persistence-service-processor-v1/src/main/java/com/iqser/red/service/persistence/management/v1/processor/service/TenantManagementService.java +++ b/persistence-service-v1/persistence-service-processor-v1/src/main/java/com/iqser/red/service/persistence/management/v1/processor/service/TenantManagementService.java @@ -87,7 +87,6 @@ public class TenantManagementService { private final KeyCloakRoleManagerService keyCloakRoleManagerService; private final KeyCloakAdminClientService keycloak; - public TenantManagementService(EncryptionDecryptionService encryptionService, @Qualifier("tenantLiquibaseProperties") LiquibaseProperties liquibaseProperties, ResourceLoader resourceLoader, @@ -299,6 +298,9 @@ public class TenantManagementService { var user = new UserRepresentation(); user.setUsername(redUser.getUsername()); user.setCredentials(List.of(credentialRepresentation)); + user.setEmail(redUser.getEmail()); + user.setFirstName(redUser.getFirstName()); + user.setLastName(redUser.getLastName()); user.setEmailVerified(true); var roles = new ArrayList(); diff --git a/persistence-service-v1/persistence-service-processor-v1/src/main/java/com/iqser/red/service/persistence/management/v1/processor/service/persistence/mulitenancy/DynamicDataSourceBasedMultiTenantConnectionProvider.java b/persistence-service-v1/persistence-service-processor-v1/src/main/java/com/iqser/red/service/persistence/management/v1/processor/service/persistence/mulitenancy/DynamicDataSourceBasedMultiTenantConnectionProvider.java index b344a53b6..12edc0018 100644 --- a/persistence-service-v1/persistence-service-processor-v1/src/main/java/com/iqser/red/service/persistence/management/v1/processor/service/persistence/mulitenancy/DynamicDataSourceBasedMultiTenantConnectionProvider.java +++ b/persistence-service-v1/persistence-service-processor-v1/src/main/java/com/iqser/red/service/persistence/management/v1/processor/service/persistence/mulitenancy/DynamicDataSourceBasedMultiTenantConnectionProvider.java @@ -19,8 +19,9 @@ import com.google.common.cache.LoadingCache; import com.google.common.cache.RemovalListener; import com.iqser.red.service.persistence.management.v1.processor.multitenancy.entity.TenantEntity; import com.iqser.red.service.persistence.management.v1.processor.service.EncryptionDecryptionService; -import com.iqser.red.service.persistence.management.v1.processor.service.persistence.mulitenancy.repository.SchemaDataSource; +import com.iqser.red.service.persistence.management.v1.processor.service.persistence.mulitenancy.repository.SchemaConnection; import com.iqser.red.service.persistence.management.v1.processor.service.persistence.mulitenancy.repository.TenantRepository; +import com.iqser.red.service.persistence.management.v1.processor.settings.TenantHikariSettings; import com.iqser.red.service.persistence.management.v1.processor.utils.jdbc.JDBCUtils; import com.zaxxer.hikari.HikariDataSource; @@ -38,6 +39,7 @@ public class DynamicDataSourceBasedMultiTenantConnectionProvider extends Abstrac private final DataSourceProperties masterDataSourceProperties; private final TenantRepository masterTenantRepository; private final EncryptionDecryptionService encryptionService; + private final TenantHikariSettings tenantHikariSettings; @Value("${multitenancy.datasource-cache.maximumSize:100}") private Long maximumSize; @@ -45,64 +47,57 @@ public class DynamicDataSourceBasedMultiTenantConnectionProvider extends Abstrac @Value("${multitenancy.datasource-cache.expireAfterAccess:10}") private Integer expireAfterAccess; - private LoadingCache tenantSchemaDataSources; + private LoadingCache connectionPerTenant; + + private LoadingCache dataSourcePerConnectionString; @PostConstruct protected void createCache() { - tenantSchemaDataSources = CacheBuilder.newBuilder() + connectionPerTenant = CacheBuilder.newBuilder().maximumSize(maximumSize).expireAfterAccess(expireAfterAccess, TimeUnit.MINUTES).build(new CacheLoader<>() { + public SchemaConnection load(String key) { + + TenantEntity tenant = masterTenantRepository.findByTenantId(key).orElseThrow(() -> new RuntimeException("No such tenant: " + key)); + var jdbcUrl = JDBCUtils.buildJdbcUrl(tenant.getDatabaseConnection()); + return SchemaConnection.builder().jdbcUrl(jdbcUrl).databaseConnection(tenant.getDatabaseConnection()).build(); + } + }); + + dataSourcePerConnectionString = CacheBuilder.newBuilder() .maximumSize(maximumSize) .expireAfterAccess(expireAfterAccess, TimeUnit.MINUTES) - .removalListener((RemovalListener) removal -> { - - var toRemove = removal.getValue(); - int numberOfSchemasForSameDataSource = 0; - for (var schemaDataSource : tenantSchemaDataSources.asMap().values()) { - if (toRemove.getJdbcUrl().equals(schemaDataSource.getJdbcUrl())) { - numberOfSchemasForSameDataSource++; + .removalListener((RemovalListener) removal -> { + HikariDataSource ds = (HikariDataSource) removal.getValue(); + ds.close(); + log.info("Closed datasource: {}", ds.getPoolName()); } - } - if (numberOfSchemasForSameDataSource == 0) { - HikariDataSource ds = (HikariDataSource) removal.getValue().getDataSource(); - ds.close(); // tear down properly - log.info("Closed datasource: {}", ds.getPoolName()); - } else { - log.info("Keeping datasource open from {} because it is still used by {} other tenants", removal.getKey(), numberOfSchemasForSameDataSource); - } - }) + + ) .build(new CacheLoader<>() { - public SchemaDataSource load(String key) { + public DataSource load(SchemaConnection schemaConnection) { - TenantEntity tenant = masterTenantRepository.findByTenantId(key).orElseThrow(() -> new RuntimeException("No such tenant: " + key)); - var jdbcUrl = JDBCUtils.buildJdbcUrl(tenant.getDatabaseConnection()); - - for (var schemaDataSource : tenantSchemaDataSources.asMap().values()) { - if (schemaDataSource.getJdbcUrl().equals(jdbcUrl)) { - return new SchemaDataSource(tenant.getDatabaseConnection().getSchema(), schemaDataSource.getDataSource(), jdbcUrl); - } - } - return createAndConfigureDataSource(tenant); + return createAndConfigureDataSource(schemaConnection); } }); } - private SchemaDataSource createAndConfigureDataSource(TenantEntity tenant) { + private DataSource createAndConfigureDataSource(SchemaConnection connection) { - String decryptedPassword = encryptionService.decrypt(tenant.getDatabaseConnection().getPassword()); + String decryptedPassword = encryptionService.decrypt(connection.getDatabaseConnection().getPassword()); - HikariDataSource ds = masterDataSourceProperties.initializeDataSourceBuilder().type(HikariDataSource.class).build(); + tenantHikariSettings.setUsername(connection.getDatabaseConnection().getUsername()); + tenantHikariSettings.setPassword(decryptedPassword); - ds.setUsername(tenant.getDatabaseConnection().getUsername()); - ds.setPassword(decryptedPassword); + var jdbcUrl = JDBCUtils.buildJdbcUrl(connection.getDatabaseConnection()); + tenantHikariSettings.setJdbcUrl(jdbcUrl); + tenantHikariSettings.setPoolName(jdbcUrl + TENANT_POOL_NAME_SUFFIX); - var jdbcUrl = JDBCUtils.buildJdbcUrl(tenant.getDatabaseConnection()); - ds.setJdbcUrl(jdbcUrl); - ds.setPoolName(jdbcUrl + TENANT_POOL_NAME_SUFFIX); + HikariDataSource ds = new HikariDataSource(tenantHikariSettings); log.info("Configured datasource: {}", ds.getPoolName()); - return new SchemaDataSource(tenant.getDatabaseConnection().getSchema(), ds, jdbcUrl); + return ds; } @@ -117,7 +112,8 @@ public class DynamicDataSourceBasedMultiTenantConnectionProvider extends Abstrac public DataSource selectDataSource(String tenantIdentifier) { try { - return tenantSchemaDataSources.get(tenantIdentifier).getDataSource(); + var connection = connectionPerTenant.get(tenantIdentifier); + return dataSourcePerConnectionString.get(connection); } catch (ExecutionException e) { throw new RuntimeException("Failed to load DataSource for tenant: " + tenantIdentifier); } @@ -128,9 +124,9 @@ public class DynamicDataSourceBasedMultiTenantConnectionProvider extends Abstrac public Connection getConnection(String tenantIdentifier) throws SQLException { try { - var schemaDataSource = tenantSchemaDataSources.get(tenantIdentifier); - Connection connection = schemaDataSource.getDataSource().getConnection(); - connection.setSchema(schemaDataSource.getSchema()); + var dataSource = selectDataSource(tenantIdentifier); + Connection connection = dataSource.getConnection(); + connection.setSchema(connectionPerTenant.get(tenantIdentifier).getDatabaseConnection().getSchema()); return connection; } catch (ExecutionException e) { throw new RuntimeException("No such tenant: " + tenantIdentifier); @@ -140,6 +136,7 @@ public class DynamicDataSourceBasedMultiTenantConnectionProvider extends Abstrac @Override public void releaseAnyConnection(Connection connection) throws SQLException { + connection.close(); } @@ -154,6 +151,7 @@ public class DynamicDataSourceBasedMultiTenantConnectionProvider extends Abstrac @Override public boolean supportsAggressiveRelease() { + return false; } diff --git a/persistence-service-v1/persistence-service-processor-v1/src/main/java/com/iqser/red/service/persistence/management/v1/processor/service/persistence/mulitenancy/repository/SchemaConnection.java b/persistence-service-v1/persistence-service-processor-v1/src/main/java/com/iqser/red/service/persistence/management/v1/processor/service/persistence/mulitenancy/repository/SchemaConnection.java new file mode 100644 index 000000000..55a78e508 --- /dev/null +++ b/persistence-service-v1/persistence-service-processor-v1/src/main/java/com/iqser/red/service/persistence/management/v1/processor/service/persistence/mulitenancy/repository/SchemaConnection.java @@ -0,0 +1,20 @@ +package com.iqser.red.service.persistence.management.v1.processor.service.persistence.mulitenancy.repository; + +import com.iqser.red.service.persistence.management.v1.processor.multitenancy.entity.DatabaseConnectionEntity; + +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Data; +import lombok.EqualsAndHashCode; + +@Data +@Builder +@AllArgsConstructor +@EqualsAndHashCode(onlyExplicitlyIncluded = true) +public class SchemaConnection { + + @EqualsAndHashCode.Include + private String jdbcUrl; + private DatabaseConnectionEntity databaseConnection; + +} diff --git a/persistence-service-v1/persistence-service-processor-v1/src/main/java/com/iqser/red/service/persistence/management/v1/processor/service/persistence/mulitenancy/repository/SchemaDataSource.java b/persistence-service-v1/persistence-service-processor-v1/src/main/java/com/iqser/red/service/persistence/management/v1/processor/service/persistence/mulitenancy/repository/SchemaDataSource.java deleted file mode 100644 index e484700c0..000000000 --- a/persistence-service-v1/persistence-service-processor-v1/src/main/java/com/iqser/red/service/persistence/management/v1/processor/service/persistence/mulitenancy/repository/SchemaDataSource.java +++ /dev/null @@ -1,18 +0,0 @@ -package com.iqser.red.service.persistence.management.v1.processor.service.persistence.mulitenancy.repository; - -import javax.sql.DataSource; - -import lombok.AllArgsConstructor; -import lombok.Builder; -import lombok.Data; - -@Data -@Builder -@AllArgsConstructor -public class SchemaDataSource { - - private String schema; - private DataSource dataSource; - private String jdbcUrl; - -} diff --git a/persistence-service-v1/persistence-service-processor-v1/src/main/java/com/iqser/red/service/persistence/management/v1/processor/settings/TenantHikariSettings.java b/persistence-service-v1/persistence-service-processor-v1/src/main/java/com/iqser/red/service/persistence/management/v1/processor/settings/TenantHikariSettings.java new file mode 100644 index 000000000..fa364cee8 --- /dev/null +++ b/persistence-service-v1/persistence-service-processor-v1/src/main/java/com/iqser/red/service/persistence/management/v1/processor/settings/TenantHikariSettings.java @@ -0,0 +1,10 @@ +package com.iqser.red.service.persistence.management.v1.processor.settings; + +import org.springframework.boot.context.properties.ConfigurationProperties; + +import com.zaxxer.hikari.HikariConfig; + +@ConfigurationProperties("multitenancy.tenant.datasource.hikari") +public class TenantHikariSettings extends HikariConfig { + +} diff --git a/persistence-service-v1/persistence-service-processor-v1/src/main/resources/db/changelog/db.changelog-master.yaml b/persistence-service-v1/persistence-service-processor-v1/src/main/resources/db/changelog/db.changelog-master.yaml index 8a632ee7b..d1fa43356 100644 --- a/persistence-service-v1/persistence-service-processor-v1/src/main/resources/db/changelog/db.changelog-master.yaml +++ b/persistence-service-v1/persistence-service-processor-v1/src/main/resources/db/changelog/db.changelog-master.yaml @@ -5,3 +5,5 @@ databaseChangeLog: file: db/changelog/master/2-quartz.changelog.yaml - include: file: db/changelog/master/3-detailed-db-connection.changelog.yaml + - include: + file: db/changelog/master/4-add-unique-constraint-for-tenants-table.yaml diff --git a/persistence-service-v1/persistence-service-processor-v1/src/main/resources/db/changelog/master/4-add-unique-constraint-for-tenants-table.yaml b/persistence-service-v1/persistence-service-processor-v1/src/main/resources/db/changelog/master/4-add-unique-constraint-for-tenants-table.yaml new file mode 100644 index 000000000..27baab89c --- /dev/null +++ b/persistence-service-v1/persistence-service-processor-v1/src/main/resources/db/changelog/master/4-add-unique-constraint-for-tenants-table.yaml @@ -0,0 +1,17 @@ +databaseChangeLog: + - changeSet: + id: add-unique-constraint-for-tenants-table + author: corinaolariu + changes: + - addUniqueConstraint: + columnNames: db_host, db_schema + constraintName: unique_constraint_tenant_host_shema + tableName: tenant + - addUniqueConstraint: + columnNames: storage_s3_endpoint, storage_s3_region, storage_s3_bucket_name + constraintName: unique_constraint_tenant_s3_storage + tableName: tenant + - addUniqueConstraint: + columnNames: storage_azure_connection_string, storage_azure_container_name + constraintName: unique_constraint_tenant_azure_storage + tableName: tenant diff --git a/persistence-service-v1/persistence-service-server-v1/src/main/resources/application.yml b/persistence-service-v1/persistence-service-server-v1/src/main/resources/application.yml index edc62b838..b4e3f270b 100644 --- a/persistence-service-v1/persistence-service-server-v1/src/main/resources/application.yml +++ b/persistence-service-v1/persistence-service-server-v1/src/main/resources/application.yml @@ -97,6 +97,8 @@ multitenancy: password: ${PSQL_PASSWORD:redaction} platform: org.hibernate.dialect.PostgreSQL95Dialect hikari: + maximumPoolSize: 5 + minimum-idle: 5 data-source-properties: cachePrepStmts: true prepStmtCacheSize: 1000 @@ -107,6 +109,8 @@ multitenancy: datasource: driverClassName: org.postgresql.Driver hikari: + maximumPoolSize: 5 + minimum-idle: 5 data-source-properties: cachePrepStmts: true prepStmtCacheSize: 1000 diff --git a/persistence-service-v1/persistence-service-server-v1/src/test/java/com/iqser/red/service/peristence/v1/server/integration/tests/DossierTest.java b/persistence-service-v1/persistence-service-server-v1/src/test/java/com/iqser/red/service/peristence/v1/server/integration/tests/DossierTest.java index 37211e256..7746a17d3 100644 --- a/persistence-service-v1/persistence-service-server-v1/src/test/java/com/iqser/red/service/peristence/v1/server/integration/tests/DossierTest.java +++ b/persistence-service-v1/persistence-service-server-v1/src/test/java/com/iqser/red/service/peristence/v1/server/integration/tests/DossierTest.java @@ -8,6 +8,7 @@ import java.util.List; import java.util.Set; import java.util.stream.IntStream; +import org.junit.jupiter.api.Disabled; import org.junit.jupiter.api.Test; import org.springframework.beans.BeanUtils; import org.springframework.beans.factory.annotation.Autowired; @@ -62,6 +63,7 @@ public class DossierTest extends AbstractPersistenceServerServiceTest { private TypeProvider typeProvider; @Test + @Disabled // TODO parallel does not work with tenantContext currently public void testDossierRaceCondition() { var dossierTemplate = dossierTemplateTesterAndProvider.provideTestTemplate(); diff --git a/persistence-service-v1/persistence-service-server-v1/src/test/java/com/iqser/red/service/peristence/v1/server/integration/tests/TenantsTest.java b/persistence-service-v1/persistence-service-server-v1/src/test/java/com/iqser/red/service/peristence/v1/server/integration/tests/TenantsTest.java new file mode 100644 index 000000000..229f1bba1 --- /dev/null +++ b/persistence-service-v1/persistence-service-server-v1/src/test/java/com/iqser/red/service/peristence/v1/server/integration/tests/TenantsTest.java @@ -0,0 +1,207 @@ +package com.iqser.red.service.peristence.v1.server.integration.tests; + +import static org.assertj.core.api.Assertions.assertThat; + +import java.util.List; +import java.util.Set; +import java.util.UUID; + +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.Test; +import org.springframework.beans.factory.annotation.Autowired; + +import com.iqser.red.keycloak.commons.roles.ApplicationRoles; +import com.iqser.red.service.peristence.v1.server.integration.client.TenantsClient; +import com.iqser.red.service.peristence.v1.server.integration.utils.AbstractPersistenceServerServiceTest; +import com.iqser.red.service.persistence.service.v1.api.shared.model.multitenancy.AzureStorageConnection; +import com.iqser.red.service.persistence.service.v1.api.shared.model.multitenancy.DatabaseConnection; +import com.iqser.red.service.persistence.service.v1.api.shared.model.multitenancy.RedUser; +import com.iqser.red.service.persistence.service.v1.api.shared.model.multitenancy.S3StorageConnection; +import com.iqser.red.service.persistence.service.v1.api.shared.model.multitenancy.SearchConnection; +import com.iqser.red.service.persistence.service.v1.api.shared.model.multitenancy.TenantRequest; +import com.iqser.red.service.persistence.service.v1.api.shared.model.multitenancy.TenantResponse; + +import feign.FeignException; + +public class TenantsTest extends AbstractPersistenceServerServiceTest { + + @Autowired + private TenantsClient tenantsClient; + + @Test + public void testCreateTenantWithSameHostAndSchema() { + + TenantResponse firstTenant = tenantsClient.getTenants().get(0); + + var tenantRequest = TenantRequest.builder() + .tenantId("redaction2") + .displayName("Redaction default2") + .guid(UUID.randomUUID().toString()) + .databaseConnection(DatabaseConnection.builder() + .driver("postgresql") + .host("localhost") + .port(firstTenant.getDatabaseConnection().getPort()) + .database("redaction") + .schema("myschema") + .username("redaction") + .password("redaction") + .build()) + .searchConnection(SearchConnection.builder() + .hosts(Set.of("elasticsearchHost2")) + .port(9200) + .scheme("https2") + .username("elastic") + .numberOfShards("1") + .numberOfReplicas("5") + .build()) + .s3StorageConnection(S3StorageConnection.builder() + .key("key") + .secret("secret") + .signerType("signerType") + .bucketName("bucketName2") + .region("eu") + .endpoint("endpoint2") + .build()) + .redUsers(List.of(RedUser.builder().username("user").password("password").redRoles(ApplicationRoles.ROLE_DATA.keySet()).build(), + RedUser.builder().username("manageradmin1@test.com").password("secret").redRoles(ApplicationRoles.ROLE_DATA.keySet()).build(), + RedUser.builder().username("manageradmin2@test.com").password("secret").redRoles(ApplicationRoles.ROLE_DATA.keySet()).build())) + .build(); + + Exception exception = Assertions.assertThrows(FeignException.Conflict.class, () -> { + tenantsClient.createTenant(tenantRequest); + }); + + String expectedMessage = "An object of type tenant already exists"; + String actualMessage = exception.getMessage(); + + assertThat(actualMessage).contains(expectedMessage); + + } + + @Test + public void testCreateTenantWithDuplicateStorageS3() { + + TenantResponse firstTenant = tenantsClient.getTenants().get(0); + + var tenantRequest = TenantRequest.builder() + .tenantId("redaction2") + .displayName("Redaction default2") + .guid(UUID.randomUUID().toString()) + .databaseConnection(DatabaseConnection.builder() + .driver("postgresql") + .host("localhost") + .port(firstTenant.getDatabaseConnection().getPort()) + .database("redaction") + .schema("myschema2") + .username("redaction") + .password("redaction") + .build()) + .searchConnection(SearchConnection.builder() + .hosts(Set.of("elasticsearchHost2")) + .port(9200) + .scheme("https2") + .username("elastic") + .numberOfShards("1") + .numberOfReplicas("5") + .build()) + .s3StorageConnection(S3StorageConnection.builder() + .key("key") + .secret("secret") + .signerType("signerType") + .bucketName("bucketName") + .region("eu") + .endpoint("endpoint") + .build()) + .redUsers(List.of(RedUser.builder().username("user").password("password").redRoles(ApplicationRoles.ROLE_DATA.keySet()).build(), + RedUser.builder().username("manageradmin1@test.com").password("secret").redRoles(ApplicationRoles.ROLE_DATA.keySet()).build(), + RedUser.builder().username("manageradmin2@test.com").password("secret").redRoles(ApplicationRoles.ROLE_DATA.keySet()).build())) + .build(); + + Exception exception = Assertions.assertThrows(FeignException.Conflict.class, () -> { + tenantsClient.createTenant(tenantRequest); + }); + + String expectedMessage = "An object of type tenant already exists"; + String actualMessage = exception.getMessage(); + + assertThat(actualMessage).contains(expectedMessage); + } + + @Test + public void testCreateTenantWithDuplicateAzure() { + + TenantResponse firstTenant = tenantsClient.getTenants().get(0); + + var tenantRequest = TenantRequest.builder() + .tenantId("redaction2") + .displayName("Redaction default2") + .guid(UUID.randomUUID().toString()) + .databaseConnection(DatabaseConnection.builder() + .driver("postgresql") + .host("localhost") + .port(firstTenant.getDatabaseConnection().getPort()) + .database("redaction") + .schema("myschema2") + .username("redaction") + .password("redaction") + .build()) + .searchConnection(SearchConnection.builder() + .hosts(Set.of("elasticsearchHost2")) + .port(9200) + .scheme("https2") + .username("elastic") + .numberOfShards("1") + .numberOfReplicas("5") + .build()) + .azureStorageConnection(AzureStorageConnection.builder() + .connectionString("connection") + .containerName("container") + .build()) + .redUsers(List.of(RedUser.builder().username("user").password("password").redRoles(ApplicationRoles.ROLE_DATA.keySet()).build(), + RedUser.builder().username("manageradmin1@test.com").password("secret").redRoles(ApplicationRoles.ROLE_DATA.keySet()).build(), + RedUser.builder().username("manageradmin2@test.com").password("secret").redRoles(ApplicationRoles.ROLE_DATA.keySet()).build())) + .build(); + + tenantsClient.createTenant(tenantRequest); + assertThat(tenantsClient.getTenants().size()).isEqualTo(2); + + var tenantRequest2 = TenantRequest.builder() + .tenantId("redaction2") + .displayName("Redaction default2") + .guid(UUID.randomUUID().toString()) + .databaseConnection(DatabaseConnection.builder() + .driver("postgresql") + .host("localhost") + .port(firstTenant.getDatabaseConnection().getPort()) + .database("redaction") + .schema("myschema3") + .username("redaction") + .password("redaction") + .build()) + .searchConnection(SearchConnection.builder() + .hosts(Set.of("elasticsearchHost2")) + .port(9200) + .scheme("https2") + .username("elastic") + .numberOfShards("1") + .numberOfReplicas("5") + .build()) + .azureStorageConnection(AzureStorageConnection.builder() + .connectionString("connection") + .containerName("container") + .build()) + .redUsers(List.of(RedUser.builder().username("user").password("password").redRoles(ApplicationRoles.ROLE_DATA.keySet()).build(), + RedUser.builder().username("manageradmin1@test.com").password("secret").redRoles(ApplicationRoles.ROLE_DATA.keySet()).build(), + RedUser.builder().username("manageradmin2@test.com").password("secret").redRoles(ApplicationRoles.ROLE_DATA.keySet()).build())) + .build(); + + Exception exception = Assertions.assertThrows(FeignException.Conflict.class, () -> { + tenantsClient.createTenant(tenantRequest); + }); + + String expectedMessage = "An object of type tenant already exists"; + String actualMessage = exception.getMessage(); + + assertThat(actualMessage).contains(expectedMessage); + } +} diff --git a/persistence-service-v1/persistence-service-server-v1/src/test/resources/application.yml b/persistence-service-v1/persistence-service-server-v1/src/test/resources/application.yml index 353ef50fb..66f8a3b11 100644 --- a/persistence-service-v1/persistence-service-server-v1/src/test/resources/application.yml +++ b/persistence-service-v1/persistence-service-server-v1/src/test/resources/application.yml @@ -96,6 +96,8 @@ multitenancy: driverClassName: org.postgresql.Driver platform: org.hibernate.dialect.PostgreSQL95Dialect hikari: + maximumPoolSize: 5 + minimum-idle: 5 data-source-properties: cachePrepStmts: true prepStmtCacheSize: 1000 @@ -107,6 +109,8 @@ multitenancy: driverClassName: org.postgresql.Driver platform: org.hibernate.dialect.PostgreSQL95Dialect hikari: + maximumPoolSize: 5 + minimum-idle: 5 data-source-properties: cachePrepStmts: true prepStmtCacheSize: 1000 diff --git a/persistence-service-v1/persistence-service-shared-api-v1/src/main/java/com/iqser/red/service/persistence/service/v1/api/shared/model/multitenancy/RedUser.java b/persistence-service-v1/persistence-service-shared-api-v1/src/main/java/com/iqser/red/service/persistence/service/v1/api/shared/model/multitenancy/RedUser.java index 464b26017..e84ee9957 100644 --- a/persistence-service-v1/persistence-service-shared-api-v1/src/main/java/com/iqser/red/service/persistence/service/v1/api/shared/model/multitenancy/RedUser.java +++ b/persistence-service-v1/persistence-service-shared-api-v1/src/main/java/com/iqser/red/service/persistence/service/v1/api/shared/model/multitenancy/RedUser.java @@ -12,6 +12,9 @@ public class RedUser { private String username; private String password; + private String email; + private String firstName; + private String lastName; @Builder.Default private Set redRoles = new HashSet<>();