diff --git a/src/factory/node.js b/src/factory/node.js index ae4ff1c29..8bd955726 100644 --- a/src/factory/node.js +++ b/src/factory/node.js @@ -3,13 +3,14 @@ import { syncManagerOnlineFactory } from '@splitsoftware/splitio-commons/src/syn import { pushManagerFactory } from '@splitsoftware/splitio-commons/src/sync/streaming/pushManager'; import { pollingManagerSSFactory } from '@splitsoftware/splitio-commons/src/sync/polling/pollingManagerSS'; import { InRedisStorage } from '@splitsoftware/splitio-commons/src/storages/inRedis'; +import { PluggableStorage } from '@splitsoftware/splitio-commons/src/storages/pluggable'; import { InMemoryStorageFactory } from '@splitsoftware/splitio-commons/src/storages/inMemory/InMemoryStorage'; import { getRolloutPlan } from '@splitsoftware/splitio-commons/src/storages/getRolloutPlan'; import { sdkManagerFactory } from '@splitsoftware/splitio-commons/src/sdkManager'; import { sdkClientMethodFactory } from '@splitsoftware/splitio-commons/src/sdkClient/sdkClientMethod'; import { impressionObserverSSFactory } from '@splitsoftware/splitio-commons/src/trackers/impressionObserver/impressionObserverSS'; import { sdkFactory } from '@splitsoftware/splitio-commons/src/sdkFactory'; -import { CONSUMER_MODE, LOCALHOST_MODE } from '@splitsoftware/splitio-commons/src/utils/constants'; +import { CONSUMER_MODE, LOCALHOST_MODE, STORAGE_REDIS, STORAGE_PLUGGABLE } from '@splitsoftware/splitio-commons/src/utils/constants'; import { isConsumerMode } from '@splitsoftware/splitio-commons/src/utils/settingsValidation/mode'; import { localhostFromFileFactory } from '../sync/offline/LocalhostFromFile'; @@ -20,9 +21,14 @@ import { bloomFilterFactory } from '../platform/filter/bloomFilter'; const syncManagerOnlineSSFactory = syncManagerOnlineFactory(pollingManagerSSFactory, pushManagerFactory); function getStorage(settings) { - return settings.storage.type === 'REDIS' ? - InRedisStorage(settings.storage) : - InMemoryStorageFactory; + switch (settings.storage.type) { + case STORAGE_REDIS: + return InRedisStorage(settings.storage); + case STORAGE_PLUGGABLE: + return PluggableStorage({ prefix: settings.storage.prefix, wrapper: settings.storage.options.wrapper }); + default: + return InMemoryStorageFactory; + } } /** diff --git a/src/settings/__tests__/node.spec.js b/src/settings/__tests__/node.spec.js index b3ee95074..e2f006ed3 100644 --- a/src/settings/__tests__/node.spec.js +++ b/src/settings/__tests__/node.spec.js @@ -3,7 +3,7 @@ import sinon from 'sinon'; import osFunction from 'os'; import * as ipFunction from '../../utils/ip'; import { settingsFactory } from '../node'; -import { CONSUMER_MODE, NA } from '@splitsoftware/splitio-commons/src/utils/constants'; +import { CONSUMER_MODE, CONSUMER_PARTIAL_MODE, NA } from '@splitsoftware/splitio-commons/src/utils/constants'; const IP_VALUE = ipFunction.address(); const HOSTNAME_VALUE = osFunction.hostname(); @@ -96,7 +96,7 @@ tape('SETTINGS / IPAddressesEnabled should be overwritable and true by default', assert.end(); }); -tape('SETTINGS / Throws exception if no "REDIS" storage is provided in consumer mode', (assert) => { +tape('SETTINGS / Throws exception if no async storage is provided in consumer mode', (assert) => { const config = { core: { authorizationKey: 'dummy token' @@ -106,13 +106,13 @@ tape('SETTINGS / Throws exception if no "REDIS" storage is provided in consumer assert.throws(() => { settingsFactory(config); - }, /A REDIS storage is required on consumer mode/); + }, /A REDIS or PLUGGABLE storage is required on consumer mode/); assert.throws(() => { settingsFactory({ ...config, storage: { type: 'invalid type' } }); - }, /A REDIS storage is required on consumer mode/); + }, /A REDIS or PLUGGABLE storage is required on consumer mode/); assert.end(); }); @@ -144,6 +144,42 @@ tape('SETTINGS / Log error and fallback to InMemory storage if no valid storage assert.end(); }); +tape('SETTINGS / PLUGGABLE storage is accepted in consumer modes', (assert) => { + const wrapper = { get: () => {}, set: () => {} }; + + [CONSUMER_MODE, CONSUMER_PARTIAL_MODE].forEach(mode => { + const settings = settingsFactory({ + core: { authorizationKey: 'dummy token' }, + mode, + storage: { type: 'PLUGGABLE', prefix: 'test_prefix', options: { wrapper } } + }); + + assert.deepEqual(settings.storage, { + type: 'PLUGGABLE', prefix: 'test_prefix', options: { wrapper } + }, `Pluggable storage settings and options should be passed through in ${mode} mode.`); + }); + + assert.end(); +}); + +tape('SETTINGS / Log error and fallback to InMemory storage if PLUGGABLE storage is used outside consumer modes', (assert) => { + const logSpy = sinon.spy(console, 'log'); + + const settings = settingsFactory({ + core: { authorizationKey: 'dummy token' }, // standalone mode + storage: { type: 'PLUGGABLE', options: { wrapper: {} } }, + debug: 'ERROR' + }); + + assert.deepEqual(logSpy.args, [ + ['[ERROR] splitio => The provided PLUGGABLE storage is invalid for this mode. It requires consumer mode. Fallback into default MEMORY storage.'] + ], 'logs error message'); + assert.equal(settings.storage.type, 'MEMORY', 'fallbacks to memory storage'); + + logSpy.restore(); + assert.end(); +}); + tape('SETTINGS / Consent is not overwritable in server-side', assert => { const settings = settingsFactory({ userConsent: 'UNKNOWN' }); diff --git a/src/settings/storage/node.js b/src/settings/storage/node.js index a38f1651d..482e7b237 100644 --- a/src/settings/storage/node.js +++ b/src/settings/storage/node.js @@ -1,4 +1,4 @@ -import { LOCALHOST_MODE, STORAGE_MEMORY, STORAGE_REDIS, CONSUMER_MODE, STANDALONE_MODE } from '@splitsoftware/splitio-commons/src/utils/constants'; +import { LOCALHOST_MODE, STORAGE_MEMORY, STORAGE_REDIS, STORAGE_PLUGGABLE, CONSUMER_MODE, STANDALONE_MODE } from '@splitsoftware/splitio-commons/src/utils/constants'; export function validateStorage(settings) { const { @@ -11,7 +11,7 @@ export function validateStorage(settings) { } = { type: STORAGE_MEMORY } } = settings; - // We can have MEMORY, REDIS or an invalid storage type + // We can have MEMORY, REDIS, PLUGGABLE or an invalid storage type switch (type) { case STORAGE_REDIS: { // If passing REDIS storage in localhost or standalone mode, we log an error and fallback to MEMORY storage @@ -68,11 +68,28 @@ export function validateStorage(settings) { }; } + case STORAGE_PLUGGABLE: { + // If passing PLUGGABLE storage in localhost or standalone mode, we log an error and fallback to MEMORY storage + if (mode === STANDALONE_MODE || mode === LOCALHOST_MODE) { + log.error('The provided PLUGGABLE storage is invalid for this mode. It requires consumer mode. Fallback into default MEMORY storage.'); + return { + type: STORAGE_MEMORY, + prefix + }; + } + + return { + type, + prefix, + options + }; + } + // For now, we don't have modifiers or settings for MEMORY in Node.js case STORAGE_MEMORY: default: { - // If passing MEMORY storage in consumer mode, throw an error (no way to fallback to REDIS storage) - if (mode === CONSUMER_MODE) throw new Error('A REDIS storage is required on consumer mode'); + // If passing MEMORY storage in consumer mode, throw an error (no way to fallback to an async storage) + if (mode === CONSUMER_MODE) throw new Error('A REDIS or PLUGGABLE storage is required on consumer mode'); // If passing an invalid storage type, log an error if (type !== STORAGE_MEMORY) log.error(`The provided '${type}' storage type is invalid. Fallback into default MEMORY storage.`); return {