From 9283a570d729bbf16b3c4382422c41049fb80172 Mon Sep 17 00:00:00 2001 From: Matias Melograno Date: Tue, 27 Apr 2021 13:56:42 -0300 Subject: [PATCH 1/2] removing old uwsgi cache --- CHANGES.txt | 3 +- splitio/client/config.py | 3 - splitio/client/factory.py | 36 -- splitio/client/util.py | 1 - splitio/engine/cache/lru.py | 4 +- splitio/engine/impressions.py | 1 - splitio/models/datatypes.py | 5 + splitio/models/notification.py | 59 +- splitio/models/token.py | 18 +- splitio/push/manager.py | 2 +- splitio/storage/adapters/cache_trait.py | 8 +- splitio/storage/adapters/util.py | 4 +- splitio/storage/adapters/uwsgi_cache.py | 133 ----- splitio/storage/uwsgi.py | 746 ------------------------ splitio/sync/segment.py | 1 - splitio/tasks/__init__.py | 1 + splitio/tasks/segment_sync.py | 1 - splitio/tasks/telemetry_sync.py | 2 - splitio/tasks/uwsgi_wrappers.py | 192 ------ splitio/version.py | 2 +- tests/client/test_config.py | 1 - tests/client/test_factory.py | 24 +- tests/client/test_input_validator.py | 6 - tests/storage/test_uwsgi.py | 320 ---------- tests/tasks/test_uwsgi_wrappers.py | 137 ----- 25 files changed, 59 insertions(+), 1651 deletions(-) delete mode 100644 splitio/storage/adapters/uwsgi_cache.py delete mode 100644 splitio/storage/uwsgi.py delete mode 100644 splitio/tasks/uwsgi_wrappers.py delete mode 100644 tests/storage/test_uwsgi.py delete mode 100644 tests/tasks/test_uwsgi_wrappers.py diff --git a/CHANGES.txt b/CHANGES.txt index 43f26b69..2ba02431 100644 --- a/CHANGES.txt +++ b/CHANGES.txt @@ -1,7 +1,8 @@ -9.0.0 (Apr 21, 2021) +9.0.0 (Apr 28, 2021) - BREAKING CHANGE: Deprecated Python2. - Removed six, future and futures libs for compatibility between Python2 and Python3. - Updated strings encoding to utf-8 by default for Redis. + - Deprecated uWSGI cache. 8.4.1 (Apr 16, 2021) - Bumped mmh3cffi dependency which now requires c99 flag to build. diff --git a/splitio/client/config.py b/splitio/client/config.py index 98dbfec8..0bdb9843 100644 --- a/splitio/client/config.py +++ b/splitio/client/config.py @@ -74,9 +74,6 @@ def _parse_operation_mode(apikey, config): if 'redisHost' in config or 'redisSentinels' in config: return 'redis-consumer' - if 'uwsgiClient' in config: - return 'uwsgi-consumer' - return 'inmemory-standalone' diff --git a/splitio/client/factory.py b/splitio/client/factory.py index 28389094..e2e56990 100644 --- a/splitio/client/factory.py +++ b/splitio/client/factory.py @@ -19,9 +19,6 @@ from splitio.storage.adapters import redis from splitio.storage.redis import RedisSplitStorage, RedisSegmentStorage, RedisImpressionsStorage, \ RedisEventsStorage, RedisTelemetryStorage -from splitio.storage.adapters.uwsgi_cache import get_uwsgi -from splitio.storage.uwsgi import UWSGIEventStorage, UWSGIImpressionStorage, UWSGISegmentStorage, \ - UWSGISplitStorage, UWSGITelemetryStorage # APIs from splitio.api.client import HttpClient @@ -420,36 +417,6 @@ def _build_redis_factory(api_key, cfg): ) -def _build_uwsgi_factory(api_key, cfg): - """Build and return a split factory with redis-based storage.""" - sdk_metadata = util.get_metadata(cfg) - uwsgi_adapter = get_uwsgi() - storages = { - 'splits': UWSGISplitStorage(uwsgi_adapter), - 'segments': UWSGISegmentStorage(uwsgi_adapter), - 'impressions': UWSGIImpressionStorage(uwsgi_adapter), - 'events': UWSGIEventStorage(uwsgi_adapter), - 'telemetry': UWSGITelemetryStorage(uwsgi_adapter) - } - recorder = StandardRecorder( - ImpressionsManager(cfg['impressionsMode'], True, - _wrap_impression_listener(cfg['impressionListener'], sdk_metadata)), - storages['telemetry'], - storages['events'], - storages['impressions'], - ) - _LOGGER.warning( - "Beware: uwsgi-cache based operation mode is soon to be deprecated. Please consider " + - "redis if you need a centralized point of syncrhonization, or in-memory (with preforking " + - "support enabled) if running uwsgi with a master and several http workers)") - return SplitFactory( - api_key, - storages, - cfg['labelsEnabled'], - recorder, - ) - - def _build_localhost_factory(cfg): """Build and return a localhost factory for testing/development purposes.""" storages = { @@ -521,9 +488,6 @@ def get_factory(api_key, **kwargs): if config['operationMode'] == 'redis-consumer': return _build_redis_factory(api_key, config) - if config['operationMode'] == 'uwsgi-consumer': - return _build_uwsgi_factory(api_key, config) - return _build_in_memory_factory( api_key, config, diff --git a/splitio/client/util.py b/splitio/client/util.py index f37ffe6e..040a09ae 100644 --- a/splitio/client/util.py +++ b/splitio/client/util.py @@ -1,6 +1,5 @@ """General purpose SDK utilities.""" -import inspect import socket from collections import namedtuple from splitio.version import __version__ diff --git a/splitio/engine/cache/lru.py b/splitio/engine/cache/lru.py index d1a3395c..2f720a35 100644 --- a/splitio/engine/cache/lru.py +++ b/splitio/engine/cache/lru.py @@ -5,7 +5,7 @@ DEFAULT_MAX_SIZE = 5000 -class SimpleLruCache(object): #pylint: disable=too-many-instance-attributes +class SimpleLruCache(object): # pylint: disable=too-many-instance-attributes """ Key/Value local memory cache. with expiration & LRU eviction. @@ -21,7 +21,7 @@ class SimpleLruCache(object): #pylint: disable=too-many-instance-attributes None <---next--- || node || <---next--- || node || ... <---next--- || node || """ - class _Node(object): #pylint: disable=too-few-public-methods + class _Node(object): # pylint: disable=too-few-public-methods """Links to previous an next items in the circular list.""" def __init__(self, key, value, previous_element, next_element): diff --git a/splitio/engine/impressions.py b/splitio/engine/impressions.py index a1184867..c8720b5d 100644 --- a/splitio/engine/impressions.py +++ b/splitio/engine/impressions.py @@ -1,5 +1,4 @@ """Split evaluator module.""" -import logging import threading from collections import defaultdict, namedtuple from enum import Enum diff --git a/splitio/models/datatypes.py b/splitio/models/datatypes.py index 7cbe466a..751c2908 100644 --- a/splitio/models/datatypes.py +++ b/splitio/models/datatypes.py @@ -1,5 +1,6 @@ """Datatypes converters for matchers.""" + def ts_truncate_seconds(timestamp): """ Set seconds to zero in a timestamp. @@ -12,6 +13,7 @@ def ts_truncate_seconds(timestamp): """ return timestamp - (timestamp % 60) + def ts_truncate_time(timestamp): """ Set time to zero in a timestamp. @@ -24,6 +26,7 @@ def ts_truncate_time(timestamp): """ return timestamp - (timestamp % 86400) + def java_ts_to_secs(java_ts): """ Convert java timestamp into unix timestamp. @@ -36,6 +39,7 @@ def java_ts_to_secs(java_ts): """ return java_ts / 1000 + def java_ts_truncate_seconds(java_ts): """ Set seconds to zero in a timestamp. @@ -48,6 +52,7 @@ def java_ts_truncate_seconds(java_ts): """ return ts_truncate_seconds(java_ts_to_secs(java_ts)) + def java_ts_truncate_time(java_ts): """ Set time to zero in a timestamp. diff --git a/splitio/models/notification.py b/splitio/models/notification.py index 68915130..ebe57175 100644 --- a/splitio/models/notification.py +++ b/splitio/models/notification.py @@ -40,7 +40,7 @@ def __init__(self, channel, notification_type, control_type): self._channel = channel self._notification_type = Type(notification_type) self._control_type = Control(control_type) - + @property def channel(self): return self._channel @@ -87,7 +87,7 @@ def change_number(self): @property def notification_type(self): return self._notification_type - + @property def segment_name(self): return self._segment_name @@ -111,7 +111,7 @@ def __init__(self, channel, notification_type, change_number): self._channel = channel self._notification_type = Type(notification_type) self._change_number = change_number - + @property def channel(self): return self._channel @@ -149,23 +149,23 @@ def __init__(self, channel, notification_type, change_number, default_treatment, self._change_number = change_number self._default_treatment = default_treatment self._split_name = split_name - + @property def channel(self): return self._channel - + @property def change_number(self): return self._change_number - + @property def default_treatment(self): return self._default_treatment - + @property def notification_type(self): return self._notification_type - + @property def split_name(self): return self._split_name @@ -178,25 +178,26 @@ def split_name(self): Type.CONTROL: lambda c, d: ControlNotification(c, Type.CONTROL, d['controlType']) } -def wrap_notification(raw_data, channel): - """ - Parse notification from raw notification payload - :param raw_data: data - :type raw_data: str - :param channel: Channel of incoming notification - :type channel: str - """ - try: - if channel is None: - raise ValueError("channel cannot be None.") - raw_data = json.loads(raw_data) - notification_type = Type(raw_data['type']) - mapper = _NOTIFICATION_MAPPERS[notification_type] - return mapper(channel, raw_data) - except ValueError: - raise ValueError("Wrong notification type received.") - except KeyError: - raise KeyError("Could not parse notification.") - except TypeError: - raise TypeError("Wrong JSON format.") +def wrap_notification(raw_data, channel): + """ + Parse notification from raw notification payload + + :param raw_data: data + :type raw_data: str + :param channel: Channel of incoming notification + :type channel: str + """ + try: + if channel is None: + raise ValueError("channel cannot be None.") + raw_data = json.loads(raw_data) + notification_type = Type(raw_data['type']) + mapper = _NOTIFICATION_MAPPERS[notification_type] + return mapper(channel, raw_data) + except ValueError: + raise ValueError("Wrong notification type received.") + except KeyError: + raise KeyError("Could not parse notification.") + except TypeError: + raise TypeError("Wrong JSON format.") diff --git a/splitio/models/token.py b/splitio/models/token.py index 3c050d57..33c4f48c 100644 --- a/splitio/models/token.py +++ b/splitio/models/token.py @@ -3,6 +3,7 @@ import base64 import json + class Token(object): """Token object class.""" @@ -30,27 +31,27 @@ def __init__(self, push_enabled, token, channels, exp, iat): self._channels = channels self._exp = exp self._iat = iat - + @property def push_enabled(self): """Return push_enabled""" return self._push_enabled - + @property def token(self): """Return token""" return self._token - + @property def channels(self): """Return channels""" return self._channels - + @property def exp(self): """Return exp""" return self._exp - + @property def iat(self): """Return iat""" @@ -66,15 +67,16 @@ def decode_token(raw_token): push_enabled = raw_token['pushEnabled'] if not push_enabled or len(token.strip()) == 0: return None, None, None - + token_parts = token.split('.') if len(token_parts) < 2: return None, None, None - + to_decode = token_parts[1] - decoded_payload = base64.b64decode(to_decode + '='*(-len(to_decode) % 4)) + decoded_payload = base64.b64decode(to_decode + '='*(-len(to_decode) % 4)) return push_enabled, token, json.loads(decoded_payload) + def from_raw(raw_token): """ Parse a new token from a raw token response. diff --git a/splitio/push/manager.py b/splitio/push/manager.py index 55814c7b..1e529b66 100644 --- a/splitio/push/manager.py +++ b/splitio/push/manager.py @@ -114,7 +114,7 @@ def _event_handler(self, event): try: handle(parsed) - except Exception: #pylint:disable=broad-except + except Exception: # pylint:disable=broad-except _LOGGER.error('something went wrong when processing message of type %s', parsed.event_type) _LOGGER.debug(str(parsed), exc_info=True) diff --git a/splitio/storage/adapters/cache_trait.py b/splitio/storage/adapters/cache_trait.py index d3db3b67..399ee383 100644 --- a/splitio/storage/adapters/cache_trait.py +++ b/splitio/storage/adapters/cache_trait.py @@ -9,7 +9,7 @@ DEFAULT_MAX_SIZE = 100 -class LocalMemoryCache(object): #pylint: disable=too-many-instance-attributes +class LocalMemoryCache(object): # pylint: disable=too-many-instance-attributes """ Key/Value local memory cache. with expiration & LRU eviction. @@ -25,10 +25,10 @@ class LocalMemoryCache(object): #pylint: disable=too-many-instance-attributes None <---next--- || node || <---next--- || node || ... <---next--- || node || """ - class _Node(object): #pylint: disable=too-few-public-methods + class _Node(object): # pylint: disable=too-few-public-methods """Links to previous an next items in the circular list.""" - def __init__(self, key, value, last_update, previous_element, next_element): #pylint: disable=too-many-arguments + def __init__(self, key, value, last_update, previous_element, next_element): # pylint: disable=too-many-arguments """Class constructor.""" self.key = key # we also keep the key for O(1) access when removing the LRU. self.value = value @@ -186,7 +186,7 @@ def _decorator(user_function): _cache = LocalMemoryCache(key_func, user_function, max_age_seconds, max_size) # The lambda below IS necessary, otherwise update_wrapper fails since the function # is an instance method and has no reference to the __module__ namespace. - wrapper = lambda *args, **kwargs: _cache.get(*args, **kwargs) #pylint: disable=unnecessary-lambda + wrapper = lambda *args, **kwargs: _cache.get(*args, **kwargs) # pylint: disable=unnecessary-lambda return update_wrapper(wrapper, user_function) return _decorator diff --git a/splitio/storage/adapters/util.py b/splitio/storage/adapters/util.py index f8602602..cf80a2ad 100644 --- a/splitio/storage/adapters/util.py +++ b/splitio/storage/adapters/util.py @@ -1,7 +1,7 @@ """Custom utilities.""" -class DynamicDecorator(object): #pylint: disable=too-few-public-methods +class DynamicDecorator(object): # pylint: disable=too-few-public-methods """ Decorator that will inject a decorator during class construction. @@ -65,7 +65,7 @@ def __call__(self, to_decorate): positional_args_lambdas = self._positional_args_lambdas keyword_args_lambdas = self._keyword_args_lambdas - class _decorated(to_decorate): #pylint: disable=too-few-public-methods + class _decorated(to_decorate): # pylint: disable=too-few-public-methods """ Decorated class wrapper. diff --git a/splitio/storage/adapters/uwsgi_cache.py b/splitio/storage/adapters/uwsgi_cache.py deleted file mode 100644 index 3cf41150..00000000 --- a/splitio/storage/adapters/uwsgi_cache.py +++ /dev/null @@ -1,133 +0,0 @@ -"""UWSGI Cache Storage adapter module.""" - -import time - -try: - # uwsgi is loaded at runtime by uwsgi app. - import uwsgi -except ImportError: - def missing_uwsgi_dependencies(*args, **kwargs): # pylint: disable=unused-argument - """Only complain for missing deps if they're used.""" - raise NotImplementedError('Missing uWSGI support dependencies.') - uwsgi = missing_uwsgi_dependencies - -# Cache used for locking & signaling keys -_SPLITIO_LOCK_CACHE_NAMESPACE = 'splitio_locks' - -# Cache where split definitions are stored -_SPLITIO_SPLITS_CACHE_NAMESPACE = 'splitio_splits' - -# Cache where segments are stored -_SPLITIO_SEGMENTS_CACHE_NAMESPACE = 'splitio_segments' - -# Cache where impressions are stored -_SPLITIO_IMPRESSIONS_CACHE_NAMESPACE = 'splitio_impressions' - -# Cache where metrics are stored -_SPLITIO_METRICS_CACHE_NAMESPACE = 'splitio_metrics' - -# Cache where events are stored (1 key with lots of blocks) -_SPLITIO_EVENTS_CACHE_NAMESPACE = 'splitio_events' - -# Cache where changeNumbers are stored -_SPLITIO_CHANGE_NUMBERS = 'splitio_changeNumbers' - -# Cache with a big block size used for lists -_SPLITIO_MISC_NAMESPACE = 'splitio_misc' - - -class UWSGILock(object): - """Context manager to be used for locking a key in the cache.""" - - def __init__(self, adapter, key, overwrite_lock_seconds=5): - """ - Initialize a lock with the key `key` and waits up to `overwrite_lock_seconds` to release. - - :param key: Key to be used. - :type key: str - - :param overwrite_lock_seconds: How many seconds to wait before force-releasing. - :type overwrite_lock_seconds: int - """ - self._key = key - self._overwrite_lock_seconds = overwrite_lock_seconds - self._uwsgi = adapter - - def __enter__(self): - """Loop until the lock is manually released or timeout occurs.""" - initial_time = time.time() - while True: - if not self._uwsgi.cache_exists(self._key, _SPLITIO_LOCK_CACHE_NAMESPACE): - self._uwsgi.cache_set(self._key, str('locked'), 0, _SPLITIO_LOCK_CACHE_NAMESPACE) - return - else: - if time.time() - initial_time > self._overwrite_lock_seconds: - return - time.sleep(0.1) - - def __exit__(self, *args): - """Remove lock.""" - self._uwsgi.cache_del(self._key, _SPLITIO_LOCK_CACHE_NAMESPACE) - - -class UWSGICacheEmulator(object): - """UWSGI mock.""" - - def __init__(self): - """ - UWSGI Cache Emulator for unit tests. Implements uwsgi cache framework interface. - - http://uwsgi-docs.readthedocs.io/en/latest/Caching.html#accessing-the-cache-from-your-applications-using-the-cache-api - """ - self._cache = dict() - - @staticmethod - def _check_string_data_type(value): - if type(value).__name__ == 'str': - return True - raise TypeError( - 'The value to add into uWSGI cache must be string and %s given' % type(value).__name__ - ) - - def cache_get(self, key, cache_namespace='default'): - """Get an element from cache.""" - if self.cache_exists(key, cache_namespace): - return self._cache[cache_namespace][key] - return None - - def cache_set(self, key, value, expires=0, cache_namespace='default'): # pylint: disable=unused-argument - """Set an elemen in the cache.""" - self._check_string_data_type(value) - - if cache_namespace in self._cache: - self._cache[cache_namespace][key] = value - else: - self._cache[cache_namespace] = {key: value} - - def cache_update(self, key, value, expires=0, cache_namespace='default'): - """Update an element.""" - self.cache_set(key, value, expires, cache_namespace) - - def cache_exists(self, key, cache_namespace='default'): - """Return whether the element exists.""" - if cache_namespace in self._cache: - if key in self._cache[cache_namespace]: - return True - return False - - def cache_del(self, key, cache_namespace='default'): - """Delete an item from the cache.""" - if cache_namespace in self._cache: - self._cache[cache_namespace].pop(key, None) - - def cache_clear(self, cache_namespace='default'): - """Delete all elements in cache.""" - self._cache.pop(cache_namespace, None) - - -def get_uwsgi(emulator=False): - """Return a uwsgi imported module or an emulator to use in unit test.""" - if emulator: - return UWSGICacheEmulator() - - return uwsgi diff --git a/splitio/storage/uwsgi.py b/splitio/storage/uwsgi.py deleted file mode 100644 index 47cc44e6..00000000 --- a/splitio/storage/uwsgi.py +++ /dev/null @@ -1,746 +0,0 @@ -"""UWSGI Cache based storages implementation module.""" -import logging -import json - -from splitio.storage import SplitStorage, SegmentStorage, ImpressionStorage, EventStorage, \ - TelemetryStorage -from splitio.models import splits, segments -from splitio.models.impressions import Impression -from splitio.models.events import Event -from splitio.storage.adapters.uwsgi_cache import _SPLITIO_CHANGE_NUMBERS, \ - _SPLITIO_EVENTS_CACHE_NAMESPACE, _SPLITIO_IMPRESSIONS_CACHE_NAMESPACE, \ - _SPLITIO_METRICS_CACHE_NAMESPACE, _SPLITIO_MISC_NAMESPACE, UWSGILock, \ - _SPLITIO_SEGMENTS_CACHE_NAMESPACE, _SPLITIO_SPLITS_CACHE_NAMESPACE, \ - _SPLITIO_LOCK_CACHE_NAMESPACE - - -_LOGGER = logging.getLogger(__name__) - - -class UWSGISplitStorage(SplitStorage): - """UWSGI-Cache based implementation of a split storage.""" - - _KEY_TEMPLATE = 'split.{suffix}' - _KEY_TILL = 'splits.till' - _KEY_FEATURE_LIST = 'splits.list' - _KEY_FEATURE_LIST_LOCK = 'splits.list.lock' - _KEY_TRAFFIC_TYPES = 'splits.traffic_types' - _KEY_TRAFFIC_TYPES_LOCK = 'splits.traffic_types.lock' - _OVERWRITE_LOCK_SECONDS = 5 - - def __init__(self, uwsgi_entrypoint): - """ - Class constructor. - - :param uwsgi_entrypoint: UWSGI module. Can be the actual module or a mock. - :type uwsgi_entrypoint: module - """ - self._uwsgi = uwsgi_entrypoint - - def get(self, split_name): - """ - Retrieve a split. - - :param split_name: Name of the feature to fetch. - :type split_name: str - - :rtype: str - """ - raw = self._uwsgi.cache_get( - self._KEY_TEMPLATE.format(suffix=split_name), - _SPLITIO_SPLITS_CACHE_NAMESPACE - ) - to_return = splits.from_raw(json.loads(raw)) if raw is not None else None - if not to_return: - _LOGGER.warning("Trying to retrieve nonexistant split %s. Ignoring.", split_name) - return to_return - - def fetch_many(self, split_names): - """ - Retrieve splits. - - :param split_names: Names of the features to fetch. - :type split_name: list(str) - - :return: A dict with split objects parsed from queue. - :rtype: dict(split_name, splitio.models.splits.Split) - """ - return {split_name: self.get(split_name) for split_name in split_names} - - def put(self, split): - """ - Store a split. - - :param split: Split object to store - :type split: splitio.models.splits.Split - """ - self._uwsgi.cache_update( - self._KEY_TEMPLATE.format(suffix=split.name), - json.dumps(split.to_json()), - 0, - _SPLITIO_SPLITS_CACHE_NAMESPACE - ) - self._add_split_to_list(split.name) - self._increase_traffic_type_count(split.traffic_type_name) - - def remove(self, split_name): - """ - Remove a split from storage. - - :param split_name: Name of the feature to remove. - :type split_name: str - - :return: True if the split was found and removed. False otherwise. - :rtype: bool - """ - # We need to fetch the split to get the traffic type name prior to deleting. - fetched = self.get(split_name) - if fetched is None: - _LOGGER.warning( - "Tried to remove feature \"%s\" not present in cache. Ignoring.", split_name - ) - return - - result = self._uwsgi.cache_del( - self._KEY_TEMPLATE.format(suffix=split_name), - _SPLITIO_SPLITS_CACHE_NAMESPACE - ) - if result is not False: - _LOGGER.warning("Trying to delete nonexistant split %s. Ignoring.", split_name) - - self._remove_split_from_list(split_name) - self._decrease_traffic_type_count(fetched.traffic_type_name) - - return result - - def get_change_number(self): - """ - Retrieve latest split change number. - - :rtype: int - """ - try: - return json.loads(self._uwsgi.cache_get(self._KEY_TILL, _SPLITIO_CHANGE_NUMBERS)) - except TypeError: - return None - - def set_change_number(self, new_change_number): - """ - Set the latest change number. - - :param new_change_number: New change number. - :type new_change_number: int - """ - self._uwsgi.cache_update(self._KEY_TILL, str(new_change_number), 0, _SPLITIO_CHANGE_NUMBERS) - - def get_split_names(self): - """ - Return a list of all the split names. - - :return: List of split names in cache. - :rtype: list(str) - """ - if self._uwsgi.cache_exists(self._KEY_FEATURE_LIST, _SPLITIO_MISC_NAMESPACE): - try: - return json.loads( - self._uwsgi.cache_get(self._KEY_FEATURE_LIST, _SPLITIO_MISC_NAMESPACE) - ) - except TypeError: # Thrown by json.loads when passing none - pass # Fall back to default return statement (empty list) - return [] - - def get_all_splits(self): - """ - Return a list of all splits in cache. - - :return: List of splits. - :rtype: list(splitio.models.splits.Split) - """ - return [self.get(split_name) for split_name in self.get_split_names()] - - def is_valid_traffic_type(self, traffic_type_name): - """ - Return whether the traffic type exists in at least one split in cache. - - :param traffic_type_name: Traffic type to validate. - :type traffic_type_name: str - - :return: True if the traffic type is valid. False otherwise. - :rtype: bool - """ - try: - tts = json.loads( - self._uwsgi.cache_get(self._KEY_TRAFFIC_TYPES, _SPLITIO_MISC_NAMESPACE) - ) - return traffic_type_name in tts - except TypeError: - return False - - def _add_split_to_list(self, split_name): - """ - Add a specific split to the list we keep track of. - - :param split_name: Name of the split to add. - :type split_name: str - """ - with UWSGILock(self._uwsgi, self._KEY_FEATURE_LIST_LOCK): - try: - current = set(json.loads( - self._uwsgi.cache_get(self._KEY_FEATURE_LIST, _SPLITIO_MISC_NAMESPACE) - )) - except TypeError: - current = set() - current.add(split_name) - self._uwsgi.cache_update( - self._KEY_FEATURE_LIST, - json.dumps(list(current)), - 0, - _SPLITIO_MISC_NAMESPACE - ) - - def _remove_split_from_list(self, split_name): - """ - Remove a specific split from the list we keep track of. - - :param split_name: Name of the split to remove. - :type split_name: str - """ - with UWSGILock(self._uwsgi, self._KEY_FEATURE_LIST_LOCK): - try: - current = set(json.loads( - self._uwsgi.cache_get(self._KEY_FEATURE_LIST, _SPLITIO_MISC_NAMESPACE) - )) - current.remove(split_name) - self._uwsgi.cache_update( - self._KEY_FEATURE_LIST, - json.dumps(list(current)), - 0, - _SPLITIO_MISC_NAMESPACE - ) - except TypeError: - # Split list not found, no need to delete anything - pass - except KeyError: - # Split not found in list. nothing to do. - pass - - def _increase_traffic_type_count(self, traffic_type_name): - """ - Increase by 1 the count for a specific traffic type. - - :param traffic_type_name: Traffic type name to increase count. - :type traffic_type_name: str - """ - with UWSGILock(self._uwsgi, self._KEY_TRAFFIC_TYPES_LOCK): - try: - tts = json.loads( - self._uwsgi.cache_get(self._KEY_TRAFFIC_TYPES, _SPLITIO_MISC_NAMESPACE) - ) - tts[traffic_type_name] = tts.get(traffic_type_name, 0) + 1 - - except TypeError: - tts = {traffic_type_name: 1} - - self._uwsgi.cache_update( - self._KEY_TRAFFIC_TYPES, json.dumps(tts), 0, _SPLITIO_MISC_NAMESPACE - ) - - def _decrease_traffic_type_count(self, traffic_type_name): - """ - Decreaase by 1 the count for a specific traffic type. - - :param traffic_type_name: Traffic type name to decrease count. - :type traffic_type_name: str - """ - with UWSGILock(self._uwsgi, self._KEY_TRAFFIC_TYPES_LOCK): - try: - tts = json.loads( - self._uwsgi.cache_get(self._KEY_TRAFFIC_TYPES, _SPLITIO_MISC_NAMESPACE) - ) - tts[traffic_type_name] = tts.get(traffic_type_name, 0) - 1 - if tts[traffic_type_name] <= 0: - del tts[traffic_type_name] - except TypeError: - # Traffic type list not present. nothing to do here. - return - - self._uwsgi.cache_update( - self._KEY_TRAFFIC_TYPES, json.dumps(tts), 0, _SPLITIO_MISC_NAMESPACE - ) - - def kill_locally(self, split_name, default_treatment, change_number): - """ - Local kill for split - - :param split_name: name of the split to perform kill - :type split_name: str - :param default_treatment: name of the default treatment to return - :type default_treatment: str - :param change_number: change_number - :type change_number: int - """ - if self.get_change_number() > change_number: - return - split = self.get(split_name) - if not split: - return - split.local_kill(default_treatment, change_number) - self.put(split) - - -class UWSGISegmentStorage(SegmentStorage): - """UWSGI-Cache based implementation of a split storage.""" - - _KEY_TEMPLATE = 'segments.{suffix}' - _SEGMENT_DATA_KEY_TEMPLATE = 'segmentData.{segment_name}' - _SEGMENT_CHANGE_NUMBER_KEY_TEMPLATE = 'segment.{segment_name}.till' - - def __init__(self, uwsgi_entrypoint): - """ - Class constructor. - - :param uwsgi_entrypoint: UWSGI module. Can be the actual module or a mock. - :type uwsgi_entrypoint: module - """ - self._uwsgi = uwsgi_entrypoint - - def get(self, segment_name): - """ - Retrieve a segment. - - :param segment_name: Name of the segment to fetch. - :type segment_name: str - - :return: Parsed segment if present. None otherwise. - :rtype: splitio.models.segments.Segment - """ - key = self._SEGMENT_DATA_KEY_TEMPLATE.format(segment_name=segment_name) - cn_key = self._SEGMENT_CHANGE_NUMBER_KEY_TEMPLATE.format(segment_name=segment_name) - try: - segment_data = json.loads(self._uwsgi.cache_get(key, _SPLITIO_SEGMENTS_CACHE_NAMESPACE)) - change_number = json.loads(self._uwsgi.cache_get(cn_key, _SPLITIO_CHANGE_NUMBERS)) - return segments.from_raw({ - 'name': segment_name, - 'added': segment_data, - 'removed': [], - 'till': change_number - }) - except TypeError: - _LOGGER.warning( - "Trying to retrieve nonexistant segment %s. Ignoring.", - segment_name - ) - return None - - def update(self, segment_name, to_add, to_remove, change_number=None): - """ - Update a segment. - - :param segment_name: Name of the segment to update. - :type segment_name: str - :param to_add: List of members to add to the segment. - :type to_add: list - :param to_remove: List of members to remove from the segment. - :type to_remove: list - """ - key = self._SEGMENT_DATA_KEY_TEMPLATE.format(segment_name=segment_name) - try: - segment_data = json.loads(self._uwsgi.cache_get(key, _SPLITIO_SEGMENTS_CACHE_NAMESPACE)) - except TypeError: - segment_data = [] - updated = set(segment_data).union(set(to_add)).difference(to_remove) - self._uwsgi.cache_update( - key, - json.dumps(list(updated)), - 0, - _SPLITIO_SEGMENTS_CACHE_NAMESPACE - ) - - if change_number is not None: - self.set_change_number(segment_name, change_number) - - def put(self, segment): - """ - Put a new segment in storage. - - :param segment: Segment to store. - :type segment: splitio.models.segments.Segent - """ - key = self._SEGMENT_DATA_KEY_TEMPLATE.format(segment_name=segment.name) - self._uwsgi.cache_update( - key, - json.dumps(list(segment.keys)), - 0, - _SPLITIO_SEGMENTS_CACHE_NAMESPACE - ) - self.set_change_number(segment.name, segment.change_number) - - def get_change_number(self, segment_name): - """ - Retrieve latest change number for a segment. - - :param segment_name: Name of the segment. - :type segment_name: str - - :rtype: int - """ - cnkey = self._SEGMENT_CHANGE_NUMBER_KEY_TEMPLATE.format(segment_name=segment_name) - try: - return json.loads(self._uwsgi.cache_get(cnkey, _SPLITIO_CHANGE_NUMBERS)) - - except TypeError: - return None - - def set_change_number(self, segment_name, new_change_number): - """ - Set the latest change number. - - :param segment_name: Name of the segment. - :type segment_name: str - :param new_change_number: New change number. - :type new_change_number: int - """ - cn_key = self._SEGMENT_CHANGE_NUMBER_KEY_TEMPLATE.format(segment_name=segment_name) - self._uwsgi.cache_update(cn_key, json.dumps(new_change_number), 0, _SPLITIO_CHANGE_NUMBERS) - - def segment_contains(self, segment_name, key): - """ - Check whether a specific key belongs to a segment in storage. - - :param segment_name: Name of the segment to search in. - :type segment_name: str - :param key: Key to search for. - :type key: str - - :return: True if the segment contains the key. False otherwise. - :rtype: bool - """ - segment = self.get(segment_name) - return segment.contains(key) - - -class UWSGIImpressionStorage(ImpressionStorage): - """Impressions storage interface.""" - - _IMPRESSIONS_KEY = 'SPLITIO.impressions.' - _LOCK_IMPRESSION_KEY = 'SPLITIO.impressions_lock' - _IMPRESSIONS_FLUSH = 'SPLITIO.impressions_flush' - _OVERWRITE_LOCK_SECONDS = 5 - - def __init__(self, adapter): - """ - Class Constructor. - - :param adapter: UWSGI Adapter/Emulator/Module. - :type: object - """ - self._uwsgi = adapter - - def put(self, impressions): - """ - Put one or more impressions in storage. - - :param impressions: List of one or more impressions to store. - :type impressions: list - """ - to_store = [i._asdict() for i in impressions] - with UWSGILock(self._uwsgi, self._LOCK_IMPRESSION_KEY): - try: - current = json.loads(self._uwsgi.cache_get( - self._IMPRESSIONS_KEY, _SPLITIO_IMPRESSIONS_CACHE_NAMESPACE - )) - except TypeError: - current = [] - - self._uwsgi.cache_update( - self._IMPRESSIONS_KEY, - json.dumps(current + to_store), - 0, - _SPLITIO_IMPRESSIONS_CACHE_NAMESPACE - ) - - def pop_many(self, count): - """ - Pop the oldest N impressions from storage. - - :param count: Number of impressions to pop. - :type count: int - """ - with UWSGILock(self._uwsgi, self._LOCK_IMPRESSION_KEY): - try: - current = json.loads(self._uwsgi.cache_get( - self._IMPRESSIONS_KEY, _SPLITIO_IMPRESSIONS_CACHE_NAMESPACE - )) - except TypeError: - return [] - - self._uwsgi.cache_update( - self._IMPRESSIONS_KEY, - json.dumps(current[count:]), - 0, - _SPLITIO_IMPRESSIONS_CACHE_NAMESPACE - ) - - return [ - Impression( - impression['matching_key'], - impression['feature_name'], - impression['treatment'], - impression['label'], - impression['change_number'], - impression['bucketing_key'], - impression['time'] - ) for impression in current[:count] - ] - - def request_flush(self): - """Set a marker in the events cache to indicate that a flush has been requested.""" - self._uwsgi.cache_set(self._IMPRESSIONS_FLUSH, 'ok', 0, _SPLITIO_LOCK_CACHE_NAMESPACE) - - def should_flush(self): - """ - Return True if a flush has been requested. - - :return: Whether a flush has been requested. - :rtype: bool - """ - value = self._uwsgi.cache_get(self._IMPRESSIONS_FLUSH, _SPLITIO_LOCK_CACHE_NAMESPACE) - return True if value is not None else False - - def acknowledge_flush(self): - """Acknowledge that a flush has been requested.""" - self._uwsgi.cache_del(self._IMPRESSIONS_FLUSH, _SPLITIO_LOCK_CACHE_NAMESPACE) - - def clear(self): - """ - Clear data. - """ - raise NotImplementedError('Not supported for uwsgi.') - - -class UWSGIEventStorage(EventStorage): - """Events storage interface.""" - - _EVENTS_KEY = 'events' - _LOCK_EVENTS_KEY = 'events_lock' - _EVENTS_FLUSH = 'events_flush' - _OVERWRITE_LOCK_SECONDS = 5 - - def __init__(self, adapter): - """ - Class Constructor. - - :param adapter: UWSGI Adapter/Emulator/Module. - :type: object - """ - self._uwsgi = adapter - - def put(self, events): - """ - Put one or more events in storage. - - :param events: List of one or more events to store. - :type events: list - """ - with UWSGILock(self._uwsgi, self._LOCK_EVENTS_KEY): - try: - current = json.loads(self._uwsgi.cache_get( - self._EVENTS_KEY, _SPLITIO_EVENTS_CACHE_NAMESPACE - )) - except TypeError: - current = [] - self._uwsgi.cache_update( - self._EVENTS_KEY, - json.dumps(current + [e.event._asdict() for e in events]), - 0, - _SPLITIO_EVENTS_CACHE_NAMESPACE - ) - - def pop_many(self, count): - """ - Pop the oldest N events from storage. - - :param count: Number of events to pop. - :type count: int - """ - with UWSGILock(self._uwsgi, self._LOCK_EVENTS_KEY): - try: - current = json.loads(self._uwsgi.cache_get( - self._EVENTS_KEY, _SPLITIO_EVENTS_CACHE_NAMESPACE - )) - except TypeError: - return [] - - self._uwsgi.cache_update( - self._EVENTS_KEY, - json.dumps(current[count:]), - 0, - _SPLITIO_EVENTS_CACHE_NAMESPACE - ) - - return [ - Event( - event['key'], - event['traffic_type_name'], - event['event_type_id'], - event['value'], - event['timestamp'], - event['properties'] - ) - for event in current[:count] - ] - - def request_flush(self): - """Set a marker in the events cache to indicate that a flush has been requested.""" - self._uwsgi.cache_set(self._EVENTS_FLUSH, 'requested', 0, _SPLITIO_LOCK_CACHE_NAMESPACE) - - def should_flush(self): - """ - Return True if a flush has been requested. - - :return: Whether a flush has been requested. - :rtype: bool - """ - value = self._uwsgi.cache_get(self._EVENTS_FLUSH, _SPLITIO_LOCK_CACHE_NAMESPACE) - return True if value is not None else False - - def acknowledge_flush(self): - """Acknowledge that a flush has been requested.""" - self._uwsgi.cache_del(self._EVENTS_FLUSH, _SPLITIO_LOCK_CACHE_NAMESPACE) - - def clear(self): - """ - Clear data. - """ - raise NotImplementedError('Not supported for uwsgi.') - - -class UWSGITelemetryStorage(TelemetryStorage): - """Telemetry storage interface.""" - - _LATENCIES_KEY = 'SPLITIO.latencies' - _GAUGES_KEY = 'SPLITIO.gauges' - _COUNTERS_KEY = 'SPLITIO.counters' - - _LATENCIES_LOCK_KEY = 'SPLITIO.latencies.lock' - _GAUGES_LOCK_KEY = 'SPLITIO.gauges.lock' - _COUNTERS_LOCK_KEY = 'SPLITIO.counters.lock' - - def __init__(self, uwsgi_entrypoint): - """ - Class constructor. - - :param uwsgi_entrypoint: uwsgi module/emulator - :type uwsgi_entrypoint: object - """ - self._uwsgi = uwsgi_entrypoint - - def inc_latency(self, name, bucket): - """ - Add a latency. - - :param name: Name of the latency metric. - :type name: str - :param value: Value of the latency metric. - :tyoe value: int - """ - if not 0 <= bucket <= 21: - _LOGGER.error('Incorect bucket "%d" for latency "%s". Ignoring.', bucket, name) - return - - with UWSGILock(self._uwsgi, self._LATENCIES_LOCK_KEY): - latencies_raw = self._uwsgi.cache_get( - self._LATENCIES_KEY, _SPLITIO_METRICS_CACHE_NAMESPACE) - latencies = json.loads(latencies_raw) if latencies_raw else {} - to_update = latencies.get(name, [0] * 22) - to_update[bucket] += 1 - latencies[name] = to_update - self._uwsgi.cache_set( - self._LATENCIES_KEY, - json.dumps(latencies), - 0, - _SPLITIO_METRICS_CACHE_NAMESPACE - ) - - def inc_counter(self, name): - """ - Increment a counter. - - :param name: Name of the counter metric. - :type name: str - """ - with UWSGILock(self._uwsgi, self._COUNTERS_LOCK_KEY): - counters_raw = self._uwsgi.cache_get( - self._COUNTERS_KEY, _SPLITIO_METRICS_CACHE_NAMESPACE) - counters = json.loads(counters_raw) if counters_raw else {} - value = counters.get(name, 0) - value += 1 - counters[name] = value - self._uwsgi.cache_set( - self._COUNTERS_KEY, - json.dumps(counters), - 0, - _SPLITIO_METRICS_CACHE_NAMESPACE - ) - - def put_gauge(self, name, value): - """ - Add a gauge metric. - - :param name: Name of the gauge metric. - :type name: str - :param value: Value of the gauge metric. - :type value: int - """ - with UWSGILock(self._uwsgi, self._GAUGES_LOCK_KEY): - gauges_raw = self._uwsgi.cache_get(self._GAUGES_KEY, _SPLITIO_METRICS_CACHE_NAMESPACE) - gauges = json.loads(gauges_raw) if gauges_raw else {} - gauges[name] = value - self._uwsgi.cache_set( - self._GAUGES_KEY, - json.dumps(gauges), - 0, - _SPLITIO_METRICS_CACHE_NAMESPACE - ) - - def pop_counters(self): - """ - Get all the counters. - - :rtype: list - """ - with UWSGILock(self._uwsgi, self._COUNTERS_LOCK_KEY): - counters_raw = self._uwsgi.cache_get( - self._COUNTERS_KEY, _SPLITIO_METRICS_CACHE_NAMESPACE) - self._uwsgi.cache_del(self._COUNTERS_KEY, _SPLITIO_METRICS_CACHE_NAMESPACE) - return json.loads(counters_raw) if counters_raw else {} - - def pop_gauges(self): - """ - Get all the gauges. - - :rtype: list - - """ - with UWSGILock(self._uwsgi, self._GAUGES_LOCK_KEY): - gauges_raw = self._uwsgi.cache_get(self._GAUGES_KEY, _SPLITIO_METRICS_CACHE_NAMESPACE) - self._uwsgi.cache_del(self._GAUGES_KEY, _SPLITIO_METRICS_CACHE_NAMESPACE) - return json.loads(gauges_raw) if gauges_raw else {} - - def pop_latencies(self): - """ - Get all latencies. - - :rtype: list - """ - with UWSGILock(self._uwsgi, self._LATENCIES_LOCK_KEY): - latencies_raw = self._uwsgi.cache_get( - self._LATENCIES_KEY, _SPLITIO_METRICS_CACHE_NAMESPACE) - self._uwsgi.cache_del(self._LATENCIES_KEY, _SPLITIO_METRICS_CACHE_NAMESPACE) - return json.loads(latencies_raw) if latencies_raw else {} - - def clear(self): - """ - Clear data. - """ - raise NotImplementedError('Not supported for uwsgi.') diff --git a/splitio/sync/segment.py b/splitio/sync/segment.py index 6e599b55..e2b08b25 100644 --- a/splitio/sync/segment.py +++ b/splitio/sync/segment.py @@ -1,7 +1,6 @@ import logging from splitio.api import APIException -from splitio.models import splits from splitio.tasks.util import workerpool from splitio.models import segments diff --git a/splitio/tasks/__init__.py b/splitio/tasks/__init__.py index 7d478a22..10c405e5 100644 --- a/splitio/tasks/__init__.py +++ b/splitio/tasks/__init__.py @@ -2,6 +2,7 @@ import abc + class BaseSynchronizationTask(object): """Syncrhonization task interface.""" diff --git a/splitio/tasks/segment_sync.py b/splitio/tasks/segment_sync.py index 5f0574e0..5297ce9f 100644 --- a/splitio/tasks/segment_sync.py +++ b/splitio/tasks/segment_sync.py @@ -1,7 +1,6 @@ """Segment syncrhonization module.""" import logging -from splitio.api import APIException from splitio.tasks import BaseSynchronizationTask from splitio.tasks.util import asynctask diff --git a/splitio/tasks/telemetry_sync.py b/splitio/tasks/telemetry_sync.py index e0339895..b17bc2ad 100644 --- a/splitio/tasks/telemetry_sync.py +++ b/splitio/tasks/telemetry_sync.py @@ -1,7 +1,5 @@ """Split Synchronization task.""" -import logging -from splitio.api import APIException from splitio.tasks import BaseSynchronizationTask from splitio.tasks.util.asynctask import AsyncTask diff --git a/splitio/tasks/uwsgi_wrappers.py b/splitio/tasks/uwsgi_wrappers.py deleted file mode 100644 index 9c304677..00000000 --- a/splitio/tasks/uwsgi_wrappers.py +++ /dev/null @@ -1,192 +0,0 @@ -"""Wrappers for tasks when using UWSGI Cache as a synchronization platform.""" - -import logging -import time - -from splitio.client.config import sanitize as sanitize_config -from splitio.client.util import get_metadata -from splitio.storage.adapters.uwsgi_cache import get_uwsgi -from splitio.storage.uwsgi import UWSGIEventStorage, UWSGIImpressionStorage, \ - UWSGISegmentStorage, UWSGISplitStorage, UWSGITelemetryStorage -from splitio.api.client import HttpClient -from splitio.api.splits import SplitsAPI -from splitio.api.segments import SegmentsAPI -from splitio.api.impressions import ImpressionsAPI -from splitio.api.telemetry import TelemetryAPI -from splitio.api.events import EventsAPI -from splitio.tasks.util import workerpool -from splitio.sync.split import SplitSynchronizer -from splitio.sync.segment import SegmentSynchronizer -from splitio.sync.impression import ImpressionSynchronizer -from splitio.sync.event import EventSynchronizer -from splitio.sync.telemetry import TelemetrySynchronizer - -_LOGGER = logging.getLogger(__name__) - - -def _get_config(user_config): - """ - Get sdk configuration using defaults + user overrides. - - :param user_config: User configuration. - :type user_config: dict - - :return: Calculated configuration. - :rtype: dict - """ - return sanitize_config(user_config['apikey'], user_config) - - -def uwsgi_update_splits(user_config): - """ - Update splits task. - - :param user_config: User-provided configuration. - :type user_config: dict - """ - config = _get_config(user_config) - metadata = get_metadata(config) - seconds = config['featuresRefreshRate'] - split_sync = SplitSynchronizer( - SplitsAPI( - HttpClient(1500, config.get('sdk_url'), config.get('events_url')), config['apikey'], - metadata - ), - UWSGISplitStorage(get_uwsgi()), - ) - - while True: - try: - split_sync.synchronize_splits() # pylint: disable=protected-access - time.sleep(seconds) - except Exception: # pylint: disable=broad-except - _LOGGER.error('Error updating splits') - _LOGGER.debug('Error: ', exc_info=True) - - -def uwsgi_update_segments(user_config): - """ - Update segments task. - - :param user_config: User-provided configuration. - :type user_config: dict - """ - config = _get_config(user_config) - seconds = config['segmentsRefreshRate'] - metadata = get_metadata(config) - segment_sync = SegmentSynchronizer( - SegmentsAPI( - HttpClient(1500, config.get('sdk_url'), config.get('events_url')), config['apikey'], - metadata - ), - UWSGISplitStorage(get_uwsgi()), - UWSGISegmentStorage(get_uwsgi()), - ) - - pool = workerpool.WorkerPool(20, segment_sync.synchronize_segment) # pylint: disable=protected-access - pool.start() - split_storage = UWSGISplitStorage(get_uwsgi()) - while True: - try: - for segment_name in split_storage.get_segment_names(): - pool.submit_work(segment_name) - time.sleep(seconds) - except Exception: # pylint: disable=broad-except - _LOGGER.error('Error updating segments') - _LOGGER.debug('Error: ', exc_info=True) - - -def uwsgi_report_impressions(user_config): - """ - Flush impressions task. - - :param user_config: User-provided configuration. - :type user_config: dict - """ - config = _get_config(user_config) - metadata = get_metadata(config) - seconds = config['impressionsRefreshRate'] - storage = UWSGIImpressionStorage(get_uwsgi()) - impressions_sync = ImpressionSynchronizer( - ImpressionsAPI( - HttpClient(1500, config.get('sdk_url'), config.get('events_url')), - config['apikey'], - metadata, - config['impressionsMode'] - ), - storage, - config['impressionsBulkSize'] - ) - - while True: - try: - impressions_sync.synchronize_impressions() # pylint: disable=protected-access - for _ in range(0, seconds): - if storage.should_flush(): - storage.acknowledge_flush() - break - time.sleep(1) - except Exception: # pylint: disable=broad-except - _LOGGER.error('Error posting impressions') - _LOGGER.debug('Error: ', exc_info=True) - - -def uwsgi_report_events(user_config): - """ - Flush events task. - - :param user_config: User-provided configuration. - :type user_config: dict - """ - config = _get_config(user_config) - metadata = get_metadata(config) - seconds = config.get('eventsRefreshRate', 30) - storage = UWSGIEventStorage(get_uwsgi()) - events_sync = EventSynchronizer( - EventsAPI( - HttpClient(1500, config.get('sdk_url'), config.get('events_url')), - config['apikey'], - metadata - ), - storage, - config['eventsBulkSize'] - ) - while True: - try: - events_sync.synchronize_events() # pylint: disable=protected-access - for _ in range(0, seconds): - if storage.should_flush(): - storage.acknowledge_flush() - break - time.sleep(1) - except Exception: # pylint: disable=broad-except - _LOGGER.error('Error posting metrics') - _LOGGER.debug('Error: ', exc_info=True) - - -def uwsgi_report_telemetry(user_config): - """ - Flush events task. - - :param user_config: User-provided configuration. - :type user_config: dict - """ - config = _get_config(user_config) - metadata = get_metadata(config) - seconds = config.get('metricsRefreshRate', 30) - storage = UWSGITelemetryStorage(get_uwsgi()) - telemetry_sync = TelemetrySynchronizer( - TelemetryAPI( - HttpClient(1500, config.get('sdk_url'), config.get('events_url')), - config['apikey'], - metadata - ), - storage, - ) - while True: - try: - telemetry_sync.synchronize_telemetry() # pylint: disable=protected-access - time.sleep(seconds) - except Exception: # pylint: disable=broad-except - _LOGGER.error('Error posting metrics') - _LOGGER.debug('Error: ', exc_info=True) diff --git a/splitio/version.py b/splitio/version.py index 33de8d16..432526bd 100644 --- a/splitio/version.py +++ b/splitio/version.py @@ -1 +1 @@ -__version__ = '9.0.0' +__version__ = '9.0.0-uwsgi' diff --git a/tests/client/test_config.py b/tests/client/test_config.py index 0bac9f24..a52600bd 100644 --- a/tests/client/test_config.py +++ b/tests/client/test_config.py @@ -13,7 +13,6 @@ def test_parse_operation_mode(self): assert config._parse_operation_mode('some', {}) == 'inmemory-standalone' assert config._parse_operation_mode('localhost', {}) == 'localhost-standalone' assert config._parse_operation_mode('some', {'redisHost': 'x'}) == 'redis-consumer' - assert config._parse_operation_mode('some', {'uwsgiClient': True}) == 'uwsgi-consumer' def test_sanitize_imp_mode(self): """Test sanitization of impressions mode.""" diff --git a/tests/client/test_factory.py b/tests/client/test_factory.py index 997540e5..8eac8363 100644 --- a/tests/client/test_factory.py +++ b/tests/client/test_factory.py @@ -8,7 +8,7 @@ from splitio.client.factory import get_factory, SplitFactory, _INSTANTIATED_FACTORIES, Status,\ _LOGGER as _logger from splitio.client.config import DEFAULT_CONFIG -from splitio.storage import redis, inmemmory, uwsgi +from splitio.storage import redis, inmemmory from splitio.tasks import events_sync, impressions_sync, split_sync, segment_sync, telemetry_sync from splitio.tasks.util import asynctask from splitio.api.splits import SplitsAPI @@ -142,25 +142,6 @@ def test_redis_client_creation(self, mocker): assert factory.ready factory.destroy() - def test_uwsgi_client_creation(self): - """Test that a client with redis storage is created correctly.""" - factory = get_factory('some_api_key', config={'uwsgiClient': True}) - assert isinstance(factory._get_storage('splits'), uwsgi.UWSGISplitStorage) - assert isinstance(factory._get_storage('segments'), uwsgi.UWSGISegmentStorage) - assert isinstance(factory._get_storage('impressions'), uwsgi.UWSGIImpressionStorage) - assert isinstance(factory._get_storage('events'), uwsgi.UWSGIEventStorage) - assert isinstance(factory._get_storage('telemetry'), uwsgi.UWSGITelemetryStorage) - assert factory._sync_manager is None - assert factory._labels_enabled is True - assert isinstance(factory._recorder, StandardRecorder) - assert isinstance(factory._recorder._impressions_manager, ImpressionsManager) - assert isinstance(factory._recorder._telemetry_storage, inmemmory.TelemetryStorage) - assert isinstance(factory._recorder._event_sotrage, inmemmory.EventStorage) - assert isinstance(factory._recorder._impression_storage, inmemmory.ImpressionStorage) - factory.block_until_ready() - assert factory.ready - factory.destroy() - def test_uwsgi_forked_client_creation(self): """Test client with preforked initialization.""" factory = get_factory('some_api_key', config={'preforkedInitialization': True}) @@ -427,14 +408,11 @@ def _make_factory_with_apikey(apikey, *_, **__): build_in_memory.side_effect = _make_factory_with_apikey build_redis = mocker.Mock() build_redis.side_effect = _make_factory_with_apikey - build_uwsgi = mocker.Mock() - build_uwsgi.side_effect = _make_factory_with_apikey build_localhost = mocker.Mock() build_localhost.side_effect = _make_factory_with_apikey mocker.patch('splitio.client.factory._LOGGER', new=factory_module_logger) mocker.patch('splitio.client.factory._build_in_memory_factory', new=build_in_memory) mocker.patch('splitio.client.factory._build_redis_factory', new=build_redis) - mocker.patch('splitio.client.factory._build_uwsgi_factory', new=build_uwsgi) mocker.patch('splitio.client.factory._build_localhost_factory', new=build_localhost) _INSTANTIATED_FACTORIES.clear() # Clear all factory counters for testing purposes diff --git a/tests/client/test_input_validator.py b/tests/client/test_input_validator.py index b52c2e2c..5ad58a25 100644 --- a/tests/client/test_input_validator.py +++ b/tests/client/test_input_validator.py @@ -1111,12 +1111,6 @@ def test_input_validation_factory(self, mocker): mocker.call("%s: you passed an invalid %s, %s must be a non-empty string.", 'factory_instantiation', 'apikey', 'apikey') ] - logger.reset_mock() - f = get_factory(True, config={'uwsgiClient': True}) - assert f is not None - assert logger.error.mock_calls == [] - f.destroy() - logger.reset_mock() f = get_factory(True, config={'redisHost': 'some-host'}) assert f is not None diff --git a/tests/storage/test_uwsgi.py b/tests/storage/test_uwsgi.py deleted file mode 100644 index e7f06bad..00000000 --- a/tests/storage/test_uwsgi.py +++ /dev/null @@ -1,320 +0,0 @@ -"""UWSGI Storage unit tests.""" -# pylint: disable=no-self-usage -import json - -from splitio.storage.uwsgi import UWSGIEventStorage, UWSGIImpressionStorage, \ - UWSGISegmentStorage, UWSGISplitStorage, UWSGITelemetryStorage - -from splitio.models.splits import Split -from splitio.models.segments import Segment -from splitio.models.impressions import Impression -from splitio.models.events import Event, EventWrapper - -from splitio.storage.adapters.uwsgi_cache import get_uwsgi - - -class UWSGISplitStorageTests(object): - """UWSGI Split Storage test cases.""" - - @staticmethod - def _get_from_raw_mock(mocker): - def _do(raw): - mock_split = mocker.Mock() - mock_split = mocker.Mock(spec=Split) - mock_split.to_json.return_value = raw - split_name = mocker.PropertyMock() - split_name.return_value = raw['name'] - type(mock_split).name = split_name - traffic_type_name = mocker.PropertyMock() - traffic_type_name.return_value = raw['trafficTypeName'] - type(mock_split).traffic_type_name = traffic_type_name - return mock_split - - from_raw_mock = mocker.Mock() - from_raw_mock.side_effect = lambda x: _do(x) - return from_raw_mock - - def test_store_retrieve_split(self, mocker): - """Test storing and retrieving splits.""" - uwsgi = get_uwsgi(True) - storage = UWSGISplitStorage(uwsgi) - from_raw_mock = self._get_from_raw_mock(mocker) - mocker.patch('splitio.models.splits.from_raw', new=from_raw_mock) - - raw_split = {'name': 'some_split', 'trafficTypeName': 'user'} - split = from_raw_mock(raw_split) - - from_raw_mock.reset_mock() # clear mock calls so they don't interfere with the testing itself. - storage.put(split) - - retrieved = storage.get('some_split') - - assert retrieved.name == split.name and retrieved.traffic_type_name == split.traffic_type_name - assert from_raw_mock.mock_calls == [mocker.call(raw_split)] - assert split.to_json.mock_calls == [mocker.call()] - - assert storage.get('nonexistant_split') is None - - storage.remove('some_split') - assert storage.get('some_split') is None - - def test_get_splits(self, mocker): - """Test retrieving a list of passed splits.""" - uwsgi = get_uwsgi(True) - storage = UWSGISplitStorage(uwsgi) - from_raw_mock = self._get_from_raw_mock(mocker) - mocker.patch('splitio.models.splits.from_raw', new=from_raw_mock) - - split_1 = from_raw_mock({'name': 'some_split_1', 'trafficTypeName': 'user'}) - split_2 = from_raw_mock({'name': 'some_split_2', 'trafficTypeName': 'user'}) - storage.put(split_1) - storage.put(split_2) - - splits = storage.fetch_many(['some_split_1', 'some_split_2', 'some_split_3']) - assert len(splits) == 3 - assert splits['some_split_1'].name == 'some_split_1' - assert splits['some_split_2'].name == 'some_split_2' - assert 'some_split_3' in splits - - def test_set_get_changenumber(self, mocker): - """Test setting and retrieving changenumber.""" - uwsgi = get_uwsgi(True) - storage = UWSGISplitStorage(uwsgi) - - assert storage.get_change_number() is None - storage.set_change_number(123) - assert storage.get_change_number() == 123 - - def test_get_split_names(self, mocker): - """Test getting all split names.""" - uwsgi = get_uwsgi(True) - storage = UWSGISplitStorage(uwsgi) - from_raw_mock = self._get_from_raw_mock(mocker) - mocker.patch('splitio.models.splits.from_raw', new=from_raw_mock) - - split_1 = from_raw_mock({'name': 'some_split_1', 'trafficTypeName': 'user'}) - split_2 = from_raw_mock({'name': 'some_split_2', 'trafficTypeName': 'user'}) - storage.put(split_1) - storage.put(split_2) - - assert set(storage.get_split_names()) == set(['some_split_1', 'some_split_2']) - storage.remove('some_split_1') - assert storage.get_split_names() == ['some_split_2'] - - def test_get_all_splits(self, mocker): - """Test fetching all splits.""" - uwsgi = get_uwsgi(True) - storage = UWSGISplitStorage(uwsgi) - from_raw_mock = self._get_from_raw_mock(mocker) - mocker.patch('splitio.models.splits.from_raw', new=from_raw_mock) - - split_1 = from_raw_mock({'name': 'some_split_1', 'trafficTypeName': 'user'}) - split_2 = from_raw_mock({'name': 'some_split_2', 'trafficTypeName': 'user'}) - storage.put(split_1) - storage.put(split_2) - - splits = storage.get_all_splits() - s1 = next(split for split in splits if split.name == 'some_split_1') - s2 = next(split for split in splits if split.name == 'some_split_2') - - assert s1.traffic_type_name == 'user' - assert s2.traffic_type_name == 'user' - - def test_is_valid_traffic_type(self, mocker): - """Test that traffic type validation works properly.""" - uwsgi = get_uwsgi(True) - storage = UWSGISplitStorage(uwsgi) - from_raw_mock = self._get_from_raw_mock(mocker) - mocker.patch('splitio.models.splits.from_raw', new=from_raw_mock) - - split_1 = from_raw_mock({'name': 'some_split_1', 'trafficTypeName': 'user'}) - storage.put(split_1) - assert storage.is_valid_traffic_type('user') is True - assert storage.is_valid_traffic_type('account') is False - - split_2 = from_raw_mock({'name': 'some_split_2', 'trafficTypeName': 'account'}) - storage.put(split_2) - assert storage.is_valid_traffic_type('user') is True - assert storage.is_valid_traffic_type('account') is True - - split_3 = from_raw_mock({'name': 'some_split_3', 'trafficTypeName': 'user'}) - storage.put(split_3) - assert storage.is_valid_traffic_type('user') is True - assert storage.is_valid_traffic_type('account') is True - - storage.remove('some_split_1') - assert storage.is_valid_traffic_type('user') is True - assert storage.is_valid_traffic_type('account') is True - - storage.remove('some_split_2') - assert storage.is_valid_traffic_type('user') is True - assert storage.is_valid_traffic_type('account') is False - - storage.remove('some_split_3') - assert storage.is_valid_traffic_type('user') is False - assert storage.is_valid_traffic_type('account') is False - - def test_kill_locally(self): - """Test kill local.""" - uwsgi = get_uwsgi(True) - storage = UWSGISplitStorage(uwsgi) - - split = Split('some_split', 123456789, False, 'some', 'traffic_type', - 'ACTIVE', 1) - storage.put(split) - storage.set_change_number(1) - - storage.kill_locally('test', 'default_treatment', 2) - assert storage.get('test') is None - - storage.kill_locally('some_split', 'default_treatment', 0) - assert storage.get('some_split').change_number == 1 - assert storage.get('some_split').killed is False - assert storage.get('some_split').default_treatment == 'some' - - storage.kill_locally('some_split', 'default_treatment', 3) - assert storage.get('some_split').change_number == 3 - - -class UWSGISegmentStorageTests(object): - """UWSGI Segment storage test cases.""" - - def test_store_retrieve_segment(self, mocker): - """Test storing and fetching segments.""" - uwsgi = get_uwsgi(True) - storage = UWSGISegmentStorage(uwsgi) - segment = mocker.Mock(spec=Segment) - segment_keys = mocker.PropertyMock() - segment_keys.return_value = ['abc'] - type(segment).keys = segment_keys - segment.to_json = {} - segment_name = mocker.PropertyMock() - segment_name.return_value = 'some_segment' - segment_change_number = mocker.PropertyMock() - segment_change_number.return_value = 123 - type(segment).name = segment_name - type(segment).change_number = segment_change_number - from_raw_mock = mocker.Mock() - from_raw_mock.return_value = 'ok' - mocker.patch('splitio.models.segments.from_raw', new=from_raw_mock) - - storage.put(segment) - assert storage.get('some_segment') == 'ok' - assert from_raw_mock.mock_calls == [mocker.call({'till': 123, 'removed': [], 'added': [u'abc'], 'name': 'some_segment'})] - assert storage.get('nonexistant-segment') is None - - def test_get_set_change_number(self, mocker): - """Test setting and getting change number.""" - uwsgi = get_uwsgi(True) - storage = UWSGISegmentStorage(uwsgi) - assert storage.get_change_number('some_segment') is None - storage.set_change_number('some_segment', 123) - assert storage.get_change_number('some_segment') == 123 - - def test_segment_contains(self, mocker): - """Test that segment contains works properly.""" - uwsgi = get_uwsgi(True) - storage = UWSGISegmentStorage(uwsgi) - - from_raw_mock = mocker.Mock() - from_raw_mock.return_value = Segment('some_segment', ['abc'], 123) - mocker.patch('splitio.models.segments.from_raw', new=from_raw_mock) - segment = mocker.Mock(spec=Segment) - segment_keys = mocker.PropertyMock() - segment_keys.return_value = ['abc'] - type(segment).keys = segment_keys - segment.to_json = {} - segment_name = mocker.PropertyMock() - segment_name.return_value = 'some_segment' - segment_change_number = mocker.PropertyMock() - segment_change_number.return_value = 123 - type(segment).name = segment_name - type(segment).change_number = segment_change_number - storage.put(segment) - - assert storage.segment_contains('some_segment', 'abc') - assert not storage.segment_contains('some_segment', 'qwe') - - -class UWSGIImpressionsStorageTests(object): - """UWSGI Impressions storage test cases.""" - - def test_put_pop_impressions(self, mocker): - """Test storing and fetching impressions.""" - uwsgi = get_uwsgi(True) - storage = UWSGIImpressionStorage(uwsgi) - impressions = [ - Impression('key1', 'feature1', 'on', 'some_label', 123456, 'buck1', 321654), - Impression('key2', 'feature2', 'on', 'some_label', 123456, 'buck1', 321654), - Impression('key3', 'feature2', 'on', 'some_label', 123456, 'buck1', 321654), - Impression('key4', 'feature1', 'on', 'some_label', 123456, 'buck1', 321654) - ] - storage.put(impressions) - res = storage.pop_many(10) - assert res == impressions - - def test_flush(self): - """Test requesting, querying and acknowledging a flush.""" - uwsgi = get_uwsgi(True) - storage = UWSGIImpressionStorage(uwsgi) - assert storage.should_flush() is False - storage.request_flush() - assert storage.should_flush() is True - storage.acknowledge_flush() - assert storage.should_flush() is False - - -class UWSGIEventsStorageTests(object): - """UWSGI Events storage test cases.""" - - def test_put_pop_events(self, mocker): - """Test storing and fetching events.""" - uwsgi = get_uwsgi(True) - storage = UWSGIEventStorage(uwsgi) - events = [ - EventWrapper(event=Event('key1', 'user', 'purchase', 10, 123456, None), size=32768), - EventWrapper(event=Event('key2', 'user', 'purchase', 10, 123456, None), size=32768), - EventWrapper(event=Event('key3', 'user', 'purchase', 10, 123456, None), size=32768), - EventWrapper(event=Event('key4', 'user', 'purchase', 10, 123456, None), size=32768), - ] - - storage.put(events) - res = storage.pop_many(10) - assert res == [ - Event('key1', 'user', 'purchase', 10, 123456, None), - Event('key2', 'user', 'purchase', 10, 123456, None), - Event('key3', 'user', 'purchase', 10, 123456, None), - Event('key4', 'user', 'purchase', 10, 123456, None) - ] - - -class UWSGITelemetryStorageTests(object): - """UWSGI-based telemetry storage test cases.""" - - def test_latencies(self): - """Test storing and popping latencies.""" - storage = UWSGITelemetryStorage(get_uwsgi(True)) - storage.inc_latency('some_latency', 2) - storage.inc_latency('some_latency', 2) - storage.inc_latency('some_latency', 2) - assert storage.pop_latencies() == { - 'some_latency': [0, 0, 3, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0] - } - assert storage.pop_latencies() == {} - - def test_counters(self): - """Test storing and popping counters.""" - storage = UWSGITelemetryStorage(get_uwsgi(True)) - storage.inc_counter('some_counter') - storage.inc_counter('some_counter') - storage.inc_counter('some_counter') - assert storage.pop_counters() == {'some_counter': 3} - assert storage.pop_counters() == {} - - def test_gauges(self): - """Test storing and popping gauges.""" - storage = UWSGITelemetryStorage(get_uwsgi(True)) - storage.put_gauge('some_gauge1', 123) - storage.put_gauge('some_gauge2', 456) - assert storage.pop_gauges() == {'some_gauge1': 123, 'some_gauge2': 456} - assert storage.pop_gauges() == {} diff --git a/tests/tasks/test_uwsgi_wrappers.py b/tests/tasks/test_uwsgi_wrappers.py deleted file mode 100644 index 71b5614a..00000000 --- a/tests/tasks/test_uwsgi_wrappers.py +++ /dev/null @@ -1,137 +0,0 @@ -"""UWSGI Task wrappers test module.""" -# pylint: disable=no-self-use,protected-access -from splitio.storage import SplitStorage -from splitio.tasks.util.workerpool import WorkerPool -from splitio.storage.uwsgi import UWSGISplitStorage -from splitio.tasks.uwsgi_wrappers import uwsgi_update_splits, uwsgi_update_segments, \ - uwsgi_report_events, uwsgi_report_impressions, uwsgi_report_telemetry -from splitio.sync.split import SplitSynchronizer -from splitio.sync.segment import SegmentSynchronizer -from splitio.sync.impression import ImpressionSynchronizer -from splitio.sync.event import EventSynchronizer -from splitio.sync.telemetry import TelemetrySynchronizer - - -class NonCatchableException(BaseException): - """Exception to be used to stop sync task's infinite loop.""" - - pass - - -class TaskWrappersTests(object): - """Task wrappers task test cases.""" - - def test_update_splits(self, mocker): - """Test split sync task wrapper.""" - data = {'executions': 0} - - def _update_splits_side_effect(*_, **__): - data['executions'] += 1 - if data['executions'] > 1: - raise NonCatchableException('asd') - - stmock = mocker.Mock(spec=SplitSynchronizer) - stmock.synchronize_splits.side_effect = _update_splits_side_effect - stmock_class = mocker.Mock(spec=SplitSynchronizer) - stmock_class.return_value = stmock - mocker.patch('splitio.tasks.uwsgi_wrappers.SplitSynchronizer', new=stmock_class) - - try: - uwsgi_update_splits({'apikey': 'asd', 'featuresRefreshRate': 1}) - except NonCatchableException: - # Make sure that the task was called before being forced to stop. - assert data['executions'] > 1 - assert len(stmock.synchronize_splits.mock_calls) > 1 - - def test_update_segments(self, mocker): - """Test split sync task wrapper.""" - data = {'executions': 0} - - def _submit_work(*_, **__): - data['executions'] += 1 - # we mock 2 segments, so we expect this to be called at least twice before ending. - if data['executions'] > 2: - raise NonCatchableException('asd') - - wpmock = mocker.Mock(spec=WorkerPool) - wpmock.submit_work.side_effect = _submit_work - wpmock_class = mocker.Mock(spec=WorkerPool) - wpmock_class.return_value = wpmock - mocker.patch('splitio.tasks.uwsgi_wrappers.workerpool.WorkerPool', new=wpmock_class) - - mocked_update_segment = mocker.patch.object(SplitStorage, 'get_segment_names') - mocked_update_segment.return_value = ['segment1', 'segment2'] - mocked_split_storage_instance = UWSGISplitStorage(True) - split_storage_mock = mocker.Mock(spec=UWSGISplitStorage) - split_storage_mock.return_value = mocked_split_storage_instance - - mocker.patch('splitio.tasks.uwsgi_wrappers.UWSGISplitStorage', new=split_storage_mock) - - try: - uwsgi_update_segments({'apikey': 'asd', 'segmentsRefreshRate': 1}) - except NonCatchableException: - # Make sure that the task was called before being forced to stop. - assert data['executions'] > 2 - assert len(wpmock.submit_work.mock_calls) > 2 - - def test_post_impressions(self, mocker): - """Test split sync task wrapper.""" - data = {'executions': 0} - - def _report_impressions_side_effect(*_, **__): - data['executions'] += 1 - if data['executions'] > 1: - raise NonCatchableException('asd') - - stmock = mocker.Mock(spec=ImpressionSynchronizer) - stmock.synchronize_impressions.side_effect = _report_impressions_side_effect - stmock_class = mocker.Mock(spec=ImpressionSynchronizer) - stmock_class.return_value = stmock - mocker.patch('splitio.tasks.uwsgi_wrappers.ImpressionSynchronizer', new=stmock_class) - try: - uwsgi_report_impressions({'apikey': 'asd', 'impressionsRefreshRate': 1}) - except NonCatchableException: - # Make sure that the task was called before being forced to stop. - assert data['executions'] > 1 - # TODO: Test impressions flushing. - - def test_post_events(self, mocker): - """Test split sync task wrapper.""" - data = {'executions': 0} - - def _send_events_side_effect(*_, **__): - data['executions'] += 1 - if data['executions'] > 1: - raise NonCatchableException('asd') - - stmock = mocker.Mock(spec=EventSynchronizer) - stmock.synchronize_events.side_effect = _send_events_side_effect - stmock_class = mocker.Mock(spec=EventSynchronizer) - stmock_class.return_value = stmock - mocker.patch('splitio.tasks.uwsgi_wrappers.EventSynchronizer', new=stmock_class) - try: - uwsgi_report_events({'apikey': 'asd', 'eventsRefreshRate': 1}) - except NonCatchableException: - # Make sure that the task was called before being forced to stop. - assert data['executions'] > 1 - # TODO: Test impressions flushing. - - def test_post_telemetry(self, mocker): - """Test split sync task wrapper.""" - data = {'executions': 0} - - def _flush_telemetry_side_effect(*_, **__): - data['executions'] += 1 - if data['executions'] > 1: - raise NonCatchableException('asd') - - stmock = mocker.Mock(spec=TelemetrySynchronizer) - stmock.synchronize_telemetry.side_effect = _flush_telemetry_side_effect - stmock_class = mocker.Mock(spec=TelemetrySynchronizer) - stmock_class.return_value = stmock - mocker.patch('splitio.tasks.uwsgi_wrappers.TelemetrySynchronizer', new=stmock_class) - try: - uwsgi_report_telemetry({'apikey': 'asd', 'metricsRefreshRate': 1}) - except NonCatchableException: - # Make sure that the task was called before being forced to stop. - assert data['executions'] > 1 From 7850aa64a2f0b9748a55aeb7bdc408806da4b499 Mon Sep 17 00:00:00 2001 From: Matias Melograno Date: Fri, 30 Apr 2021 10:57:42 -0300 Subject: [PATCH 2/2] preparing release --- CHANGES.txt | 7 ++++--- splitio/client/config.py | 2 -- splitio/version.py | 2 +- 3 files changed, 5 insertions(+), 6 deletions(-) diff --git a/CHANGES.txt b/CHANGES.txt index 2ba02431..dbdf20ba 100644 --- a/CHANGES.txt +++ b/CHANGES.txt @@ -1,8 +1,9 @@ -9.0.0 (Apr 28, 2021) - - BREAKING CHANGE: Deprecated Python2. +9.0.0 (Apr 30, 2021) + - BREAKING CHANGE: Removed splitSdkMachineIp and splitSdkMachineName configs. + - BREAKING CHANGE: Deprecated uWSGI local cache. + - BREAKING CHANGE: Deprecated Python2 support. - Removed six, future and futures libs for compatibility between Python2 and Python3. - Updated strings encoding to utf-8 by default for Redis. - - Deprecated uWSGI cache. 8.4.1 (Apr 16, 2021) - Bumped mmh3cffi dependency which now requires c99 flag to build. diff --git a/splitio/client/config.py b/splitio/client/config.py index 0bdb9843..84141f9c 100644 --- a/splitio/client/config.py +++ b/splitio/client/config.py @@ -11,8 +11,6 @@ DEFAULT_CONFIG = { 'operationMode': 'in-memory', 'connectionTimeout': 1500, - 'splitSdkMachineName': None, - 'splitSdkMachineIp': None, 'streamingEnabled': True, 'featuresRefreshRate': 30, 'segmentsRefreshRate': 30, diff --git a/splitio/version.py b/splitio/version.py index 432526bd..c37cb6f7 100644 --- a/splitio/version.py +++ b/splitio/version.py @@ -1 +1 @@ -__version__ = '9.0.0-uwsgi' +__version__ = '9.0.0-all'