4
4
import json
5
5
6
6
from splitio .api import APIException , headers_from_metadata
7
- from splitio .api .commons import build_fetch
7
+ from splitio .api .commons import build_fetch , FetchOptions
8
8
from splitio .api .client import HttpClientException
9
9
from splitio .models .telemetry import HTTPExceptionsAndLatencies
10
+ from splitio .util .time import utctime_ms
11
+ from splitio .spec import SPEC_VERSION
12
+ from splitio .sync import util
10
13
11
14
_LOGGER = logging .getLogger (__name__ )
15
+ _SPEC_1_1 = "1.1"
16
+ _PROXY_CHECK_INTERVAL_MILLISECONDS_SS = 24 * 60 * 60 * 1000
12
17
13
-
14
- class SplitsAPI (object ): # pylint: disable=too-few-public-methods
18
+ class SplitsAPIBase (object ): # pylint: disable=too-few-public-methods
15
19
"""Class that uses an httpClient to communicate with the splits API."""
16
20
17
21
def __init__ (self , client , sdk_key , sdk_metadata , telemetry_runtime_producer ):
@@ -30,22 +34,66 @@ def __init__(self, client, sdk_key, sdk_metadata, telemetry_runtime_producer):
30
34
self ._metadata = headers_from_metadata (sdk_metadata )
31
35
self ._telemetry_runtime_producer = telemetry_runtime_producer
32
36
self ._client .set_telemetry_data (HTTPExceptionsAndLatencies .SPLIT , self ._telemetry_runtime_producer )
37
+ self ._spec_version = SPEC_VERSION
38
+ self ._last_proxy_check_timestamp = 0
39
+ self .clear_storage = False
40
+ self ._old_spec_since = None
41
+
42
+ def _check_last_proxy_check_timestamp (self , since ):
43
+ if self ._spec_version == _SPEC_1_1 and ((utctime_ms () - self ._last_proxy_check_timestamp ) >= _PROXY_CHECK_INTERVAL_MILLISECONDS_SS ):
44
+ _LOGGER .info ("Switching to new Feature flag spec (%s) and fetching." , SPEC_VERSION );
45
+ self ._spec_version = SPEC_VERSION
46
+ self ._old_spec_since = since
47
+
48
+ def _check_old_spec_since (self , change_number ):
49
+ if self ._spec_version == _SPEC_1_1 and self ._old_spec_since is not None :
50
+ since = self ._old_spec_since
51
+ self ._old_spec_since = None
52
+ return since
53
+ return change_number
54
+
55
+
56
+ class SplitsAPI (SplitsAPIBase ): # pylint: disable=too-few-public-methods
57
+ """Class that uses an httpClient to communicate with the splits API."""
58
+
59
+ def __init__ (self , client , sdk_key , sdk_metadata , telemetry_runtime_producer ):
60
+ """
61
+ Class constructor.
62
+
63
+ :param client: HTTP Client responsble for issuing calls to the backend.
64
+ :type client: HttpClient
65
+ :param sdk_key: User sdk_key token.
66
+ :type sdk_key: string
67
+ :param sdk_metadata: SDK version & machine name & IP.
68
+ :type sdk_metadata: splitio.client.util.SdkMetadata
69
+ """
70
+ SplitsAPIBase .__init__ (self , client , sdk_key , sdk_metadata , telemetry_runtime_producer )
33
71
34
- def fetch_splits (self , change_number , fetch_options ):
72
+ def fetch_splits (self , change_number , rbs_change_number , fetch_options ):
35
73
"""
36
74
Fetch feature flags from backend.
37
75
38
76
:param change_number: Last known timestamp of a split modification.
39
77
:type change_number: int
40
78
79
+ :param rbs_change_number: Last known timestamp of a rule based segment modification.
80
+ :type rbs_change_number: int
81
+
41
82
:param fetch_options: Fetch options for getting feature flag definitions.
42
83
:type fetch_options: splitio.api.commons.FetchOptions
43
84
44
85
:return: Json representation of a splitChanges response.
45
86
:rtype: dict
46
87
"""
47
88
try :
48
- query , extra_headers = build_fetch (change_number , fetch_options , self ._metadata )
89
+ self ._check_last_proxy_check_timestamp (change_number )
90
+ change_number = self ._check_old_spec_since (change_number )
91
+
92
+ if self ._spec_version == _SPEC_1_1 :
93
+ fetch_options = FetchOptions (fetch_options .cache_control_headers , fetch_options .change_number ,
94
+ None , fetch_options .sets , self ._spec_version )
95
+ rbs_change_number = None
96
+ query , extra_headers = build_fetch (change_number , fetch_options , self ._metadata , rbs_change_number )
49
97
response = self ._client .get (
50
98
'sdk' ,
51
99
'splitChanges' ,
@@ -54,19 +102,32 @@ def fetch_splits(self, change_number, fetch_options):
54
102
query = query ,
55
103
)
56
104
if 200 <= response .status_code < 300 :
105
+ if self ._spec_version == _SPEC_1_1 :
106
+ return util .convert_to_new_spec (json .loads (response .body ))
107
+
108
+ self .clear_storage = self ._last_proxy_check_timestamp != 0
109
+ self ._last_proxy_check_timestamp = 0
57
110
return json .loads (response .body )
58
111
59
112
else :
60
113
if response .status_code == 414 :
61
114
_LOGGER .error ('Error fetching feature flags; the amount of flag sets provided are too big, causing uri length error.' )
115
+
116
+ if self ._client .is_sdk_endpoint_overridden () and response .status_code == 400 and self ._spec_version == SPEC_VERSION :
117
+ _LOGGER .warning ('Detected proxy response error, changing spec version from %s to %s and re-fetching.' , self ._spec_version , _SPEC_1_1 )
118
+ self ._spec_version = _SPEC_1_1
119
+ self ._last_proxy_check_timestamp = utctime_ms ()
120
+ return self .fetch_splits (change_number , None , FetchOptions (fetch_options .cache_control_headers , fetch_options .change_number ,
121
+ None , fetch_options .sets , self ._spec_version ))
122
+
62
123
raise APIException (response .body , response .status_code )
124
+
63
125
except HttpClientException as exc :
64
126
_LOGGER .error ('Error fetching feature flags because an exception was raised by the HTTPClient' )
65
127
_LOGGER .debug ('Error: ' , exc_info = True )
66
128
raise APIException ('Feature flags not fetched correctly.' ) from exc
67
129
68
-
69
- class SplitsAPIAsync (object ): # pylint: disable=too-few-public-methods
130
+ class SplitsAPIAsync (SplitsAPIBase ): # pylint: disable=too-few-public-methods
70
131
"""Class that uses an httpClient to communicate with the splits API."""
71
132
72
133
def __init__ (self , client , sdk_key , sdk_metadata , telemetry_runtime_producer ):
@@ -80,18 +141,17 @@ def __init__(self, client, sdk_key, sdk_metadata, telemetry_runtime_producer):
80
141
:param sdk_metadata: SDK version & machine name & IP.
81
142
:type sdk_metadata: splitio.client.util.SdkMetadata
82
143
"""
83
- self ._client = client
84
- self ._sdk_key = sdk_key
85
- self ._metadata = headers_from_metadata (sdk_metadata )
86
- self ._telemetry_runtime_producer = telemetry_runtime_producer
87
- self ._client .set_telemetry_data (HTTPExceptionsAndLatencies .SPLIT , self ._telemetry_runtime_producer )
144
+ SplitsAPIBase .__init__ (self , client , sdk_key , sdk_metadata , telemetry_runtime_producer )
88
145
89
- async def fetch_splits (self , change_number , fetch_options ):
146
+ async def fetch_splits (self , change_number , rbs_change_number , fetch_options ):
90
147
"""
91
148
Fetch feature flags from backend.
92
149
93
150
:param change_number: Last known timestamp of a split modification.
94
151
:type change_number: int
152
+
153
+ :param rbs_change_number: Last known timestamp of a rule based segment modification.
154
+ :type rbs_change_number: int
95
155
96
156
:param fetch_options: Fetch options for getting feature flag definitions.
97
157
:type fetch_options: splitio.api.commons.FetchOptions
@@ -100,7 +160,14 @@ async def fetch_splits(self, change_number, fetch_options):
100
160
:rtype: dict
101
161
"""
102
162
try :
103
- query , extra_headers = build_fetch (change_number , fetch_options , self ._metadata )
163
+ self ._check_last_proxy_check_timestamp (change_number )
164
+ change_number = self ._check_old_spec_since (change_number )
165
+ if self ._spec_version == _SPEC_1_1 :
166
+ fetch_options = FetchOptions (fetch_options .cache_control_headers , fetch_options .change_number ,
167
+ None , fetch_options .sets , self ._spec_version )
168
+ rbs_change_number = None
169
+
170
+ query , extra_headers = build_fetch (change_number , fetch_options , self ._metadata , rbs_change_number )
104
171
response = await self ._client .get (
105
172
'sdk' ,
106
173
'splitChanges' ,
@@ -109,12 +176,26 @@ async def fetch_splits(self, change_number, fetch_options):
109
176
query = query ,
110
177
)
111
178
if 200 <= response .status_code < 300 :
179
+ if self ._spec_version == _SPEC_1_1 :
180
+ return util .convert_to_new_spec (json .loads (response .body ))
181
+
182
+ self .clear_storage = self ._last_proxy_check_timestamp != 0
183
+ self ._last_proxy_check_timestamp = 0
112
184
return json .loads (response .body )
113
185
114
186
else :
115
187
if response .status_code == 414 :
116
188
_LOGGER .error ('Error fetching feature flags; the amount of flag sets provided are too big, causing uri length error.' )
189
+
190
+ if self ._client .is_sdk_endpoint_overridden () and response .status_code == 400 and self ._spec_version == SPEC_VERSION :
191
+ _LOGGER .warning ('Detected proxy response error, changing spec version from %s to %s and re-fetching.' , self ._spec_version , _SPEC_1_1 )
192
+ self ._spec_version = _SPEC_1_1
193
+ self ._last_proxy_check_timestamp = utctime_ms ()
194
+ return await self .fetch_splits (change_number , None , FetchOptions (fetch_options .cache_control_headers , fetch_options .change_number ,
195
+ None , fetch_options .sets , self ._spec_version ))
196
+
117
197
raise APIException (response .body , response .status_code )
198
+
118
199
except HttpClientException as exc :
119
200
_LOGGER .error ('Error fetching feature flags because an exception was raised by the HTTPClient' )
120
201
_LOGGER .debug ('Error: ' , exc_info = True )
0 commit comments