Skip to content

Commit db5eafc

Browse files
authored
Merge pull request #561 from splitio/rbs_redis_pluggable
updated redis, pluggable and localjson storages
2 parents 3396b5f + cc990a9 commit db5eafc

File tree

10 files changed

+1016
-186
lines changed

10 files changed

+1016
-186
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/inmemmory.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -109,6 +109,7 @@ def remove_flag_set(self, flag_sets, feature_flag_name, should_filter):
109109

110110
class InMemoryRuleBasedSegmentStorage(RuleBasedSegmentsStorage):
111111
"""InMemory implementation of a feature flag storage base."""
112+
112113
def __init__(self):
113114
"""Constructor."""
114115
self._lock = threading.RLock()
@@ -192,7 +193,7 @@ def _set_change_number(self, new_change_number):
192193

193194
def get_segment_names(self):
194195
"""
195-
Retrieve a list of all excluded segments names.
196+
Retrieve a list of all rule based segments names.
196197
197198
:return: List of segment names.
198199
:rtype: list(str)

splitio/storage/pluggable.py

Lines changed: 245 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -5,15 +5,257 @@
55
import threading
66

77
from splitio.optional.loaders import asyncio
8-
from splitio.models import splits, segments
8+
from splitio.models import splits, segments, rule_based_segments
99
from splitio.models.impressions import Impression
1010
from splitio.models.telemetry import MethodExceptions, MethodLatencies, TelemetryConfig, MAX_TAGS,\
1111
MethodLatenciesAsync, MethodExceptionsAsync, TelemetryConfigAsync
12-
from splitio.storage import FlagSetsFilter, SplitStorage, SegmentStorage, ImpressionStorage, EventStorage, TelemetryStorage
12+
from splitio.storage import FlagSetsFilter, SplitStorage, SegmentStorage, ImpressionStorage, EventStorage, TelemetryStorage, RuleBasedSegmentsStorage
1313
from splitio.util.storage_helper import get_valid_flag_sets, combine_valid_flag_sets
1414

1515
_LOGGER = logging.getLogger(__name__)
1616

17+
class PluggableRuleBasedSegmentsStorageBase(RuleBasedSegmentsStorage):
18+
"""Pluggable storage for rule based segments."""
19+
20+
_TILL_LENGTH = 4
21+
22+
def __init__(self, pluggable_adapter, prefix=None):
23+
"""
24+
Class constructor.
25+
26+
:param redis_client: Redis client or compliant interface.
27+
:type redis_client: splitio.storage.adapters.redis.RedisAdapter
28+
"""
29+
self._pluggable_adapter = pluggable_adapter
30+
self._prefix = "SPLITIO.rbsegment.{segment_name}"
31+
self._rb_segments_till_prefix = "SPLITIO.rbsegments.till"
32+
self._rb_segment_name_length = 18
33+
if prefix is not None:
34+
self._rb_segment_name_length += len(prefix) + 1
35+
self._prefix = prefix + "." + self._prefix
36+
self._rb_segments_till_prefix = prefix + "." + self._rb_segments_till_prefix
37+
38+
def get(self, segment_name):
39+
"""
40+
Retrieve a rule based segment.
41+
42+
:param segment_name: Name of the segment to fetch.
43+
:type segment_name: str
44+
45+
:rtype: str
46+
"""
47+
pass
48+
49+
def get_change_number(self):
50+
"""
51+
Retrieve latest rule based segment change number.
52+
53+
:rtype: int
54+
"""
55+
pass
56+
57+
def contains(self, segment_names):
58+
"""
59+
Return whether the segments exists in rule based segment in cache.
60+
61+
:param segment_names: segment name to validate.
62+
:type segment_names: str
63+
64+
:return: True if segment names exists. False otherwise.
65+
:rtype: bool
66+
"""
67+
pass
68+
69+
def get_segment_names(self):
70+
"""
71+
Retrieve a list of all excluded segments names.
72+
73+
:return: List of segment names.
74+
:rtype: list(str)
75+
"""
76+
pass
77+
78+
def update(self, to_add, to_delete, new_change_number):
79+
"""
80+
Update rule based segment..
81+
82+
:param to_add: List of rule based segment. to add
83+
:type to_add: list[splitio.models.rule_based_segments.RuleBasedSegment]
84+
:param to_delete: List of rule based segment. to delete
85+
:type to_delete: list[splitio.models.rule_based_segments.RuleBasedSegment]
86+
:param new_change_number: New change number.
87+
:type new_change_number: int
88+
"""
89+
raise NotImplementedError('Only redis-consumer mode is supported.')
90+
91+
def get_large_segment_names(self):
92+
"""
93+
Retrieve a list of all excluded large segments names.
94+
95+
:return: List of segment names.
96+
:rtype: list(str)
97+
"""
98+
pass
99+
100+
class PluggableRuleBasedSegmentsStorage(PluggableRuleBasedSegmentsStorageBase):
101+
"""Pluggable storage for rule based segments."""
102+
103+
def __init__(self, pluggable_adapter, prefix=None):
104+
"""
105+
Class constructor.
106+
107+
:param redis_client: Redis client or compliant interface.
108+
:type redis_client: splitio.storage.adapters.redis.RedisAdapter
109+
"""
110+
PluggableRuleBasedSegmentsStorageBase.__init__(self, pluggable_adapter, prefix)
111+
112+
def get(self, segment_name):
113+
"""
114+
Retrieve a rule based segment.
115+
116+
:param segment_name: Name of the segment to fetch.
117+
:type segment_name: str
118+
119+
:rtype: str
120+
"""
121+
try:
122+
rb_segment = self._pluggable_adapter.get(self._prefix.format(segment_name=segment_name))
123+
if not rb_segment:
124+
return None
125+
126+
return rule_based_segments.from_raw(rb_segment)
127+
128+
except Exception:
129+
_LOGGER.error('Error getting rule based segment from storage')
130+
_LOGGER.debug('Error: ', exc_info=True)
131+
return None
132+
133+
def get_change_number(self):
134+
"""
135+
Retrieve latest rule based segment change number.
136+
137+
:rtype: int
138+
"""
139+
try:
140+
return self._pluggable_adapter.get(self._rb_segments_till_prefix)
141+
142+
except Exception:
143+
_LOGGER.error('Error getting change number in rule based segment storage')
144+
_LOGGER.debug('Error: ', exc_info=True)
145+
return None
146+
147+
def contains(self, segment_names):
148+
"""
149+
Return whether the segments exists in rule based segment in cache.
150+
151+
:param segment_names: segment name to validate.
152+
:type segment_names: str
153+
154+
:return: True if segment names exists. False otherwise.
155+
:rtype: bool
156+
"""
157+
return set(segment_names).issubset(self.get_segment_names())
158+
159+
def get_segment_names(self):
160+
"""
161+
Retrieve a list of all rule based segments names.
162+
163+
:return: List of segment names.
164+
:rtype: list(str)
165+
"""
166+
try:
167+
_LOGGER.error(self._rb_segment_name_length)
168+
_LOGGER.error(self._prefix)
169+
_LOGGER.error(self._prefix[:self._rb_segment_name_length])
170+
keys = []
171+
for key in self._pluggable_adapter.get_keys_by_prefix(self._prefix[:self._rb_segment_name_length]):
172+
if key[-self._TILL_LENGTH:] != 'till':
173+
keys.append(key[len(self._prefix[:self._rb_segment_name_length]):])
174+
return keys
175+
176+
except Exception:
177+
_LOGGER.error('Error getting rule based segments names from storage')
178+
_LOGGER.debug('Error: ', exc_info=True)
179+
return None
180+
181+
class PluggableRuleBasedSegmentsStorageAsync(PluggableRuleBasedSegmentsStorageBase):
182+
"""Pluggable storage for rule based segments."""
183+
184+
def __init__(self, pluggable_adapter, prefix=None):
185+
"""
186+
Class constructor.
187+
188+
:param redis_client: Redis client or compliant interface.
189+
:type redis_client: splitio.storage.adapters.redis.RedisAdapter
190+
"""
191+
PluggableRuleBasedSegmentsStorageBase.__init__(self, pluggable_adapter, prefix)
192+
193+
async def get(self, segment_name):
194+
"""
195+
Retrieve a rule based segment.
196+
197+
:param segment_name: Name of the segment to fetch.
198+
:type segment_name: str
199+
200+
:rtype: str
201+
"""
202+
try:
203+
rb_segment = await self._pluggable_adapter.get(self._prefix.format(segment_name=segment_name))
204+
if not rb_segment:
205+
return None
206+
207+
return rule_based_segments.from_raw(rb_segment)
208+
209+
except Exception:
210+
_LOGGER.error('Error getting rule based segment from storage')
211+
_LOGGER.debug('Error: ', exc_info=True)
212+
return None
213+
214+
async def get_change_number(self):
215+
"""
216+
Retrieve latest rule based segment change number.
217+
218+
:rtype: int
219+
"""
220+
try:
221+
return await self._pluggable_adapter.get(self._rb_segments_till_prefix)
222+
223+
except Exception:
224+
_LOGGER.error('Error getting change number in rule based segment storage')
225+
_LOGGER.debug('Error: ', exc_info=True)
226+
return None
227+
228+
async def contains(self, segment_names):
229+
"""
230+
Return whether the segments exists in rule based segment in cache.
231+
232+
:param segment_names: segment name to validate.
233+
:type segment_names: str
234+
235+
:return: True if segment names exists. False otherwise.
236+
:rtype: bool
237+
"""
238+
return set(segment_names).issubset(await self.get_segment_names())
239+
240+
async def get_segment_names(self):
241+
"""
242+
Retrieve a list of all rule based segments names.
243+
244+
:return: List of segment names.
245+
:rtype: list(str)
246+
"""
247+
try:
248+
keys = []
249+
for key in await self._pluggable_adapter.get_keys_by_prefix(self._prefix[:self._rb_segment_name_length]):
250+
if key[-self._TILL_LENGTH:] != 'till':
251+
keys.append(key[len(self._prefix[:self._rb_segment_name_length]):])
252+
return keys
253+
254+
except Exception:
255+
_LOGGER.error('Error getting rule based segments names from storage')
256+
_LOGGER.debug('Error: ', exc_info=True)
257+
return None
258+
17259
class PluggableSplitStorageBase(SplitStorage):
18260
"""InMemory implementation of a feature flag storage."""
19261

@@ -90,7 +332,7 @@ def update(self, to_add, to_delete, new_change_number):
90332
:param new_change_number: New change number.
91333
:type new_change_number: int
92334
"""
93-
# pass
335+
pass
94336
# try:
95337
# split = self.get(feature_flag_name)
96338
# if not split:

0 commit comments

Comments
 (0)