From 8e1d5c380a94cbfa8fcb9e5b39d427a1bc4e325a Mon Sep 17 00:00:00 2001 From: Sattvik Chakravarthy Date: Tue, 6 Feb 2024 18:28:16 +0530 Subject: [PATCH 1/5] fix: load only cud --- CHANGELOG.md | 4 + config.yaml | 4 + devConfig.yaml | 4 + .../io/supertokens/config/CoreConfig.java | 19 ++ .../multitenancy/MultitenancyHelper.java | 50 +++- .../supertokens/webserver/WebserverAPI.java | 28 +- .../api/multitenancy/BaseCreateOrUpdate.java | 10 + .../test/multitenant/ConfigTest.java | 4 +- .../test/multitenant/LoadOnlyCUDTest.java | 252 ++++++++++++++++++ 9 files changed, 355 insertions(+), 20 deletions(-) create mode 100644 src/test/java/io/supertokens/test/multitenant/LoadOnlyCUDTest.java diff --git a/CHANGELOG.md b/CHANGELOG.md index 2cbd96e26..ac2bf6358 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,6 +7,10 @@ to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). ## [unreleased] +## [6.0.17] - 2024-02-06 + +- Adds new config `supertokens_saas_load_only_cud` that makes the core instance load a particular CUD only, irrespective of the CUDs present in the db. + ## [6.0.16] - 2023-11-03 - Collects requests stats per app diff --git a/config.yaml b/config.yaml index bc6e7cbce..c8731d1ed 100644 --- a/config.yaml +++ b/config.yaml @@ -146,3 +146,7 @@ core_config_version: 0 # when CDI version is not specified in the request. When set to null, the core will assume the latest version of the # CDI. # supertokens_max_cdi_version: + +# (OPTIONAL | Default: null) string value. If specified, the supertokens service will only load the specified CUD even +# if there are more CUDs in the database and block all other CUDs from being used from this instance. +# supertokens_saas_load_only_cud: diff --git a/devConfig.yaml b/devConfig.yaml index 73ccf220d..276b35d42 100644 --- a/devConfig.yaml +++ b/devConfig.yaml @@ -147,3 +147,7 @@ disable_telemetry: true # when CDI version is not specified in the request. When set to null, the core will assume the latest version of the # CDI. # supertokens_max_cdi_version: + +# (OPTIONAL | Default: null) string value. If specified, the supertokens service will only load the specified CUD even +# if there are more CUDs in the database and block all other CUDs from being used from this instance. +# supertokens_saas_load_only_cud: diff --git a/src/main/java/io/supertokens/config/CoreConfig.java b/src/main/java/io/supertokens/config/CoreConfig.java index e02fadb4d..4d3d823ac 100644 --- a/src/main/java/io/supertokens/config/CoreConfig.java +++ b/src/main/java/io/supertokens/config/CoreConfig.java @@ -30,7 +30,9 @@ import io.supertokens.pluginInterface.LOG_LEVEL; import io.supertokens.pluginInterface.exceptions.InvalidConfigException; import io.supertokens.utils.SemVer; +import io.supertokens.webserver.Utils; import io.supertokens.webserver.WebserverAPI; +import jakarta.servlet.ServletException; import org.apache.catalina.filters.RemoteAddrFilter; import org.jetbrains.annotations.TestOnly; @@ -197,6 +199,10 @@ public class CoreConfig { @JsonProperty private String supertokens_max_cdi_version = null; + @ConfigYamlOnly + @JsonProperty + private String supertokens_saas_load_only_cud = null; + @IgnoreForAnnotationCheck private Set allowedLogLevels = null; @@ -254,6 +260,10 @@ public String getBasePath() { return base_path; } + public String getSuperTokensLoadOnlyCUD() { + return supertokens_saas_load_only_cud; + } + public enum PASSWORD_HASHING_ALG { ARGON2, BCRYPT, FIREBASE_SCRYPT } @@ -663,6 +673,15 @@ void normalizeAndValidate(Main main) throws InvalidConfigException { host = cliHost; } + if (supertokens_saas_load_only_cud != null) { + try { + supertokens_saas_load_only_cud = + Utils.normalizeAndValidateConnectionUriDomain(supertokens_saas_load_only_cud, true); + } catch (ServletException e) { + throw new InvalidConfigException("supertokens_saas_load_only_cud is invalid"); + } + } + access_token_validity = access_token_validity * 1000; access_token_dynamic_signing_key_update_interval = access_token_dynamic_signing_key_update_interval * 3600 * 1000; refresh_token_validity = refresh_token_validity * 60 * 1000; diff --git a/src/main/java/io/supertokens/multitenancy/MultitenancyHelper.java b/src/main/java/io/supertokens/multitenancy/MultitenancyHelper.java index fe9397622..15cb1a5b3 100644 --- a/src/main/java/io/supertokens/multitenancy/MultitenancyHelper.java +++ b/src/main/java/io/supertokens/multitenancy/MultitenancyHelper.java @@ -51,9 +51,20 @@ public class MultitenancyHelper extends ResourceDistributor.SingletonResource { private Main main; private TenantConfig[] tenantConfigs; + // when the core has `supertokens_saas_load_only_cud` set, the tenantConfigs array will be filtered + // based on the config value. However, we need to keep all the list of CUDs from the db to be able + // to check if the CUD is present in the DB or not, while processing the requests. + private final Set dangerous_allCUDsFromDb = new HashSet<>(); + private MultitenancyHelper(Main main) throws StorageQueryException { this.main = main; - this.tenantConfigs = getAllTenantsFromDb(); + TenantConfig[] allTenantsFromDb = getAllTenantsFromDb(); + this.tenantConfigs = this.getFilteredTenantConfigs(allTenantsFromDb); + this.dangerous_allCUDsFromDb.clear(); + + for (TenantConfig config : allTenantsFromDb) { + this.dangerous_allCUDsFromDb.add(config.tenantIdentifier.getConnectionUriDomain()); + } } public static MultitenancyHelper getInstance(Main main) { @@ -81,7 +92,7 @@ public static void init(Main main) throws StorageQueryException, IOException { // instance of eeFeatureFlag. This is applicable only when the core is starting on // an empty database as no tenants are loaded from the db yet. } catch (CannotModifyBaseConfigException | BadPermissionException | FeatureNotEnabledException | - InvalidConfigException | InvalidProviderConfigException | TenantOrAppNotFoundException e) { + InvalidConfigException | InvalidProviderConfigException | TenantOrAppNotFoundException e) { throw new IllegalStateException(e); } } @@ -108,10 +119,11 @@ public List refreshTenantsInCoreBasedOnChangesInCoreConfigOrIf return main.getResourceDistributor().withResourceDistributorLock(() -> { try { TenantConfig[] tenantsFromDb = getAllTenantsFromDb(); + TenantConfig[] filteredTenantsFromDb = this.getFilteredTenantConfigs(tenantsFromDb); Map normalizedTenantsFromDb = Config.getNormalisedConfigsForAllTenants( - tenantsFromDb, Config.getBaseConfigAsJsonObject(main)); + filteredTenantsFromDb, Config.getBaseConfigAsJsonObject(main)); Map normalizedTenantsFromMemory = Config.getNormalisedConfigsForAllTenants( @@ -129,9 +141,14 @@ public List refreshTenantsInCoreBasedOnChangesInCoreConfigOrIf } } - boolean sameNumberOfTenants = tenantsFromDb.length == this.tenantConfigs.length; + boolean sameNumberOfTenants = + filteredTenantsFromDb.length == this.tenantConfigs.length; - this.tenantConfigs = tenantsFromDb; + this.dangerous_allCUDsFromDb.clear(); + for (TenantConfig tenant : tenantsFromDb) { + this.dangerous_allCUDsFromDb.add(tenant.tenantIdentifier.getConnectionUriDomain()); + } + this.tenantConfigs = filteredTenantsFromDb; if (tenantsThatChanged.size() == 0 && sameNumberOfTenants) { return tenantsThatChanged; } @@ -190,7 +207,7 @@ public void loadStorageLayer() throws IOException, InvalidConfigException { public void loadFeatureFlag(List tenantsThatChanged) { List apps = new ArrayList<>(); Set appsSet = new HashSet<>(); - for (TenantConfig t : tenantConfigs) { + for (TenantConfig t : this.tenantConfigs) { if (appsSet.contains(t.tenantIdentifier.toAppIdentifier())) { continue; } @@ -204,7 +221,7 @@ public void loadSigningKeys(List tenantsThatChanged) throws UnsupportedJWTSigningAlgorithmException { List apps = new ArrayList<>(); Set appsSet = new HashSet<>(); - for (TenantConfig t : tenantConfigs) { + for (TenantConfig t : this.tenantConfigs) { if (appsSet.contains(t.tenantIdentifier.toAppIdentifier())) { continue; } @@ -238,4 +255,21 @@ public TenantConfig[] getAllTenants() { throw new IllegalStateException(e); } } -} + + private TenantConfig[] getFilteredTenantConfigs(TenantConfig[] inputTenantConfigs) { + String loadOnlyCUD = Config.getBaseConfig(main).getSuperTokensLoadOnlyCUD(); + + if (loadOnlyCUD == null) { + return inputTenantConfigs; + } + + return Arrays.stream(inputTenantConfigs) + .filter(tenantConfig -> tenantConfig.tenantIdentifier.getConnectionUriDomain().equals(loadOnlyCUD) + || tenantConfig.tenantIdentifier.getConnectionUriDomain().equals(TenantIdentifier.DEFAULT_CONNECTION_URI)) + .toArray(TenantConfig[]::new); + } + + public boolean isConnectionUriDomainPresentInDb(String cud) { + return this.dangerous_allCUDsFromDb.contains(cud); + } +} \ No newline at end of file diff --git a/src/main/java/io/supertokens/webserver/WebserverAPI.java b/src/main/java/io/supertokens/webserver/WebserverAPI.java index ffb70b542..a785d28fb 100644 --- a/src/main/java/io/supertokens/webserver/WebserverAPI.java +++ b/src/main/java/io/supertokens/webserver/WebserverAPI.java @@ -24,6 +24,7 @@ import io.supertokens.config.CoreConfig; import io.supertokens.exceptions.QuitProgramException; import io.supertokens.featureflag.exceptions.FeatureNotEnabledException; +import io.supertokens.multitenancy.MultitenancyHelper; import io.supertokens.multitenancy.exception.BadPermissionException; import io.supertokens.output.Logging; import io.supertokens.pluginInterface.Storage; @@ -286,15 +287,18 @@ private String getConnectionUriDomain(HttpServletRequest req) throws ServletExce String connectionUriDomain = req.getServerName(); connectionUriDomain = Utils.normalizeAndValidateConnectionUriDomain(connectionUriDomain, false); - try { - if (Config.getConfig(new TenantIdentifier(connectionUriDomain, null, null), main) == - Config.getConfig(new TenantIdentifier(null, null, null), main)) { - return null; + if (MultitenancyHelper.getInstance(main).isConnectionUriDomainPresentInDb(connectionUriDomain)) { + CoreConfig baseConfig = Config.getBaseConfig(main); + if (baseConfig.getSuperTokensLoadOnlyCUD() != null) { + if (!connectionUriDomain.equals(baseConfig.getSuperTokensLoadOnlyCUD())) { + throw new ServletException(new BadRequestException("Connection URI domain is disallowed")); + } } - } catch (TenantOrAppNotFoundException e) { - throw new IllegalStateException(e); + + return connectionUriDomain; } - return connectionUriDomain; + + return null; } @TestOnly @@ -480,10 +484,12 @@ protected void service(HttpServletRequest req, HttpServletResponse resp) throws } Logging.info(main, tenantIdentifier, "API ended: " + req.getRequestURI() + ". Method: " + req.getMethod(), false); - try { - RequestStats.getInstance(main, tenantIdentifier.toAppIdentifier()).updateRequestStats(); - } catch (TenantOrAppNotFoundException e) { - // Ignore the error as we would have already sent the response for tenantNotFound + if (tenantIdentifier != null) { + try { + RequestStats.getInstance(main, tenantIdentifier.toAppIdentifier()).updateRequestStats(); + } catch (TenantOrAppNotFoundException e) { + // Ignore the error as we would have already sent the response for tenantNotFound + } } } diff --git a/src/main/java/io/supertokens/webserver/api/multitenancy/BaseCreateOrUpdate.java b/src/main/java/io/supertokens/webserver/api/multitenancy/BaseCreateOrUpdate.java index ac930fb7b..87bc319b5 100644 --- a/src/main/java/io/supertokens/webserver/api/multitenancy/BaseCreateOrUpdate.java +++ b/src/main/java/io/supertokens/webserver/api/multitenancy/BaseCreateOrUpdate.java @@ -19,6 +19,8 @@ import com.google.gson.JsonElement; import com.google.gson.JsonObject; import io.supertokens.Main; +import io.supertokens.config.Config; +import io.supertokens.config.CoreConfig; import io.supertokens.featureflag.exceptions.FeatureNotEnabledException; import io.supertokens.multitenancy.Multitenancy; import io.supertokens.multitenancy.exception.BadPermissionException; @@ -49,6 +51,14 @@ protected void handle(HttpServletRequest req, TenantIdentifier sourceTenantIdent HttpServletResponse resp) throws ServletException, IOException { + CoreConfig baseConfig = Config.getBaseConfig(main); + if (baseConfig.getSuperTokensLoadOnlyCUD() != null) { + if (!(targetTenantIdentifier.getConnectionUriDomain().equals(TenantIdentifier.DEFAULT_CONNECTION_URI) || targetTenantIdentifier.getConnectionUriDomain().equals(baseConfig.getSuperTokensLoadOnlyCUD()))) { + throw new ServletException(new BadRequestException("Creation of connection uri domain or app or " + + "tenant is disallowed")); + } + } + TenantConfig tenantConfig = Multitenancy.getTenantInfo(main, new TenantIdentifier(targetTenantIdentifier.getConnectionUriDomain(), targetTenantIdentifier.getAppId(), targetTenantIdentifier.getTenantId())); diff --git a/src/test/java/io/supertokens/test/multitenant/ConfigTest.java b/src/test/java/io/supertokens/test/multitenant/ConfigTest.java index 2885d4318..13cdfe6be 100644 --- a/src/test/java/io/supertokens/test/multitenant/ConfigTest.java +++ b/src/test/java/io/supertokens/test/multitenant/ConfigTest.java @@ -1914,6 +1914,7 @@ public void testAllConflictingConfigs() throws Exception { "argon2_memory_kb", "argon2_parallelism", "bcrypt_log_rounds", + "supertokens_saas_load_only_cud" }; Object[] disallowedValues = new Object[]{ 3567, // port @@ -1930,6 +1931,7 @@ public void testAllConflictingConfigs() throws Exception { 87795, // argon2_memory_kb 2, // argon2_parallelism 11, // bcrypt_log_rounds + "mydomain.com", // supertokens_saas_load_only_cud }; process.kill(); @@ -1995,7 +1997,7 @@ public void testAllConflictingConfigs() throws Exception { new Object[]{true, false}, // disable_telemetry new Object[]{"BCRYPT", "ARGON2"}, // password_hashing_alg new Object[]{"abcd1234abcd1234abcd1234abcd1234", "qwer1234qwer1234qwer1234qwer1234"}, // firebase_password_hashing_signer_key - new Object[]{"2.21", "3.0"} // supertokens_max_cdi_version + new Object[]{"2.21", "3.0"}, // supertokens_max_cdi_version }; for (int i=0; i> uniqueUserPoolIdsTenants = StorageLayer.getTenantsWithUniqueUserPoolId(process.getProcess()); + Cronjobs.addCronjob(process.getProcess(), LoadOnlyCUDTest.PerAppCronjob.getInstance(process.getProcess(), uniqueUserPoolIdsTenants)); + + Thread.sleep(3000); + Set appIdentifiersFromCron = PerAppCronjob.getInstance(process.getProcess(), uniqueUserPoolIdsTenants).appIdentifiers; + assertEquals(2, appIdentifiersFromCron.size()); + for (AppIdentifier app : appIdentifiersFromCron) { + assertNotEquals("localhost.org", app.getConnectionUriDomain()); + } + + process.kill(); + assertNotNull(process.checkOrWaitForEvent(ProcessState.PROCESS_STATE.STOPPED)); + } + + static class PerAppCronjob extends CronTask { + private static final String RESOURCE_ID = "io.supertokens.test.CronjobTest.NormalCronjob"; + + private PerAppCronjob(Main main, List> tenantsInfo) { + super("PerTenantCronjob", main, tenantsInfo, true); + } + + Set appIdentifiers = new HashSet<>(); + + public static LoadOnlyCUDTest.PerAppCronjob getInstance(Main main, List> tenantsInfo) { + try { + return (LoadOnlyCUDTest.PerAppCronjob) main.getResourceDistributor().getResource(new TenantIdentifier(null, null, null), RESOURCE_ID); + } catch (TenantOrAppNotFoundException e) { + return (LoadOnlyCUDTest.PerAppCronjob) main.getResourceDistributor() + .setResource(new TenantIdentifier(null, null, null), RESOURCE_ID, new LoadOnlyCUDTest.PerAppCronjob(main, tenantsInfo)); + } + } + + @Override + public int getIntervalTimeSeconds() { + return 1; + } + + @Override + public int getInitialWaitTimeSeconds() { + return 0; + } + + @Override + protected void doTaskPerApp(AppIdentifier app) throws Exception { + appIdentifiers.add(app); + } + } +} \ No newline at end of file From a260afc5460f396abc12822065338d784047334f Mon Sep 17 00:00:00 2001 From: Sattvik Chakravarthy Date: Tue, 6 Feb 2024 18:36:16 +0530 Subject: [PATCH 2/5] fix: connection pool handling --- CHANGELOG.md | 1 + .../io/supertokens/storageLayer/StorageLayer.java | 11 ++++++++--- 2 files changed, 9 insertions(+), 3 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index ac2bf6358..a0db64080 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -10,6 +10,7 @@ to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). ## [6.0.17] - 2024-02-06 - Adds new config `supertokens_saas_load_only_cud` that makes the core instance load a particular CUD only, irrespective of the CUDs present in the db. +- Fixes connection pool handling when connection pool size changes for a tenant. ## [6.0.16] - 2023-11-03 diff --git a/src/main/java/io/supertokens/storageLayer/StorageLayer.java b/src/main/java/io/supertokens/storageLayer/StorageLayer.java index afa73ba5b..fc9476860 100644 --- a/src/main/java/io/supertokens/storageLayer/StorageLayer.java +++ b/src/main/java/io/supertokens/storageLayer/StorageLayer.java @@ -242,7 +242,7 @@ public static void loadAllTenantStorage(Main main, TenantConfig[] tenants) } main.getResourceDistributor().clearAllResourcesWithResourceKey(RESOURCE_KEY); - Set userPoolsInUse = new HashSet<>(); + Set uniquePoolsInUse = new HashSet<>(); for (ResourceDistributor.KeyClass key : resourceKeyToStorageMap.keySet()) { Storage currStorage = resourceKeyToStorageMap.get(key); @@ -259,11 +259,16 @@ public static void loadAllTenantStorage(Main main, TenantConfig[] tenants) main.getResourceDistributor().setResource(key.getTenantIdentifier(), RESOURCE_KEY, new StorageLayer(resourceKeyToStorageMap.get(key))); - userPoolsInUse.add(userPoolId); + uniquePoolsInUse.add(uniqueId); } for (ResourceDistributor.KeyClass key : existingStorageMap.keySet()) { - if (!userPoolsInUse.contains(((StorageLayer) existingStorageMap.get(key)).storage.getUserPoolId())) { + Storage existingStorage = ((StorageLayer) existingStorageMap.get(key)).storage; + String userPoolId = existingStorage.getUserPoolId(); + String connectionPoolId = existingStorage.getConnectionPoolId(); + String uniqueId = userPoolId + "~" + connectionPoolId; + + if (!uniquePoolsInUse.contains(uniqueId)) { ((StorageLayer) existingStorageMap.get(key)).storage.close(); ((StorageLayer) existingStorageMap.get(key)).storage.stopLogging(); } From 5fa13439844ed6d76401b130b1c11a997a235ad2 Mon Sep 17 00:00:00 2001 From: Sattvik Chakravarthy Date: Wed, 7 Feb 2024 11:17:32 +0530 Subject: [PATCH 3/5] fix: tests --- .../io/supertokens/test/PathRouterTest.java | 48 ++++++++++++++----- .../test/multitenant/LoadOnlyCUDTest.java | 28 +++++++++++ 2 files changed, 65 insertions(+), 11 deletions(-) diff --git a/src/test/java/io/supertokens/test/PathRouterTest.java b/src/test/java/io/supertokens/test/PathRouterTest.java index 555007e24..0d5a6d409 100644 --- a/src/test/java/io/supertokens/test/PathRouterTest.java +++ b/src/test/java/io/supertokens/test/PathRouterTest.java @@ -1533,7 +1533,9 @@ protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws I public void tenantNotFoundTest3() throws InterruptedException, IOException, io.supertokens.httpRequest.HttpResponseException, InvalidConfigException, - io.supertokens.test.httpRequest.HttpResponseException, TenantOrAppNotFoundException { + io.supertokens.test.httpRequest.HttpResponseException, TenantOrAppNotFoundException, + InvalidProviderConfigException, StorageQueryException, FeatureNotEnabledException, + CannotModifyBaseConfigException, BadPermissionException { String[] args = {"../"}; Utils.setValueInConfig("host", "\"0.0.0.0\""); @@ -1556,15 +1558,26 @@ public void tenantNotFoundTest3() StorageLayer.getStorage(new TenantIdentifier(null, null, null), process.getProcess()) .modifyConfigToAddANewUserPoolForTesting(tenantConfig, 2); - Config.loadAllTenantConfig(process.getProcess(), new TenantConfig[]{ - new TenantConfig(new TenantIdentifier("localhost", null, null), new EmailPasswordConfig(false), + Multitenancy.addNewOrUpdateAppOrTenant( + process.getProcess(), + new TenantConfig( + new TenantIdentifier("localhost", null, null), + new EmailPasswordConfig(false), new ThirdPartyConfig(false, new ThirdPartyConfig.Provider[0]), new PasswordlessConfig(false), tenantConfig), - new TenantConfig(new TenantIdentifier("localhost", null, "t1"), new EmailPasswordConfig(false), + false + ); + Multitenancy.addNewOrUpdateAppOrTenant( + process.getProcess(), + new TenantConfig( + new TenantIdentifier("localhost", null, "t1"), + new EmailPasswordConfig(false), new ThirdPartyConfig(false, new ThirdPartyConfig.Provider[0]), new PasswordlessConfig(false), - tenantConfig)}, new ArrayList<>()); + tenantConfig), + false + ); Webserver.getInstance(process.getProcess()).addAPI(new WebserverAPI(process.getProcess(), "") { @@ -2788,7 +2801,9 @@ protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws I public void tenantNotFoundWithAppIdTest3() throws InterruptedException, IOException, io.supertokens.httpRequest.HttpResponseException, InvalidConfigException, - io.supertokens.test.httpRequest.HttpResponseException, TenantOrAppNotFoundException { + io.supertokens.test.httpRequest.HttpResponseException, TenantOrAppNotFoundException, + InvalidProviderConfigException, StorageQueryException, FeatureNotEnabledException, + CannotModifyBaseConfigException, BadPermissionException { String[] args = {"../"}; Utils.setValueInConfig("host", "\"0.0.0.0\""); @@ -2811,15 +2826,26 @@ public void tenantNotFoundWithAppIdTest3() StorageLayer.getStorage(new TenantIdentifier(null, null, null), process.getProcess()) .modifyConfigToAddANewUserPoolForTesting(tenantConfig, 2); - Config.loadAllTenantConfig(process.getProcess(), new TenantConfig[]{ - new TenantConfig(new TenantIdentifier("localhost", null, null), new EmailPasswordConfig(false), + Multitenancy.addNewOrUpdateAppOrTenant( + process.getProcess(), + new TenantConfig( + new TenantIdentifier("localhost", null, null), + new EmailPasswordConfig(false), new ThirdPartyConfig(false, new ThirdPartyConfig.Provider[0]), new PasswordlessConfig(false), tenantConfig), - new TenantConfig(new TenantIdentifier("localhost", "app1", "t1"), new EmailPasswordConfig(false), + false + ); + Multitenancy.addNewOrUpdateAppOrTenant( + process.getProcess(), + new TenantConfig( + new TenantIdentifier("localhost", "app1", "t1"), + new EmailPasswordConfig(false), new ThirdPartyConfig(false, new ThirdPartyConfig.Provider[0]), new PasswordlessConfig(false), - tenantConfig)}, new ArrayList<>()); + tenantConfig), + false + ); Webserver.getInstance(process.getProcess()).addAPI(new WebserverAPI(process.getProcess(), "") { @@ -2875,4 +2901,4 @@ protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws I process.kill(); assertNotNull(process.checkOrWaitForEvent(ProcessState.PROCESS_STATE.STOPPED)); } -} +} \ No newline at end of file diff --git a/src/test/java/io/supertokens/test/multitenant/LoadOnlyCUDTest.java b/src/test/java/io/supertokens/test/multitenant/LoadOnlyCUDTest.java index 05a8d4544..0973cb659 100644 --- a/src/test/java/io/supertokens/test/multitenant/LoadOnlyCUDTest.java +++ b/src/test/java/io/supertokens/test/multitenant/LoadOnlyCUDTest.java @@ -68,6 +68,14 @@ public void testAPIChecksForLoadOnlyCUD() throws Exception { process.startProcess(); assertNotNull(process.checkOrWaitForEvent(ProcessState.PROCESS_STATE.STARTED)); + if (StorageLayer.getStorage(process.getProcess()).getType() != STORAGE_TYPE.SQL) { + return; + } + + if (StorageLayer.isInMemDb(process.getProcess())) { + return; + } + JsonObject coreConfig = new JsonObject(); StorageLayer.getStorage(process.getProcess()).modifyConfigToAddANewUserPoolForTesting(coreConfig, 1); @@ -118,6 +126,10 @@ public void testCreationOfCUDWithLoadOnlyCUD() throws Exception { return; } + if (StorageLayer.isInMemDb(process.getProcess())) { + return; + } + try { TestMultitenancyAPIHelper.createConnectionUriDomain(process.getProcess(), TenantIdentifier.BASE_TENANT, "localhost:3567", true, true, true, new JsonObject()); @@ -146,6 +158,14 @@ public void testThatResourcesAreNotLoadedWithLoadOnlyCUD() throws Exception { process.startProcess(); assertNotNull(process.checkOrWaitForEvent(ProcessState.PROCESS_STATE.STARTED)); + if (StorageLayer.getStorage(process.getProcess()).getType() != STORAGE_TYPE.SQL) { + return; + } + + if (StorageLayer.isInMemDb(process.getProcess())) { + return; + } + JsonObject coreConfig = new JsonObject(); StorageLayer.getStorage(process.getProcess()).modifyConfigToAddANewUserPoolForTesting(coreConfig, 1); TestMultitenancyAPIHelper.createConnectionUriDomain(process.getProcess(), TenantIdentifier.BASE_TENANT, @@ -185,6 +205,14 @@ public void testCronDoesNotRunForOtherCUDsWithLoadOnlyCUD() throws Exception { process.startProcess(); assertNotNull(process.checkOrWaitForEvent(ProcessState.PROCESS_STATE.STARTED)); + if (StorageLayer.getStorage(process.getProcess()).getType() != STORAGE_TYPE.SQL) { + return; + } + + if (StorageLayer.isInMemDb(process.getProcess())) { + return; + } + JsonObject coreConfig = new JsonObject(); StorageLayer.getStorage(process.getProcess()).modifyConfigToAddANewUserPoolForTesting(coreConfig, 1); TestMultitenancyAPIHelper.createConnectionUriDomain(process.getProcess(), TenantIdentifier.BASE_TENANT, From 5a3ffa89adeb6007cfd072cc089bae8646c45176 Mon Sep 17 00:00:00 2001 From: Sattvik Chakravarthy Date: Wed, 7 Feb 2024 11:31:15 +0530 Subject: [PATCH 4/5] fix: version update --- build.gradle | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/build.gradle b/build.gradle index 7b1e47f56..9a6addf6f 100644 --- a/build.gradle +++ b/build.gradle @@ -19,7 +19,7 @@ compileTestJava { options.encoding = "UTF-8" } // } //} -version = "6.0.16" +version = "6.0.17" repositories { From cee3dddcf6fc454b459551a579a316b3318156ea Mon Sep 17 00:00:00 2001 From: Sattvik Chakravarthy Date: Fri, 9 Feb 2024 23:55:22 +0530 Subject: [PATCH 5/5] fix: tests --- .../io/supertokens/test/TestGetUserSpeed.java | 159 ++++++++++++++++++ 1 file changed, 159 insertions(+) create mode 100644 src/test/java/io/supertokens/test/TestGetUserSpeed.java diff --git a/src/test/java/io/supertokens/test/TestGetUserSpeed.java b/src/test/java/io/supertokens/test/TestGetUserSpeed.java new file mode 100644 index 000000000..f51e2654a --- /dev/null +++ b/src/test/java/io/supertokens/test/TestGetUserSpeed.java @@ -0,0 +1,159 @@ +/* + * Copyright (c) 2023, VRAI Labs and/or its affiliates. All rights reserved. + * + * This software is licensed under the Apache License, Version 2.0 (the + * "License") as published by the Apache Software Foundation. + * + * You may not use this file except in compliance with the License. You may + * obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations + * under the License. + */ + +package io.supertokens.test; + +import io.supertokens.ProcessState; +import io.supertokens.featureflag.EE_FEATURES; +import io.supertokens.featureflag.FeatureFlagTestContent; +import io.supertokens.pluginInterface.STORAGE_TYPE; +import io.supertokens.pluginInterface.authRecipe.AuthRecipeUserInfo; +import io.supertokens.storageLayer.StorageLayer; +import io.supertokens.thirdparty.ThirdParty; +import org.junit.AfterClass; +import org.junit.Before; +import org.junit.Rule; +import org.junit.Test; +import org.junit.rules.TestRule; + +import java.util.ArrayList; +import java.util.List; +import java.util.concurrent.ExecutorService; +import java.util.concurrent.Executors; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.locks.Lock; +import java.util.concurrent.locks.ReentrantLock; + +import static org.junit.Assert.assertNotNull; + +public class TestGetUserSpeed { + @Rule + public TestRule watchman = Utils.getOnFailure(); + + @AfterClass + public static void afterTesting() { + Utils.afterTesting(); + } + + @Before + public void beforeEach() { + Utils.reset(); + } + + public void testUserCreationLinkingAndGetByIdSpeedsCommon(TestingProcessManager.TestingProcess process, + long createTime, long getTime) throws Exception { + + if (StorageLayer.getStorage(process.getProcess()).getType() != STORAGE_TYPE.SQL) { + return; + } + + if (StorageLayer.isInMemDb(process.getProcess())) { + return; + } + + int numberOfUsers = 10000; + + List userIds = new ArrayList<>(); + List userIds2 = new ArrayList<>(); + Lock lock = new ReentrantLock(); + { + ExecutorService es = Executors.newFixedThreadPool(32); + long start = System.currentTimeMillis(); + for (int i = 0; i < numberOfUsers; i++) { + int finalI = i; + es.execute(() -> { + try { + String email = "user" + finalI + "@example.com"; + AuthRecipeUserInfo user = ThirdParty.signInUp( + process.getProcess(), "google", "googleid" + finalI, email).user; + lock.lock(); + userIds.add(user.id); + userIds2.add(user.id); + lock.unlock(); + } catch (Exception e) { + throw new RuntimeException(e); + } + }); + } + es.shutdown(); + es.awaitTermination(5, TimeUnit.MINUTES); + + long end = System.currentTimeMillis(); + System.out.println("Created users " + numberOfUsers + " in " + (end - start) + "ms"); + assert end - start < createTime; // 25 sec + } + + Thread.sleep(10000); // wait for index + + Thread.sleep(10000); // wait for index + + { + ExecutorService es = Executors.newFixedThreadPool(32); + long start = System.currentTimeMillis(); + for (String userId : userIds2) { + es.execute(() -> { + try { + ThirdParty.getUser(process.getProcess(), userId); + } catch (Exception e) { + throw new RuntimeException(e); + } + }); + } + es.shutdown(); + es.awaitTermination(5, TimeUnit.MINUTES); + long end = System.currentTimeMillis(); + System.out.println("Time taken for " + numberOfUsers + " users: " + (end - start) + "ms"); + assert end - start < getTime; // 20 sec + } + + process.kill(); + assertNotNull(process.checkOrWaitForEvent(ProcessState.PROCESS_STATE.STOPPED)); + } + + @Test + public void testUserCreationLinkingAndGetByIdSpeedsWithoutMinIdle() throws Exception { + String[] args = {"../"}; + TestingProcessManager.TestingProcess process = TestingProcessManager.start(args, false); + Utils.setValueInConfig("postgresql_connection_pool_size", "100"); + Utils.setValueInConfig("mysql_connection_pool_size", "100"); + + FeatureFlagTestContent.getInstance(process.getProcess()) + .setKeyValue(FeatureFlagTestContent.ENABLED_FEATURES, new EE_FEATURES[]{ + EE_FEATURES.ACCOUNT_LINKING, EE_FEATURES.MULTI_TENANCY}); + process.startProcess(); + assertNotNull(process.checkOrWaitForEvent(ProcessState.PROCESS_STATE.STARTED)); + + testUserCreationLinkingAndGetByIdSpeedsCommon(process, 25000, 5000); + } + + @Test + public void testUserCreationLinkingAndGetByIdSpeedsWithMinIdle() throws Exception { + String[] args = {"../"}; + TestingProcessManager.TestingProcess process = TestingProcessManager.start(args, false); + Utils.setValueInConfig("postgresql_connection_pool_size", "100"); + Utils.setValueInConfig("mysql_connection_pool_size", "100"); + Utils.setValueInConfig("postgresql_minimum_idle_connections", "1"); + Utils.setValueInConfig("mysql_minimum_idle_connections", "1"); + + FeatureFlagTestContent.getInstance(process.getProcess()) + .setKeyValue(FeatureFlagTestContent.ENABLED_FEATURES, new EE_FEATURES[]{ + EE_FEATURES.ACCOUNT_LINKING, EE_FEATURES.MULTI_TENANCY}); + process.startProcess(); + assertNotNull(process.checkOrWaitForEvent(ProcessState.PROCESS_STATE.STARTED)); + + testUserCreationLinkingAndGetByIdSpeedsCommon(process, 60000, 5000); + } +} \ No newline at end of file