Skip to content

Commit b92badd

Browse files
authored
Merge pull request #610 from splitio/FME-12220-sdk-events-manager
added events manager and events delivery
2 parents a5aa01d + 59a5530 commit b92badd

File tree

5 files changed

+325
-0
lines changed

5 files changed

+325
-0
lines changed

splitio/events/__init__.py

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
"""Base storage interfaces."""
2+
import abc
3+
4+
class EventsManagerInterface(object, metaclass=abc.ABCMeta):
5+
"""Events manager interface implemented as an abstract class."""
6+
7+
@abc.abstractmethod
8+
def register(self, sdk_event, event_handler):
9+
pass
10+
11+
@abc.abstractmethod
12+
def unregister(self, sdk_event):
13+
pass
14+
15+
@abc.abstractmethod
16+
def notify_internal_event(self, sdk_internal_event, event_metadata):
17+
pass
18+
19+
20+
class EventsDeliveryInterface(object, metaclass=abc.ABCMeta):
21+
"""Events Delivery interface."""
22+
23+
@abc.abstractmethod
24+
def deliver(self, sdk_event, event_metadata, event_handler):
25+
pass

splitio/events/events_delivery.py

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
"""Events Manager."""
2+
import logging
3+
4+
from splitio.events import EventsDeliveryInterface
5+
6+
_LOGGER = logging.getLogger(__name__)
7+
8+
class EventsDelivery(EventsDeliveryInterface):
9+
"""Events Manager class."""
10+
11+
def __init__(self):
12+
"""
13+
Construct Events Manager instance.
14+
"""
15+
16+
def deliver(self, sdk_event, event_metadata, event_handler):
17+
try:
18+
event_handler(event_metadata)
19+
except Exception as ex:
20+
_LOGGER.error("Exception when calling handler for Sdk Event %s", sdk_event)
21+
_LOGGER.error(ex)

splitio/events/events_manager.py

Lines changed: 152 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,152 @@
1+
"""Events Manager."""
2+
import threading
3+
import logging
4+
from collections import namedtuple
5+
import pytest
6+
7+
from splitio.events import EventsManagerInterface
8+
9+
_LOGGER = logging.getLogger(__name__)
10+
11+
ValidSdkEvent = namedtuple('ValidSdkEvent', ['sdk_event', 'valid'])
12+
ActiveSubscriptions = namedtuple('ActiveSubscriptions', ['triggered', 'handler'])
13+
14+
class EventsManager(EventsManagerInterface):
15+
"""Events Manager class."""
16+
17+
def __init__(self, events_configurations, events_delivery):
18+
"""
19+
Construct Events Manager instance.
20+
"""
21+
self._active_subscriptions = {}
22+
self._internal_events_status = {}
23+
self._events_delivery = events_delivery
24+
self._manager_config = events_configurations
25+
self._lock = threading.RLock()
26+
27+
def register(self, sdk_event, event_handler):
28+
if self._active_subscriptions.get(sdk_event) != None:
29+
return
30+
31+
with self._lock:
32+
self._active_subscriptions[sdk_event] = ActiveSubscriptions(False, event_handler)
33+
34+
def unregister(self, sdk_event):
35+
if self._active_subscriptions.get(sdk_event) == None:
36+
return
37+
38+
with self._lock:
39+
del self._active_subscriptions[sdk_event]
40+
41+
def notify_internal_event(self, sdk_internal_event, event_metadata):
42+
with self._lock:
43+
for sorted_event in self._manager_config.evaluation_order:
44+
if sorted_event in self._get_sdk_event_if_applicable(sdk_internal_event):
45+
_LOGGER.debug("EventsManager: Firing Sdk event %s", sorted_event)
46+
if self._get_event_handler(sorted_event) != None:
47+
notify_event = threading.Thread(target=self._events_delivery.deliver, args=[sorted_event, event_metadata, self._get_event_handler(sorted_event)],
48+
name='SplitSDKEventNotify', daemon=True)
49+
notify_event.start()
50+
self._set_sdk_event_triggered(sorted_event)
51+
52+
def _event_already_triggered(self, sdk_event):
53+
if self._active_subscriptions.get(sdk_event) != None:
54+
return self._active_subscriptions.get(sdk_event).triggered
55+
56+
return False
57+
58+
def _get_internal_event_status(self, sdk_internal_event):
59+
if self._internal_events_status.get(sdk_internal_event) != None:
60+
return self._internal_events_status[sdk_internal_event]
61+
62+
return False
63+
64+
def _update_internal_event_status(self, sdk_internal_event, status):
65+
with self._lock:
66+
self._internal_events_status[sdk_internal_event] = status
67+
68+
def _set_sdk_event_triggered(self, sdk_event):
69+
if self._active_subscriptions.get(sdk_event) == None:
70+
return
71+
72+
if self._active_subscriptions.get(sdk_event).triggered == True:
73+
return
74+
75+
self._active_subscriptions[sdk_event] = self._active_subscriptions[sdk_event]._replace(triggered = True)
76+
77+
def _get_event_handler(self, sdk_event):
78+
if self._active_subscriptions.get(sdk_event) == None:
79+
return None
80+
81+
return self._active_subscriptions.get(sdk_event).handler
82+
83+
def _get_sdk_event_if_applicable(self, sdk_internal_event):
84+
final_sdk_event = ValidSdkEvent(None, False)
85+
self._update_internal_event_status(sdk_internal_event, True)
86+
87+
events_to_fire = []
88+
require_any_sdk_event = self._check_require_any(sdk_internal_event)
89+
if require_any_sdk_event.valid:
90+
if (not self._set_sdk_event_triggered(require_any_sdk_event.sdk_event) and
91+
self._execution_limit(require_any_sdk_event.sdk_event) == 1) or \
92+
self._execution_limit(require_any_sdk_event.sdk_event) == -1:
93+
final_sdk_event = final_sdk_event._replace(sdk_event = require_any_sdk_event.sdk_event,
94+
valid = self._check_prerequisites(require_any_sdk_event.sdk_event) and \
95+
self._check_suppressed_by(require_any_sdk_event.sdk_event))
96+
97+
if final_sdk_event.valid:
98+
events_to_fire.append(final_sdk_event.sdk_event)
99+
100+
[events_to_fire.append(sdk_event) for sdk_event in self._check_require_all()]
101+
102+
return events_to_fire
103+
104+
def _check_require_all(self):
105+
events = []
106+
for require_name, require_value in self._manager_config.require_all.items():
107+
final_status = True
108+
for val in require_value:
109+
final_status &= self._get_internal_event_status(val)
110+
111+
if final_status and \
112+
self._check_prerequisites(require_name) and \
113+
((not self._event_already_triggered(require_name) and
114+
self._execution_limit(require_name) == 1) or \
115+
self._execution_limit(require_name) == -1) and \
116+
len(require_value) > 0:
117+
118+
events.append(require_name)
119+
120+
return events
121+
122+
def _check_prerequisites(self, sdk_event):
123+
for name, value in self._manager_config.prerequisites.items():
124+
for val in value:
125+
if name == sdk_event and not self._event_already_triggered(val):
126+
return False
127+
128+
return True
129+
130+
def _check_suppressed_by(self, sdk_event):
131+
for name, value in self._manager_config.suppressed_by.items():
132+
for val in value:
133+
if name == sdk_event and self._event_already_triggered(val):
134+
return False
135+
136+
return True
137+
138+
def _execution_limit(self, sdk_event):
139+
limit = self._manager_config.execution_limits.get(sdk_event)
140+
if limit == None:
141+
return -1
142+
143+
return limit
144+
145+
def _check_require_any(self, sdk_internal_event):
146+
valid_sdk_event = ValidSdkEvent(None, False)
147+
for name, val in self._manager_config.require_any.items():
148+
if sdk_internal_event in val:
149+
valid_sdk_event = valid_sdk_event._replace(valid = True, sdk_event = name)
150+
return valid_sdk_event
151+
152+
return valid_sdk_event
Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
"""EventsManager test module."""
2+
from splitio.models.events import SdkEvent, SdkInternalEvent
3+
from splitio.events.events_metadata import EventsMetadata
4+
from splitio.events.events_delivery import EventsDelivery
5+
from splitio.events.events_metadata import SdkEventType
6+
7+
class EventsDeliveryTests(object):
8+
"""Tests for EventsManager."""
9+
10+
sdk_ready_flag = False
11+
metadata = None
12+
13+
def test_firing_events(self):
14+
events_delivery = EventsDelivery()
15+
16+
metadata = EventsMetadata(SdkEventType.FLAG_UPDATE, { "feature1" })
17+
events_delivery.deliver(SdkEvent.SDK_READY, metadata, self._sdk_ready_callback)
18+
assert self.sdk_ready_flag
19+
self._verify_metadata(metadata)
20+
21+
def _sdk_ready_callback(self, metadata):
22+
self.sdk_ready_flag = True
23+
self.metadata = metadata
24+
25+
def _verify_metadata(self, metadata):
26+
assert metadata.get_type() == self.metadata.get_type()
27+
assert metadata.get_names() == self.metadata.get_names()
Lines changed: 100 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,100 @@
1+
"""EventsManager test module."""
2+
import pytest
3+
from splitio.models.events import SdkEvent, SdkInternalEvent
4+
from splitio.events.events_metadata import EventsMetadata
5+
from splitio.events.events_manager_config import EventsManagerConfig
6+
from splitio.events.events_delivery import EventsDelivery
7+
from splitio.events.events_manager import EventsManager
8+
from splitio.events.events_metadata import SdkEventType
9+
10+
class EventsManagerTests(object):
11+
"""Tests for EventsManager."""
12+
13+
sdk_ready_flag = False
14+
sdk_timed_out_flag = False
15+
sdk_update_flag = False
16+
metadata = None
17+
18+
def test_firing_events(self):
19+
events_manager = EventsManager(EventsManagerConfig(), EventsDelivery())
20+
events_manager.register(SdkEvent.SDK_READY, self._sdk_ready_callback)
21+
events_manager.register(SdkEvent.SDK_UPDATE, self._sdk_update_callback)
22+
23+
metadata = EventsMetadata(SdkEventType.FLAG_UPDATE, { "feature1" })
24+
events_manager.notify_internal_event(SdkInternalEvent.FLAGS_UPDATED, metadata)
25+
events_manager.notify_internal_event(SdkInternalEvent.FLAG_KILLED_NOTIFICATION, metadata)
26+
events_manager.notify_internal_event(SdkInternalEvent.RB_SEGMENTS_UPDATED, metadata)
27+
events_manager.notify_internal_event(SdkInternalEvent.SEGMENTS_UPDATED, metadata)
28+
assert not self.sdk_ready_flag
29+
assert not self.sdk_timed_out_flag
30+
assert not self.sdk_update_flag
31+
32+
self._reset_flags()
33+
events_manager.notify_internal_event(SdkInternalEvent.SDK_TIMED_OUT, metadata)
34+
assert not self.sdk_ready_flag
35+
assert not self.sdk_timed_out_flag # not registered yet
36+
assert not self.sdk_update_flag
37+
38+
events_manager.register(SdkEvent.SDK_READY_TIMED_OUT, self._sdk_timeout_callback)
39+
events_manager.notify_internal_event(SdkInternalEvent.SDK_TIMED_OUT, metadata)
40+
assert not self.sdk_ready_flag
41+
assert self.sdk_timed_out_flag
42+
assert not self.sdk_update_flag
43+
self._verify_metadata(metadata)
44+
45+
self._reset_flags()
46+
events_manager.notify_internal_event(SdkInternalEvent.SDK_READY, metadata)
47+
assert self.sdk_ready_flag
48+
assert not self.sdk_timed_out_flag
49+
assert not self.sdk_update_flag
50+
self._verify_metadata(metadata)
51+
52+
self._reset_flags()
53+
events_manager.notify_internal_event(SdkInternalEvent.RB_SEGMENTS_UPDATED, metadata)
54+
assert not self.sdk_ready_flag
55+
assert not self.sdk_timed_out_flag
56+
assert self.sdk_update_flag
57+
self._verify_metadata(metadata)
58+
59+
self._reset_flags()
60+
events_manager.notify_internal_event(SdkInternalEvent.FLAG_KILLED_NOTIFICATION, metadata)
61+
assert not self.sdk_ready_flag
62+
assert not self.sdk_timed_out_flag
63+
assert self.sdk_update_flag
64+
self._verify_metadata(metadata)
65+
66+
self._reset_flags()
67+
events_manager.notify_internal_event(SdkInternalEvent.FLAGS_UPDATED, metadata)
68+
assert not self.sdk_ready_flag
69+
assert not self.sdk_timed_out_flag
70+
assert self.sdk_update_flag
71+
self._verify_metadata(metadata)
72+
73+
self._reset_flags()
74+
events_manager.notify_internal_event(SdkInternalEvent.SEGMENTS_UPDATED, metadata)
75+
assert not self.sdk_ready_flag
76+
assert not self.sdk_timed_out_flag
77+
assert self.sdk_update_flag
78+
self._verify_metadata(metadata)
79+
80+
def _reset_flags(self):
81+
self.sdk_ready_flag = False
82+
self.sdk_timed_out_flag = False
83+
self.sdk_update_flag = False
84+
self.metadata = None
85+
86+
def _sdk_ready_callback(self, metadata):
87+
self.sdk_ready_flag = True
88+
self.metadata = metadata
89+
90+
def _sdk_update_callback(self, metadata):
91+
self.sdk_update_flag = True
92+
self.metadata = metadata
93+
94+
def _sdk_timeout_callback(self, metadata):
95+
self.sdk_timed_out_flag = True
96+
self.metadata = metadata
97+
98+
def _verify_metadata(self, metadata):
99+
assert metadata.get_type() == self.metadata.get_type()
100+
assert metadata.get_names() == self.metadata.get_names()

0 commit comments

Comments
 (0)