Skip to content

Commit 4d8327c

Browse files
committed
Updated redis, pluggable and localjson storages
1 parent 7cd34eb commit 4d8327c

File tree

9 files changed

+482
-163
lines changed

9 files changed

+482
-163
lines changed

splitio/models/rule_based_segments.py

Lines changed: 24 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -11,14 +11,14 @@
1111
class RuleBasedSegment(object):
1212
"""RuleBasedSegment object class."""
1313

14-
def __init__(self, name, traffic_yype_Name, change_number, status, conditions, excluded):
14+
def __init__(self, name, traffic_type_name, change_number, status, conditions, excluded):
1515
"""
1616
Class constructor.
1717
1818
:param name: Segment name.
1919
:type name: str
20-
:param traffic_yype_Name: traffic type name.
21-
:type traffic_yype_Name: str
20+
:param traffic_type_name: traffic type name.
21+
:type traffic_type_name: str
2222
:param change_number: change number.
2323
:type change_number: str
2424
:param status: status.
@@ -29,7 +29,7 @@ def __init__(self, name, traffic_yype_Name, change_number, status, conditions, e
2929
:type excluded: Excluded
3030
"""
3131
self._name = name
32-
self._traffic_yype_Name = traffic_yype_Name
32+
self._traffic_type_name = traffic_type_name
3333
self._change_number = change_number
3434
self._status = status
3535
self._conditions = conditions
@@ -41,9 +41,9 @@ def name(self):
4141
return self._name
4242

4343
@property
44-
def traffic_yype_Name(self):
44+
def traffic_type_name(self):
4545
"""Return traffic type name."""
46-
return self._traffic_yype_Name
46+
return self._traffic_type_name
4747

4848
@property
4949
def change_number(self):
@@ -65,6 +65,17 @@ def excluded(self):
6565
"""Return excluded."""
6666
return self._excluded
6767

68+
def to_json(self):
69+
"""Return a JSON representation of this rule based segment."""
70+
return {
71+
'changeNumber': self.change_number,
72+
'trafficTypeName': self.traffic_type_name,
73+
'name': self.name,
74+
'status': self.status,
75+
'conditions': [c.to_json() for c in self.conditions],
76+
'excluded': self.excluded.to_json()
77+
}
78+
6879
def from_raw(raw_rule_based_segment):
6980
"""
7081
Parse a Rule based segment from a JSON portion of splitChanges.
@@ -111,3 +122,10 @@ def get_excluded_keys(self):
111122
def get_excluded_segments(self):
112123
"""Return excluded segments"""
113124
return self._segments
125+
126+
def to_json(self):
127+
"""Return a JSON representation of this object."""
128+
return {
129+
'keys': self._keys,
130+
'segments': self._segments
131+
}

splitio/storage/pluggable.py

Lines changed: 12 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,6 @@
1717
class PluggableRuleBasedSegmentsStorageBase(RuleBasedSegmentsStorage):
1818
"""RedPluggable storage for rule based segments."""
1919

20-
_RB_SEGMENT_NAME_LENGTH = 23
2120
_TILL_LENGTH = 4
2221

2322
def __init__(self, pluggable_adapter, prefix=None):
@@ -28,9 +27,11 @@ def __init__(self, pluggable_adapter, prefix=None):
2827
:type redis_client: splitio.storage.adapters.redis.RedisAdapter
2928
"""
3029
self._pluggable_adapter = pluggable_adapter
31-
self._prefix = "SPLITIO.rbsegment.${segmen_name}"
30+
self._prefix = "SPLITIO.rbsegment.{segment_name}"
3231
self._rb_segments_till_prefix = "SPLITIO.rbsegments.till"
32+
self._rb_segment_name_length = 18
3333
if prefix is not None:
34+
self._rb_segment_name_length += len(prefix) + 1
3435
self._prefix = prefix + "." + self._prefix
3536
self._rb_segments_till_prefix = prefix + "." + self._rb_segments_till_prefix
3637

@@ -163,18 +164,21 @@ def get_segment_names(self):
163164
:rtype: list(str)
164165
"""
165166
try:
167+
_LOGGER.error(self._rb_segment_name_length)
168+
_LOGGER.error(self._prefix)
169+
_LOGGER.error(self._prefix[:self._rb_segment_name_length])
166170
keys = []
167-
for key in self._pluggable_adapter.get_keys_by_prefix(self._prefix[:-self._RB_SEGMENT_NAME_LENGTH]):
171+
for key in self._pluggable_adapter.get_keys_by_prefix(self._prefix[:self._rb_segment_name_length]):
168172
if key[-self._TILL_LENGTH:] != 'till':
169-
keys.append(key[len(self._prefix[:-self._RB_SEGMENT_NAME_LENGTH]):])
173+
keys.append(key[len(self._prefix[:self._rb_segment_name_length]):])
170174
return keys
171175

172176
except Exception:
173177
_LOGGER.error('Error getting rule based segments names from storage')
174178
_LOGGER.debug('Error: ', exc_info=True)
175179
return None
176180

177-
class PluggableRuleBasedSegmentsStorageAsync(RuleBasedSegmentsStorage):
181+
class PluggableRuleBasedSegmentsStorageAsync(PluggableRuleBasedSegmentsStorageBase):
178182
"""RedPluggable storage for rule based segments."""
179183

180184
def __init__(self, pluggable_adapter, prefix=None):
@@ -231,7 +235,7 @@ async def contains(self, segment_names):
231235
:return: True if segment names exists. False otherwise.
232236
:rtype: bool
233237
"""
234-
return await set(segment_names).issubset(self.get_segment_names())
238+
return set(segment_names).issubset(await self.get_segment_names())
235239

236240
async def get_segment_names(self):
237241
"""
@@ -242,9 +246,9 @@ async def get_segment_names(self):
242246
"""
243247
try:
244248
keys = []
245-
for key in await self._pluggable_adapter.get_keys_by_prefix(self._prefix[:-self._RB_SEGMENT_NAME_LENGTH]):
249+
for key in await self._pluggable_adapter.get_keys_by_prefix(self._prefix[:self._rb_segment_name_length]):
246250
if key[-self._TILL_LENGTH:] != 'till':
247-
keys.append(key[len(self._prefix[:-self._RB_SEGMENT_NAME_LENGTH]):])
251+
keys.append(key[len(self._prefix[:self._rb_segment_name_length]):])
248252
return keys
249253

250254
except Exception:

splitio/storage/redis.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,7 @@
1919
class RedisRuleBasedSegmentsStorage(RuleBasedSegmentsStorage):
2020
"""Redis-based storage for rule based segments."""
2121

22-
_RB_SEGMENT_KEY = 'SPLITIO.rbsegment.${segmen_name}'
22+
_RB_SEGMENT_KEY = 'SPLITIO.rbsegment.{segment_name}'
2323
_RB_SEGMENT_TILL_KEY = 'SPLITIO.rbsegments.till'
2424

2525
def __init__(self, redis_client):
@@ -134,7 +134,7 @@ def get_large_segment_names(self):
134134
class RedisRuleBasedSegmentsStorageAsync(RuleBasedSegmentsStorage):
135135
"""Redis-based storage for rule based segments."""
136136

137-
_RB_SEGMENT_KEY = 'SPLITIO.rbsegment.${segmen_name}'
137+
_RB_SEGMENT_KEY = 'SPLITIO.rbsegment.{segment_name}'
138138
_RB_SEGMENT_TILL_KEY = 'SPLITIO.rbsegments.till'
139139

140140
def __init__(self, redis_client):

splitio/sync/split.py

Lines changed: 15 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -545,6 +545,7 @@ def _sanitize_rb_segment_elements(self, parsed_rb_segments):
545545
('changeNumber', 0, 0, None, None, None)]:
546546
rb_segment = util._sanitize_object_element(rb_segment, 'rule based segment', element[0], element[1], lower_value=element[2], upper_value=element[3], in_list=element[4], not_in_list=element[5])
547547
rb_segment = self._sanitize_condition(rb_segment)
548+
rb_segment = self._remove_partition(rb_segment)
548549
sanitized_rb_segments.append(rb_segment)
549550
return sanitized_rb_segments
550551

@@ -599,6 +600,15 @@ def _sanitize_condition(self, feature_flag):
599600
})
600601

601602
return feature_flag
603+
604+
def _remove_partition(self, rb_segment):
605+
sanitized = []
606+
for condition in rb_segment['conditions']:
607+
if 'partition' in condition:
608+
del condition['partition']
609+
sanitized.append(condition)
610+
rb_segment['conditions'] = sanitized
611+
return rb_segment
602612

603613
@classmethod
604614
def _convert_yaml_to_feature_flag(cls, parsed):
@@ -769,8 +779,8 @@ def _read_feature_flags_from_json_file(self, filename):
769779
with open(filename, 'r') as flo:
770780
parsed = json.load(flo)
771781
santitized = self._sanitize_json_elements(parsed)
772-
santitized['ff'] = self._sanitize_feature_flag_elements(santitized['ff'])
773-
santitized['rbs'] = self._sanitize_rb_segment_elements(santitized['rbs'])
782+
santitized['ff']['d'] = self._sanitize_feature_flag_elements(santitized['ff']['d'])
783+
santitized['rbs']['d'] = self._sanitize_rb_segment_elements(santitized['rbs']['d'])
774784
return santitized
775785

776786
except Exception as exc:
@@ -903,7 +913,7 @@ async def _synchronize_json(self):
903913

904914
if await self._rule_based_segment_storage.get_change_number() <= parsed['rbs']['t'] or parsed['rbs']['t'] == self._DEFAULT_FEATURE_FLAG_TILL:
905915
fetched_rb_segments = [rule_based_segments.from_raw(rb_segment) for rb_segment in parsed['rbs']['d']]
906-
segment_list.update(await update_rule_based_segment_storage(self._rule_based_segment_storage, fetched_rb_segments, parsed['rbs']['t']))
916+
segment_list.update(await update_rule_based_segment_storage_async(self._rule_based_segment_storage, fetched_rb_segments, parsed['rbs']['t']))
907917

908918
return segment_list
909919

@@ -925,8 +935,8 @@ async def _read_feature_flags_from_json_file(self, filename):
925935
async with aiofiles.open(filename, 'r') as flo:
926936
parsed = json.loads(await flo.read())
927937
santitized = self._sanitize_json_elements(parsed)
928-
santitized['ff'] = self._sanitize_feature_flag_elements(santitized['ff'])
929-
santitized['rbs'] = self._sanitize_rb_segment_elements(santitized['rbs'])
938+
santitized['ff']['d'] = self._sanitize_feature_flag_elements(santitized['ff']['d'])
939+
santitized['rbs']['d'] = self._sanitize_rb_segment_elements(santitized['rbs']['d'])
930940
return santitized
931941
except Exception as exc:
932942
_LOGGER.debug('Exception: ', exc_info=True)

tests/integration/__init__.py

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -47,3 +47,7 @@
4747
"splitChange6_2": split62,
4848
"splitChange6_3": split63,
4949
}
50+
51+
rbsegments_json = {
52+
"segment1": {"changeNumber": 12, "name": "some_segment", "status": "ACTIVE","trafficTypeName": "user","excluded":{"keys":[],"segments":[]},"conditions": []}
53+
}

tests/storage/test_pluggable.py

Lines changed: 125 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,20 +1,21 @@
11
"""Pluggable storage test module."""
22
import json
33
import threading
4+
import copy
45
import pytest
56

67
from splitio.optional.loaders import asyncio
78
from splitio.models.splits import Split
8-
from splitio.models import splits, segments
9+
from splitio.models import splits, segments, rule_based_segments
910
from splitio.models.segments import Segment
1011
from splitio.models.impressions import Impression
1112
from splitio.models.events import Event, EventWrapper
1213
from splitio.storage.pluggable import PluggableSplitStorage, PluggableSegmentStorage, PluggableImpressionsStorage, PluggableEventsStorage, \
1314
PluggableTelemetryStorage, PluggableEventsStorageAsync, PluggableSegmentStorageAsync, PluggableImpressionsStorageAsync,\
14-
PluggableSplitStorageAsync, PluggableTelemetryStorageAsync
15+
PluggableSplitStorageAsync, PluggableTelemetryStorageAsync, PluggableRuleBasedSegmentsStorage, PluggableRuleBasedSegmentsStorageAsync
1516
from splitio.client.util import get_metadata, SdkMetadata
1617
from splitio.models.telemetry import MAX_TAGS, MethodExceptionsAndLatencies, OperationMode
17-
from tests.integration import splits_json
18+
from tests.integration import splits_json, rbsegments_json
1819

1920
class StorageMockAdapter(object):
2021
def __init__(self):
@@ -1372,3 +1373,124 @@ async def test_push_config_stats(self):
13721373
await pluggable_telemetry_storage.record_active_and_redundant_factories(2, 1)
13731374
await pluggable_telemetry_storage.push_config_stats()
13741375
assert(self.mock_adapter._keys[pluggable_telemetry_storage._telemetry_config_key + "::" + pluggable_telemetry_storage._sdk_metadata] == '{"aF": 2, "rF": 1, "sT": "memory", "oM": 0, "t": []}')
1376+
1377+
class PluggableRuleBasedSegmentStorageTests(object):
1378+
"""In memory rule based segment storage test cases."""
1379+
1380+
def setup_method(self):
1381+
"""Prepare storages with test data."""
1382+
self.mock_adapter = StorageMockAdapter()
1383+
1384+
def test_get(self):
1385+
self.mock_adapter._keys = {}
1386+
for sprefix in [None, 'myprefix']:
1387+
pluggable_rbs_storage = PluggableRuleBasedSegmentsStorage(self.mock_adapter, prefix=sprefix)
1388+
1389+
rbs1 = rule_based_segments.from_raw(rbsegments_json['segment1'])
1390+
rbs_name = rbsegments_json['segment1']['name']
1391+
1392+
self.mock_adapter.set(pluggable_rbs_storage._prefix.format(segment_name=rbs_name), rbs1.to_json())
1393+
assert(pluggable_rbs_storage.get(rbs_name).to_json() == rule_based_segments.from_raw(rbsegments_json['segment1']).to_json())
1394+
assert(pluggable_rbs_storage.get('not_existing') == None)
1395+
1396+
def test_get_change_number(self):
1397+
self.mock_adapter._keys = {}
1398+
for sprefix in [None, 'myprefix']:
1399+
pluggable_rbs_storage = PluggableRuleBasedSegmentsStorage(self.mock_adapter, prefix=sprefix)
1400+
if sprefix == 'myprefix':
1401+
prefix = 'myprefix.'
1402+
else:
1403+
prefix = ''
1404+
self.mock_adapter.set(prefix + "SPLITIO.rbsegments.till", 1234)
1405+
assert(pluggable_rbs_storage.get_change_number() == 1234)
1406+
1407+
def test_get_segment_names(self):
1408+
self.mock_adapter._keys = {}
1409+
for sprefix in [None, 'myprefix']:
1410+
pluggable_rbs_storage = PluggableRuleBasedSegmentsStorage(self.mock_adapter, prefix=sprefix)
1411+
rbs1 = rule_based_segments.from_raw(rbsegments_json['segment1'])
1412+
rbs2_temp = copy.deepcopy(rbsegments_json['segment1'])
1413+
rbs2_temp['name'] = 'another_segment'
1414+
rbs2 = rule_based_segments.from_raw(rbs2_temp)
1415+
self.mock_adapter.set(pluggable_rbs_storage._prefix.format(segment_name=rbs1.name), rbs1.to_json())
1416+
self.mock_adapter.set(pluggable_rbs_storage._prefix.format(segment_name=rbs2.name), rbs2.to_json())
1417+
assert(pluggable_rbs_storage.get_segment_names() == [rbs1.name, rbs2.name])
1418+
1419+
def test_contains(self):
1420+
self.mock_adapter._keys = {}
1421+
for sprefix in [None, 'myprefix']:
1422+
pluggable_rbs_storage = PluggableRuleBasedSegmentsStorage(self.mock_adapter, prefix=sprefix)
1423+
rbs1 = rule_based_segments.from_raw(rbsegments_json['segment1'])
1424+
rbs2_temp = copy.deepcopy(rbsegments_json['segment1'])
1425+
rbs2_temp['name'] = 'another_segment'
1426+
rbs2 = rule_based_segments.from_raw(rbs2_temp)
1427+
self.mock_adapter.set(pluggable_rbs_storage._prefix.format(segment_name=rbs1.name), rbs1.to_json())
1428+
self.mock_adapter.set(pluggable_rbs_storage._prefix.format(segment_name=rbs2.name), rbs2.to_json())
1429+
1430+
assert(pluggable_rbs_storage.contains([rbs1.name, rbs2.name]))
1431+
assert(pluggable_rbs_storage.contains([rbs2.name]))
1432+
assert(not pluggable_rbs_storage.contains(['none-exists', rbs2.name]))
1433+
assert(not pluggable_rbs_storage.contains(['none-exists', 'none-exists2']))
1434+
1435+
class PluggableRuleBasedSegmentStorageAsyncTests(object):
1436+
"""In memory rule based segment storage test cases."""
1437+
1438+
def setup_method(self):
1439+
"""Prepare storages with test data."""
1440+
self.mock_adapter = StorageMockAdapterAsync()
1441+
1442+
@pytest.mark.asyncio
1443+
async def test_get(self):
1444+
self.mock_adapter._keys = {}
1445+
for sprefix in [None, 'myprefix']:
1446+
pluggable_rbs_storage = PluggableRuleBasedSegmentsStorageAsync(self.mock_adapter, prefix=sprefix)
1447+
1448+
rbs1 = rule_based_segments.from_raw(rbsegments_json['segment1'])
1449+
rbs_name = rbsegments_json['segment1']['name']
1450+
1451+
await self.mock_adapter.set(pluggable_rbs_storage._prefix.format(segment_name=rbs_name), rbs1.to_json())
1452+
rbs = await pluggable_rbs_storage.get(rbs_name)
1453+
assert(rbs.to_json() == rule_based_segments.from_raw(rbsegments_json['segment1']).to_json())
1454+
assert(await pluggable_rbs_storage.get('not_existing') == None)
1455+
1456+
@pytest.mark.asyncio
1457+
async def test_get_change_number(self):
1458+
self.mock_adapter._keys = {}
1459+
for sprefix in [None, 'myprefix']:
1460+
pluggable_rbs_storage = PluggableRuleBasedSegmentsStorageAsync(self.mock_adapter, prefix=sprefix)
1461+
if sprefix == 'myprefix':
1462+
prefix = 'myprefix.'
1463+
else:
1464+
prefix = ''
1465+
await self.mock_adapter.set(prefix + "SPLITIO.rbsegments.till", 1234)
1466+
assert(await pluggable_rbs_storage.get_change_number() == 1234)
1467+
1468+
@pytest.mark.asyncio
1469+
async def test_get_segment_names(self):
1470+
self.mock_adapter._keys = {}
1471+
for sprefix in [None, 'myprefix']:
1472+
pluggable_rbs_storage = PluggableRuleBasedSegmentsStorageAsync(self.mock_adapter, prefix=sprefix)
1473+
rbs1 = rule_based_segments.from_raw(rbsegments_json['segment1'])
1474+
rbs2_temp = copy.deepcopy(rbsegments_json['segment1'])
1475+
rbs2_temp['name'] = 'another_segment'
1476+
rbs2 = rule_based_segments.from_raw(rbs2_temp)
1477+
await self.mock_adapter.set(pluggable_rbs_storage._prefix.format(segment_name=rbs1.name), rbs1.to_json())
1478+
await self.mock_adapter.set(pluggable_rbs_storage._prefix.format(segment_name=rbs2.name), rbs2.to_json())
1479+
assert(await pluggable_rbs_storage.get_segment_names() == [rbs1.name, rbs2.name])
1480+
1481+
@pytest.mark.asyncio
1482+
async def test_contains(self):
1483+
self.mock_adapter._keys = {}
1484+
for sprefix in [None, 'myprefix']:
1485+
pluggable_rbs_storage = PluggableRuleBasedSegmentsStorageAsync(self.mock_adapter, prefix=sprefix)
1486+
rbs1 = rule_based_segments.from_raw(rbsegments_json['segment1'])
1487+
rbs2_temp = copy.deepcopy(rbsegments_json['segment1'])
1488+
rbs2_temp['name'] = 'another_segment'
1489+
rbs2 = rule_based_segments.from_raw(rbs2_temp)
1490+
await self.mock_adapter.set(pluggable_rbs_storage._prefix.format(segment_name=rbs1.name), rbs1.to_json())
1491+
await self.mock_adapter.set(pluggable_rbs_storage._prefix.format(segment_name=rbs2.name), rbs2.to_json())
1492+
1493+
assert(await pluggable_rbs_storage.contains([rbs1.name, rbs2.name]))
1494+
assert(await pluggable_rbs_storage.contains([rbs2.name]))
1495+
assert(not await pluggable_rbs_storage.contains(['none-exists', rbs2.name]))
1496+
assert(not await pluggable_rbs_storage.contains(['none-exists', 'none-exists2']))

0 commit comments

Comments
 (0)