Skip to content

Commit 64f1084

Browse files
authored
Merge pull request #215 from splitio/development
Development
2 parents 8ab97c1 + f725995 commit 64f1084

File tree

12 files changed

+142
-34
lines changed

12 files changed

+142
-34
lines changed

CHANGES.txt

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,7 @@
1+
8.3.1 (Nov 20, 2020)
2+
- Fixed error handling when split server fails, so that it doesn't bring streaming down.
3+
- Added SDK Metadata headers to split & segments API clients
4+
15
8.3.0 (Nov 4, 2020)
26
- Added local impressions deduping. Defaulting to optimized
37
- Added support for the new Split streaming architecture. When enabled (default), the SDK will not poll for updates but instead receive notifications every time there's a change in your environments, allowing to process those much quicker. If disabled or in the event of an issue, the SDK will fallback to the known polling mechanism to provide a seamless experience.

splitio/api/segments.py

Lines changed: 8 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@
55

66
from future.utils import raise_from
77

8-
from splitio.api import APIException
8+
from splitio.api import APIException, headers_from_metadata
99
from splitio.api.client import HttpClientException
1010

1111

@@ -15,17 +15,21 @@
1515
class SegmentsAPI(object): # pylint: disable=too-few-public-methods
1616
"""Class that uses an httpClient to communicate with the segments API."""
1717

18-
def __init__(self, http_client, apikey):
18+
def __init__(self, http_client, apikey, sdk_metadata):
1919
"""
2020
Class constructor.
2121
2222
:param client: HTTP Client responsble for issuing calls to the backend.
2323
:type client: client.HttpClient
2424
:param apikey: User apikey token.
2525
:type apikey: string
26+
:param sdk_metadata: SDK version & machine name & IP.
27+
:type sdk_metadata: splitio.client.util.SdkMetadata
28+
2629
"""
2730
self._client = http_client
2831
self._apikey = apikey
32+
self._metadata = headers_from_metadata(sdk_metadata)
2933

3034
def fetch_segment(self, segment_name, change_number):
3135
"""
@@ -44,7 +48,8 @@ def fetch_segment(self, segment_name, change_number):
4448
'sdk',
4549
'/segmentChanges/{segment_name}'.format(segment_name=segment_name),
4650
self._apikey,
47-
{'since': change_number}
51+
extra_headers=self._metadata,
52+
query={'since': change_number}
4853
)
4954

5055
if 200 <= response.status_code < 300:

splitio/api/splits.py

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@
55

66
from future.utils import raise_from
77

8-
from splitio.api import APIException
8+
from splitio.api import APIException, headers_from_metadata
99
from splitio.api.client import HttpClientException
1010

1111

@@ -15,17 +15,20 @@
1515
class SplitsAPI(object): # pylint: disable=too-few-public-methods
1616
"""Class that uses an httpClient to communicate with the splits API."""
1717

18-
def __init__(self, client, apikey):
18+
def __init__(self, client, apikey, sdk_metadata):
1919
"""
2020
Class constructor.
2121
2222
:param client: HTTP Client responsble for issuing calls to the backend.
2323
:type client: HttpClient
2424
:param apikey: User apikey token.
2525
:type apikey: string
26+
:param sdk_metadata: SDK version & machine name & IP.
27+
:type sdk_metadata: splitio.client.util.SdkMetadata
2628
"""
2729
self._client = client
2830
self._apikey = apikey
31+
self._metadata = headers_from_metadata(sdk_metadata)
2932

3033
def fetch_splits(self, change_number):
3134
"""
@@ -42,7 +45,8 @@ def fetch_splits(self, change_number):
4245
'sdk',
4346
'/splitChanges',
4447
self._apikey,
45-
{'since': change_number}
48+
extra_headers=self._metadata,
49+
query={'since': change_number}
4650
)
4751
if 200 <= response.status_code < 300:
4852
return json.loads(response.body)

splitio/client/factory.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -261,8 +261,8 @@ def _build_in_memory_factory(api_key, cfg, sdk_url=None, events_url=None, # pyl
261261
sdk_metadata = util.get_metadata(cfg)
262262
apis = {
263263
'auth': AuthAPI(http_client, api_key, sdk_metadata),
264-
'splits': SplitsAPI(http_client, api_key),
265-
'segments': SegmentsAPI(http_client, api_key),
264+
'splits': SplitsAPI(http_client, api_key, sdk_metadata),
265+
'segments': SegmentsAPI(http_client, api_key, sdk_metadata),
266266
'impressions': ImpressionsAPI(http_client, api_key, sdk_metadata, cfg['impressionsMode']),
267267
'events': EventsAPI(http_client, api_key, sdk_metadata),
268268
'telemetry': TelemetryAPI(http_client, api_key, sdk_metadata)

splitio/push/manager.py

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -66,7 +66,6 @@ def update_workers_status(self, enabled):
6666
"""
6767
self._processor.update_workers_status(enabled)
6868

69-
7069
def start(self):
7170
"""Start a new connection if not already running."""
7271
if self._running:

splitio/sync/synchronizer.py

Lines changed: 33 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -235,28 +235,51 @@ def synchronize_segment(self, segment_name, till):
235235
:type till: int
236236
"""
237237
_LOGGER.debug('Synchronizing segment %s', segment_name)
238-
return self._split_synchronizers.segment_sync.synchronize_segment(segment_name, till)
238+
success = self._split_synchronizers.segment_sync.synchronize_segment(segment_name, till)
239+
if not success:
240+
_LOGGER.error('Failed to sync some segments.')
241+
return success
239242

240243
def synchronize_splits(self, till):
241244
"""
242245
Synchronize all splits.
243246
244247
:param till: to fetch
245248
:type till: int
249+
250+
:returns: whether the synchronization was successful or not.
251+
:rtype: bool
246252
"""
247253
_LOGGER.debug('Starting splits synchronization')
248-
return self._split_synchronizers.split_sync.synchronize_splits(till)
254+
try:
255+
self._split_synchronizers.split_sync.synchronize_splits(till)
256+
return True
257+
except APIException:
258+
_LOGGER.error('Failed syncing splits')
259+
_LOGGER.debug('Error: ', exc_info=True)
260+
return False
249261

250262
def sync_all(self):
251263
"""Synchronize all split data."""
252-
try:
253-
self.synchronize_splits(None)
254-
if not self._synchronize_segments():
255-
_LOGGER.error('Failed syncing segments')
256-
raise RuntimeError('Failed syncing segments')
257-
except APIException as exc:
258-
_LOGGER.error('Failed syncing splits')
259-
raise_from(APIException('Failed to sync splits'), exc)
264+
attempts = 3
265+
while attempts > 0:
266+
try:
267+
if not self.synchronize_splits(None):
268+
attempts -= 1
269+
continue
270+
271+
# Only retrying splits, since segments may trigger too many calls.
272+
if not self._synchronize_segments():
273+
_LOGGER.warn('Segments failed to synchronize.')
274+
275+
# All is good
276+
return
277+
except Exception as exc: # pylint:disable=broad-except
278+
attempts -= 1
279+
_LOGGER.error("Exception caught when trying to sync all data: %s", str(exc))
280+
_LOGGER.debug('Error: ', exc_info=True)
281+
282+
_LOGGER.error("Could not correctly synchronize splits and segments after 3 attempts.")
260283

261284
def shutdown(self, blocking):
262285
"""

splitio/tasks/uwsgi_wrappers.py

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -45,10 +45,12 @@ def uwsgi_update_splits(user_config):
4545
:type user_config: dict
4646
"""
4747
config = _get_config(user_config)
48+
metadata = get_metadata(config)
4849
seconds = config['featuresRefreshRate']
4950
split_sync = SplitSynchronizer(
5051
SplitsAPI(
51-
HttpClient(1500, config.get('sdk_url'), config.get('events_url')), config['apikey']
52+
HttpClient(1500, config.get('sdk_url'), config.get('events_url')), config['apikey'],
53+
metadata
5254
),
5355
UWSGISplitStorage(get_uwsgi()),
5456
)
@@ -71,9 +73,11 @@ def uwsgi_update_segments(user_config):
7173
"""
7274
config = _get_config(user_config)
7375
seconds = config['segmentsRefreshRate']
76+
metadata = get_metadata(config)
7477
segment_sync = SegmentSynchronizer(
7578
SegmentsAPI(
76-
HttpClient(1500, config.get('sdk_url'), config.get('events_url')), config['apikey']
79+
HttpClient(1500, config.get('sdk_url'), config.get('events_url')), config['apikey'],
80+
metadata
7781
),
7882
UWSGISplitStorage(get_uwsgi()),
7983
UWSGISegmentStorage(get_uwsgi()),

splitio/version.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1 +1 @@
1-
__version__ = '8.3.0'
1+
__version__ = '8.3.1'

tests/api/test_segments_api.py

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22

33
import pytest
44
from splitio.api import segments, client, APIException
5+
from splitio.client.util import SdkMetadata
56

67

78
class SegmentAPITests(object):
@@ -11,11 +12,17 @@ def test_fetch_segment_changes(self, mocker):
1112
"""Test segment changes fetching API call."""
1213
httpclient = mocker.Mock(spec=client.HttpClient)
1314
httpclient.get.return_value = client.HttpResponse(200, '{"prop1": "value1"}')
14-
segment_api = segments.SegmentsAPI(httpclient, 'some_api_key')
15+
segment_api = segments.SegmentsAPI(httpclient, 'some_api_key', SdkMetadata('1.0', 'some', '1.2.3.4'))
1516
response = segment_api.fetch_segment('some_segment', 123)
1617

1718
assert response['prop1'] == 'value1'
18-
assert httpclient.get.mock_calls == [mocker.call('sdk', '/segmentChanges/some_segment', 'some_api_key', {'since': 123})]
19+
assert httpclient.get.mock_calls == [mocker.call('sdk', '/segmentChanges/some_segment', 'some_api_key',
20+
extra_headers={
21+
'SplitSDKVersion': '1.0',
22+
'SplitSDKMachineIP': '1.2.3.4',
23+
'SplitSDKMachineName': 'some'
24+
},
25+
query={'since': 123})]
1926

2027
httpclient.reset_mock()
2128
def raise_exception(*args, **kwargs):

tests/api/test_splits_api.py

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22

33
import pytest
44
from splitio.api import splits, client, APIException
5+
from splitio.client.util import SdkMetadata
56

67

78
class SplitAPITests(object):
@@ -11,11 +12,17 @@ def test_fetch_split_changes(self, mocker):
1112
"""Test split changes fetching API call."""
1213
httpclient = mocker.Mock(spec=client.HttpClient)
1314
httpclient.get.return_value = client.HttpResponse(200, '{"prop1": "value1"}')
14-
split_api = splits.SplitsAPI(httpclient, 'some_api_key')
15+
split_api = splits.SplitsAPI(httpclient, 'some_api_key', SdkMetadata('1.0', 'some', '1.2.3.4'))
1516
response = split_api.fetch_splits(123)
1617

1718
assert response['prop1'] == 'value1'
18-
assert httpclient.get.mock_calls == [mocker.call('sdk', '/splitChanges', 'some_api_key', {'since': 123})]
19+
assert httpclient.get.mock_calls == [mocker.call('sdk', '/splitChanges', 'some_api_key',
20+
extra_headers={
21+
'SplitSDKVersion': '1.0',
22+
'SplitSDKMachineIP': '1.2.3.4',
23+
'SplitSDKMachineName': 'some'
24+
},
25+
query={'since': 123})]
1926

2027
httpclient.reset_mock()
2128
def raise_exception(*args, **kwargs):

0 commit comments

Comments
 (0)