diff --git a/splitio/api/__init__.py b/splitio/api/__init__.py deleted file mode 100644 index be820f14..00000000 --- a/splitio/api/__init__.py +++ /dev/null @@ -1,46 +0,0 @@ -"""Split API module.""" - - -class APIException(Exception): - """Exception to raise when an API call fails.""" - - def __init__(self, custom_message, status_code=None): - """Constructor.""" - Exception.__init__(self, custom_message) - self._status_code = status_code if status_code else -1 - - @property - def status_code(self): - """Return HTTP status code.""" - return self._status_code - -class APIUriException(APIException): - """Exception to raise when an API call fails due to 414 http error.""" - - def __init__(self, custom_message, status_code=None): - """Constructor.""" - APIException.__init__(self, custom_message, status_code) - -def headers_from_metadata(sdk_metadata, client_key=None): - """ - Generate a dict with headers required by data-recording API endpoints. - :param sdk_metadata: SDK Metadata object, generated at sdk initialization time. - :type sdk_metadata: splitio.client.util.SdkMetadata - :param client_key: client key. - :type client_key: str - :return: A dictionary with headers. - :rtype: dict - """ - - metadata = { - 'SplitSDKVersion': sdk_metadata.sdk_version, - 'SplitSDKMachineIP': sdk_metadata.instance_ip, - 'SplitSDKMachineName': sdk_metadata.instance_name - } if sdk_metadata.instance_ip != 'NA' and sdk_metadata.instance_ip != 'unknown' else { - 'SplitSDKVersion': sdk_metadata.sdk_version, - } - - if client_key is not None: - metadata['SplitSDKClientKey'] = client_key - - return metadata \ No newline at end of file diff --git a/splitio/api/auth.py b/splitio/api/auth.py deleted file mode 100644 index fe5d4411..00000000 --- a/splitio/api/auth.py +++ /dev/null @@ -1,109 +0,0 @@ -"""Auth API module.""" - -import logging -import json - -from splitio.api import APIException, headers_from_metadata -from splitio.api.commons import headers_from_metadata, record_telemetry -from splitio.spec import SPEC_VERSION -from splitio.util.time import get_current_epoch_time_ms -from splitio.api.client import HttpClientException -from harness_commons.models.token import from_raw -from harness_commons.models.telemetry import HTTPExceptionsAndLatencies - -_LOGGER = logging.getLogger(__name__) - - -class AuthAPI(object): # pylint: disable=too-few-public-methods - """Class that uses an httpClient to communicate with the SDK Auth Service API.""" - - def __init__(self, client, sdk_key, sdk_metadata, telemetry_runtime_producer): - """ - Class constructor. - - :param client: HTTP Client responsble for issuing calls to the backend. - :type client: HttpClient - :param sdk_key: User sdk key. - :type sdk_key: string - :param sdk_metadata: SDK version & machine name & IP. - :type sdk_metadata: splitio.client.util.SdkMetadata - """ - self._client = client - self._sdk_key = sdk_key - self._metadata = headers_from_metadata(sdk_metadata) - self._telemetry_runtime_producer = telemetry_runtime_producer - self._client.set_telemetry_data(HTTPExceptionsAndLatencies.TOKEN, self._telemetry_runtime_producer) - - def authenticate(self): - """ - Perform authentication. - - :return: Json representation of an authentication. - :rtype: harness_commons.models.token.Token - """ - try: - response = self._client.get( - 'auth', - 'v2/auth?s=' + SPEC_VERSION, - self._sdk_key, - extra_headers=self._metadata, - ) - if 200 <= response.status_code < 300: - payload = json.loads(response.body) - return from_raw(payload) - - else: - if (response.status_code >= 400 and response.status_code < 500): - self._telemetry_runtime_producer.record_auth_rejections() - raise APIException(response.body, response.status_code) - except HttpClientException as exc: - _LOGGER.error('Exception raised while authenticating') - _LOGGER.debug('Exception information: ', exc_info=True) - raise APIException('Could not perform authentication.') from exc - -class AuthAPIAsync(object): # pylint: disable=too-few-public-methods - """Async Class that uses an httpClient to communicate with the SDK Auth Service API.""" - - def __init__(self, client, sdk_key, sdk_metadata, telemetry_runtime_producer): - """ - Class constructor. - - :param client: HTTP Client responsble for issuing calls to the backend. - :type client: HttpClient - :param sdk_key: User sdk key. - :type sdk_key: string - :param sdk_metadata: SDK version & machine name & IP. - :type sdk_metadata: splitio.client.util.SdkMetadata - """ - self._client = client - self._sdk_key = sdk_key - self._metadata = headers_from_metadata(sdk_metadata) - self._telemetry_runtime_producer = telemetry_runtime_producer - self._client.set_telemetry_data(HTTPExceptionsAndLatencies.TOKEN, self._telemetry_runtime_producer) - - async def authenticate(self): - """ - Perform authentication. - - :return: Json representation of an authentication. - :rtype: harness_commons.models.token.Token - """ - try: - response = await self._client.get( - 'auth', - 'v2/auth?s=' + SPEC_VERSION, - self._sdk_key, - extra_headers=self._metadata, - ) - if 200 <= response.status_code < 300: - payload = json.loads(response.body) - return from_raw(payload) - - else: - if (response.status_code >= 400 and response.status_code < 500): - await self._telemetry_runtime_producer.record_auth_rejections() - raise APIException(response.body, response.status_code) - except HttpClientException as exc: - _LOGGER.error('Exception raised while authenticating') - _LOGGER.debug('Exception information: ', exc_info=True) - raise APIException('Could not perform authentication.') from exc diff --git a/splitio/api/client.py b/splitio/api/client.py deleted file mode 100644 index c9032e0e..00000000 --- a/splitio/api/client.py +++ /dev/null @@ -1,565 +0,0 @@ -"""Synchronous HTTP Client for split API.""" -from collections import namedtuple -import requests -import urllib -import abc -import logging -import json -import threading -from urllib3.util import parse_url - -from splitio.optional.loaders import HTTPKerberosAuth, OPTIONAL -from splitio.client.config import AuthenticateScheme -from splitio.optional.loaders import aiohttp -from splitio.util.time import get_current_epoch_time_ms - -SDK_URL = 'https://sdk.split.io/api' -EVENTS_URL = 'https://events.split.io/api' -AUTH_URL = 'https://auth.split.io/api' -TELEMETRY_URL = 'https://telemetry.split.io/api' - -_LOGGER = logging.getLogger(__name__) -_EXC_MSG = '{source} library is throwing exceptions' - -HttpResponse = namedtuple('HttpResponse', ['status_code', 'body', 'headers']) - -def _build_url(server, path, urls): - """ - Build URL according to server specified. - - :param server: Server for whith the request is being made. - :type server: str - :param path: URL path to be appended to base host. - :type path: str - - :return: A fully qualified URL. - :rtype: str - """ - url = urls[server] - url += '/' if urls[server][:-1] != '/' else '' - return urllib.parse.urljoin(url, path) - -def _construct_urls(sdk_url=None, events_url=None, auth_url=None, telemetry_url=None): - return { - 'sdk': sdk_url if sdk_url is not None else SDK_URL, - 'events': events_url if events_url is not None else EVENTS_URL, - 'auth': auth_url if auth_url is not None else AUTH_URL, - 'telemetry': telemetry_url if telemetry_url is not None else TELEMETRY_URL, - } - -def _build_basic_headers(sdk_key): - """ - Build basic headers with auth. - - :param sdk_key: API token used to identify backend calls. - :type sdk_key: str - """ - return { - 'Content-Type': 'application/json', - 'Authorization': "Bearer %s" % sdk_key - } - -class HttpClientException(Exception): - """HTTP Client exception.""" - - def __init__(self, message): - """ - Class constructor. - - :param message: Information on why this exception happened. - :type message: str - """ - Exception.__init__(self, message) - -class HTTPAdapterWithProxyKerberosAuth(requests.adapters.HTTPAdapter): - """HTTPAdapter override for Kerberos Proxy auth""" - - def __init__(self, principal=None, password=None): - requests.adapters.HTTPAdapter.__init__(self) - self._principal = principal - self._password = password - - def proxy_headers(self, proxy): - headers = {} - if self._principal is not None: - auth = HTTPKerberosAuth(principal=self._principal, password=self._password) - else: - auth = HTTPKerberosAuth() - negotiate_details = auth.generate_request_header(None, parse_url(proxy).host, is_preemptive=True) - headers['Proxy-Authorization'] = negotiate_details - return headers - -class HttpClientBase(object, metaclass=abc.ABCMeta): - """HttpClient wrapper template.""" - - def __init__(self, timeout=None, sdk_url=None, events_url=None, auth_url=None, telemetry_url=None): - """ - Class constructor. - - :param timeout: How many milliseconds to wait until the server responds. - :type timeout: int - :param sdk_url: Optional alternative sdk URL. - :type sdk_url: str - :param events_url: Optional alternative events URL. - :type events_url: str - :param auth_url: Optional alternative auth URL. - :type auth_url: str - :param telemetry_url: Optional alternative telemetry URL. - :type telemetry_url: str - """ - _LOGGER.debug("Initializing httpclient") - self._timeout = timeout/1000 if timeout else None # Convert ms to seconds. - self._urls = _construct_urls(sdk_url, events_url, auth_url, telemetry_url) - - @abc.abstractmethod - def get(self, server, path, apikey): - """http get request""" - - @abc.abstractmethod - def post(self, server, path, apikey): - """http post request""" - - def set_telemetry_data(self, metric_name, telemetry_runtime_producer): - """ - Set the data needed for telemetry call - - :param metric_name: metric name for telemetry - :type metric_name: str - - :param telemetry_runtime_producer: telemetry recording instance - :type telemetry_runtime_producer: splitio.engine.telemetry.TelemetryRuntimeProducer - """ - self._telemetry_runtime_producer = telemetry_runtime_producer - self._metric_name = metric_name - - def is_sdk_endpoint_overridden(self): - return self._urls['sdk'] != SDK_URL - - def _get_headers(self, extra_headers, sdk_key): - headers = _build_basic_headers(sdk_key) - if extra_headers is not None: - headers.update(extra_headers) - return headers - - def _record_telemetry(self, status_code, elapsed): - """ - Record Telemetry info - - :param status_code: http request status code - :type status_code: int - - :param elapsed: response time elapsed. - :type status_code: int - """ - self._telemetry_runtime_producer.record_sync_latency(self._metric_name, elapsed) - if 200 <= status_code < 300: - self._telemetry_runtime_producer.record_successful_sync(self._metric_name, get_current_epoch_time_ms()) - return - - self._telemetry_runtime_producer.record_sync_error(self._metric_name, status_code) - -class HttpClient(HttpClientBase): - """HttpClient wrapper.""" - - def __init__(self, timeout=None, sdk_url=None, events_url=None, auth_url=None, telemetry_url=None): - """ - Class constructor. - - :param timeout: How many milliseconds to wait until the server responds. - :type timeout: int - :param sdk_url: Optional alternative sdk URL. - :type sdk_url: str - :param events_url: Optional alternative events URL. - :type events_url: str - :param auth_url: Optional alternative auth URL. - :type auth_url: str - :param telemetry_url: Optional alternative telemetry URL. - :type telemetry_url: str - """ - HttpClientBase.__init__(self, timeout, sdk_url, events_url, auth_url, telemetry_url) - - def get(self, server, path, sdk_key, query=None, extra_headers=None): # pylint: disable=too-many-arguments - """ - Issue a get request. - - :param server: Whether the request is for SDK server, Events server or Auth server. - :typee server: str - :param path: path to append to the host url. - :type path: str - :param sdk_key: sdk key. - :type sdk_key: str - :param query: Query string passed as dictionary. - :type query: dict - :param extra_headers: key/value pairs of possible extra headers. - :type extra_headers: dict - - :return: Tuple of status_code & response text - :rtype: HttpResponse - """ - start = get_current_epoch_time_ms() - try: - response = requests.get( - _build_url(server, path, self._urls), - params=query, - headers=self._get_headers(extra_headers, sdk_key), - timeout=self._timeout - ) - self._record_telemetry(response.status_code, get_current_epoch_time_ms() - start) - return HttpResponse(response.status_code, response.text, response.headers) - - except requests.exceptions.ChunkedEncodingError as exc: - _LOGGER.error("IncompleteRead exception detected: %s", exc) - return HttpResponse(400, "", {}) - - except Exception as exc: # pylint: disable=broad-except - raise HttpClientException(_EXC_MSG.format(source='request')) from exc - - def post(self, server, path, sdk_key, body, query=None, extra_headers=None): # pylint: disable=too-many-arguments - """ - Issue a POST request. - - :param server: Whether the request is for SDK server or Events server. - :typee server: str - :param path: path to append to the host url. - :type path: str - :param sdk_key: sdk key. - :type sdk_key: str - :param body: body sent in the request. - :type body: str - :param query: Query string passed as dictionary. - :type query: dict - :param extra_headers: key/value pairs of possible extra headers. - :type extra_headers: dict - - :return: Tuple of status_code & response text - :rtype: HttpResponse - """ - start = get_current_epoch_time_ms() - try: - response = requests.post( - _build_url(server, path, self._urls), - json=body, - params=query, - headers=self._get_headers(extra_headers, sdk_key), - timeout=self._timeout, - ) - self._record_telemetry(response.status_code, get_current_epoch_time_ms() - start) - return HttpResponse(response.status_code, response.text, response.headers) - except Exception as exc: # pylint: disable=broad-except - raise HttpClientException(_EXC_MSG.format(source='request')) from exc - -class HttpClientAsync(HttpClientBase): - """HttpClientAsync wrapper.""" - - def __init__(self, timeout=None, sdk_url=None, events_url=None, auth_url=None, telemetry_url=None): - """ - Class constructor. - :param timeout: How many milliseconds to wait until the server responds. - :type timeout: int - :param sdk_url: Optional alternative sdk URL. - :type sdk_url: str - :param events_url: Optional alternative events URL. - :type events_url: str - :param auth_url: Optional alternative auth URL. - :type auth_url: str - :param telemetry_url: Optional alternative telemetry URL. - :type telemetry_url: str - """ - HttpClientBase.__init__(self, timeout, sdk_url, events_url, auth_url, telemetry_url) - self._session = aiohttp.ClientSession() - - async def get(self, server, path, apikey, query=None, extra_headers=None): # pylint: disable=too-many-arguments - """ - Issue a get request. - :param server: Whether the request is for SDK server, Events server or Auth server. - :typee server: str - :param path: path to append to the host url. - :type path: str - :param apikey: api token. - :type apikey: str - :param query: Query string passed as dictionary. - :type query: dict - :param extra_headers: key/value pairs of possible extra headers. - :type extra_headers: dict - :return: Tuple of status_code & response text - :rtype: HttpResponse - """ - start = get_current_epoch_time_ms() - headers = self._get_headers(extra_headers, apikey) - try: - url = _build_url(server, path, self._urls) - _LOGGER.debug("GET request: %s", url) - _LOGGER.debug("query params: %s", query) - _LOGGER.debug("headers: %s", headers) - async with self._session.get( - url, - params=query, - headers=headers, - timeout=self._timeout - ) as response: - body = await response.text() - _LOGGER.debug("Response:") - _LOGGER.debug(response) - _LOGGER.debug(body) - await self._record_telemetry(response.status, get_current_epoch_time_ms() - start) - return HttpResponse(response.status, body, response.headers) - - except aiohttp.ClientPayloadError as exc: - _LOGGER.error("ContentLengthError exception detected: %s", exc) - return HttpResponse(400, "", {}) - - except aiohttp.ClientError as exc: # pylint: disable=broad-except - raise HttpClientException(_EXC_MSG.format(source='aiohttp')) from exc - - async def post(self, server, path, apikey, body, query=None, extra_headers=None): # pylint: disable=too-many-arguments - """ - Issue a POST request. - :param server: Whether the request is for SDK server or Events server. - :typee server: str - :param path: path to append to the host url. - :type path: str - :param apikey: api token. - :type apikey: str - :param body: body sent in the request. - :type body: str - :param query: Query string passed as dictionary. - :type query: dict - :param extra_headers: key/value pairs of possible extra headers. - :type extra_headers: dict - :return: Tuple of status_code & response text - :rtype: HttpResponse - """ - headers = self._get_headers(extra_headers, apikey) - start = get_current_epoch_time_ms() - try: - headers['Accept-Encoding'] = 'gzip' - _LOGGER.debug("POST request: %s", _build_url(server, path, self._urls)) - _LOGGER.debug("query params: %s", query) - _LOGGER.debug("headers: %s", headers) - _LOGGER.debug("payload: ") - _LOGGER.debug(str(json.dumps(body)).encode('utf-8')) - async with self._session.post( - _build_url(server, path, self._urls), - params=query, - headers=headers, - json=body, - timeout=self._timeout - ) as response: - body = await response.text() - _LOGGER.debug("Response:") - _LOGGER.debug(response) - _LOGGER.debug(body) - await self._record_telemetry(response.status, get_current_epoch_time_ms() - start) - return HttpResponse(response.status, body, response.headers) - - except aiohttp.ClientError as exc: # pylint: disable=broad-except - raise HttpClientException(_EXC_MSG.format(source='aiohttp')) from exc - - async def _record_telemetry(self, status_code, elapsed): - """ - Record Telemetry info - - :param status_code: http request status code - :type status_code: int - - :param elapsed: response time elapsed. - :type status_code: int - """ - await self._telemetry_runtime_producer.record_sync_latency(self._metric_name, elapsed) - if 200 <= status_code < 300: - await self._telemetry_runtime_producer.record_successful_sync(self._metric_name, get_current_epoch_time_ms()) - return - - await self._telemetry_runtime_producer.record_sync_error(self._metric_name, status_code) - - async def close_session(self): - if not self._session.closed: - await self._session.close() - -class HttpClientKerberos(HttpClientBase): - """HttpClient wrapper.""" - - def __init__(self, timeout=None, sdk_url=None, events_url=None, auth_url=None, telemetry_url=None, authentication_scheme=None, authentication_params=None): - """ - Class constructor. - - :param timeout: How many milliseconds to wait until the server responds. - :type timeout: int - :param sdk_url: Optional alternative sdk URL. - :type sdk_url: str - :param events_url: Optional alternative events URL. - :type events_url: str - :param auth_url: Optional alternative auth URL. - :type auth_url: str - :param telemetry_url: Optional alternative telemetry URL. - :type telemetry_url: str - :param authentication_scheme: Optional authentication scheme to use. - :type authentication_scheme: splitio.client.config.AuthenticateScheme - :param authentication_params: Optional authentication username and password to use. - :type authentication_params: [str, str] - """ - _LOGGER.debug("Initializing httpclient for Kerberos auth") - self._timeout = timeout/1000 if timeout else None # Convert ms to seconds. - self._urls = _construct_urls(sdk_url, events_url, auth_url, telemetry_url) - self._authentication_scheme = authentication_scheme - self._authentication_params = authentication_params - self._lock = threading.RLock() - self._sessions = {'sdk': requests.Session(), - 'events': requests.Session(), - 'auth': requests.Session(), - 'telemetry': requests.Session()} - self._set_authentication() - - def get(self, server, path, sdk_key, query=None, extra_headers=None): # pylint: disable=too-many-arguments - """ - Issue a get request. - :param server: Whether the request is for SDK server, Events server or Auth server. - :typee server: str - :param path: path to append to the host url. - :type path: str - :param sdk_key: sdk key. - :type sdk_key: str - :param query: Query string passed as dictionary. - :type query: dict - :param extra_headers: key/value pairs of possible extra headers. - :type extra_headers: dict - - :return: Tuple of status_code & response text - :rtype: HttpResponse - """ - with self._lock: - start = get_current_epoch_time_ms() - try: - return self._do_get(server, path, sdk_key, query, extra_headers, start) - - except requests.exceptions.ProxyError as exc: - _LOGGER.debug("Proxy Exception caught, resetting the http session") - self._sessions[server].close() - self._sessions[server] = requests.Session() - self._set_authentication(server_name=server) - try: - return self._do_get(server, path, sdk_key, query, extra_headers, start) - - except Exception as exc: - raise HttpClientException(_EXC_MSG.format(source='request')) from exc - - except Exception as exc: # pylint: disable=broad-except - raise HttpClientException(_EXC_MSG.format(source='request')) from exc - - def _do_get(self, server, path, sdk_key, query, extra_headers, start): - """ - Issue a get request. - :param server: Whether the request is for SDK server, Events server or Auth server. - :typee server: str - :param path: path to append to the host url. - :type path: str - :param sdk_key: sdk key. - :type sdk_key: str - :param query: Query string passed as dictionary. - :type query: dict - :param extra_headers: key/value pairs of possible extra headers. - :type extra_headers: dict - - :return: Tuple of status_code & response text - :rtype: HttpResponse - """ - with self._sessions[server].get( - _build_url(server, path, self._urls), - headers=self._get_headers(extra_headers, sdk_key), - params=query, - timeout=self._timeout - ) as response: - self._record_telemetry(response.status_code, get_current_epoch_time_ms() - start) - return HttpResponse(response.status_code, response.text, response.headers) - - def post(self, server, path, sdk_key, body, query=None, extra_headers=None): # pylint: disable=too-many-arguments - """ - Issue a POST request. - - :param server: Whether the request is for SDK server or Events server. - :typee server: str - :param path: path to append to the host url. - :type path: str - :param sdk_key: sdk key. - :type sdk_key: str - :param body: body sent in the request. - :type body: str - :param query: Query string passed as dictionary. - :type query: dict - :param extra_headers: key/value pairs of possible extra headers. - :type extra_headers: dict - - :return: Tuple of status_code & response text - :rtype: HttpResponse - """ - with self._lock: - start = get_current_epoch_time_ms() - try: - return self._do_post(server, path, sdk_key, query, extra_headers, body, start) - - except requests.exceptions.ProxyError as exc: - _LOGGER.debug("Proxy Exception caught, resetting the http session") - self._sessions[server].close() - self._sessions[server] = requests.Session() - self._set_authentication(server_name=server) - try: - return self._do_post(server, path, sdk_key, query, extra_headers, body, start) - - except Exception as exc: - raise HttpClientException(_EXC_MSG.format(source='request')) from exc - - except Exception as exc: # pylint: disable=broad-except - raise HttpClientException(_EXC_MSG.format(source='request')) from exc - - def _do_post(self, server, path, sdk_key, query, extra_headers, body, start): - """ - Issue a POST request. - - :param server: Whether the request is for SDK server or Events server. - :typee server: str - :param path: path to append to the host url. - :type path: str - :param sdk_key: sdk key. - :type sdk_key: str - :param body: body sent in the request. - :type body: str - :param query: Query string passed as dictionary. - :type query: dict - :param extra_headers: key/value pairs of possible extra headers. - :type extra_headers: dict - - :return: Tuple of status_code & response text - :rtype: HttpResponse - """ - with self._sessions[server].post( - _build_url(server, path, self._urls), - params=query, - headers=self._get_headers(extra_headers, sdk_key), - json=body, - timeout=self._timeout, - ) as response: - self._record_telemetry(response.status_code, get_current_epoch_time_ms() - start) - return HttpResponse(response.status_code, response.text, response.headers) - - def _set_authentication(self, server_name=None): - """ - Set the authentication for all self._sessions variables based on authentication scheme. - - :param server: If set, will only add the auth for its session variable, otherwise will set all sessions. - :typee server: str - """ - for server in ['sdk', 'events', 'auth', 'telemetry']: - if server_name is not None and server_name != server: - continue - if self._authentication_scheme == AuthenticateScheme.KERBEROS_SPNEGO: - _LOGGER.debug("Using Kerberos Spnego Authentication") - if self._authentication_params != [None, None]: - self._sessions[server].auth = HTTPKerberosAuth(principal=self._authentication_params[0], password=self._authentication_params[1], mutual_authentication=OPTIONAL) - else: - self._sessions[server].auth = HTTPKerberosAuth(mutual_authentication=OPTIONAL) - elif self._authentication_scheme == AuthenticateScheme.KERBEROS_PROXY: - _LOGGER.debug("Using Kerberos Proxy Authentication") - if self._authentication_params != [None, None]: - self._sessions[server].mount('https://', HTTPAdapterWithProxyKerberosAuth(principal=self._authentication_params[0], password=self._authentication_params[1])) - else: - self._sessions[server].mount('https://', HTTPAdapterWithProxyKerberosAuth()) diff --git a/splitio/api/commons.py b/splitio/api/commons.py deleted file mode 100644 index 9dda1ee0..00000000 --- a/splitio/api/commons.py +++ /dev/null @@ -1,157 +0,0 @@ -"""Commons module.""" -from splitio.util.time import get_current_epoch_time_ms -from splitio.spec import SPEC_VERSION - -_CACHE_CONTROL = 'Cache-Control' -_CACHE_CONTROL_NO_CACHE = 'no-cache' - -def headers_from_metadata(sdk_metadata, client_key=None): - """ - Generate a dict with headers required by data-recording API endpoints. - - :param sdk_metadata: SDK Metadata object, generated at sdk initialization time. - :type sdk_metadata: splitio.client.util.SdkMetadata - - :param client_key: client key. - :type client_key: str - - :return: A dictionary with headers. - :rtype: dict - """ - - metadata = { - 'SplitSDKVersion': sdk_metadata.sdk_version, - 'SplitSDKMachineIP': sdk_metadata.instance_ip, - 'SplitSDKMachineName': sdk_metadata.instance_name - } if sdk_metadata.instance_ip != 'NA' and sdk_metadata.instance_ip != 'unknown' else { - 'SplitSDKVersion': sdk_metadata.sdk_version, - } - - if client_key is not None: - metadata['SplitSDKClientKey'] = client_key - - return metadata - -def record_telemetry(status_code, elapsed, metric_name, telemetry_runtime_producer): - """ - Record Telemetry info - - :param status_code: http request status code - :type status_code: int - - :param elapsed: response time elapsed. - :type status_code: int - - :param metric_name: metric name for telemetry - :type metric_name: str - - :param telemetry_runtime_producer: telemetry recording instance - :type telemetry_runtime_producer: splitio.engine.telemetry.TelemetryRuntimeProducer - """ - telemetry_runtime_producer.record_sync_latency(metric_name, elapsed) - if 200 <= status_code < 300: - telemetry_runtime_producer.record_successful_sync(metric_name, get_current_epoch_time_ms()) - return - telemetry_runtime_producer.record_sync_error(metric_name, status_code) - -class FetchOptions(object): - """Fetch Options object.""" - - def __init__(self, cache_control_headers=False, change_number=None, rbs_change_number=None, sets=None, spec=SPEC_VERSION): - """ - Class constructor. - - :param cache_control_headers: Flag for Cache-Control header - :type cache_control_headers: bool - - :param change_number: ChangeNumber to use for bypassing CDN in request. - :type change_number: int - - :param sets: list of flag sets - :type sets: list - """ - self._cache_control_headers = cache_control_headers - self._change_number = change_number - self._rbs_change_number = rbs_change_number - self._sets = sets - self._spec = spec - - @property - def cache_control_headers(self): - """Return cache control headers.""" - return self._cache_control_headers - - @property - def change_number(self): - """Return change number.""" - return self._change_number - - @property - def rbs_change_number(self): - """Return change number.""" - return self._rbs_change_number - - @property - def sets(self): - """Return sets.""" - return self._sets - - @property - def spec(self): - """Return sets.""" - return self._spec - - def __eq__(self, other): - """Match between other options.""" - if self._cache_control_headers != other._cache_control_headers: - return False - - if self._change_number != other._change_number: - return False - - if self._rbs_change_number != other._rbs_change_number: - return False - - if self._sets != other._sets: - return False - - if self._spec != other._spec: - return False - - return True - - -def build_fetch(change_number, fetch_options, metadata, rbs_change_number=None): - """ - Build fetch with new flags if that is the case. - - :param change_number: Last known timestamp of definition. - :type change_number: int - - :param fetch_options: Fetch options for getting definitions. - :type fetch_options: splitio.api.commons.FetchOptions - - :param metadata: Metadata Headers. - :type metadata: dict - - :param rbs_change_number: Last known timestamp of a rule based segment modification. - :type rbs_change_number: int - - :return: Objects for fetch - :rtype: dict, dict - """ - query = {'s': fetch_options.spec} if fetch_options.spec is not None else {} - query['since'] = change_number - if rbs_change_number is not None: - query['rbSince'] = rbs_change_number - extra_headers = metadata - if fetch_options is None: - return query, extra_headers - - if fetch_options.cache_control_headers: - extra_headers[_CACHE_CONTROL] = _CACHE_CONTROL_NO_CACHE - if fetch_options.sets is not None: - query['sets'] = fetch_options.sets - if fetch_options.change_number is not None: - query['till'] = fetch_options.change_number - return query, extra_headers \ No newline at end of file diff --git a/splitio/api/events.py b/splitio/api/events.py deleted file mode 100644 index 58c686a8..00000000 --- a/splitio/api/events.py +++ /dev/null @@ -1,129 +0,0 @@ -"""Events API module.""" -import logging - -from splitio.api import APIException, headers_from_metadata -from splitio.api.client import HttpClientException -from harness_commons.models.telemetry import HTTPExceptionsAndLatencies - - -_LOGGER = logging.getLogger(__name__) - - -class EventsAPIBase(object): # pylint: disable=too-few-public-methods - """Base Class that uses an httpClient to communicate with the events API.""" - - @staticmethod - def _build_bulk(events): - """ - Build event bulk as expected by the API. - - :param events: Events to be bundled. - :type events: list(harness_commons.models.events.Event) - - :return: Formatted bulk. - :rtype: dict - """ - return [ - { - 'key': event.key, - 'trafficTypeName': event.traffic_type_name, - 'eventTypeId': event.event_type_id, - 'value': event.value, - 'timestamp': event.timestamp, - 'properties': event.properties, - } - for event in events - ] - - -class EventsAPI(EventsAPIBase): # pylint: disable=too-few-public-methods - """Class that uses an httpClient to communicate with the events API.""" - - def __init__(self, http_client, sdk_key, sdk_metadata, telemetry_runtime_producer): - """ - Class constructor. - - :param http_client: HTTP Client responsble for issuing calls to the backend. - :type http_client: HttpClient - :param sdk_key: sdk key. - :type sdk_key: string - :param sdk_metadata: SDK version & machine name & IP. - :type sdk_metadata: splitio.client.util.SdkMetadata - """ - self._client = http_client - self._sdk_key = sdk_key - self._metadata = headers_from_metadata(sdk_metadata) - self._telemetry_runtime_producer = telemetry_runtime_producer - self._client.set_telemetry_data(HTTPExceptionsAndLatencies.EVENT, self._telemetry_runtime_producer) - - def flush_events(self, events): - """ - Send events to the backend. - - :param events: Events bulk - :type events: list - - :return: True if flush was successful. False otherwise - :rtype: bool - """ - bulk = self._build_bulk(events) - try: - response = self._client.post( - 'events', - 'events/bulk', - self._sdk_key, - body=bulk, - extra_headers=self._metadata, - ) - if not 200 <= response.status_code < 300: - raise APIException(response.body, response.status_code) - except HttpClientException as exc: - _LOGGER.error('Error posting events because an exception was raised by the HTTPClient') - _LOGGER.debug('Error: ', exc_info=True) - raise APIException('Events not flushed properly.') from exc - -class EventsAPIAsync(EventsAPIBase): # pylint: disable=too-few-public-methods - """Async Class that uses an httpClient to communicate with the events API.""" - - def __init__(self, http_client, sdk_key, sdk_metadata, telemetry_runtime_producer): - """ - Class constructor. - - :param http_client: HTTP Client responsble for issuing calls to the backend. - :type http_client: HttpClient - :param sdk_key: sdk key. - :type sdk_key: string - :param sdk_metadata: SDK version & machine name & IP. - :type sdk_metadata: splitio.client.util.SdkMetadata - """ - self._client = http_client - self._sdk_key = sdk_key - self._metadata = headers_from_metadata(sdk_metadata) - self._telemetry_runtime_producer = telemetry_runtime_producer - self._client.set_telemetry_data(HTTPExceptionsAndLatencies.EVENT, self._telemetry_runtime_producer) - - async def flush_events(self, events): - """ - Send events to the backend. - - :param events: Events bulk - :type events: list - - :return: True if flush was successful. False otherwise - :rtype: bool - """ - bulk = self._build_bulk(events) - try: - response = await self._client.post( - 'events', - 'events/bulk', - self._sdk_key, - body=bulk, - extra_headers=self._metadata, - ) - if not 200 <= response.status_code < 300: - raise APIException(response.body, response.status_code) - except HttpClientException as exc: - _LOGGER.error('Error posting events because an exception was raised by the HTTPClient') - _LOGGER.debug('Error: ', exc_info=True) - raise APIException('Events not flushed properly.') from exc diff --git a/splitio/api/impressions.py b/splitio/api/impressions.py deleted file mode 100644 index 79244d06..00000000 --- a/splitio/api/impressions.py +++ /dev/null @@ -1,230 +0,0 @@ -"""Impressions API module.""" - -import logging -from itertools import groupby - -from splitio.api import APIException, headers_from_metadata -from splitio.api.client import HttpClientException -from splitio.engine.impressions import ImpressionsMode -from harness_commons.models.telemetry import HTTPExceptionsAndLatencies - - -_LOGGER = logging.getLogger(__name__) - - -class ImpressionsAPIBase(object): # pylint: disable=too-few-public-methods - """Base Class that uses an httpClient to communicate with the impressions API.""" - - @staticmethod - def _build_bulk(impressions): - """ - Build an impression bulk formatted as the API expects it. - - :param impressions: List of impressions to bundle. - :type impressions: list(harness_commons.models.impressions.Impression) - - :return: Dictionary of lists of impressions. - :rtype: list - """ - return [ - { - 'f': test_name, - 'i': [ - ImpressionsAPIBase._filter_out_null_prop(impression) - for impression in imps - ] - } - for (test_name, imps) in groupby( - sorted(impressions, key=lambda i: i.feature_name), - lambda i: i.feature_name - ) - ] - - @staticmethod - def _filter_out_null_prop(impression): - if impression.properties == None: - return { - 'k': impression.matching_key, - 't': impression.treatment, - 'm': impression.time, - 'c': impression.change_number, - 'r': impression.label, - 'b': impression.bucketing_key, - 'pt': impression.previous_time - } - - return { - 'k': impression.matching_key, - 't': impression.treatment, - 'm': impression.time, - 'c': impression.change_number, - 'r': impression.label, - 'b': impression.bucketing_key, - 'pt': impression.previous_time, - 'properties': impression.properties - } - - @staticmethod - def _build_counters(counters): - """ - Build an impression bulk formatted as the API expects it. - - :param counters: List of impression counters per feature. - :type counters: list[splitio.engine.impressions.Counter.CountPerFeature] - - :return: dict with list of impression count dtos - :rtype: dict - """ - return { - 'pf': [ - { - 'f': pf_count.feature, - 'm': pf_count.timeframe, - 'rc': pf_count.count - } for pf_count in counters - ] - } - - -class ImpressionsAPI(ImpressionsAPIBase): # pylint: disable=too-few-public-methods - """Class that uses an httpClient to communicate with the impressions API.""" - - def __init__(self, client, sdk_key, sdk_metadata, telemetry_runtime_producer, mode=ImpressionsMode.OPTIMIZED): - """ - Class constructor. - - :param client: HTTP Client responsble for issuing calls to the backend. - :type client: HttpClient - :param sdk_key: sdk key. - :type sdk_key: string - """ - self._client = client - self._sdk_key = sdk_key - self._metadata = headers_from_metadata(sdk_metadata) - self._metadata['SplitSDKImpressionsMode'] = mode.name - self._telemetry_runtime_producer = telemetry_runtime_producer - - def flush_impressions(self, impressions): - """ - Send impressions to the backend. - - :param impressions: Impressions bulk - :type impressions: list - """ - bulk = self._build_bulk(impressions) - self._client.set_telemetry_data(HTTPExceptionsAndLatencies.IMPRESSION, self._telemetry_runtime_producer) - try: - response = self._client.post( - 'events', - 'testImpressions/bulk', - self._sdk_key, - body=bulk, - extra_headers=self._metadata, - ) - if not 200 <= response.status_code < 300: - raise APIException(response.body, response.status_code) - except HttpClientException as exc: - _LOGGER.error( - 'Error posting impressions because an exception was raised by the HTTPClient' - ) - _LOGGER.debug('Error: ', exc_info=True) - raise APIException('Impressions not flushed properly.') from exc - - def flush_counters(self, counters): - """ - Send impressions to the backend. - - :param impressions: Impressions bulk - :type impressions: list - """ - bulk = self._build_counters(counters) - self._client.set_telemetry_data(HTTPExceptionsAndLatencies.IMPRESSION_COUNT, self._telemetry_runtime_producer) - try: - response = self._client.post( - 'events', - 'testImpressions/count', - self._sdk_key, - body=bulk, - extra_headers=self._metadata, - ) - if not 200 <= response.status_code < 300: - raise APIException(response.body, response.status_code) - except HttpClientException as exc: - _LOGGER.error( - 'Error posting impressions counters because an exception was raised by the ' - 'HTTPClient' - ) - _LOGGER.debug('Error: ', exc_info=True) - raise APIException('Impressions not flushed properly.') from exc - - -class ImpressionsAPIAsync(ImpressionsAPIBase): # pylint: disable=too-few-public-methods - """Async Class that uses an httpClient to communicate with the impressions API.""" - - def __init__(self, client, sdk_key, sdk_metadata, telemetry_runtime_producer, mode=ImpressionsMode.OPTIMIZED): - """ - Class constructor. - - :param client: HTTP Client responsble for issuing calls to the backend. - :type client: HttpClient - :param sdk_key: sdk key. - :type sdk_key: string - """ - self._client = client - self._sdk_key = sdk_key - self._metadata = headers_from_metadata(sdk_metadata) - self._metadata['SplitSDKImpressionsMode'] = mode.name - self._telemetry_runtime_producer = telemetry_runtime_producer - - async def flush_impressions(self, impressions): - """ - Send impressions to the backend. - - :param impressions: Impressions bulk - :type impressions: list - """ - bulk = self._build_bulk(impressions) - self._client.set_telemetry_data(HTTPExceptionsAndLatencies.IMPRESSION, self._telemetry_runtime_producer) - try: - response = await self._client.post( - 'events', - 'testImpressions/bulk', - self._sdk_key, - body=bulk, - extra_headers=self._metadata, - ) - if not 200 <= response.status_code < 300: - raise APIException(response.body, response.status_code) - except HttpClientException as exc: - _LOGGER.error( - 'Error posting impressions because an exception was raised by the HTTPClient' - ) - _LOGGER.debug('Error: ', exc_info=True) - raise APIException('Impressions not flushed properly.') from exc - - async def flush_counters(self, counters): - """ - Send impressions to the backend. - - :param impressions: Impressions bulk - :type impressions: list - """ - bulk = self._build_counters(counters) - self._client.set_telemetry_data(HTTPExceptionsAndLatencies.IMPRESSION_COUNT, self._telemetry_runtime_producer) - try: - response = await self._client.post( - 'events', - 'testImpressions/count', - self._sdk_key, - body=bulk, - extra_headers=self._metadata, - ) - if not 200 <= response.status_code < 300: - raise APIException(response.body, response.status_code) - except HttpClientException as exc: - _LOGGER.error( - 'Error posting impressions counters because an exception was raised by the ' - 'HTTPClient' - ) - _LOGGER.debug('Error: ', exc_info=True) - raise APIException('Impressions not flushed properly.') from exc diff --git a/splitio/api/kerberos_client.py b/splitio/api/kerberos_client.py new file mode 100644 index 00000000..8b30a291 --- /dev/null +++ b/splitio/api/kerberos_client.py @@ -0,0 +1,236 @@ +"""Synchronous HTTP Client for split API.""" +from collections import namedtuple +import requests +import urllib +import abc +import logging +import json +import threading +from urllib3.util import parse_url + +from harness_commons.api.client import HttpClientBase, build_url, construct_urls, HttpResponse +from splitio.optional.loaders import HTTPKerberosAuth, OPTIONAL +from splitio.client.config import AuthenticateScheme +from splitio.optional.loaders import aiohttp +from harness_commons.util.time import get_current_epoch_time_ms + +_LOGGER = logging.getLogger(__name__) +_EXC_MSG = '{source} library is throwing exceptions' + +class HttpClientException(Exception): + """HTTP Client exception.""" + + def __init__(self, message): + """ + Class constructor. + + :param message: Information on why this exception happened. + :type message: str + """ + Exception.__init__(self, message) + +class HTTPAdapterWithProxyKerberosAuth(requests.adapters.HTTPAdapter): + """HTTPAdapter override for Kerberos Proxy auth""" + + def __init__(self, principal=None, password=None): + requests.adapters.HTTPAdapter.__init__(self) + self._principal = principal + self._password = password + + def proxy_headers(self, proxy): + headers = {} + if self._principal is not None: + auth = HTTPKerberosAuth(principal=self._principal, password=self._password) + else: + auth = HTTPKerberosAuth() + negotiate_details = auth.generate_request_header(None, parse_url(proxy).host, is_preemptive=True) + headers['Proxy-Authorization'] = negotiate_details + return headers + +class HttpClientKerberos(HttpClientBase): + """HttpClient wrapper.""" + + def __init__(self, timeout=None, sdk_url=None, events_url=None, auth_url=None, telemetry_url=None, authentication_scheme=None, authentication_params=None): + """ + Class constructor. + + :param timeout: How many milliseconds to wait until the server responds. + :type timeout: int + :param sdk_url: Optional alternative sdk URL. + :type sdk_url: str + :param events_url: Optional alternative events URL. + :type events_url: str + :param auth_url: Optional alternative auth URL. + :type auth_url: str + :param telemetry_url: Optional alternative telemetry URL. + :type telemetry_url: str + :param authentication_scheme: Optional authentication scheme to use. + :type authentication_scheme: splitio.client.config.AuthenticateScheme + :param authentication_params: Optional authentication username and password to use. + :type authentication_params: [str, str] + """ + _LOGGER.debug("Initializing httpclient for Kerberos auth") + self._timeout = timeout/1000 if timeout else None # Convert ms to seconds. + self._urls = construct_urls(sdk_url, events_url, auth_url, telemetry_url) + self._authentication_scheme = authentication_scheme + self._authentication_params = authentication_params + self._lock = threading.RLock() + self._sessions = {'sdk': requests.Session(), + 'events': requests.Session(), + 'auth': requests.Session(), + 'telemetry': requests.Session()} + self._set_authentication() + + def get(self, server, path, sdk_key, query=None, extra_headers=None): # pylint: disable=too-many-arguments + """ + Issue a get request. + :param server: Whether the request is for SDK server, Events server or Auth server. + :typee server: str + :param path: path to append to the host url. + :type path: str + :param sdk_key: sdk key. + :type sdk_key: str + :param query: Query string passed as dictionary. + :type query: dict + :param extra_headers: key/value pairs of possible extra headers. + :type extra_headers: dict + + :return: Tuple of status_code & response text + :rtype: HttpResponse + """ + with self._lock: + start = get_current_epoch_time_ms() + try: + return self._do_get(server, path, sdk_key, query, extra_headers, start) + + except requests.exceptions.ProxyError as exc: + _LOGGER.debug("Proxy Exception caught, resetting the http session") + self._sessions[server].close() + self._sessions[server] = requests.Session() + self._set_authentication(server_name=server) + try: + return self._do_get(server, path, sdk_key, query, extra_headers, start) + + except Exception as exc: + raise HttpClientException(_EXC_MSG.format(source='request')) from exc + + except Exception as exc: # pylint: disable=broad-except + raise HttpClientException(_EXC_MSG.format(source='request')) from exc + + def _do_get(self, server, path, sdk_key, query, extra_headers, start): + """ + Issue a get request. + :param server: Whether the request is for SDK server, Events server or Auth server. + :typee server: str + :param path: path to append to the host url. + :type path: str + :param sdk_key: sdk key. + :type sdk_key: str + :param query: Query string passed as dictionary. + :type query: dict + :param extra_headers: key/value pairs of possible extra headers. + :type extra_headers: dict + + :return: Tuple of status_code & response text + :rtype: HttpResponse + """ + with self._sessions[server].get( + build_url(server, path, self._urls), + headers=self._get_headers(extra_headers, sdk_key), + params=query, + timeout=self._timeout + ) as response: + self._record_telemetry(response.status_code, get_current_epoch_time_ms() - start) + return HttpResponse(response.status_code, response.text, response.headers) + + def post(self, server, path, sdk_key, body, query=None, extra_headers=None): # pylint: disable=too-many-arguments + """ + Issue a POST request. + + :param server: Whether the request is for SDK server or Events server. + :typee server: str + :param path: path to append to the host url. + :type path: str + :param sdk_key: sdk key. + :type sdk_key: str + :param body: body sent in the request. + :type body: str + :param query: Query string passed as dictionary. + :type query: dict + :param extra_headers: key/value pairs of possible extra headers. + :type extra_headers: dict + + :return: Tuple of status_code & response text + :rtype: HttpResponse + """ + with self._lock: + start = get_current_epoch_time_ms() + try: + return self._do_post(server, path, sdk_key, query, extra_headers, body, start) + + except requests.exceptions.ProxyError as exc: + _LOGGER.debug("Proxy Exception caught, resetting the http session") + self._sessions[server].close() + self._sessions[server] = requests.Session() + self._set_authentication(server_name=server) + try: + return self._do_post(server, path, sdk_key, query, extra_headers, body, start) + + except Exception as exc: + raise HttpClientException(_EXC_MSG.format(source='request')) from exc + + except Exception as exc: # pylint: disable=broad-except + raise HttpClientException(_EXC_MSG.format(source='request')) from exc + + def _do_post(self, server, path, sdk_key, query, extra_headers, body, start): + """ + Issue a POST request. + + :param server: Whether the request is for SDK server or Events server. + :typee server: str + :param path: path to append to the host url. + :type path: str + :param sdk_key: sdk key. + :type sdk_key: str + :param body: body sent in the request. + :type body: str + :param query: Query string passed as dictionary. + :type query: dict + :param extra_headers: key/value pairs of possible extra headers. + :type extra_headers: dict + + :return: Tuple of status_code & response text + :rtype: HttpResponse + """ + with self._sessions[server].post( + build_url(server, path, self._urls), + params=query, + headers=self._get_headers(extra_headers, sdk_key), + json=body, + timeout=self._timeout, + ) as response: + self._record_telemetry(response.status_code, get_current_epoch_time_ms() - start) + return HttpResponse(response.status_code, response.text, response.headers) + + def _set_authentication(self, server_name=None): + """ + Set the authentication for all self._sessions variables based on authentication scheme. + + :param server: If set, will only add the auth for its session variable, otherwise will set all sessions. + :typee server: str + """ + for server in ['sdk', 'events', 'auth', 'telemetry']: + if server_name is not None and server_name != server: + continue + if self._authentication_scheme == AuthenticateScheme.KERBEROS_SPNEGO: + _LOGGER.debug("Using Kerberos Spnego Authentication") + if self._authentication_params != [None, None]: + self._sessions[server].auth = HTTPKerberosAuth(principal=self._authentication_params[0], password=self._authentication_params[1], mutual_authentication=OPTIONAL) + else: + self._sessions[server].auth = HTTPKerberosAuth(mutual_authentication=OPTIONAL) + elif self._authentication_scheme == AuthenticateScheme.KERBEROS_PROXY: + _LOGGER.debug("Using Kerberos Proxy Authentication") + if self._authentication_params != [None, None]: + self._sessions[server].mount('https://', HTTPAdapterWithProxyKerberosAuth(principal=self._authentication_params[0], password=self._authentication_params[1])) + else: + self._sessions[server].mount('https://', HTTPAdapterWithProxyKerberosAuth()) diff --git a/splitio/api/segments.py b/splitio/api/segments.py deleted file mode 100644 index c9b67a4c..00000000 --- a/splitio/api/segments.py +++ /dev/null @@ -1,130 +0,0 @@ -"""Segments API module.""" - -import json -import logging - -from splitio.api import APIException, headers_from_metadata -from splitio.api.commons import build_fetch -from splitio.api.client import HttpClientException -from harness_commons.models.telemetry import HTTPExceptionsAndLatencies - - -_LOGGER = logging.getLogger(__name__) - - -class SegmentsAPI(object): # pylint: disable=too-few-public-methods - """Class that uses an httpClient to communicate with the segments API.""" - - def __init__(self, http_client, sdk_key, sdk_metadata, telemetry_runtime_producer): - """ - Class constructor. - - :param client: HTTP Client responsble for issuing calls to the backend. - :type client: client.HttpClient - :param sdk_key: User sdk_key token. - :type sdk_key: string - :param sdk_metadata: SDK version & machine name & IP. - :type sdk_metadata: splitio.client.util.SdkMetadata - - """ - self._client = http_client - self._sdk_key = sdk_key - self._metadata = headers_from_metadata(sdk_metadata) - self._telemetry_runtime_producer = telemetry_runtime_producer - self._client.set_telemetry_data(HTTPExceptionsAndLatencies.SEGMENT, self._telemetry_runtime_producer) - - def fetch_segment(self, segment_name, change_number, fetch_options): - """ - Fetch splits from backend. - - :param segment_name: Name of the segment to fetch changes for. - :type segment_name: str - - :param change_number: Last known timestamp of a segment modification. - :type change_number: int - - :param fetch_options: Fetch options for getting segment definitions. - :type fetch_options: splitio.api.commons.FetchOptions - - :return: Json representation of a segmentChange response. - :rtype: dict - """ - try: - query, extra_headers = build_fetch(change_number, fetch_options, self._metadata) - response = self._client.get( - 'sdk', - 'segmentChanges/{segment_name}'.format(segment_name=segment_name), - self._sdk_key, - extra_headers=extra_headers, - query=query, - ) - if 200 <= response.status_code < 300: - return json.loads(response.body) - - raise APIException(response.body, response.status_code) - except HttpClientException as exc: - _LOGGER.error( - 'Error fetching %s because an exception was raised by the HTTPClient', - segment_name - ) - _LOGGER.debug('Error: ', exc_info=True) - raise APIException('Segments not fetched properly.') from exc - - -class SegmentsAPIAsync(object): # pylint: disable=too-few-public-methods - """Async Class that uses an httpClient to communicate with the segments API.""" - - def __init__(self, http_client, sdk_key, sdk_metadata, telemetry_runtime_producer): - """ - Class constructor. - - :param client: HTTP Client responsble for issuing calls to the backend. - :type client: client.HttpClient - :param sdk_key: User sdk_key token. - :type sdk_key: string - :param sdk_metadata: SDK version & machine name & IP. - :type sdk_metadata: splitio.client.util.SdkMetadata - - """ - self._client = http_client - self._sdk_key = sdk_key - self._metadata = headers_from_metadata(sdk_metadata) - self._telemetry_runtime_producer = telemetry_runtime_producer - self._client.set_telemetry_data(HTTPExceptionsAndLatencies.SEGMENT, self._telemetry_runtime_producer) - - async def fetch_segment(self, segment_name, change_number, fetch_options): - """ - Fetch splits from backend. - - :param segment_name: Name of the segment to fetch changes for. - :type segment_name: str - - :param change_number: Last known timestamp of a segment modification. - :type change_number: int - - :param fetch_options: Fetch options for getting segment definitions. - :type fetch_options: splitio.api.commons.FetchOptions - - :return: Json representation of a segmentChange response. - :rtype: dict - """ - try: - query, extra_headers = build_fetch(change_number, fetch_options, self._metadata) - response = await self._client.get( - 'sdk', - 'segmentChanges/{segment_name}'.format(segment_name=segment_name), - self._sdk_key, - extra_headers=extra_headers, - query=query, - ) - if 200 <= response.status_code < 300: - return json.loads(response.body) - - raise APIException(response.body, response.status_code) - except HttpClientException as exc: - _LOGGER.error( - 'Error fetching %s because an exception was raised by the HTTPClient', - segment_name - ) - _LOGGER.debug('Error: ', exc_info=True) - raise APIException('Segments not fetched properly.') from exc diff --git a/splitio/api/splits.py b/splitio/api/splits.py index 0be23069..6ff1feec 100644 --- a/splitio/api/splits.py +++ b/splitio/api/splits.py @@ -3,13 +3,13 @@ import logging import json -from splitio.api import APIException, headers_from_metadata -from splitio.api.commons import build_fetch, FetchOptions -from splitio.api.client import HttpClientException +from harness_commons.api import APIException, headers_from_metadata +from harness_commons.api.commons import build_fetch, FetchOptions +from harness_commons.api.client import HttpClientException from harness_commons.models.telemetry import HTTPExceptionsAndLatencies -from splitio.util.time import utctime_ms +from harness_commons.util.time import utctime_ms from splitio.spec import SPEC_VERSION -from splitio.sync import util +from harness_commons.sync import util _LOGGER = logging.getLogger(__name__) _SPEC_1_1 = "1.1" @@ -70,6 +70,9 @@ def __init__(self, client, sdk_key, sdk_metadata, telemetry_runtime_producer): SplitsAPIBase.__init__(self, client, sdk_key, sdk_metadata, telemetry_runtime_producer) def fetch_splits(self, change_number, rbs_change_number, fetch_options): + return self.fetch_definitions(change_number, rbs_change_number, fetch_options) + + def fetch_definitions(self, change_number, rbs_change_number, fetch_options): """ Fetch feature flags from backend. @@ -80,7 +83,7 @@ def fetch_splits(self, change_number, rbs_change_number, fetch_options): :type rbs_change_number: int :param fetch_options: Fetch options for getting feature flag definitions. - :type fetch_options: splitio.api.commons.FetchOptions + :type fetch_options: harness_commons.api.commons.FetchOptions :return: Json representation of a splitChanges response. :rtype: dict @@ -144,6 +147,9 @@ def __init__(self, client, sdk_key, sdk_metadata, telemetry_runtime_producer): SplitsAPIBase.__init__(self, client, sdk_key, sdk_metadata, telemetry_runtime_producer) async def fetch_splits(self, change_number, rbs_change_number, fetch_options): + return await self.fetch_definitions(change_number, rbs_change_number, fetch_options) + + async def fetch_definitions(self, change_number, rbs_change_number, fetch_options): """ Fetch feature flags from backend. @@ -154,7 +160,7 @@ async def fetch_splits(self, change_number, rbs_change_number, fetch_options): :type rbs_change_number: int :param fetch_options: Fetch options for getting feature flag definitions. - :type fetch_options: splitio.api.commons.FetchOptions + :type fetch_options: harness_commons.api.commons.FetchOptions :return: Json representation of a splitChanges response. :rtype: dict diff --git a/splitio/api/telemetry.py b/splitio/api/telemetry.py deleted file mode 100644 index 6bae4d3d..00000000 --- a/splitio/api/telemetry.py +++ /dev/null @@ -1,187 +0,0 @@ -"""Impressions API module.""" -import logging - -from splitio.api import APIException, headers_from_metadata -from splitio.api.client import HttpClientException -from harness_commons.models.telemetry import HTTPExceptionsAndLatencies - -_LOGGER = logging.getLogger(__name__) - -class TelemetryAPI(object): # pylint: disable=too-few-public-methods - """Class that uses an httpClient to communicate with the Telemetry API.""" - - def __init__(self, client, sdk_key, sdk_metadata, telemetry_runtime_producer): - """ - Class constructor. - - :param client: HTTP Client responsble for issuing calls to the backend. - :type client: HttpClient - :param sdk_key: User sdk_key token. - :type sdk_key: string - """ - self._client = client - self._sdk_key = sdk_key - self._metadata = headers_from_metadata(sdk_metadata) - self._telemetry_runtime_producer = telemetry_runtime_producer - self._client.set_telemetry_data(HTTPExceptionsAndLatencies.TELEMETRY, self._telemetry_runtime_producer) - - def record_unique_keys(self, uniques): - """ - Send unique keys to the backend. - - :param uniques: Unique Keys - :type json - """ - try: - response = self._client.post( - 'telemetry', - 'v1/keys/ss', - self._sdk_key, - body=uniques, - extra_headers=self._metadata - ) - if not 200 <= response.status_code < 300: - raise APIException(response.body, response.status_code) - except HttpClientException as exc: - _LOGGER.debug( - 'Error posting unique keys because an exception was raised by the HTTPClient' - ) - _LOGGER.debug('Error: ', exc_info=True) - raise APIException('Unique keys not flushed properly.') from exc - - def record_init(self, configs): - """ - Send init config data to the backend. - - :param configs: configs - :type json - """ - try: - response = self._client.post( - 'telemetry', - 'v1/metrics/config', - self._sdk_key, - body=configs, - extra_headers=self._metadata, - ) - if not 200 <= response.status_code < 300: - raise APIException(response.body, response.status_code) - except HttpClientException as exc: - _LOGGER.debug( - 'Error posting init config because an exception was raised by the HTTPClient' - ) - _LOGGER.debug('Error: ', exc_info=True) - - def record_stats(self, stats): - """ - Send runtime stats to the backend. - - :param stats: stats - :type json - """ - try: - response = self._client.post( - 'telemetry', - 'v1/metrics/usage', - self._sdk_key, - body=stats, - extra_headers=self._metadata, - ) - if not 200 <= response.status_code < 300: - raise APIException(response.body, response.status_code) - except HttpClientException as exc: - _LOGGER.debug( - 'Error posting runtime stats because an exception was raised by the HTTPClient' - ) - _LOGGER.debug('Error: ', exc_info=True) - raise APIException('Runtime stats not flushed properly.') from exc - - -class TelemetryAPIAsync(object): # pylint: disable=too-few-public-methods - """Async Class that uses an httpClient to communicate with the Telemetry API.""" - - def __init__(self, client, sdk_key, sdk_metadata, telemetry_runtime_producer): - """ - Class constructor. - - :param client: HTTP Client responsble for issuing calls to the backend. - :type client: HttpClient - :param sdk_key: User sdk_key token. - :type sdk_key: string - """ - self._client = client - self._sdk_key = sdk_key - self._metadata = headers_from_metadata(sdk_metadata) - self._telemetry_runtime_producer = telemetry_runtime_producer - self._client.set_telemetry_data(HTTPExceptionsAndLatencies.TELEMETRY, self._telemetry_runtime_producer) - - async def record_unique_keys(self, uniques): - """ - Send unique keys to the backend. - - :param uniques: Unique Keys - :type json - """ - try: - response = await self._client.post( - 'telemetry', - 'v1/keys/ss', - self._sdk_key, - body=uniques, - extra_headers=self._metadata - ) - if not 200 <= response.status_code < 300: - raise APIException(response.body, response.status_code) - except HttpClientException as exc: - _LOGGER.debug( - 'Error posting unique keys because an exception was raised by the HTTPClient' - ) - _LOGGER.debug('Error: ', exc_info=True) - raise APIException('Unique keys not flushed properly.') from exc - - async def record_init(self, configs): - """ - Send init config data to the backend. - - :param configs: configs - :type json - """ - try: - response = await self._client.post( - 'telemetry', - 'v1/metrics/config', - self._sdk_key, - body=configs, - extra_headers=self._metadata, - ) - if not 200 <= response.status_code < 300: - raise APIException(response.body, response.status_code) - except HttpClientException as exc: - _LOGGER.debug( - 'Error posting init config because an exception was raised by the HTTPClient' - ) - _LOGGER.debug('Error: ', exc_info=True) - - async def record_stats(self, stats): - """ - Send runtime stats to the backend. - - :param stats: stats - :type json - """ - try: - response = await self._client.post( - 'telemetry', - 'v1/metrics/usage', - self._sdk_key, - body=stats, - extra_headers=self._metadata, - ) - if not 200 <= response.status_code < 300: - raise APIException(response.body, response.status_code) - except HttpClientException as exc: - _LOGGER.debug( - 'Error posting runtime stats because an exception was raised by the HTTPClient' - ) - _LOGGER.debug('Error: ', exc_info=True) - raise APIException('Runtime stats not flushed properly.') from exc diff --git a/splitio/client/factory.py b/splitio/client/factory.py index d14e3d43..5f2d9fe6 100644 --- a/splitio/client/factory.py +++ b/splitio/client/factory.py @@ -44,14 +44,16 @@ PluggableRuleBasedSegmentsStorage, PluggableRuleBasedSegmentsStorageAsync # APIs -from splitio.api.client import HttpClient, HttpClientAsync, HttpClientKerberos +from harness_commons.api.client import HttpClient, HttpClientAsync +from splitio.api.kerberos_client import HttpClientKerberos from splitio.api.splits import SplitsAPI, SplitsAPIAsync -from splitio.api.segments import SegmentsAPI, SegmentsAPIAsync -from splitio.api.impressions import ImpressionsAPI, ImpressionsAPIAsync -from splitio.api.events import EventsAPI, EventsAPIAsync -from splitio.api.auth import AuthAPI, AuthAPIAsync -from splitio.api.telemetry import TelemetryAPI, TelemetryAPIAsync +from harness_commons.api.segments import SegmentsAPI, SegmentsAPIAsync +from harness_commons.api.impressions import ImpressionsAPI, ImpressionsAPIAsync +from harness_commons.api.events import EventsAPI, EventsAPIAsync +from harness_commons.api.auth import AuthAPI, AuthAPIAsync +from harness_commons.api.telemetry import TelemetryAPI, TelemetryAPIAsync from splitio.util.time import get_current_epoch_time_ms +from splitio.spec import SPEC_VERSION # Tasks from splitio.tasks.split_sync import SplitSynchronizationTask, SplitSynchronizationTaskAsync @@ -560,7 +562,7 @@ def _build_in_memory_factory(api_key, cfg, sdk_url=None, events_url=None, # pyl sdk_metadata = util.get_metadata(cfg) apis = { - 'auth': AuthAPI(http_client, api_key, sdk_metadata, telemetry_runtime_producer), + 'auth': AuthAPI(http_client, api_key, sdk_metadata, telemetry_runtime_producer, SPEC_VERSION), 'splits': SplitsAPI(http_client, api_key, sdk_metadata, telemetry_runtime_producer), 'segments': SegmentsAPI(http_client, api_key, sdk_metadata, telemetry_runtime_producer), 'impressions': ImpressionsAPI(http_client, api_key, sdk_metadata, telemetry_runtime_producer, cfg['impressionsMode']), @@ -695,7 +697,7 @@ async def _build_in_memory_factory_async(api_key, cfg, sdk_url=None, events_url= sdk_metadata = util.get_metadata(cfg) apis = { - 'auth': AuthAPIAsync(http_client, api_key, sdk_metadata, telemetry_runtime_producer), + 'auth': AuthAPIAsync(http_client, api_key, sdk_metadata, telemetry_runtime_producer, SPEC_VERSION), 'splits': SplitsAPIAsync(http_client, api_key, sdk_metadata, telemetry_runtime_producer), 'segments': SegmentsAPIAsync(http_client, api_key, sdk_metadata, telemetry_runtime_producer), 'impressions': ImpressionsAPIAsync(http_client, api_key, sdk_metadata, telemetry_runtime_producer, cfg['impressionsMode']), diff --git a/splitio/engine/impressions/adapters.py b/splitio/engine/impressions/adapters.py index d5e3dcaf..4be23319 100644 --- a/splitio/engine/impressions/adapters.py +++ b/splitio/engine/impressions/adapters.py @@ -53,7 +53,7 @@ def __init__(self, telemtry_http_client): Initialize In memory sender adapter instance :param telemtry_http_client: instance of telemetry http api - :type telemtry_http_client: splitio.api.telemetry.TelemetryAPI + :type telemtry_http_client: harness_commons.api.telemetry.TelemetryAPI """ self._telemtry_http_client = telemtry_http_client @@ -78,7 +78,7 @@ def __init__(self, telemtry_http_client): Initialize In memory sender adapter instance :param telemtry_http_client: instance of telemetry http api - :type telemtry_http_client: splitio.api.telemetry.TelemetryAPI + :type telemtry_http_client: harness_commons.api.telemetry.TelemetryAPI """ self._telemtry_http_client = telemtry_http_client @@ -103,7 +103,7 @@ def __init__(self, redis_client): Initialize Redis sender adapter instance :param telemtry_http_client: instance of telemetry http api - :type telemtry_http_client: splitio.api.telemetry.TelemetryAPI + :type telemtry_http_client: harness_commons.api.telemetry.TelemetryAPI """ self._redis_client = redis_client @@ -176,7 +176,7 @@ def __init__(self, redis_client): Initialize Redis sender adapter instance :param telemtry_http_client: instance of telemetry http api - :type telemtry_http_client: splitio.api.telemetry.TelemetryAPI + :type telemtry_http_client: harness_commons.api.telemetry.TelemetryAPI """ self._redis_client = redis_client @@ -249,7 +249,7 @@ def __init__(self, adapter_client, prefix=None): Initialize pluggable sender adapter instance :param telemtry_http_client: instance of telemetry http api - :type telemtry_http_client: splitio.api.telemetry.TelemetryAPI + :type telemtry_http_client: harness_commons.api.telemetry.TelemetryAPI """ self._adapter_client = adapter_client self._prefix = "" @@ -321,7 +321,7 @@ def __init__(self, adapter_client, prefix=None): Initialize pluggable sender adapter instance :param telemtry_http_client: instance of telemetry http api - :type telemtry_http_client: splitio.api.telemetry.TelemetryAPI + :type telemtry_http_client: harness_commons.api.telemetry.TelemetryAPI """ self._adapter_client = adapter_client self._prefix = "" diff --git a/splitio/push/manager.py b/splitio/push/manager.py index 1ecd4b49..f3fceb2a 100644 --- a/splitio/push/manager.py +++ b/splitio/push/manager.py @@ -5,7 +5,7 @@ import sys from splitio.optional.loaders import asyncio -from splitio.api import APIException +from harness_commons.api import APIException from splitio.util.time import get_current_epoch_time_ms from splitio.push import AuthException from splitio.push.splitsse import SplitSSEClient, SplitSSEClientAsync @@ -50,7 +50,7 @@ def __init__(self, auth_api, synchronizer, feedback_loop, sdk_metadata, telemetr Class constructor. :param auth_api: sdk-auth-service api client - :type auth_api: splitio.api.auth.AuthAPI + :type auth_api: harness_commons.api.auth.AuthAPI :param synchronizer: split data synchronizer facade :type synchronizer: splitio.sync.synchronizer.Synchronizer @@ -283,7 +283,7 @@ def __init__(self, auth_api, synchronizer, feedback_loop, sdk_metadata, telemetr Class constructor. :param auth_api: sdk-auth-service api client - :type auth_api: splitio.api.auth.AuthAPI + :type auth_api: harness_commons.api.auth.AuthAPI :param synchronizer: split data synchronizer facade :type synchronizer: splitio.sync.synchronizer.Synchronizer diff --git a/splitio/push/splitsse.py b/splitio/push/splitsse.py index 120e27f5..411ce22a 100644 --- a/splitio/push/splitsse.py +++ b/splitio/push/splitsse.py @@ -7,7 +7,7 @@ from splitio.push.sse import SSEClient, SSEClientAsync, SSE_EVENT_ERROR from splitio.util.threadutil import EventGroup -from splitio.api import headers_from_metadata +from harness_commons.api import headers_from_metadata from splitio.optional.loaders import asyncio if sys.version_info.major == 3 and sys.version_info.minor < 10: diff --git a/splitio/sync/event.py b/splitio/sync/event.py index ff761670..c9023be5 100644 --- a/splitio/sync/event.py +++ b/splitio/sync/event.py @@ -1,7 +1,7 @@ import logging import queue -from splitio.api import APIException +from harness_commons.api import APIException from splitio.optional.loaders import asyncio _LOGGER = logging.getLogger(__name__) @@ -14,7 +14,7 @@ def __init__(self, events_api, storage, bulk_size): Class constructor. :param events_api: Events Api object to send data to the backend - :type events_api: splitio.api.events.EventsAPI + :type events_api: harness_commons.api.events.EventsAPI :param storage: Events Storage :type storage: splitio.storage.EventStorage :param bulk_size: How many events to send per push. @@ -75,7 +75,7 @@ def __init__(self, events_api, storage, bulk_size): Class constructor. :param events_api: Events Api object to send data to the backend - :type events_api: splitio.api.events.EventsAPI + :type events_api: harness_commons.api.events.EventsAPI :param storage: Events Storage :type storage: splitio.storage.EventStorage :param bulk_size: How many events to send per push. diff --git a/splitio/sync/impression.py b/splitio/sync/impression.py index 8fd54051..c8c010b5 100644 --- a/splitio/sync/impression.py +++ b/splitio/sync/impression.py @@ -1,7 +1,7 @@ import logging import queue -from splitio.api import APIException +from harness_commons.api import APIException from splitio.optional.loaders import asyncio _LOGGER = logging.getLogger(__name__) @@ -14,7 +14,7 @@ def __init__(self, impressions_api, storage, bulk_size): Class constructor. :param impressions_api: Impressions Api object to send data to the backend - :type impressions_api: splitio.api.impressions.ImpressionsAPI + :type impressions_api: harness_commons.api.impressions.ImpressionsAPI :param storage: Impressions Storage :type storage: splitio.storage.ImpressionsStorage :param bulk_size: How many impressions to send per push. @@ -74,7 +74,7 @@ def __init__(self, impressions_api, imp_counter): Class constructor. :param impressions_api: Impressions Api object to send data to the backend - :type impressions_api: splitio.api.impressions.ImpressionsAPI + :type impressions_api: harness_commons.api.impressions.ImpressionsAPI :param impressions_manager: Impressions manager instance :type impressions_manager: splitio.engine.impressions.Manager @@ -106,7 +106,7 @@ def __init__(self, impressions_api, storage, bulk_size): Class constructor. :param impressions_api: Impressions Api object to send data to the backend - :type impressions_api: splitio.api.impressions.ImpressionsAPI + :type impressions_api: harness_commons.api.impressions.ImpressionsAPI :param storage: Impressions Storage :type storage: splitio.storage.ImpressionsStorage :param bulk_size: How many impressions to send per push. @@ -166,7 +166,7 @@ def __init__(self, impressions_api, imp_counter): Class constructor. :param impressions_api: Impressions Api object to send data to the backend - :type impressions_api: splitio.api.impressions.ImpressionsAPI + :type impressions_api: harness_commons.api.impressions.ImpressionsAPI :param impressions_manager: Impressions manager instance :type impressions_manager: splitio.engine.impressions.Manager diff --git a/splitio/sync/manager.py b/splitio/sync/manager.py index 47fa5b57..32483a5b 100644 --- a/splitio/sync/manager.py +++ b/splitio/sync/manager.py @@ -6,7 +6,7 @@ from splitio.optional.loaders import asyncio from splitio.push.manager import PushManager, PushManagerAsync, Status -from splitio.api import APIException +from harness_commons.api import APIException from splitio.util.backoff import Backoff from splitio.util.time import get_current_epoch_time_ms from harness_commons.models.telemetry import SSESyncMode, StreamingEventTypes @@ -31,7 +31,7 @@ def __init__(self, ready_flag, synchronizer, auth_api, streaming_enabled, sdk_me :type split_synchronizers: splitio.sync.synchronizer.Synchronizer :param auth_api: Authentication api client - :type auth_api: splitio.api.auth.AuthAPI + :type auth_api: harness_commons.api.auth.AuthAPI :param sdk_metadata: SDK version & machine name & IP. :type sdk_metadata: splitio.client.util.SdkMetadata @@ -149,7 +149,7 @@ def __init__(self, synchronizer, auth_api, streaming_enabled, sdk_metadata, tele :type split_synchronizers: splitio.sync.synchronizer.Synchronizer :param auth_api: Authentication api client - :type auth_api: splitio.api.auth.AuthAPI + :type auth_api: harness_commons.api.auth.AuthAPI :param sdk_metadata: SDK version & machine name & IP. :type sdk_metadata: splitio.client.util.SdkMetadata diff --git a/splitio/sync/segment.py b/splitio/sync/segment.py index 61c8681a..d22ec72a 100644 --- a/splitio/sync/segment.py +++ b/splitio/sync/segment.py @@ -3,8 +3,8 @@ import json import os -from splitio.api import APIException -from splitio.api.commons import FetchOptions +from harness_commons.api import APIException +from harness_commons.api.commons import FetchOptions from splitio.tasks.util import workerpool from harness_commons.models import segments from splitio.util.backoff import Backoff @@ -28,7 +28,7 @@ def __init__(self, segment_api, feature_flag_storage, segment_storage, rule_base Class constructor. :param segment_api: API to retrieve segments from backend. - :type segment_api: splitio.api.SegmentApi + :type segment_api: harness_commons.api.segmentApi :param feature_flag_storage: Feature Flag Storage. :type feature_flag_storage: splitio.storage.InMemorySplitStorage @@ -214,7 +214,7 @@ def __init__(self, segment_api, feature_flag_storage, segment_storage, rule_base Class constructor. :param segment_api: API to retrieve segments from backend. - :type segment_api: splitio.api.SegmentApi + :type segment_api: harness_commons.api.segmentApi :param feature_flag_storage: Feature Flag Storage. :type feature_flag_storage: splitio.storage.InMemorySplitStorage diff --git a/splitio/sync/split.py b/splitio/sync/split.py index 4b00c0d3..b0bf91b8 100644 --- a/splitio/sync/split.py +++ b/splitio/sync/split.py @@ -7,8 +7,8 @@ import json from enum import Enum -from splitio.api import APIException, APIUriException -from splitio.api.commons import FetchOptions +from harness_commons.api import APIException, APIUriException +from harness_commons.api.commons import FetchOptions from splitio.client.input_validator import validate_flag_sets from splitio.models import splits from harness_commons.models import rule_based_segments @@ -18,6 +18,7 @@ update_rule_based_segment_storage, update_rule_based_segment_storage_async from splitio.sync import util +from splitio.spec import SPEC_VERSION from splitio.optional.loaders import asyncio, aiofiles _LEGACY_COMMENT_LINE_RE = re.compile(r'^#.*$') @@ -207,7 +208,7 @@ def synchronize_splits(self, till=None, rbs_till=None): :type rbs_till: int """ final_segment_list = set() - fetch_options = FetchOptions(True, sets=self._get_config_sets()) # Set Cache-Control to no-cache + fetch_options = FetchOptions(True, sets=self._get_config_sets(), spec=SPEC_VERSION) # Set Cache-Control to no-cache successful_sync, remaining_attempts, change_number, rbs_change_number, segment_list = self._attempt_feature_flag_sync(fetch_options, till, rbs_till) final_segment_list.update(segment_list) @@ -216,7 +217,7 @@ def synchronize_splits(self, till=None, rbs_till=None): _LOGGER.debug('Refresh completed in %d attempts.', attempts) return final_segment_list - with_cdn_bypass = FetchOptions(True, change_number, rbs_change_number, sets=self._get_config_sets()) # Set flag for bypassing CDN + with_cdn_bypass = FetchOptions(True, change_number, rbs_change_number, sets=self._get_config_sets(), spec=SPEC_VERSION) # Set flag for bypassing CDN without_cdn_successful_sync, remaining_attempts, change_number, rbs_change_number, segment_list = self._attempt_feature_flag_sync(with_cdn_bypass, till, rbs_till) final_segment_list.update(segment_list) without_cdn_attempts = _ON_DEMAND_FETCH_BACKOFF_MAX_RETRIES - remaining_attempts @@ -354,7 +355,7 @@ async def synchronize_splits(self, till=None, rbs_till=None): :type rbs_till: int """ final_segment_list = set() - fetch_options = FetchOptions(True, sets=self._get_config_sets()) # Set Cache-Control to no-cache + fetch_options = FetchOptions(True, sets=self._get_config_sets(), spec=SPEC_VERSION) # Set Cache-Control to no-cache successful_sync, remaining_attempts, change_number, rbs_change_number, segment_list = await self._attempt_feature_flag_sync(fetch_options, till, rbs_till) final_segment_list.update(segment_list) @@ -363,7 +364,7 @@ async def synchronize_splits(self, till=None, rbs_till=None): _LOGGER.debug('Refresh completed in %d attempts.', attempts) return final_segment_list - with_cdn_bypass = FetchOptions(True, change_number, rbs_change_number, sets=self._get_config_sets()) # Set flag for bypassing CDN + with_cdn_bypass = FetchOptions(True, change_number, rbs_change_number, sets=self._get_config_sets(), spec=SPEC_VERSION) # Set flag for bypassing CDN without_cdn_successful_sync, remaining_attempts, change_number, rbs_change_number, segment_list = await self._attempt_feature_flag_sync(with_cdn_bypass, till, rbs_till) final_segment_list.update(segment_list) without_cdn_attempts = _ON_DEMAND_FETCH_BACKOFF_MAX_RETRIES - remaining_attempts diff --git a/splitio/sync/synchronizer.py b/splitio/sync/synchronizer.py index a6ca6214..fec604ab 100644 --- a/splitio/sync/synchronizer.py +++ b/splitio/sync/synchronizer.py @@ -7,7 +7,7 @@ from collections import namedtuple from splitio.optional.loaders import asyncio -from splitio.api import APIException, APIUriException +from harness_commons.api import APIException, APIUriException from splitio.util.backoff import Backoff from splitio.sync.split import _ON_DEMAND_FETCH_BACKOFF_BASE, _ON_DEMAND_FETCH_BACKOFF_MAX_RETRIES, _ON_DEMAND_FETCH_BACKOFF_MAX_WAIT, LocalhostMode diff --git a/splitio/tasks/events_sync.py b/splitio/tasks/events_sync.py index a9b9f255..242b8e78 100644 --- a/splitio/tasks/events_sync.py +++ b/splitio/tasks/events_sync.py @@ -42,7 +42,7 @@ def __init__(self, synchronize_events, period): Class constructor. :param synchronize_events: Events Api object to send data to the backend - :type synchronize_events: splitio.api.events.EventsAPI + :type synchronize_events: harness_commons.api.events.EventsAPI :param period: How many seconds to wait between subsequent event pushes to the BE. :type period: int @@ -63,7 +63,7 @@ def __init__(self, synchronize_events, period): Class constructor. :param synchronize_events: Events Api object to send data to the backend - :type synchronize_events: splitio.api.events.EventsAPIAsync + :type synchronize_events: harness_commons.api.events.EventsAPIAsync :param period: How many seconds to wait between subsequent event pushes to the BE. :type period: int diff --git a/tests/api/test_auth.py b/tests/api/test_auth.py deleted file mode 100644 index 175977a2..00000000 --- a/tests/api/test_auth.py +++ /dev/null @@ -1,108 +0,0 @@ -"""Split API tests module.""" - -import pytest -import unittest.mock as mock - -from splitio.api import auth, client, APIException -from splitio.client.util import get_metadata -from splitio.client.config import DEFAULT_CONFIG -from splitio.version import __version__ -from splitio.engine.telemetry import TelemetryStorageProducer, TelemetryStorageProducerAsync -from splitio.storage.inmemmory import InMemoryTelemetryStorage, InMemoryTelemetryStorageAsync - -class AuthAPITests(object): - """Auth API test cases.""" - - def test_auth(self, mocker): - """Test auth API call.""" - token = "eyJhbGciOiJIUzI1NiIsImtpZCI6IjVZOU05US45QnJtR0EiLCJ0eXAiOiJKV1QifQ.eyJ4LWFibHktY2FwYWJpbGl0eSI6IntcIk56TTJNREk1TXpjMF9NVGd5TlRnMU1UZ3dOZz09X3NlZ21lbnRzXCI6W1wic3Vic2NyaWJlXCJdLFwiTnpNMk1ESTVNemMwX01UZ3lOVGcxTVRnd05nPT1fc3BsaXRzXCI6W1wic3Vic2NyaWJlXCJdLFwiY29udHJvbF9wcmlcIjpbXCJzdWJzY3JpYmVcIixcImNoYW5uZWwtbWV0YWRhdGE6cHVibGlzaGVyc1wiXSxcImNvbnRyb2xfc2VjXCI6W1wic3Vic2NyaWJlXCIsXCJjaGFubmVsLW1ldGFkYXRhOnB1Ymxpc2hlcnNcIl19IiwieC1hYmx5LWNsaWVudElkIjoiY2xpZW50SWQiLCJleHAiOjE2MDIwODgxMjcsImlhdCI6MTYwMjA4NDUyN30.5_MjWonhs6yoFhw44hNJm3H7_YMjXpSW105DwjjppqE" - httpclient = mocker.Mock(spec=client.HttpClient) - payload = '{{"pushEnabled": true, "token": "{token}"}}'.format(token=token) - cfg = DEFAULT_CONFIG.copy() - cfg.update({'IPAddressesEnabled': True, 'machineName': 'some_machine_name', 'machineIp': '123.123.123.123'}) - sdk_metadata = get_metadata(cfg) - httpclient.get.return_value = client.HttpResponse(200, payload, {}) - telemetry_storage = InMemoryTelemetryStorage() - telemetry_producer = TelemetryStorageProducer(telemetry_storage) - telemetry_runtime_producer = telemetry_producer.get_telemetry_runtime_producer() - auth_api = auth.AuthAPI(httpclient, 'some_api_key', sdk_metadata, telemetry_runtime_producer) - response = auth_api.authenticate() - - assert response.push_enabled == True - assert response.token == token - - call_made = httpclient.get.mock_calls[0] - - # validate positional arguments - assert call_made[1] == ('auth', 'v2/auth?s=1.3', 'some_api_key') - - # validate key-value args (headers) - assert call_made[2]['extra_headers'] == { - 'SplitSDKVersion': 'python-%s' % __version__, - 'SplitSDKMachineIP': '123.123.123.123', - 'SplitSDKMachineName': 'some_machine_name' - } - - httpclient.reset_mock() - def raise_exception(*args, **kwargs): - raise client.HttpClientException('some_message') - httpclient.get.side_effect = raise_exception - with pytest.raises(APIException) as exc_info: - response = auth_api.authenticate() - assert exc_info.type == APIException - assert exc_info.value.message == 'some_message' - - -class AuthAPIAsyncTests(object): - """Auth async API test cases.""" - - @pytest.mark.asyncio - async def test_auth(self, mocker): - """Test auth API call.""" - self.token = "eyJhbGciOiJIUzI1NiIsImtpZCI6IjVZOU05US45QnJtR0EiLCJ0eXAiOiJKV1QifQ.eyJ4LWFibHktY2FwYWJpbGl0eSI6IntcIk56TTJNREk1TXpjMF9NVGd5TlRnMU1UZ3dOZz09X3NlZ21lbnRzXCI6W1wic3Vic2NyaWJlXCJdLFwiTnpNMk1ESTVNemMwX01UZ3lOVGcxTVRnd05nPT1fc3BsaXRzXCI6W1wic3Vic2NyaWJlXCJdLFwiY29udHJvbF9wcmlcIjpbXCJzdWJzY3JpYmVcIixcImNoYW5uZWwtbWV0YWRhdGE6cHVibGlzaGVyc1wiXSxcImNvbnRyb2xfc2VjXCI6W1wic3Vic2NyaWJlXCIsXCJjaGFubmVsLW1ldGFkYXRhOnB1Ymxpc2hlcnNcIl19IiwieC1hYmx5LWNsaWVudElkIjoiY2xpZW50SWQiLCJleHAiOjE2MDIwODgxMjcsImlhdCI6MTYwMjA4NDUyN30.5_MjWonhs6yoFhw44hNJm3H7_YMjXpSW105DwjjppqE" - httpclient = mocker.Mock(spec=client.HttpClientAsync) - cfg = DEFAULT_CONFIG.copy() - cfg.update({'IPAddressesEnabled': True, 'machineName': 'some_machine_name', 'machineIp': '123.123.123.123'}) - sdk_metadata = get_metadata(cfg) - telemetry_storage = InMemoryTelemetryStorageAsync() - telemetry_producer = TelemetryStorageProducerAsync(telemetry_storage) - telemetry_runtime_producer = telemetry_producer.get_telemetry_runtime_producer() - auth_api = auth.AuthAPIAsync(httpclient, 'some_api_key', sdk_metadata, telemetry_runtime_producer) - - self.verb = None - self.url = None - self.key = None - self.headers = None - async def get(verb, url, key, extra_headers): - self.url = url - self.verb = verb - self.key = key - self.headers = extra_headers - payload = '{{"pushEnabled": true, "token": "{token}"}}'.format(token=self.token) - return client.HttpResponse(200, payload, {}) - - httpclient.get = get - - response = await auth_api.authenticate() - assert response.push_enabled == True - assert response.token == self.token - - # validate positional arguments - assert self.verb == 'auth' - assert self.url == 'v2/auth?s=1.3' - assert self.key == 'some_api_key' - assert self.headers == { - 'SplitSDKVersion': 'python-%s' % __version__, - 'SplitSDKMachineIP': '123.123.123.123', - 'SplitSDKMachineName': 'some_machine_name' - } - - httpclient.reset_mock() - def raise_exception(*args, **kwargs): - raise client.HttpClientException('some_message') - - httpclient.get = raise_exception - with pytest.raises(APIException) as exc_info: - response = await auth_api.authenticate() - assert exc_info.type == APIException - assert exc_info.value.message == 'some_message' diff --git a/tests/api/test_events.py b/tests/api/test_events.py deleted file mode 100644 index eeb3deca..00000000 --- a/tests/api/test_events.py +++ /dev/null @@ -1,193 +0,0 @@ -"""Impressions API tests module.""" - -import pytest -import unittest.mock as mock - -from splitio.api import events, client, APIException -from harness_commons.models.events import Event -from splitio.client.util import get_metadata -from splitio.client.config import DEFAULT_CONFIG -from splitio.version import __version__ -from splitio.engine.telemetry import TelemetryStorageProducer, TelemetryStorageProducerAsync -from splitio.storage.inmemmory import InMemoryTelemetryStorage, InMemoryTelemetryStorageAsync - - -class EventsAPITests(object): - """Impressions API test cases.""" - events = [ - Event('k1', 'user', 'purchase', 12.50, 123456, None), - Event('k2', 'user', 'purchase', 12.50, 123456, None), - Event('k3', 'user', 'purchase', None, 123456, {"test": 1234}), - Event('k4', 'user', 'purchase', None, 123456, None) - ] - eventsExpected = [ - {'key': 'k1', 'trafficTypeName': 'user', 'eventTypeId': 'purchase', 'value': 12.50, 'timestamp': 123456, 'properties': None}, - {'key': 'k2', 'trafficTypeName': 'user', 'eventTypeId': 'purchase', 'value': 12.50, 'timestamp': 123456, 'properties': None}, - {'key': 'k3', 'trafficTypeName': 'user', 'eventTypeId': 'purchase', 'value': None, 'timestamp': 123456, 'properties': {"test": 1234}}, - {'key': 'k4', 'trafficTypeName': 'user', 'eventTypeId': 'purchase', 'value': None, 'timestamp': 123456, 'properties': None}, - ] - - def test_post_events(self, mocker): - """Test impressions posting API call.""" - httpclient = mocker.Mock(spec=client.HttpClient) - httpclient.post.return_value = client.HttpResponse(200, '', {}) - cfg = DEFAULT_CONFIG.copy() - cfg.update({'IPAddressesEnabled': True, 'machineName': 'some_machine_name', 'machineIp': '123.123.123.123'}) - sdk_metadata = get_metadata(cfg) - telemetry_storage = InMemoryTelemetryStorage() - telemetry_producer = TelemetryStorageProducer(telemetry_storage) - telemetry_runtime_producer = telemetry_producer.get_telemetry_runtime_producer() - events_api = events.EventsAPI(httpclient, 'some_api_key', sdk_metadata, telemetry_runtime_producer) - response = events_api.flush_events(self.events) - - call_made = httpclient.post.mock_calls[0] - - # validate positional arguments - assert call_made[1] == ('events', 'events/bulk', 'some_api_key') - - # validate key-value args (headers) - assert call_made[2]['extra_headers'] == { - 'SplitSDKVersion': 'python-%s' % __version__, - 'SplitSDKMachineIP': '123.123.123.123', - 'SplitSDKMachineName': 'some_machine_name' - } - - # validate key-value args (body) - assert call_made[2]['body'] == self.eventsExpected - - httpclient.reset_mock() - def raise_exception(*args, **kwargs): - raise client.HttpClientException('some_message') - httpclient.post.side_effect = raise_exception - with pytest.raises(APIException) as exc_info: - response = events_api.flush_events(self.events) - assert exc_info.type == APIException - assert exc_info.value.message == 'some_message' - - def test_post_events_ip_address_disabled(self, mocker): - """Test impressions posting API call.""" - httpclient = mocker.Mock(spec=client.HttpClient) - httpclient.post.return_value = client.HttpResponse(200, '', {}) - cfg = DEFAULT_CONFIG.copy() - cfg.update({'IPAddressesEnabled': False}) - sdk_metadata = get_metadata(cfg) - events_api = events.EventsAPI(httpclient, 'some_api_key', sdk_metadata, mocker.Mock()) - response = events_api.flush_events(self.events) - - call_made = httpclient.post.mock_calls[0] - - # validate positional arguments - assert call_made[1] == ('events', 'events/bulk', 'some_api_key') - - # validate key-value args (headers) - assert call_made[2]['extra_headers'] == { - 'SplitSDKVersion': 'python-%s' % __version__, - } - - # validate key-value args (body) - assert call_made[2]['body'] == self.eventsExpected - - -class EventsAPIAsyncTests(object): - """Impressions Async API test cases.""" - events = [ - Event('k1', 'user', 'purchase', 12.50, 123456, None), - Event('k2', 'user', 'purchase', 12.50, 123456, None), - Event('k3', 'user', 'purchase', None, 123456, {"test": 1234}), - Event('k4', 'user', 'purchase', None, 123456, None) - ] - eventsExpected = [ - {'key': 'k1', 'trafficTypeName': 'user', 'eventTypeId': 'purchase', 'value': 12.50, 'timestamp': 123456, 'properties': None}, - {'key': 'k2', 'trafficTypeName': 'user', 'eventTypeId': 'purchase', 'value': 12.50, 'timestamp': 123456, 'properties': None}, - {'key': 'k3', 'trafficTypeName': 'user', 'eventTypeId': 'purchase', 'value': None, 'timestamp': 123456, 'properties': {"test": 1234}}, - {'key': 'k4', 'trafficTypeName': 'user', 'eventTypeId': 'purchase', 'value': None, 'timestamp': 123456, 'properties': None}, - ] - - @pytest.mark.asyncio - async def test_post_events(self, mocker): - """Test impressions posting API call.""" - httpclient = mocker.Mock(spec=client.HttpClientAsync) - cfg = DEFAULT_CONFIG.copy() - cfg.update({'IPAddressesEnabled': True, 'machineName': 'some_machine_name', 'machineIp': '123.123.123.123'}) - sdk_metadata = get_metadata(cfg) - telemetry_storage = InMemoryTelemetryStorageAsync() - telemetry_producer = TelemetryStorageProducerAsync(telemetry_storage) - telemetry_runtime_producer = telemetry_producer.get_telemetry_runtime_producer() - events_api = events.EventsAPIAsync(httpclient, 'some_api_key', sdk_metadata, telemetry_runtime_producer) - - self.verb = None - self.url = None - self.key = None - self.headers = None - self.body = None - async def post(verb, url, key, body, extra_headers): - self.url = url - self.verb = verb - self.key = key - self.headers = extra_headers - self.body = body - return client.HttpResponse(200, '', {}) - httpclient.post = post - - response = await events_api.flush_events(self.events) - # validate positional arguments - assert self.verb == 'events' - assert self.url == 'events/bulk' - assert self.key == 'some_api_key' - - # validate key-value args (headers) - assert self.headers == { - 'SplitSDKVersion': 'python-%s' % __version__, - 'SplitSDKMachineIP': '123.123.123.123', - 'SplitSDKMachineName': 'some_machine_name' - } - - # validate key-value args (body) - assert self.body == self.eventsExpected - - httpclient.reset_mock() - def raise_exception(*args, **kwargs): - raise client.HttpClientException('some_message') - httpclient.post = raise_exception - with pytest.raises(APIException) as exc_info: - response = await events_api.flush_events(self.events) - assert exc_info.type == APIException - assert exc_info.value.message == 'some_message' - - @pytest.mark.asyncio - async def test_post_events_ip_address_disabled(self, mocker): - """Test impressions posting API call.""" - httpclient = mocker.Mock(spec=client.HttpClientAsync) - cfg = DEFAULT_CONFIG.copy() - cfg.update({'IPAddressesEnabled': False}) - sdk_metadata = get_metadata(cfg) - events_api = events.EventsAPIAsync(httpclient, 'some_api_key', sdk_metadata, mocker.Mock()) - - self.verb = None - self.url = None - self.key = None - self.headers = None - self.body = None - async def post(verb, url, key, body, extra_headers): - self.url = url - self.verb = verb - self.key = key - self.headers = extra_headers - self.body = body - return client.HttpResponse(200, '', {}) - httpclient.post = post - - response = await events_api.flush_events(self.events) - - # validate positional arguments - assert self.verb == 'events' - assert self.url == 'events/bulk' - assert self.key == 'some_api_key' - - # validate key-value args (headers) - assert self.headers == { - 'SplitSDKVersion': 'python-%s' % __version__, - } - - # validate key-value args (body) - assert self.body == self.eventsExpected diff --git a/tests/api/test_httpclient.py b/tests/api/test_httpclient.py deleted file mode 100644 index 837997aa..00000000 --- a/tests/api/test_httpclient.py +++ /dev/null @@ -1,735 +0,0 @@ -"""HTTPClient test module.""" -from requests_kerberos import HTTPKerberosAuth -import pytest -import unittest.mock as mock -import requests - -from splitio.client.config import AuthenticateScheme -from splitio.api import client -from splitio.engine.telemetry import TelemetryStorageProducer, TelemetryStorageProducerAsync -from splitio.storage.inmemmory import InMemoryTelemetryStorage, InMemoryTelemetryStorageAsync - -class HttpClientTests(object): - """Http Client test cases.""" - - def test_get(self, mocker): - """Test HTTP GET verb requests.""" - response_mock = mocker.Mock() - response_mock.status_code = 200 - response_mock.headers = {} - response_mock.text = 'ok' - get_mock = mocker.Mock() - get_mock.return_value = response_mock - mocker.patch('splitio.api.client.requests.get', new=get_mock) - httpclient = client.HttpClient() - httpclient.set_telemetry_data("metric", mocker.Mock()) - response = httpclient.get('sdk', 'test1', 'some_api_key', {'param1': 123}, {'h1': 'abc'}) - call = mocker.call( - client.SDK_URL + '/test1', - headers={'Authorization': 'Bearer some_api_key', 'h1': 'abc', 'Content-Type': 'application/json'}, - params={'param1': 123}, - timeout=None - ) - assert response.status_code == 200 - assert response.body == 'ok' - assert get_mock.mock_calls == [call] - get_mock.reset_mock() - - response = httpclient.get('events', 'test1', 'some_api_key', {'param1': 123}, {'h1': 'abc'}) - call = mocker.call( - client.EVENTS_URL + '/test1', - headers={'Authorization': 'Bearer some_api_key', 'h1': 'abc', 'Content-Type': 'application/json'}, - params={'param1': 123}, - timeout=None - ) - assert get_mock.mock_calls == [call] - assert response.status_code == 200 - assert response.body == 'ok' - - def test_get_custom_urls(self, mocker): - """Test HTTP GET verb requests.""" - response_mock = mocker.Mock() - response_mock.status_code = 200 - response_mock.headers = {} - response_mock.text = 'ok' - get_mock = mocker.Mock() - get_mock.return_value = response_mock - mocker.patch('splitio.api.client.requests.get', new=get_mock) - httpclient = client.HttpClient(sdk_url='https://sdk.com', events_url='https://events.com') - httpclient.set_telemetry_data("metric", mocker.Mock()) - response = httpclient.get('sdk', 'test1', 'some_api_key', {'param1': 123}, {'h1': 'abc'}) - call = mocker.call( - 'https://sdk.com/test1', - headers={'Authorization': 'Bearer some_api_key', 'h1': 'abc', 'Content-Type': 'application/json'}, - params={'param1': 123}, - timeout=None - ) - assert get_mock.mock_calls == [call] - assert response.status_code == 200 - assert response.body == 'ok' - get_mock.reset_mock() - - response = httpclient.get('events', 'test1', 'some_api_key', {'param1': 123}, {'h1': 'abc'}) - call = mocker.call( - 'https://events.com/test1', - headers={'Authorization': 'Bearer some_api_key', 'h1': 'abc', 'Content-Type': 'application/json'}, - params={'param1': 123}, - timeout=None - ) - assert response.status_code == 200 - assert response.body == 'ok' - assert get_mock.mock_calls == [call] - - - def test_post(self, mocker): - """Test HTTP GET verb requests.""" - response_mock = mocker.Mock() - response_mock.status_code = 200 - response_mock.headers = {} - response_mock.text = 'ok' - get_mock = mocker.Mock() - get_mock.return_value = response_mock - mocker.patch('splitio.api.client.requests.post', new=get_mock) - httpclient = client.HttpClient() - httpclient.set_telemetry_data("metric", mocker.Mock()) - response = httpclient.post('sdk', 'test1', 'some_api_key', {'p1': 'a'}, {'param1': 123}, {'h1': 'abc'}) - call = mocker.call( - client.SDK_URL + '/test1', - json={'p1': 'a'}, - headers={'Authorization': 'Bearer some_api_key', 'h1': 'abc', 'Content-Type': 'application/json'}, - params={'param1': 123}, - timeout=None - ) - assert response.status_code == 200 - assert response.body == 'ok' - assert get_mock.mock_calls == [call] - get_mock.reset_mock() - - response = httpclient.post('events', 'test1', 'some_api_key', {'p1': 'a'}, {'param1': 123}, {'h1': 'abc'}) - call = mocker.call( - client.EVENTS_URL + '/test1', - json={'p1': 'a'}, - headers={'Authorization': 'Bearer some_api_key', 'h1': 'abc', 'Content-Type': 'application/json'}, - params={'param1': 123}, - timeout=None - ) - assert response.status_code == 200 - assert response.body == 'ok' - assert get_mock.mock_calls == [call] - - def test_post_custom_urls(self, mocker): - """Test HTTP GET verb requests.""" - response_mock = mocker.Mock() - response_mock.status_code = 200 - response_mock.headers = {} - response_mock.text = 'ok' - get_mock = mocker.Mock() - get_mock.return_value = response_mock - mocker.patch('splitio.api.client.requests.post', new=get_mock) - httpclient = client.HttpClient(sdk_url='https://sdk.com', events_url='https://events.com') - httpclient.set_telemetry_data("metric", mocker.Mock()) - response = httpclient.post('sdk', 'test1', 'some_api_key', {'p1': 'a'}, {'param1': 123}, {'h1': 'abc'}) - call = mocker.call( - 'https://sdk.com' + '/test1', - json={'p1': 'a'}, - headers={'Authorization': 'Bearer some_api_key', 'h1': 'abc', 'Content-Type': 'application/json'}, - params={'param1': 123}, - timeout=None - ) - assert response.status_code == 200 - assert response.body == 'ok' - assert get_mock.mock_calls == [call] - get_mock.reset_mock() - - response = httpclient.post('events', 'test1', 'some_api_key', {'p1': 'a'}, {'param1': 123}, {'h1': 'abc'}) - call = mocker.call( - 'https://events.com' + '/test1', - json={'p1': 'a'}, - headers={'Authorization': 'Bearer some_api_key', 'h1': 'abc', 'Content-Type': 'application/json'}, - params={'param1': 123}, - timeout=None - ) - assert response.status_code == 200 - assert response.body == 'ok' - assert get_mock.mock_calls == [call] - - def test_telemetry(self, mocker): - telemetry_storage = InMemoryTelemetryStorage() - telemetry_producer = TelemetryStorageProducer(telemetry_storage) - telemetry_runtime_producer = telemetry_producer.get_telemetry_runtime_producer() - - response_mock = mocker.Mock() - response_mock.status_code = 200 - response_mock.headers = {} - response_mock.text = 'ok' - get_mock = mocker.Mock() - get_mock.return_value = response_mock - mocker.patch('splitio.api.client.requests.post', new=get_mock) - httpclient = client.HttpClient(timeout=1500, sdk_url='https://sdk.com', events_url='https://events.com') - httpclient.set_telemetry_data("metric", telemetry_runtime_producer) - - self.metric1 = None - self.cur_time = 0 - def record_successful_sync(metric_name, cur_time): - self.metric1 = metric_name - self.cur_time = cur_time - httpclient._telemetry_runtime_producer.record_successful_sync = record_successful_sync - - self.metric2 = None - self.elapsed = 0 - def record_sync_latency(metric_name, elapsed): - self.metric2 = metric_name - self.elapsed = elapsed - httpclient._telemetry_runtime_producer.record_sync_latency = record_sync_latency - - self.metric3 = None - self.status = 0 - def record_sync_error(metric_name, elapsed): - self.metric3 = metric_name - self.status = elapsed - httpclient._telemetry_runtime_producer.record_sync_error = record_sync_error - - httpclient.post('sdk', 'test1', 'some_api_key', {'p1': 'a'}, {'param1': 123}, {'h1': 'abc'}) - assert (self.metric2 == "metric") - assert (self.metric1 == "metric") - assert (self.cur_time > self.elapsed) - - response_mock.status_code = 400 - response_mock.headers = {} - response_mock.text = 'ok' - httpclient.post('sdk', 'test1', 'some_api_key', {'p1': 'a'}, {'param1': 123}, {'h1': 'abc'}) - assert (self.metric3 == "metric") - assert (self.status == 400) - - # testing get call - mocker.patch('splitio.api.client.requests.get', new=get_mock) - self.metric1 = None - self.cur_time = 0 - self.metric2 = None - self.elapsed = 0 - response_mock.status_code = 200 - httpclient.get('sdk', 'test1', 'some_api_key', {'param1': 123}, {'h1': 'abc'}) - assert (self.metric2 == "metric") - assert (self.metric1 == "metric") - assert (self.cur_time > self.elapsed) - - self.metric3 = None - self.status = 0 - response_mock.status_code = 400 - httpclient.get('sdk', 'test1', 'some_api_key', {'param1': 123}, {'h1': 'abc'}) - assert (self.metric3 == "metric") - assert (self.status == 400) - -class HttpClientKerberosTests(object): - """Http Client test cases.""" - - def test_authentication_scheme(self, mocker): - global turl - global theaders - global tparams - global ttimeout - global tjson - - turl = None - theaders = None - tparams = None - ttimeout = None - class get_mock(object): - def __init__(self, url, headers, params, timeout): - global turl - global theaders - global tparams - global ttimeout - turl = url - theaders = headers - tparams = params - ttimeout = timeout - - def __enter__(self): - response_mock = mocker.Mock() - response_mock.status_code = 200 - response_mock.text = 'ok' - return response_mock - - def __exit__(self, exc_type, exc_val, exc_tb): - pass - - mocker.patch('splitio.api.client.requests.Session.get', new=get_mock) - httpclient = client.HttpClientKerberos(sdk_url='https://sdk.com', authentication_scheme=AuthenticateScheme.KERBEROS_SPNEGO, authentication_params=[None, None]) - httpclient.set_telemetry_data("metric", mocker.Mock()) - response = httpclient.get('sdk', '/test1', 'some_api_key', {'param1': 123}, {'h1': 'abc'}) - assert turl == 'https://sdk.com/test1' - assert theaders == {'Authorization': 'Bearer some_api_key', 'h1': 'abc', 'Content-Type': 'application/json'} - assert tparams == {'param1': 123} - assert ttimeout == None - assert response.status_code == 200 - assert response.body == 'ok' - - turl = None - theaders = None - tparams = None - ttimeout = None - httpclient = client.HttpClientKerberos(sdk_url='https://sdk.com', authentication_scheme=AuthenticateScheme.KERBEROS_SPNEGO, authentication_params=['bilal', 'split']) - httpclient.set_telemetry_data("metric", mocker.Mock()) - response = httpclient.get('sdk', '/test1', 'some_api_key', {'param1': 123}, {'h1': 'abc'}) - assert turl == 'https://sdk.com/test1' - assert theaders == {'Authorization': 'Bearer some_api_key', 'h1': 'abc', 'Content-Type': 'application/json'} - assert tparams == {'param1': 123} - assert ttimeout == None - - assert response.status_code == 200 - assert response.body == 'ok' - - response_mock = mocker.Mock() - response_mock.status_code = 200 - response_mock.headers = {} - response_mock.text = 'ok' - - turl = None - theaders = None - tparams = None - ttimeout = None - tjson = None - class post_mock(object): - def __init__(self, url, params, headers, json, timeout): - global turl - global theaders - global tparams - global ttimeout - global tjson - turl = url - theaders = headers - tparams = params - ttimeout = timeout - tjson = json - - def __enter__(self): - response_mock = mocker.Mock() - response_mock.status_code = 200 - response_mock.text = 'ok' - return response_mock - - def __exit__(self, exc_type, exc_val, exc_tb): - pass - mocker.patch('splitio.api.client.requests.Session.post', new=post_mock) - - httpclient = client.HttpClientKerberos(timeout=1500, sdk_url='https://sdk.com', events_url='https://events.com', authentication_scheme=AuthenticateScheme.KERBEROS_PROXY, authentication_params=[None, None]) - httpclient.set_telemetry_data("metric", mocker.Mock()) - - response = httpclient.post('events', 'test1', 'some_api_key', {'p1': 'a'}, {'param1': 123}, {'h1': 'abc'}) - assert turl == 'https://events.com/test1' - assert tjson == {'p1': 'a'} - assert theaders == {'Authorization': 'Bearer some_api_key', 'h1': 'abc', 'Content-Type': 'application/json'} - assert tparams == {'param1': 123} - assert ttimeout == 1.5 - - assert response.status_code == 200 - assert response.body == 'ok' - - turl = None - theaders = None - tparams = None - ttimeout = None - mocker.patch('splitio.api.client.requests.Session.get', new=get_mock) - httpclient = client.HttpClientKerberos(timeout=1500, sdk_url='https://sdk.com', authentication_scheme=AuthenticateScheme.KERBEROS_PROXY, authentication_params=['bilal', 'split']) - httpclient.set_telemetry_data("metric", mocker.Mock()) - response = httpclient.get('sdk', '/test1', 'some_api_key', {'param1': 123}, {'h1': 'abc'}) - assert turl == 'https://sdk.com/test1' - assert theaders == {'Authorization': 'Bearer some_api_key', 'h1': 'abc', 'Content-Type': 'application/json'} - assert tparams == {'param1': 123} - assert ttimeout == 1.5 - - assert response.status_code == 200 - assert response.body == 'ok' - - # test auth settings - httpclient = client.HttpClientKerberos(sdk_url='https://sdk.com', authentication_scheme=AuthenticateScheme.KERBEROS_SPNEGO, authentication_params=['bilal', 'split']) - httpclient._set_authentication('sdk') - for server in ['sdk', 'events', 'auth', 'telemetry']: - assert(httpclient._sessions[server].auth.principal == 'bilal') - assert(httpclient._sessions[server].auth.password == 'split') - assert(isinstance(httpclient._sessions[server].auth, HTTPKerberosAuth)) - - httpclient._sessions['sdk'].close() - httpclient._sessions['events'].close() - httpclient._sessions['sdk'] = requests.Session() - httpclient._sessions['events'] = requests.Session() - assert(httpclient._sessions['sdk'].auth == None) - assert(httpclient._sessions['events'].auth == None) - - httpclient._set_authentication('sdk') - assert(httpclient._sessions['sdk'].auth.principal == 'bilal') - assert(httpclient._sessions['sdk'].auth.password == 'split') - assert(isinstance(httpclient._sessions['sdk'].auth, HTTPKerberosAuth)) - assert(httpclient._sessions['events'].auth == None) - - httpclient2 = client.HttpClientKerberos(sdk_url='https://sdk.com', authentication_scheme=AuthenticateScheme.KERBEROS_SPNEGO, authentication_params=[None, None]) - for server in ['sdk', 'events', 'auth', 'telemetry']: - assert(httpclient2._sessions[server].auth.principal == None) - assert(httpclient2._sessions[server].auth.password == None) - assert(isinstance(httpclient2._sessions[server].auth, HTTPKerberosAuth)) - - httpclient3 = client.HttpClientKerberos(sdk_url='https://sdk.com', authentication_scheme=AuthenticateScheme.KERBEROS_PROXY, authentication_params=['bilal', 'split']) - for server in ['sdk', 'events', 'auth', 'telemetry']: - assert(httpclient3._sessions[server].adapters['https://']._principal == 'bilal') - assert(httpclient3._sessions[server].adapters['https://']._password == 'split') - assert(isinstance(httpclient3._sessions[server].adapters['https://'], client.HTTPAdapterWithProxyKerberosAuth)) - - httpclient4 = client.HttpClientKerberos(sdk_url='https://sdk.com', authentication_scheme=AuthenticateScheme.KERBEROS_PROXY, authentication_params=[None, None]) - for server in ['sdk', 'events', 'auth', 'telemetry']: - assert(httpclient4._sessions[server].adapters['https://']._principal == None) - assert(httpclient4._sessions[server].adapters['https://']._password == None) - assert(isinstance(httpclient4._sessions[server].adapters['https://'], client.HTTPAdapterWithProxyKerberosAuth)) - - def test_proxy_exception(self, mocker): - global count - count = 0 - class get_mock(object): - def __init__(self, url, params, headers, timeout): - pass - - def __enter__(self): - global count - count += 1 - if count == 1: - raise requests.exceptions.ProxyError() - - response_mock = mocker.Mock() - response_mock.status_code = 200 - response_mock.text = 'ok' - return response_mock - - def __exit__(self, exc_type, exc_val, exc_tb): - pass - - mocker.patch('splitio.api.client.requests.Session.get', new=get_mock) - httpclient = client.HttpClientKerberos(sdk_url='https://sdk.com', authentication_scheme=AuthenticateScheme.KERBEROS_SPNEGO, authentication_params=[None, None]) - httpclient.set_telemetry_data("metric", mocker.Mock()) - response = httpclient.get('sdk', '/test1', 'some_api_key', {'param1': 123}, {'h1': 'abc'}) - assert response.status_code == 200 - assert response.body == 'ok' - - count = 0 - class post_mock(object): - def __init__(self, url, params, headers, json, timeout): - pass - - def __enter__(self): - global count - count += 1 - if count == 1: - raise requests.exceptions.ProxyError() - - response_mock = mocker.Mock() - response_mock.status_code = 200 - response_mock.text = 'ok' - return response_mock - - def __exit__(self, exc_type, exc_val, exc_tb): - pass - mocker.patch('splitio.api.client.requests.Session.post', new=post_mock) - - httpclient = client.HttpClientKerberos(timeout=1500, sdk_url='https://sdk.com', events_url='https://events.com', authentication_scheme=AuthenticateScheme.KERBEROS_PROXY, authentication_params=[None, None]) - httpclient.set_telemetry_data("metric", mocker.Mock()) - response = httpclient.post('events', 'test1', 'some_api_key', {'p1': 'a'}, {'param1': 123}, {'h1': 'abc'}) - assert response.status_code == 200 - assert response.body == 'ok' - - - - def test_telemetry(self, mocker): - telemetry_storage = InMemoryTelemetryStorage() - telemetry_producer = TelemetryStorageProducer(telemetry_storage) - telemetry_runtime_producer = telemetry_producer.get_telemetry_runtime_producer() - - response_mock = mocker.Mock() - response_mock.status_code = 200 - response_mock.headers = {} - response_mock.text = 'ok' - get_mock = mocker.Mock() - get_mock.return_value = response_mock - mocker.patch('splitio.api.client.requests.post', new=get_mock) - httpclient = client.HttpClient(timeout=1500, sdk_url='https://sdk.com', events_url='https://events.com') - httpclient.set_telemetry_data("metric", telemetry_runtime_producer) - - self.metric1 = None - self.cur_time = 0 - def record_successful_sync(metric_name, cur_time): - self.metric1 = metric_name - self.cur_time = cur_time - httpclient._telemetry_runtime_producer.record_successful_sync = record_successful_sync - - self.metric2 = None - self.elapsed = 0 - def record_sync_latency(metric_name, elapsed): - self.metric2 = metric_name - self.elapsed = elapsed - httpclient._telemetry_runtime_producer.record_sync_latency = record_sync_latency - - self.metric3 = None - self.status = 0 - def record_sync_error(metric_name, elapsed): - self.metric3 = metric_name - self.status = elapsed - httpclient._telemetry_runtime_producer.record_sync_error = record_sync_error - - httpclient.post('sdk', 'test1', 'some_api_key', {'p1': 'a'}, {'param1': 123}, {'h1': 'abc'}) - assert (self.metric2 == "metric") - assert (self.metric1 == "metric") - assert (self.cur_time > self.elapsed) - - response_mock.status_code = 400 - response_mock.headers = {} - response_mock.text = 'ok' - httpclient.post('sdk', 'test1', 'some_api_key', {'p1': 'a'}, {'param1': 123}, {'h1': 'abc'}) - assert (self.metric3 == "metric") - assert (self.status == 400) - - # testing get call - mocker.patch('splitio.api.client.requests.get', new=get_mock) - self.metric1 = None - self.cur_time = 0 - self.metric2 = None - self.elapsed = 0 - response_mock.status_code = 200 - httpclient.get('sdk', 'test1', 'some_api_key', {'param1': 123}, {'h1': 'abc'}) - assert (self.metric2 == "metric") - assert (self.metric1 == "metric") - assert (self.cur_time > self.elapsed) - - self.metric3 = None - self.status = 0 - response_mock.status_code = 400 - httpclient.get('sdk', 'test1', 'some_api_key', {'param1': 123}, {'h1': 'abc'}) - assert (self.metric3 == "metric") - assert (self.status == 400) - -class MockResponse: - def __init__(self, text, status, headers): - self._text = text - self.status = status - self.headers = headers - - async def text(self): - return self._text - - async def __aexit__(self, exc_type, exc, tb): - pass - - async def __aenter__(self): - return self - -class HttpClientAsyncTests(object): - """Http Client test cases.""" - - @pytest.mark.asyncio - async def test_get(self, mocker): - """Test HTTP GET verb requests.""" - telemetry_storage = await InMemoryTelemetryStorageAsync.create() - telemetry_producer = TelemetryStorageProducerAsync(telemetry_storage) - telemetry_runtime_producer = telemetry_producer.get_telemetry_runtime_producer() - response_mock = MockResponse('ok', 200, {}) - get_mock = mocker.Mock() - get_mock.return_value = response_mock - mocker.patch('splitio.optional.loaders.aiohttp.ClientSession.get', new=get_mock) - httpclient = client.HttpClientAsync() - httpclient.set_telemetry_data("metric", telemetry_runtime_producer) - response = await httpclient.get('sdk', 'test1', 'some_api_key', {'param1': 123}, {'h1': 'abc'}) - assert response.status_code == 200 - assert response.body == 'ok' - call = mocker.call( - client.SDK_URL + '/test1', - headers={'Authorization': 'Bearer some_api_key', 'h1': 'abc', 'Content-Type': 'application/json'}, - params={'param1': 123}, - timeout=None - ) - assert get_mock.mock_calls == [call] - get_mock.reset_mock() - - response = await httpclient.get('events', 'test1', 'some_api_key', {'param1': 123}, {'h1': 'abc'}) - call = mocker.call( - client.EVENTS_URL + '/test1', - headers={'Authorization': 'Bearer some_api_key', 'h1': 'abc', 'Content-Type': 'application/json'}, - params={'param1': 123}, - timeout=None - ) - assert get_mock.mock_calls == [call] - assert response.status_code == 200 - assert response.body == 'ok' - - @pytest.mark.asyncio - async def test_get_custom_urls(self, mocker): - """Test HTTP GET verb requests.""" - telemetry_storage = await InMemoryTelemetryStorageAsync.create() - telemetry_producer = TelemetryStorageProducerAsync(telemetry_storage) - telemetry_runtime_producer = telemetry_producer.get_telemetry_runtime_producer() - response_mock = MockResponse('ok', 200, {}) - get_mock = mocker.Mock() - get_mock.return_value = response_mock - mocker.patch('splitio.optional.loaders.aiohttp.ClientSession.get', new=get_mock) - httpclient = client.HttpClientAsync(sdk_url='https://sdk.com', events_url='https://events.com') - httpclient.set_telemetry_data("metric", telemetry_runtime_producer) - response = await httpclient.get('sdk', 'test1', 'some_api_key', {'param1': 123}, {'h1': 'abc'}) - call = mocker.call( - 'https://sdk.com/test1', - headers={'Authorization': 'Bearer some_api_key', 'h1': 'abc', 'Content-Type': 'application/json'}, - params={'param1': 123}, - timeout=None - ) - assert get_mock.mock_calls == [call] - assert response.status_code == 200 - assert response.body == 'ok' - get_mock.reset_mock() - - response = await httpclient.get('events', 'test1', 'some_api_key', {'param1': 123}, {'h1': 'abc'}) - call = mocker.call( - 'https://events.com/test1', - headers={'Authorization': 'Bearer some_api_key', 'h1': 'abc', 'Content-Type': 'application/json'}, - params={'param1': 123}, - timeout=None - ) - assert response.status_code == 200 - assert response.body == 'ok' - assert get_mock.mock_calls == [call] - - @pytest.mark.asyncio - async def test_post(self, mocker): - """Test HTTP POST verb requests.""" - telemetry_storage = await InMemoryTelemetryStorageAsync.create() - telemetry_producer = TelemetryStorageProducerAsync(telemetry_storage) - telemetry_runtime_producer = telemetry_producer.get_telemetry_runtime_producer() - response_mock = MockResponse('ok', 200, {}) - get_mock = mocker.Mock() - get_mock.return_value = response_mock - mocker.patch('splitio.optional.loaders.aiohttp.ClientSession.post', new=get_mock) - httpclient = client.HttpClientAsync() - httpclient.set_telemetry_data("metric", telemetry_runtime_producer) - response = await httpclient.post('sdk', 'test1', 'some_api_key', {'p1': 'a'}, {'param1': 123}, {'h1': 'abc'}) - call = mocker.call( - client.SDK_URL + '/test1', - json={"p1": "a"}, - headers={'Content-Type': 'application/json', 'Authorization': 'Bearer some_api_key', 'h1': 'abc', 'Accept-Encoding': 'gzip'}, - params={'param1': 123}, - timeout=None - ) - assert response.status_code == 200 - assert response.body == 'ok' - assert get_mock.mock_calls == [call] - get_mock.reset_mock() - - response = await httpclient.post('events', 'test1', 'some_api_key', {'p1': 'a'}, {'param1': 123}, {'h1': 'abc'}) - call = mocker.call( - client.EVENTS_URL + '/test1', - json={'p1': 'a'}, - headers={'Authorization': 'Bearer some_api_key', 'h1': 'abc', 'Content-Type': 'application/json', 'Accept-Encoding': 'gzip'}, - params={'param1': 123}, - timeout=None - ) - assert response.status_code == 200 - assert response.body == 'ok' - assert get_mock.mock_calls == [call] - - @pytest.mark.asyncio - async def test_post_custom_urls(self, mocker): - """Test HTTP GET verb requests.""" - telemetry_storage = await InMemoryTelemetryStorageAsync.create() - telemetry_producer = TelemetryStorageProducerAsync(telemetry_storage) - telemetry_runtime_producer = telemetry_producer.get_telemetry_runtime_producer() - response_mock = MockResponse('ok', 200, {}) - get_mock = mocker.Mock() - get_mock.return_value = response_mock - mocker.patch('splitio.optional.loaders.aiohttp.ClientSession.post', new=get_mock) - httpclient = client.HttpClientAsync(sdk_url='https://sdk.com', events_url='https://events.com') - httpclient.set_telemetry_data("metric", telemetry_runtime_producer) - response = await httpclient.post('sdk', 'test1', 'some_api_key', {'p1': 'a'}, {'param1': 123}, {'h1': 'abc'}) - call = mocker.call( - 'https://sdk.com' + '/test1', - json={"p1": "a"}, - headers={'Authorization': 'Bearer some_api_key', 'h1': 'abc', 'Content-Type': 'application/json', 'Accept-Encoding': 'gzip'}, - params={'param1': 123}, - timeout=None - ) - assert response.status_code == 200 - assert response.body == 'ok' - assert get_mock.mock_calls == [call] - get_mock.reset_mock() - - response = await httpclient.post('events', 'test1', 'some_api_key', {'p1': 'a'}, {'param1': 123}, {'h1': 'abc'}) - call = mocker.call( - 'https://events.com' + '/test1', - json={"p1": "a"}, - headers={'Authorization': 'Bearer some_api_key', 'h1': 'abc', 'Content-Type': 'application/json', 'Accept-Encoding': 'gzip'}, - params={'param1': 123}, - timeout=None - ) - assert response.status_code == 200 - assert response.body == 'ok' - assert get_mock.mock_calls == [call] - - @pytest.mark.asyncio - async def test_telemetry(self, mocker): - telemetry_storage = await InMemoryTelemetryStorageAsync.create() - telemetry_producer = TelemetryStorageProducerAsync(telemetry_storage) - telemetry_runtime_producer = telemetry_producer.get_telemetry_runtime_producer() - response_mock = MockResponse('ok', 200, {}) - get_mock = mocker.Mock() - get_mock.return_value = response_mock - mocker.patch('splitio.optional.loaders.aiohttp.ClientSession.post', new=get_mock) - httpclient = client.HttpClientAsync(sdk_url='https://sdk.com', events_url='https://events.com') - httpclient.set_telemetry_data("metric", telemetry_runtime_producer) - - self.metric1 = None - self.cur_time = 0 - async def record_successful_sync(metric_name, cur_time): - self.metric1 = metric_name - self.cur_time = cur_time - httpclient._telemetry_runtime_producer.record_successful_sync = record_successful_sync - - self.metric2 = None - self.elapsed = 0 - async def record_sync_latency(metric_name, elapsed): - self.metric2 = metric_name - self.elapsed = elapsed - httpclient._telemetry_runtime_producer.record_sync_latency = record_sync_latency - - self.metric3 = None - self.status = 0 - async def record_sync_error(metric_name, elapsed): - self.metric3 = metric_name - self.status = elapsed - httpclient._telemetry_runtime_producer.record_sync_error = record_sync_error - - await httpclient.post('events', 'test1', 'some_api_key', {'p1': 'a'}, {'param1': 123}, {'h1': 'abc'}) - assert (self.metric2 == "metric") - assert (self.metric1 == "metric") - assert (self.cur_time > self.elapsed) - - response_mock = MockResponse('ok', 400, {}) - get_mock = mocker.Mock() - get_mock.return_value = response_mock - mocker.patch('splitio.optional.loaders.aiohttp.ClientSession.post', new=get_mock) - await httpclient.post('sdk', 'test1', 'some_api_key', {'p1': 'a'}, {'param1': 123}, {'h1': 'abc'}) - assert (self.metric3 == "metric") - assert (self.status == 400) - - # testing get call - response_mock = MockResponse('ok', 200, {}) - get_mock = mocker.Mock() - get_mock.return_value = response_mock - mocker.patch('splitio.optional.loaders.aiohttp.ClientSession.get', new=get_mock) - self.metric1 = None - self.cur_time = 0 - self.metric2 = None - self.elapsed = 0 - await httpclient.get('sdk', 'test1', 'some_api_key', {'param1': 123}, {'h1': 'abc'}) - assert (self.metric2 == "metric") - assert (self.metric1 == "metric") - assert (self.cur_time > self.elapsed) - - self.metric3 = None - self.status = 0 - response_mock = MockResponse('ok', 400, {}) - get_mock.return_value = response_mock - await httpclient.get('sdk', 'test1', 'some_api_key', {'param1': 123}, {'h1': 'abc'}) - assert (self.metric3 == "metric") - assert (self.status == 400) diff --git a/tests/api/test_impressions_api.py b/tests/api/test_impressions_api.py deleted file mode 100644 index 28e99668..00000000 --- a/tests/api/test_impressions_api.py +++ /dev/null @@ -1,294 +0,0 @@ -"""Impressions API tests module.""" - -import pytest -import unittest.mock as mock - -from splitio.api import impressions, client, APIException -from harness_commons.models.impressions import Impression -from splitio.engine.impressions.impressions import ImpressionsMode -from splitio.engine.impressions.manager import Counter -from splitio.client.util import get_metadata -from splitio.client.config import DEFAULT_CONFIG -from splitio.version import __version__ -from splitio.engine.telemetry import TelemetryStorageProducer, TelemetryStorageProducerAsync -from splitio.storage.inmemmory import InMemoryTelemetryStorage, InMemoryTelemetryStorageAsync - -impressions_mock = [ - Impression('k1', 'f1', 'on', 'l1', 123456, 'b1', 321654, None, {'prop': 'val'}), - Impression('k2', 'f2', 'off', 'l1', 123456, 'b1', 321654, None, None), - Impression('k3', 'f1', 'on', 'l1', 123456, 'b1', 321654, None, None) -] -expectedImpressions = [{ - 'f': 'f1', - 'i': [ - {'k': 'k1', 'b': 'b1', 't': 'on', 'r': 'l1', 'm': 321654, 'c': 123456, 'pt': None, 'properties': {"prop": "val"}}, - {'k': 'k3', 'b': 'b1', 't': 'on', 'r': 'l1', 'm': 321654, 'c': 123456, 'pt': None}, - ], -}, { - 'f': 'f2', - 'i': [ - {'k': 'k2', 'b': 'b1', 't': 'off', 'r': 'l1', 'm': 321654, 'c': 123456, 'pt': None}, - ] -}] - -counters = [ - Counter.CountPerFeature('f1', 123, 2), - Counter.CountPerFeature('f2', 123, 123), - Counter.CountPerFeature('f1', 456, 111), - Counter.CountPerFeature('f2', 456, 222) -] - -expected_counters = { - 'pf': [ - {'f': 'f1', 'm': 123, 'rc': 2}, - {'f': 'f2', 'm': 123, 'rc': 123}, - {'f': 'f1', 'm': 456, 'rc': 111}, - {'f': 'f2', 'm': 456, 'rc': 222}, - ] -} - -class ImpressionsAPITests(object): - """Impressions API test cases.""" - - def test_post_impressions(self, mocker): - """Test impressions posting API call.""" - httpclient = mocker.Mock(spec=client.HttpClient) - httpclient.post.return_value = client.HttpResponse(200, '', {}) - cfg = DEFAULT_CONFIG.copy() - cfg.update({'IPAddressesEnabled': True, 'machineName': 'some_machine_name', 'machineIp': '123.123.123.123'}) - sdk_metadata = get_metadata(cfg) - telemetry_storage = InMemoryTelemetryStorage() - telemetry_producer = TelemetryStorageProducer(telemetry_storage) - telemetry_runtime_producer = telemetry_producer.get_telemetry_runtime_producer() - impressions_api = impressions.ImpressionsAPI(httpclient, 'some_api_key', sdk_metadata, telemetry_runtime_producer) - response = impressions_api.flush_impressions(impressions_mock) - - call_made = httpclient.post.mock_calls[0] - - # validate positional arguments - assert call_made[1] == ('events', 'testImpressions/bulk', 'some_api_key') - - # validate key-value args (headers) - assert call_made[2]['extra_headers'] == { - 'SplitSDKVersion': 'python-%s' % __version__, - 'SplitSDKMachineIP': '123.123.123.123', - 'SplitSDKMachineName': 'some_machine_name', - 'SplitSDKImpressionsMode': 'OPTIMIZED' - } - - # validate key-value args (body) - assert call_made[2]['body'] == expectedImpressions - - httpclient.reset_mock() - def raise_exception(*args, **kwargs): - raise client.HttpClientException('some_message') - httpclient.post.side_effect = raise_exception - with pytest.raises(APIException) as exc_info: - response = impressions_api.flush_impressions(impressions_mock) - assert exc_info.type == APIException - assert exc_info.value.message == 'some_message' - - def test_post_impressions_ip_address_disabled(self, mocker): - """Test impressions posting API call.""" - httpclient = mocker.Mock(spec=client.HttpClient) - httpclient.post.return_value = client.HttpResponse(200, '', {}) - cfg = DEFAULT_CONFIG.copy() - cfg.update({'IPAddressesEnabled': False}) - sdk_metadata = get_metadata(cfg) - impressions_api = impressions.ImpressionsAPI(httpclient, 'some_api_key', sdk_metadata, mocker.Mock(), ImpressionsMode.DEBUG) - response = impressions_api.flush_impressions(impressions_mock) - - call_made = httpclient.post.mock_calls[0] - - # validate positional arguments - assert call_made[1] == ('events', 'testImpressions/bulk', 'some_api_key') - - # validate key-value args (headers) - assert call_made[2]['extra_headers'] == { - 'SplitSDKVersion': 'python-%s' % __version__, - 'SplitSDKImpressionsMode': 'DEBUG' - } - - # validate key-value args (body) - assert call_made[2]['body'] == expectedImpressions - - def test_post_counters(self, mocker): - """Test impressions posting API call.""" - httpclient = mocker.Mock(spec=client.HttpClient) - httpclient.post.return_value = client.HttpResponse(200, '', {}) - cfg = DEFAULT_CONFIG.copy() - cfg.update({'IPAddressesEnabled': True, 'machineName': 'some_machine_name', 'machineIp': '123.123.123.123'}) - sdk_metadata = get_metadata(cfg) - impressions_api = impressions.ImpressionsAPI(httpclient, 'some_api_key', sdk_metadata, mocker.Mock()) - response = impressions_api.flush_counters(counters) - - call_made = httpclient.post.mock_calls[0] - - # validate positional arguments - assert call_made[1] == ('events', 'testImpressions/count', 'some_api_key') - - # validate key-value args (headers) - assert call_made[2]['extra_headers'] == { - 'SplitSDKVersion': 'python-%s' % __version__, - 'SplitSDKMachineIP': '123.123.123.123', - 'SplitSDKMachineName': 'some_machine_name', - 'SplitSDKImpressionsMode': 'OPTIMIZED' - } - - # validate key-value args (body) - assert call_made[2]['body'] == expected_counters - - httpclient.reset_mock() - def raise_exception(*args, **kwargs): - raise client.HttpClientException('some_message') - httpclient.post.side_effect = raise_exception - with pytest.raises(APIException) as exc_info: - response = impressions_api.flush_counters(counters) - assert exc_info.type == APIException - assert exc_info.value.message == 'some_message' - - -class ImpressionsAPIAsyncTests(object): - """Impressions API test cases.""" - - @pytest.mark.asyncio - async def test_post_impressions(self, mocker): - """Test impressions posting API call.""" - httpclient = mocker.Mock(spec=client.HttpClientAsync) - cfg = DEFAULT_CONFIG.copy() - cfg.update({'IPAddressesEnabled': True, 'machineName': 'some_machine_name', 'machineIp': '123.123.123.123'}) - sdk_metadata = get_metadata(cfg) - telemetry_storage = InMemoryTelemetryStorageAsync() - telemetry_producer = TelemetryStorageProducerAsync(telemetry_storage) - telemetry_runtime_producer = telemetry_producer.get_telemetry_runtime_producer() - impressions_api = impressions.ImpressionsAPIAsync(httpclient, 'some_api_key', sdk_metadata, telemetry_runtime_producer) - - self.verb = None - self.url = None - self.key = None - self.headers = None - self.body = None - async def post(verb, url, key, body, extra_headers): - self.url = url - self.verb = verb - self.key = key - self.headers = extra_headers - self.body = body - return client.HttpResponse(200, '', {}) - httpclient.post = post - - response = await impressions_api.flush_impressions(impressions_mock) - - # validate positional arguments - assert self.verb == 'events' - assert self.url == 'testImpressions/bulk' - assert self.key == 'some_api_key' - - # validate key-value args (headers) - assert self.headers == { - 'SplitSDKVersion': 'python-%s' % __version__, - 'SplitSDKMachineIP': '123.123.123.123', - 'SplitSDKMachineName': 'some_machine_name', - 'SplitSDKImpressionsMode': 'OPTIMIZED' - } - - # validate key-value args (body) - assert self.body == expectedImpressions - - httpclient.reset_mock() - def raise_exception(*args, **kwargs): - raise client.HttpClientException('some_message') - httpclient.post = raise_exception - with pytest.raises(APIException) as exc_info: - response = await impressions_api.flush_impressions(impressions_mock) - assert exc_info.type == APIException - assert exc_info.value.message == 'some_message' - - @pytest.mark.asyncio - async def test_post_impressions_ip_address_disabled(self, mocker): - """Test impressions posting API call.""" - httpclient = mocker.Mock(spec=client.HttpClientAsync) - cfg = DEFAULT_CONFIG.copy() - cfg.update({'IPAddressesEnabled': False}) - sdk_metadata = get_metadata(cfg) - impressions_api = impressions.ImpressionsAPIAsync(httpclient, 'some_api_key', sdk_metadata, mocker.Mock(), ImpressionsMode.DEBUG) - - self.verb = None - self.url = None - self.key = None - self.headers = None - self.body = None - async def post(verb, url, key, body, extra_headers): - self.url = url - self.verb = verb - self.key = key - self.headers = extra_headers - self.body = body - return client.HttpResponse(200, '', {}) - httpclient.post = post - - response = await impressions_api.flush_impressions(impressions_mock) - - # validate positional arguments - assert self.verb == 'events' - assert self.url == 'testImpressions/bulk' - assert self.key == 'some_api_key' - - # validate key-value args (headers) - assert self.headers == { - 'SplitSDKVersion': 'python-%s' % __version__, - 'SplitSDKImpressionsMode': 'DEBUG' - } - - # validate key-value args (body) - assert self.body == expectedImpressions - - @pytest.mark.asyncio - async def test_post_counters(self, mocker): - """Test impressions posting API call.""" - httpclient = mocker.Mock(spec=client.HttpClientAsync) - cfg = DEFAULT_CONFIG.copy() - cfg.update({'IPAddressesEnabled': True, 'machineName': 'some_machine_name', 'machineIp': '123.123.123.123'}) - sdk_metadata = get_metadata(cfg) - impressions_api = impressions.ImpressionsAPIAsync(httpclient, 'some_api_key', sdk_metadata, mocker.Mock()) - - self.verb = None - self.url = None - self.key = None - self.headers = None - self.body = None - async def post(verb, url, key, body, extra_headers): - self.url = url - self.verb = verb - self.key = key - self.headers = extra_headers - self.body = body - return client.HttpResponse(200, '', {}) - httpclient.post = post - - response = await impressions_api.flush_counters(counters) - - # validate positional arguments - assert self.verb == 'events' - assert self.url == 'testImpressions/count' - assert self.key == 'some_api_key' - - # validate key-value args (headers) - assert self.headers == { - 'SplitSDKVersion': 'python-%s' % __version__, - 'SplitSDKMachineIP': '123.123.123.123', - 'SplitSDKMachineName': 'some_machine_name', - 'SplitSDKImpressionsMode': 'OPTIMIZED' - } - - # validate key-value args (body) - assert self.body == expected_counters - - httpclient.reset_mock() - def raise_exception(*args, **kwargs): - raise client.HttpClientException('some_message') - httpclient.post = raise_exception - with pytest.raises(APIException) as exc_info: - response = await impressions_api.flush_counters(counters) - assert exc_info.type == APIException - assert exc_info.value.message == 'some_message' diff --git a/tests/api/test_kerberos_client.py b/tests/api/test_kerberos_client.py new file mode 100644 index 00000000..86289fd8 --- /dev/null +++ b/tests/api/test_kerberos_client.py @@ -0,0 +1,298 @@ +"""HTTPClient test module.""" +from requests_kerberos import HTTPKerberosAuth +import pytest +import unittest.mock as mock +import requests + +from splitio.client.config import AuthenticateScheme +from splitio.api import kerberos_client as client + +from harness_commons.engine.telemetry import TelemetryStorageProducer, TelemetryStorageProducerAsync +from harness_commons.storage.inmemmory import InMemoryTelemetryStorage, InMemoryTelemetryStorageAsync + +class HttpClientKerberosTests(object): + """Http Client test cases.""" + + def test_authentication_scheme(self, mocker): + global turl + global theaders + global tparams + global ttimeout + global tjson + + turl = None + theaders = None + tparams = None + ttimeout = None + class get_mock(object): + def __init__(self, url, headers, params, timeout): + global turl + global theaders + global tparams + global ttimeout + turl = url + theaders = headers + tparams = params + ttimeout = timeout + + def __enter__(self): + response_mock = mocker.Mock() + response_mock.status_code = 200 + response_mock.text = 'ok' + return response_mock + + def __exit__(self, exc_type, exc_val, exc_tb): + pass + + mocker.patch('splitio.api.kerberos_client.requests.Session.get', new=get_mock) + httpclient = client.HttpClientKerberos(sdk_url='https://sdk.com', authentication_scheme=AuthenticateScheme.KERBEROS_SPNEGO, authentication_params=[None, None]) + httpclient.set_telemetry_data("metric", mocker.Mock()) + response = httpclient.get('sdk', '/test1', 'some_api_key', {'param1': 123}, {'h1': 'abc'}) + assert turl == 'https://sdk.com/test1' + assert theaders == {'Authorization': 'Bearer some_api_key', 'h1': 'abc', 'Content-Type': 'application/json'} + assert tparams == {'param1': 123} + assert ttimeout == None + assert response.status_code == 200 + assert response.body == 'ok' + + turl = None + theaders = None + tparams = None + ttimeout = None + httpclient = client.HttpClientKerberos(sdk_url='https://sdk.com', authentication_scheme=AuthenticateScheme.KERBEROS_SPNEGO, authentication_params=['bilal', 'split']) + httpclient.set_telemetry_data("metric", mocker.Mock()) + response = httpclient.get('sdk', '/test1', 'some_api_key', {'param1': 123}, {'h1': 'abc'}) + assert turl == 'https://sdk.com/test1' + assert theaders == {'Authorization': 'Bearer some_api_key', 'h1': 'abc', 'Content-Type': 'application/json'} + assert tparams == {'param1': 123} + assert ttimeout == None + + assert response.status_code == 200 + assert response.body == 'ok' + + response_mock = mocker.Mock() + response_mock.status_code = 200 + response_mock.headers = {} + response_mock.text = 'ok' + + turl = None + theaders = None + tparams = None + ttimeout = None + tjson = None + class post_mock(object): + def __init__(self, url, params, headers, json, timeout): + global turl + global theaders + global tparams + global ttimeout + global tjson + turl = url + theaders = headers + tparams = params + ttimeout = timeout + tjson = json + + def __enter__(self): + response_mock = mocker.Mock() + response_mock.status_code = 200 + response_mock.text = 'ok' + return response_mock + + def __exit__(self, exc_type, exc_val, exc_tb): + pass + mocker.patch('splitio.api.kerberos_client.requests.Session.post', new=post_mock) + + httpclient = client.HttpClientKerberos(timeout=1500, sdk_url='https://sdk.com', events_url='https://events.com', authentication_scheme=AuthenticateScheme.KERBEROS_PROXY, authentication_params=[None, None]) + httpclient.set_telemetry_data("metric", mocker.Mock()) + + response = httpclient.post('events', 'test1', 'some_api_key', {'p1': 'a'}, {'param1': 123}, {'h1': 'abc'}) + assert turl == 'https://events.com/test1' + assert tjson == {'p1': 'a'} + assert theaders == {'Authorization': 'Bearer some_api_key', 'h1': 'abc', 'Content-Type': 'application/json'} + assert tparams == {'param1': 123} + assert ttimeout == 1.5 + + assert response.status_code == 200 + assert response.body == 'ok' + + turl = None + theaders = None + tparams = None + ttimeout = None + mocker.patch('splitio.api.kerberos_client.requests.Session.get', new=get_mock) + httpclient = client.HttpClientKerberos(timeout=1500, sdk_url='https://sdk.com', authentication_scheme=AuthenticateScheme.KERBEROS_PROXY, authentication_params=['bilal', 'split']) + httpclient.set_telemetry_data("metric", mocker.Mock()) + response = httpclient.get('sdk', '/test1', 'some_api_key', {'param1': 123}, {'h1': 'abc'}) + assert turl == 'https://sdk.com/test1' + assert theaders == {'Authorization': 'Bearer some_api_key', 'h1': 'abc', 'Content-Type': 'application/json'} + assert tparams == {'param1': 123} + assert ttimeout == 1.5 + + assert response.status_code == 200 + assert response.body == 'ok' + + # test auth settings + httpclient = client.HttpClientKerberos(sdk_url='https://sdk.com', authentication_scheme=AuthenticateScheme.KERBEROS_SPNEGO, authentication_params=['bilal', 'split']) + httpclient._set_authentication('sdk') + for server in ['sdk', 'events', 'auth', 'telemetry']: + assert(httpclient._sessions[server].auth.principal == 'bilal') + assert(httpclient._sessions[server].auth.password == 'split') + assert(isinstance(httpclient._sessions[server].auth, HTTPKerberosAuth)) + + httpclient._sessions['sdk'].close() + httpclient._sessions['events'].close() + httpclient._sessions['sdk'] = requests.Session() + httpclient._sessions['events'] = requests.Session() + assert(httpclient._sessions['sdk'].auth == None) + assert(httpclient._sessions['events'].auth == None) + + httpclient._set_authentication('sdk') + assert(httpclient._sessions['sdk'].auth.principal == 'bilal') + assert(httpclient._sessions['sdk'].auth.password == 'split') + assert(isinstance(httpclient._sessions['sdk'].auth, HTTPKerberosAuth)) + assert(httpclient._sessions['events'].auth == None) + + httpclient2 = client.HttpClientKerberos(sdk_url='https://sdk.com', authentication_scheme=AuthenticateScheme.KERBEROS_SPNEGO, authentication_params=[None, None]) + for server in ['sdk', 'events', 'auth', 'telemetry']: + assert(httpclient2._sessions[server].auth.principal == None) + assert(httpclient2._sessions[server].auth.password == None) + assert(isinstance(httpclient2._sessions[server].auth, HTTPKerberosAuth)) + + httpclient3 = client.HttpClientKerberos(sdk_url='https://sdk.com', authentication_scheme=AuthenticateScheme.KERBEROS_PROXY, authentication_params=['bilal', 'split']) + for server in ['sdk', 'events', 'auth', 'telemetry']: + assert(httpclient3._sessions[server].adapters['https://']._principal == 'bilal') + assert(httpclient3._sessions[server].adapters['https://']._password == 'split') + assert(isinstance(httpclient3._sessions[server].adapters['https://'], client.HTTPAdapterWithProxyKerberosAuth)) + + httpclient4 = client.HttpClientKerberos(sdk_url='https://sdk.com', authentication_scheme=AuthenticateScheme.KERBEROS_PROXY, authentication_params=[None, None]) + for server in ['sdk', 'events', 'auth', 'telemetry']: + assert(httpclient4._sessions[server].adapters['https://']._principal == None) + assert(httpclient4._sessions[server].adapters['https://']._password == None) + assert(isinstance(httpclient4._sessions[server].adapters['https://'], client.HTTPAdapterWithProxyKerberosAuth)) + + def test_proxy_exception(self, mocker): + global count + count = 0 + class get_mock(object): + def __init__(self, url, params, headers, timeout): + pass + + def __enter__(self): + global count + count += 1 + if count == 1: + raise requests.exceptions.ProxyError() + + response_mock = mocker.Mock() + response_mock.status_code = 200 + response_mock.text = 'ok' + return response_mock + + def __exit__(self, exc_type, exc_val, exc_tb): + pass + + mocker.patch('splitio.api.kerberos_client.requests.Session.get', new=get_mock) + httpclient = client.HttpClientKerberos(sdk_url='https://sdk.com', authentication_scheme=AuthenticateScheme.KERBEROS_SPNEGO, authentication_params=[None, None]) + httpclient.set_telemetry_data("metric", mocker.Mock()) + response = httpclient.get('sdk', '/test1', 'some_api_key', {'param1': 123}, {'h1': 'abc'}) + assert response.status_code == 200 + assert response.body == 'ok' + + count = 0 + class post_mock(object): + def __init__(self, url, params, headers, json, timeout): + pass + + def __enter__(self): + global count + count += 1 + if count == 1: + raise requests.exceptions.ProxyError() + + response_mock = mocker.Mock() + response_mock.status_code = 200 + response_mock.text = 'ok' + return response_mock + + def __exit__(self, exc_type, exc_val, exc_tb): + pass + mocker.patch('splitio.api.kerberos_client.requests.Session.post', new=post_mock) + + httpclient = client.HttpClientKerberos(timeout=1500, sdk_url='https://sdk.com', events_url='https://events.com', authentication_scheme=AuthenticateScheme.KERBEROS_PROXY, authentication_params=[None, None]) + httpclient.set_telemetry_data("metric", mocker.Mock()) + response = httpclient.post('events', 'test1', 'some_api_key', {'p1': 'a'}, {'param1': 123}, {'h1': 'abc'}) + assert response.status_code == 200 + assert response.body == 'ok' + + def test_telemetry(self, mocker): + telemetry_storage = InMemoryTelemetryStorage() + telemetry_producer = TelemetryStorageProducer(telemetry_storage) + telemetry_runtime_producer = telemetry_producer.get_telemetry_runtime_producer() + + response_mock = mocker.Mock() + response_mock.status_code = 200 + response_mock.headers = {} + response_mock.text = 'ok' + count = 0 + class post_mock(object): + def __init__(self, url, params, headers, json, timeout): + pass + + def __enter__(self): + global count + count += 1 + if count == 1: + raise requests.exceptions.ProxyError() + + response_mock = mocker.Mock() + response_mock.status_code = 200 + response_mock.text = 'ok' + return response_mock + + def __exit__(self, exc_type, exc_val, exc_tb): + pass + mocker.patch('splitio.api.kerberos_client.requests.Session.post', new=post_mock) + httpclient = client.HttpClientKerberos(timeout=1500, sdk_url='https://sdk.com', events_url='https://events.com') + httpclient.set_telemetry_data("metric", telemetry_runtime_producer) + + self.metric1 = None + self.cur_time = 0 + def record_successful_sync(metric_name, cur_time): + self.metric1 = metric_name + self.cur_time = cur_time + httpclient._telemetry_runtime_producer.record_successful_sync = record_successful_sync + + self.metric2 = None + self.elapsed = 0 + def record_sync_latency(metric_name, elapsed): + self.metric2 = metric_name + self.elapsed = elapsed + httpclient._telemetry_runtime_producer.record_sync_latency = record_sync_latency + + self.metric3 = None + self.status = 0 + def record_sync_error(metric_name, elapsed): + self.metric3 = metric_name + self.status = elapsed + httpclient._telemetry_runtime_producer.record_sync_error = record_sync_error + + httpclient.post('sdk', 'test1', 'some_api_key', {'p1': 'a'}, {'param1': 123}, {'h1': 'abc'}) + assert (self.metric2 == "metric") + assert (self.metric1 == "metric") + assert (self.cur_time > self.elapsed) + +class MockResponse: + def __init__(self, text, status, headers): + self._text = text + self.status = status + self.headers = headers + + async def text(self): + return self._text + + async def __aexit__(self, exc_type, exc, tb): + pass + + async def __aenter__(self): + return self \ No newline at end of file diff --git a/tests/api/test_segments_api.py b/tests/api/test_segments_api.py deleted file mode 100644 index 8681be59..00000000 --- a/tests/api/test_segments_api.py +++ /dev/null @@ -1,133 +0,0 @@ -"""Segment API tests module.""" - -import pytest -import unittest.mock as mock - -from splitio.api import segments, client, APIException -from splitio.api.commons import FetchOptions -from splitio.client.util import SdkMetadata - -class SegmentAPITests(object): - """Segment API test cases.""" - - def test_fetch_segment_changes(self, mocker): - """Test segment changes fetching API call.""" - httpclient = mocker.Mock(spec=client.HttpClient) - httpclient.get.return_value = client.HttpResponse(200, '{"prop1": "value1"}', {}) - segment_api = segments.SegmentsAPI(httpclient, 'some_api_key', SdkMetadata('1.0', 'some', '1.2.3.4'), mocker.Mock()) - - response = segment_api.fetch_segment('some_segment', 123, FetchOptions(None, None, None, None, None)) - assert response['prop1'] == 'value1' - assert httpclient.get.mock_calls == [mocker.call('sdk', 'segmentChanges/some_segment', 'some_api_key', - extra_headers={ - 'SplitSDKVersion': '1.0', - 'SplitSDKMachineIP': '1.2.3.4', - 'SplitSDKMachineName': 'some' - }, - query={'since': 123})] - - httpclient.reset_mock() - response = segment_api.fetch_segment('some_segment', 123, FetchOptions(True, None, None, None, None)) - assert response['prop1'] == 'value1' - assert httpclient.get.mock_calls == [mocker.call('sdk', 'segmentChanges/some_segment', 'some_api_key', - extra_headers={ - 'SplitSDKVersion': '1.0', - 'SplitSDKMachineIP': '1.2.3.4', - 'SplitSDKMachineName': 'some', - 'Cache-Control': 'no-cache' - }, - query={'since': 123})] - - httpclient.reset_mock() - response = segment_api.fetch_segment('some_segment', 123, FetchOptions(True, 123, None, None, None)) - assert response['prop1'] == 'value1' - assert httpclient.get.mock_calls == [mocker.call('sdk', 'segmentChanges/some_segment', 'some_api_key', - extra_headers={ - 'SplitSDKVersion': '1.0', - 'SplitSDKMachineIP': '1.2.3.4', - 'SplitSDKMachineName': 'some', - 'Cache-Control': 'no-cache' - }, - query={'since': 123, 'till': 123})] - - httpclient.reset_mock() - def raise_exception(*args, **kwargs): - raise client.HttpClientException('some_message') - httpclient.get.side_effect = raise_exception - with pytest.raises(APIException) as exc_info: - response = segment_api.fetch_segment('some_segment', 123, FetchOptions()) - assert exc_info.type == APIException - assert exc_info.value.message == 'some_message' - - -class SegmentAPIAsyncTests(object): - """Segment async API test cases.""" - - @pytest.mark.asyncio - async def test_fetch_segment_changes(self, mocker): - """Test segment changes fetching API call.""" - httpclient = mocker.Mock(spec=client.HttpClientAsync) - segment_api = segments.SegmentsAPIAsync(httpclient, 'some_api_key', SdkMetadata('1.0', 'some', '1.2.3.4'), mocker.Mock()) - - self.verb = None - self.url = None - self.key = None - self.headers = None - self.query = None - async def get(verb, url, key, query, extra_headers): - self.url = url - self.verb = verb - self.key = key - self.headers = extra_headers - self.query = query - return client.HttpResponse(200, '{"prop1": "value1"}', {}) - httpclient.get = get - - response = await segment_api.fetch_segment('some_segment', 123, FetchOptions(None, None, None, None, None)) - assert response['prop1'] == 'value1' - assert self.verb == 'sdk' - assert self.url == 'segmentChanges/some_segment' - assert self.key == 'some_api_key' - assert self.headers == { - 'SplitSDKVersion': '1.0', - 'SplitSDKMachineIP': '1.2.3.4', - 'SplitSDKMachineName': 'some' - } - assert self.query == {'since': 123} - - httpclient.reset_mock() - response = await segment_api.fetch_segment('some_segment', 123, FetchOptions(True, None, None, None, None)) - assert response['prop1'] == 'value1' - assert self.verb == 'sdk' - assert self.url == 'segmentChanges/some_segment' - assert self.key == 'some_api_key' - assert self.headers == { - 'SplitSDKVersion': '1.0', - 'SplitSDKMachineIP': '1.2.3.4', - 'SplitSDKMachineName': 'some', - 'Cache-Control': 'no-cache' - } - assert self.query == {'since': 123} - - httpclient.reset_mock() - response = await segment_api.fetch_segment('some_segment', 123, FetchOptions(True, 123, None, None, None)) - assert response['prop1'] == 'value1' - assert self.verb == 'sdk' - assert self.url == 'segmentChanges/some_segment' - assert self.key == 'some_api_key' - assert self.headers == { - 'SplitSDKVersion': '1.0', - 'SplitSDKMachineIP': '1.2.3.4', - 'SplitSDKMachineName': 'some', - 'Cache-Control': 'no-cache' - } - assert self.query == {'since': 123, 'till': 123} - - httpclient.reset_mock() - def raise_exception(*args, **kwargs): - raise client.HttpClientException('some_message') - httpclient.get = raise_exception - with pytest.raises(APIException) as exc_info: - response = await segment_api.fetch_segment('some_segment', 123, FetchOptions(None, None, None, None, None)) - assert exc_info.type == APIException - assert exc_info.value.message == 'some_message' diff --git a/tests/api/test_splits_api.py b/tests/api/test_splits_api.py index c9aeee8b..b1fcbd66 100644 --- a/tests/api/test_splits_api.py +++ b/tests/api/test_splits_api.py @@ -4,8 +4,10 @@ import unittest.mock as mock import time -from splitio.api import splits, client, APIException -from splitio.api.commons import FetchOptions +from splitio.spec import SPEC_VERSION +from splitio.api import splits +from harness_commons.api import client, APIException +from harness_commons.api.commons import FetchOptions from splitio.client.util import SdkMetadata class SplitAPITests(object): @@ -17,7 +19,7 @@ def test_fetch_split_changes(self, mocker): httpclient.get.return_value = client.HttpResponse(200, '{"prop1": "value1"}', {}) split_api = splits.SplitsAPI(httpclient, 'some_api_key', SdkMetadata('1.0', 'some', '1.2.3.4'), mocker.Mock()) - response = split_api.fetch_splits(123, -1, FetchOptions(False, None, None, 'set1,set2')) + response = split_api.fetch_splits(123, -1, FetchOptions(False, None, None, 'set1,set2', SPEC_VERSION)) assert response['prop1'] == 'value1' assert httpclient.get.mock_calls == [mocker.call('sdk', 'splitChanges', 'some_api_key', extra_headers={ @@ -28,7 +30,7 @@ def test_fetch_split_changes(self, mocker): query={'s': '1.3', 'since': 123, 'rbSince': -1, 'sets': 'set1,set2'})] httpclient.reset_mock() - response = split_api.fetch_splits(123, 1, FetchOptions(True, 123, None,'set3')) + response = split_api.fetch_splits(123, 1, FetchOptions(True, 123, None,'set3', SPEC_VERSION)) assert response['prop1'] == 'value1' assert httpclient.get.mock_calls == [mocker.call('sdk', 'splitChanges', 'some_api_key', extra_headers={ @@ -40,7 +42,7 @@ def test_fetch_split_changes(self, mocker): query={'s': '1.3', 'since': 123, 'rbSince': 1, 'till': 123, 'sets': 'set3'})] httpclient.reset_mock() - response = split_api.fetch_splits(123, 122, FetchOptions(True, 123, None, 'set3')) + response = split_api.fetch_splits(123, 122, FetchOptions(True, 123, None, 'set3', SPEC_VERSION)) assert response['prop1'] == 'value1' assert httpclient.get.mock_calls == [mocker.call('sdk', 'splitChanges', 'some_api_key', extra_headers={ @@ -78,7 +80,7 @@ def get(sdk, splitChanges, sdk_key, extra_headers, query): httpclient.is_sdk_endpoint_overridden.return_value = False try: - response = split_api.fetch_splits(123, -1, FetchOptions(False, None, None, None)) + response = split_api.fetch_splits(123, -1, FetchOptions(False, None, None, None, SPEC_VERSION)) except Exception as e: print(e) @@ -88,7 +90,7 @@ def get(sdk, splitChanges, sdk_key, extra_headers, query): httpclient.is_sdk_endpoint_overridden.return_value = True self.query = [] self.counter = 0 - response = split_api.fetch_splits(123, -1, FetchOptions(False, None, None, None)) + response = split_api.fetch_splits(123, -1, FetchOptions(False, None, None, None, SPEC_VERSION)) assert response == {"ff": {"d": [], "s": 123, "t": 456}, "rbs": {"d": [], "s": -1, "t": -1}} assert self.query == [{'s': '1.3', 'since': 123, 'rbSince': -1}, {'s': '1.1', 'since': 123}] assert not split_api.clear_storage @@ -111,14 +113,14 @@ def get(sdk, splitChanges, sdk_key, extra_headers, query): httpclient.is_sdk_endpoint_overridden.return_value = True httpclient.get = get split_api = splits.SplitsAPI(httpclient, 'some_api_key', SdkMetadata('1.0', 'some', '1.2.3.4'), mocker.Mock()) - response = split_api.fetch_splits(123, -1, FetchOptions(False, None, None, None)) + response = split_api.fetch_splits(123, -1, FetchOptions(False, None, None, None, SPEC_VERSION)) assert response == {"ff": {"d": [], "s": 123, "t": 456}, "rbs": {"d": [], "s": -1, "t": -1}} assert self.query == [{'s': '1.3', 'since': 123, 'rbSince': -1}, {'s': '1.1', 'since': 123}] assert not split_api.clear_storage time.sleep(1) splits._PROXY_CHECK_INTERVAL_MILLISECONDS_SS = 10 - response = split_api.fetch_splits(123, -1, FetchOptions(False, None, None, None)) + response = split_api.fetch_splits(123, -1, FetchOptions(False, None, None, None, SPEC_VERSION)) assert self.query[2] == {'s': '1.3', 'since': 123, 'rbSince': -1} assert response == {"ff": {"d": [], "s": 123, "t": 456}, "rbs": {"d": [], "s": 123, "t": -1}} assert split_api.clear_storage @@ -143,7 +145,7 @@ def get(sdk, splitChanges, sdk_key, extra_headers, query): httpclient.is_sdk_endpoint_overridden.return_value = True httpclient.get = get split_api = splits.SplitsAPI(httpclient, 'some_api_key', SdkMetadata('1.0', 'some', '1.2.3.4'), mocker.Mock()) - response = split_api.fetch_splits(123, -1, FetchOptions(False, None, None, None)) + response = split_api.fetch_splits(123, -1, FetchOptions(False, None, None, None, SPEC_VERSION)) assert response == {"ff": {"d": [], "s": 123, "t": 456}, "rbs": {"d": [], "s": -1, "t": -1}} assert self.query == [{'s': '1.3', 'since': 123, 'rbSince': -1}, {'s': '1.1', 'since': 123}] assert not split_api.clear_storage @@ -151,7 +153,7 @@ def get(sdk, splitChanges, sdk_key, extra_headers, query): time.sleep(1) splits._PROXY_CHECK_INTERVAL_MILLISECONDS_SS = 10 - response = split_api.fetch_splits(456, -1, FetchOptions(False, None, None, None)) + response = split_api.fetch_splits(456, -1, FetchOptions(False, None, None, None, SPEC_VERSION)) time.sleep(1) splits._PROXY_CHECK_INTERVAL_MILLISECONDS_SS = 1000000 assert self.query[2] == {'s': '1.3', 'since': 456, 'rbSince': -1} @@ -180,7 +182,7 @@ async def get(verb, url, key, query, extra_headers): return client.HttpResponse(200, '{"prop1": "value1"}', {}) httpclient.get = get - response = await split_api.fetch_splits(123, -1, FetchOptions(False, None, None, 'set1,set2')) + response = await split_api.fetch_splits(123, -1, FetchOptions(False, None, None, 'set1,set2', SPEC_VERSION)) assert response['prop1'] == 'value1' assert self.verb == 'sdk' assert self.url == 'splitChanges' @@ -193,7 +195,7 @@ async def get(verb, url, key, query, extra_headers): assert self.query == {'s': '1.3', 'since': 123, 'rbSince': -1, 'sets': 'set1,set2'} httpclient.reset_mock() - response = await split_api.fetch_splits(123, 1, FetchOptions(True, 123, None, 'set3')) + response = await split_api.fetch_splits(123, 1, FetchOptions(True, 123, None, 'set3', SPEC_VERSION)) assert response['prop1'] == 'value1' assert self.verb == 'sdk' assert self.url == 'splitChanges' @@ -207,7 +209,7 @@ async def get(verb, url, key, query, extra_headers): assert self.query == {'s': '1.3', 'since': 123, 'rbSince': 1, 'till': 123, 'sets': 'set3'} httpclient.reset_mock() - response = await split_api.fetch_splits(123, 122, FetchOptions(True, 123, None)) + response = await split_api.fetch_splits(123, 122, FetchOptions(True, 123, None, None, SPEC_VERSION)) assert response['prop1'] == 'value1' assert self.verb == 'sdk' assert self.url == 'splitChanges' @@ -249,7 +251,7 @@ async def get(sdk, splitChanges, sdk_key, extra_headers, query): httpclient.is_sdk_endpoint_overridden.return_value = False try: - response = await split_api.fetch_splits(123, -1, FetchOptions(False, None, None, None)) + response = await split_api.fetch_splits(123, -1, FetchOptions(False, None, None, None, SPEC_VERSION)) except Exception as e: print(e) @@ -259,7 +261,7 @@ async def get(sdk, splitChanges, sdk_key, extra_headers, query): httpclient.is_sdk_endpoint_overridden.return_value = True self.query = [] self.counter = 0 - response = await split_api.fetch_splits(123, -1, FetchOptions(False, None, None, None)) + response = await split_api.fetch_splits(123, -1, FetchOptions(False, None, None, None, SPEC_VERSION)) assert response == {"ff": {"d": [], "s": 123, "t": 456}, "rbs": {"d": [], "s": -1, "t": -1}} assert self.query == [{'s': '1.3', 'since': 123, 'rbSince': -1}, {'s': '1.1', 'since': 123}] assert not split_api.clear_storage @@ -283,14 +285,14 @@ async def get(sdk, splitChanges, sdk_key, extra_headers, query): httpclient.is_sdk_endpoint_overridden.return_value = True httpclient.get = get split_api = splits.SplitsAPIAsync(httpclient, 'some_api_key', SdkMetadata('1.0', 'some', '1.2.3.4'), mocker.Mock()) - response = await split_api.fetch_splits(123, -1, FetchOptions(False, None, None, None)) + response = await split_api.fetch_splits(123, -1, FetchOptions(False, None, None, None, SPEC_VERSION)) assert response == {"ff": {"d": [], "s": 123, "t": 456}, "rbs": {"d": [], "s": -1, "t": -1}} assert self.query == [{'s': '1.3', 'since': 123, 'rbSince': -1}, {'s': '1.1', 'since': 123}] assert not split_api.clear_storage time.sleep(1) splits._PROXY_CHECK_INTERVAL_MILLISECONDS_SS = 10 - response = await split_api.fetch_splits(123, -1, FetchOptions(False, None, None, None)) + response = await split_api.fetch_splits(123, -1, FetchOptions(False, None, None, None, SPEC_VERSION)) assert self.query[2] == {'s': '1.3', 'since': 123, 'rbSince': -1} assert response == {"ff": {"d": [], "s": 123, "t": 456}, "rbs": {"d": [], "s": 123, "t": -1}} assert split_api.clear_storage @@ -316,7 +318,7 @@ async def get(sdk, splitChanges, sdk_key, extra_headers, query): httpclient.is_sdk_endpoint_overridden.return_value = True httpclient.get = get split_api = splits.SplitsAPIAsync(httpclient, 'some_api_key', SdkMetadata('1.0', 'some', '1.2.3.4'), mocker.Mock()) - response = await split_api.fetch_splits(123, -1, FetchOptions(False, None, None, None)) + response = await split_api.fetch_splits(123, -1, FetchOptions(False, None, None, None, SPEC_VERSION)) assert response == {"ff": {"d": [], "s": 123, "t": 456}, "rbs": {"d": [], "s": -1, "t": -1}} assert self.query == [{'s': '1.3', 'since': 123, 'rbSince': -1}, {'s': '1.1', 'since': 123}] assert not split_api.clear_storage @@ -324,7 +326,7 @@ async def get(sdk, splitChanges, sdk_key, extra_headers, query): time.sleep(1) splits._PROXY_CHECK_INTERVAL_MILLISECONDS_SS = 10 - response = await split_api.fetch_splits(456, -1, FetchOptions(False, None, None, None)) + response = await split_api.fetch_splits(456, -1, FetchOptions(False, None, None, None, SPEC_VERSION)) time.sleep(1) splits._PROXY_CHECK_INTERVAL_MILLISECONDS_SS = 1000000 assert self.query[2] == {'s': '1.3', 'since': 456, 'rbSince': -1} diff --git a/tests/api/test_telemetry_api.py b/tests/api/test_telemetry_api.py deleted file mode 100644 index 639c7b6f..00000000 --- a/tests/api/test_telemetry_api.py +++ /dev/null @@ -1,266 +0,0 @@ -"""Impressions API tests module.""" - -import pytest -import unittest.mock as mock - -from splitio.api import telemetry, client, APIException -#from harness_commons.models.telemetry import -from splitio.client.util import get_metadata -from splitio.client.config import DEFAULT_CONFIG -from splitio.version import __version__ -from splitio.engine.telemetry import TelemetryStorageProducer, TelemetryStorageProducerAsync -from splitio.storage.inmemmory import InMemoryTelemetryStorage, InMemoryTelemetryStorageAsync - - -class TelemetryAPITests(object): - """Telemetry API test cases.""" - - def test_record_unique_keys(self, mocker): - """Test telemetry posting unique keys.""" - httpclient = mocker.Mock(spec=client.HttpClient) - httpclient.post.return_value = client.HttpResponse(200, '', {}) - uniques = {'keys': [1, 2, 3]} - cfg = DEFAULT_CONFIG.copy() - cfg.update({'IPAddressesEnabled': True, 'machineName': 'some_machine_name', 'machineIp': '123.123.123.123'}) - sdk_metadata = get_metadata(cfg) - telemetry_storage = InMemoryTelemetryStorage() - telemetry_producer = TelemetryStorageProducer(telemetry_storage) - telemetry_runtime_producer = telemetry_producer.get_telemetry_runtime_producer() - telemetry_api = telemetry.TelemetryAPI(httpclient, 'some_api_key', sdk_metadata, telemetry_runtime_producer) - response = telemetry_api.record_unique_keys(uniques) - - call_made = httpclient.post.mock_calls[0] - - # validate positional arguments - assert call_made[1] == ('telemetry', 'v1/keys/ss', 'some_api_key') - - # validate key-value args (headers) - assert call_made[2]['extra_headers'] == { - 'SplitSDKVersion': 'python-%s' % __version__, - 'SplitSDKMachineIP': '123.123.123.123', - 'SplitSDKMachineName': 'some_machine_name' - } - - # validate key-value args (body) - assert call_made[2]['body'] == uniques - - httpclient.reset_mock() - def raise_exception(*args, **kwargs): - raise client.HttpClientException('some_message') - httpclient.post.side_effect = raise_exception - with pytest.raises(APIException) as exc_info: - response = telemetry_api.record_unique_keys(uniques) - assert exc_info.type == APIException - assert exc_info.value.message == 'some_message' - - def test_record_init(self, mocker): - """Test telemetry posting init configs.""" - httpclient = mocker.Mock(spec=client.HttpClient) - httpclient.post.return_value = client.HttpResponse(200, '', {}) - uniques = {'keys': [1, 2, 3]} - cfg = DEFAULT_CONFIG.copy() - cfg.update({'IPAddressesEnabled': True, 'machineName': 'some_machine_name', 'machineIp': '123.123.123.123'}) - sdk_metadata = get_metadata(cfg) - telemetry_storage = InMemoryTelemetryStorage() - telemetry_producer = TelemetryStorageProducer(telemetry_storage) - telemetry_runtime_producer = telemetry_producer.get_telemetry_runtime_producer() - telemetry_api = telemetry.TelemetryAPI(httpclient, 'some_api_key', sdk_metadata, telemetry_runtime_producer) - response = telemetry_api.record_init(uniques) - - call_made = httpclient.post.mock_calls[0] - - # validate positional arguments - assert call_made[1] == ('telemetry', 'v1/metrics/config', 'some_api_key') - - # validate key-value args (headers) - assert call_made[2]['extra_headers'] == { - 'SplitSDKVersion': 'python-%s' % __version__, - 'SplitSDKMachineIP': '123.123.123.123', - 'SplitSDKMachineName': 'some_machine_name' - } - - # validate key-value args (body) - assert call_made[2]['body'] == uniques - - def test_record_stats(self, mocker): - """Test telemetry posting stats.""" - httpclient = mocker.Mock(spec=client.HttpClient) - httpclient.post.return_value = client.HttpResponse(200, '', {}) - uniques = {'keys': [1, 2, 3]} - cfg = DEFAULT_CONFIG.copy() - cfg.update({'IPAddressesEnabled': True, 'machineName': 'some_machine_name', 'machineIp': '123.123.123.123'}) - sdk_metadata = get_metadata(cfg) - telemetry_storage = InMemoryTelemetryStorage() - telemetry_producer = TelemetryStorageProducer(telemetry_storage) - telemetry_runtime_producer = telemetry_producer.get_telemetry_runtime_producer() - telemetry_api = telemetry.TelemetryAPI(httpclient, 'some_api_key', sdk_metadata, telemetry_runtime_producer) - response = telemetry_api.record_stats(uniques) - - call_made = httpclient.post.mock_calls[0] - - # validate positional arguments - assert call_made[1] == ('telemetry', 'v1/metrics/usage', 'some_api_key') - - # validate key-value args (headers) - assert call_made[2]['extra_headers'] == { - 'SplitSDKVersion': 'python-%s' % __version__, - 'SplitSDKMachineIP': '123.123.123.123', - 'SplitSDKMachineName': 'some_machine_name' - } - - # validate key-value args (body) - assert call_made[2]['body'] == uniques - - httpclient.reset_mock() - def raise_exception(*args, **kwargs): - raise client.HttpClientException('some_message') - httpclient.post.side_effect = raise_exception - with pytest.raises(APIException) as exc_info: - response = telemetry_api.record_stats(uniques) - assert exc_info.type == APIException - assert exc_info.value.message == 'some_message' - - -class TelemetryAPIAsyncTests(object): - """Telemetry API test cases.""" - - @pytest.mark.asyncio - async def test_record_unique_keys(self, mocker): - """Test telemetry posting unique keys.""" - httpclient = mocker.Mock(spec=client.HttpClientAsync) - uniques = {'keys': [1, 2, 3]} - cfg = DEFAULT_CONFIG.copy() - cfg.update({'IPAddressesEnabled': True, 'machineName': 'some_machine_name', 'machineIp': '123.123.123.123'}) - sdk_metadata = get_metadata(cfg) - telemetry_storage = InMemoryTelemetryStorageAsync() - telemetry_producer = TelemetryStorageProducerAsync(telemetry_storage) - telemetry_runtime_producer = telemetry_producer.get_telemetry_runtime_producer() - telemetry_api = telemetry.TelemetryAPIAsync(httpclient, 'some_api_key', sdk_metadata, telemetry_runtime_producer) - self.verb = None - self.url = None - self.key = None - self.headers = None - self.body = None - async def post(verb, url, key, body, extra_headers): - self.url = url - self.verb = verb - self.key = key - self.headers = extra_headers - self.body = body - return client.HttpResponse(200, '', {}) - httpclient.post = post - - response = await telemetry_api.record_unique_keys(uniques) - assert self.verb == 'telemetry' - assert self.url == 'v1/keys/ss' - assert self.key == 'some_api_key' - - # validate key-value args (headers) - assert self.headers == { - 'SplitSDKVersion': 'python-%s' % __version__, - 'SplitSDKMachineIP': '123.123.123.123', - 'SplitSDKMachineName': 'some_machine_name' - } - - # validate key-value args (body) - assert self.body == uniques - - httpclient.reset_mock() - def raise_exception(*args, **kwargs): - raise client.HttpClientException('some_message') - httpclient.post = raise_exception - with pytest.raises(APIException) as exc_info: - response = await telemetry_api.record_unique_keys(uniques) - assert exc_info.type == APIException - assert exc_info.value.message == 'some_message' - - @pytest.mark.asyncio - async def test_record_init(self, mocker): - """Test telemetry posting unique keys.""" - httpclient = mocker.Mock(spec=client.HttpClientAsync) - uniques = {'keys': [1, 2, 3]} - cfg = DEFAULT_CONFIG.copy() - cfg.update({'IPAddressesEnabled': True, 'machineName': 'some_machine_name', 'machineIp': '123.123.123.123'}) - sdk_metadata = get_metadata(cfg) - telemetry_storage = InMemoryTelemetryStorageAsync() - telemetry_producer = TelemetryStorageProducerAsync(telemetry_storage) - telemetry_runtime_producer = telemetry_producer.get_telemetry_runtime_producer() - telemetry_api = telemetry.TelemetryAPIAsync(httpclient, 'some_api_key', sdk_metadata, telemetry_runtime_producer) - self.verb = None - self.url = None - self.key = None - self.headers = None - self.body = None - async def post(verb, url, key, body, extra_headers): - self.url = url - self.verb = verb - self.key = key - self.headers = extra_headers - self.body = body - return client.HttpResponse(200, '', {}) - httpclient.post = post - - response = await telemetry_api.record_init(uniques) - assert self.verb == 'telemetry' - assert self.url == 'v1/metrics/config' - assert self.key == 'some_api_key' - - # validate key-value args (headers) - assert self.headers == { - 'SplitSDKVersion': 'python-%s' % __version__, - 'SplitSDKMachineIP': '123.123.123.123', - 'SplitSDKMachineName': 'some_machine_name' - } - - # validate key-value args (body) - assert self.body == uniques - - @pytest.mark.asyncio - async def test_record_stats(self, mocker): - """Test telemetry posting unique keys.""" - httpclient = mocker.Mock(spec=client.HttpClientAsync) - uniques = {'keys': [1, 2, 3]} - cfg = DEFAULT_CONFIG.copy() - cfg.update({'IPAddressesEnabled': True, 'machineName': 'some_machine_name', 'machineIp': '123.123.123.123'}) - sdk_metadata = get_metadata(cfg) - telemetry_storage = InMemoryTelemetryStorageAsync() - telemetry_producer = TelemetryStorageProducerAsync(telemetry_storage) - telemetry_runtime_producer = telemetry_producer.get_telemetry_runtime_producer() - telemetry_api = telemetry.TelemetryAPIAsync(httpclient, 'some_api_key', sdk_metadata, telemetry_runtime_producer) - self.verb = None - self.url = None - self.key = None - self.headers = None - self.body = None - async def post(verb, url, key, body, extra_headers): - self.url = url - self.verb = verb - self.key = key - self.headers = extra_headers - self.body = body - return client.HttpResponse(200, '', {}) - httpclient.post = post - - response = await telemetry_api.record_stats(uniques) - assert self.verb == 'telemetry' - assert self.url == 'v1/metrics/usage' - assert self.key == 'some_api_key' - - # validate key-value args (headers) - assert self.headers == { - 'SplitSDKVersion': 'python-%s' % __version__, - 'SplitSDKMachineIP': '123.123.123.123', - 'SplitSDKMachineName': 'some_machine_name' - } - - # validate key-value args (body) - assert self.body == uniques - - httpclient.reset_mock() - def raise_exception(*args, **kwargs): - raise client.HttpClientException('some_message') - httpclient.post = raise_exception - with pytest.raises(APIException) as exc_info: - response = await telemetry_api.record_stats(uniques) - assert exc_info.type == APIException - assert exc_info.value.message == 'some_message' diff --git a/tests/api/test_util.py b/tests/api/test_util.py deleted file mode 100644 index c7097708..00000000 --- a/tests/api/test_util.py +++ /dev/null @@ -1,40 +0,0 @@ -"""Split API tests module.""" - -import pytest -import unittest.mock as mock - -from splitio.api import headers_from_metadata -from splitio.client.util import SdkMetadata -from splitio.engine.telemetry import TelemetryStorageProducer -from splitio.storage.inmemmory import InMemoryTelemetryStorage -from harness_commons.models.telemetry import HTTPExceptionsAndLatencies - - -class UtilTests(object): - """Util test cases.""" - - def test_headers_from_metadata(self, mocker): - """Test headers from metadata call.""" - metadata = headers_from_metadata(SdkMetadata('1.0', 'some', '1.2.3.4')) - assert metadata['SplitSDKVersion'] == '1.0' - assert metadata['SplitSDKMachineIP'] == '1.2.3.4' - assert metadata['SplitSDKMachineName'] == 'some' - assert 'SplitSDKClientKey' not in metadata - - metadata = headers_from_metadata(SdkMetadata('1.0', 'some', '1.2.3.4'), 'abcd') - assert metadata['SplitSDKVersion'] == '1.0' - assert metadata['SplitSDKMachineIP'] == '1.2.3.4' - assert metadata['SplitSDKMachineName'] == 'some' - assert metadata['SplitSDKClientKey'] == 'abcd' - - metadata = headers_from_metadata(SdkMetadata('1.0', 'some', 'NA')) - assert metadata['SplitSDKVersion'] == '1.0' - assert 'SplitSDKMachineIP' not in metadata - assert 'SplitSDKMachineName' not in metadata - assert 'SplitSDKClientKey' not in metadata - - metadata = headers_from_metadata(SdkMetadata('1.0', 'some', 'unknown')) - assert metadata['SplitSDKVersion'] == '1.0' - assert 'SplitSDKMachineIP' not in metadata - assert 'SplitSDKMachineName' not in metadata - assert 'SplitSDKClientKey' not in metadata diff --git a/tests/engine/test_send_adapters.py b/tests/engine/test_send_adapters.py index 97a17531..eaace673 100644 --- a/tests/engine/test_send_adapters.py +++ b/tests/engine/test_send_adapters.py @@ -7,7 +7,7 @@ from splitio.engine.impressions.adapters import InMemorySenderAdapter, RedisSenderAdapter, PluggableSenderAdapter, \ InMemorySenderAdapterAsync, RedisSenderAdapterAsync, PluggableSenderAdapterAsync from splitio.engine.impressions import adapters -from splitio.api.telemetry import TelemetryAPI, TelemetryAPIAsync +from harness_commons.api.telemetry import TelemetryAPI, TelemetryAPIAsync from splitio.storage.adapters.redis import RedisAdapter, RedisAdapterAsync from splitio.engine.impressions.manager import Counter from tests.storage.test_pluggable import StorageMockAdapter, StorageMockAdapterAsync @@ -32,7 +32,7 @@ def test_uniques_formatter(self, mocker): assert(sorted(sender_adapter._uniques_formatter(uniques)[i]["ks"]) == sorted(formatted[i]["ks"])) - @mock.patch('splitio.api.telemetry.TelemetryAPI.record_unique_keys') + @mock.patch('harness_commons.api.telemetry.TelemetryAPI.record_unique_keys') def test_record_unique_keys(self, mocker): """Test sending unique keys.""" diff --git a/tests/push/test_manager.py b/tests/push/test_manager.py index 29cd45d9..3c316ee0 100644 --- a/tests/push/test_manager.py +++ b/tests/push/test_manager.py @@ -4,7 +4,7 @@ from queue import Queue import pytest -from splitio.api import APIException +from harness_commons.api import APIException from harness_commons.models.token import Token from splitio.push.sse import SSEEvent from splitio.push.parser import parse_incoming_event, EventType, ControlType, ControlMessage, \ diff --git a/tests/push/test_segment_worker.py b/tests/push/test_segment_worker.py index 328a6308..d79319ad 100644 --- a/tests/push/test_segment_worker.py +++ b/tests/push/test_segment_worker.py @@ -3,7 +3,7 @@ import queue import pytest -from splitio.api import APIException +from harness_commons.api import APIException from splitio.push.workers import SegmentWorker, SegmentWorkerAsync from harness_commons.models.notification import SegmentChangeNotification from splitio.optional.loaders import asyncio diff --git a/tests/push/test_split_worker.py b/tests/push/test_split_worker.py index 1a9810af..2691e8e6 100644 --- a/tests/push/test_split_worker.py +++ b/tests/push/test_split_worker.py @@ -4,7 +4,7 @@ import base64 import pytest -from splitio.api import APIException +from harness_commons.api import APIException from splitio.push.workers import SplitWorker, SplitWorkerAsync from harness_commons.models.notification import SplitChangeNotification from splitio.optional.loaders import asyncio diff --git a/tests/sync/test_events_synchronizer.py b/tests/sync/test_events_synchronizer.py index 425ca5a3..dd300dda 100644 --- a/tests/sync/test_events_synchronizer.py +++ b/tests/sync/test_events_synchronizer.py @@ -4,8 +4,8 @@ import time import pytest -from splitio.api.client import HttpResponse -from splitio.api import APIException +from harness_commons.api.client import HttpResponse +from harness_commons.api import APIException from splitio.storage import EventStorage from harness_commons.models.events import Event from splitio.sync.event import EventSynchronizer, EventSynchronizerAsync diff --git a/tests/sync/test_impressions_count_synchronizer.py b/tests/sync/test_impressions_count_synchronizer.py index 3db1753e..860f033c 100644 --- a/tests/sync/test_impressions_count_synchronizer.py +++ b/tests/sync/test_impressions_count_synchronizer.py @@ -4,13 +4,13 @@ import time import pytest -from splitio.api.client import HttpResponse -from splitio.api import APIException +from harness_commons.api.client import HttpResponse +from harness_commons.api import APIException from splitio.engine.impressions.impressions import Manager as ImpressionsManager from splitio.engine.impressions.manager import Counter from splitio.engine.impressions.strategies import StrategyOptimizedMode from splitio.sync.impression import ImpressionsCountSynchronizer, ImpressionsCountSynchronizerAsync -from splitio.api.impressions import ImpressionsAPI +from harness_commons.api.impressions import ImpressionsAPI class ImpressionsCountSynchronizerTests(object): diff --git a/tests/sync/test_impressions_synchronizer.py b/tests/sync/test_impressions_synchronizer.py index 6516ea23..c6d5b859 100644 --- a/tests/sync/test_impressions_synchronizer.py +++ b/tests/sync/test_impressions_synchronizer.py @@ -4,8 +4,8 @@ import time import pytest -from splitio.api.client import HttpResponse -from splitio.api import APIException +from harness_commons.api.client import HttpResponse +from harness_commons.api import APIException from splitio.storage import ImpressionStorage from harness_commons.models.impressions import Impression from splitio.sync.impression import ImpressionSynchronizer, ImpressionSynchronizerAsync diff --git a/tests/sync/test_manager.py b/tests/sync/test_manager.py index 68f82dff..3b96d028 100644 --- a/tests/sync/test_manager.py +++ b/tests/sync/test_manager.py @@ -6,8 +6,8 @@ import pytest from splitio.optional.loaders import asyncio -from splitio.api.auth import AuthAPI -from splitio.api import auth, client, APIException +from harness_commons.api.auth import AuthAPI +from harness_commons.api import auth, client, APIException from splitio.client.util import get_metadata from splitio.client.config import DEFAULT_CONFIG from splitio.tasks.split_sync import SplitSynchronizationTask, SplitSynchronizationTaskAsync @@ -25,7 +25,7 @@ from splitio.sync.synchronizer import Synchronizer, SynchronizerAsync, SplitTasks, SplitSynchronizers, RedisSynchronizer, RedisSynchronizerAsync from splitio.sync.manager import Manager, ManagerAsync, RedisManager, RedisManagerAsync from splitio.storage import SplitStorage, RuleBasedSegmentsStorage -from splitio.api import APIException +from harness_commons.api import APIException from splitio.client.util import SdkMetadata diff --git a/tests/sync/test_segments_synchronizer.py b/tests/sync/test_segments_synchronizer.py index 08aaf7b2..db4a2cbd 100644 --- a/tests/sync/test_segments_synchronizer.py +++ b/tests/sync/test_segments_synchronizer.py @@ -3,8 +3,8 @@ import os from splitio.util.backoff import Backoff -from splitio.api import APIException -from splitio.api.commons import FetchOptions +from harness_commons.api import APIException +from harness_commons.api.commons import FetchOptions from splitio.storage import SplitStorage, SegmentStorage, RuleBasedSegmentsStorage from splitio.storage.inmemmory import InMemorySegmentStorage, InMemorySegmentStorageAsync, InMemorySplitStorage, InMemorySplitStorageAsync from splitio.sync.segment import SegmentSynchronizer, SegmentSynchronizerAsync, LocalSegmentSynchronizer, LocalSegmentSynchronizerAsync diff --git a/tests/sync/test_splits_synchronizer.py b/tests/sync/test_splits_synchronizer.py index bf770117..335a80de 100644 --- a/tests/sync/test_splits_synchronizer.py +++ b/tests/sync/test_splits_synchronizer.py @@ -7,8 +7,8 @@ import queue from splitio.util.backoff import Backoff -from splitio.api import APIException -from splitio.api.commons import FetchOptions +from harness_commons.api import APIException +from harness_commons.api.commons import FetchOptions from splitio.storage import SplitStorage, RuleBasedSegmentsStorage from splitio.storage.inmemmory import InMemorySplitStorage, InMemorySplitStorageAsync, InMemoryRuleBasedSegmentStorage, InMemoryRuleBasedSegmentStorageAsync from splitio.storage import FlagSetsFilter diff --git a/tests/sync/test_synchronizer.py b/tests/sync/test_synchronizer.py index 1e4d9d97..937bb239 100644 --- a/tests/sync/test_synchronizer.py +++ b/tests/sync/test_synchronizer.py @@ -15,7 +15,7 @@ from splitio.sync.impression import ImpressionSynchronizer, ImpressionSynchronizerAsync, ImpressionsCountSynchronizer, ImpressionsCountSynchronizerAsync from splitio.sync.event import EventSynchronizer, EventSynchronizerAsync from splitio.storage import SegmentStorage, SplitStorage, RuleBasedSegmentsStorage -from splitio.api import APIException, APIUriException +from harness_commons.api import APIException, APIUriException from splitio.models.splits import Split from harness_commons.models.segments import Segment from splitio.storage.inmemmory import InMemorySegmentStorage, InMemorySplitStorage, InMemorySegmentStorageAsync, InMemorySplitStorageAsync, \ diff --git a/tests/sync/test_telemetry.py b/tests/sync/test_telemetry.py index 29f25744..dcbb8b4f 100644 --- a/tests/sync/test_telemetry.py +++ b/tests/sync/test_telemetry.py @@ -10,7 +10,7 @@ from splitio.models.splits import Split, Status from harness_commons.models.segments import Segment from harness_commons.models.telemetry import StreamingEvents, StreamingEventsAsync -from splitio.api.telemetry import TelemetryAPI +from harness_commons.api.telemetry import TelemetryAPI class TelemetrySynchronizerTests(object): """Telemetry synchronizer test cases.""" diff --git a/tests/tasks/test_events_sync.py b/tests/tasks/test_events_sync.py index 39b3681a..5b6fc1e0 100644 --- a/tests/tasks/test_events_sync.py +++ b/tests/tasks/test_events_sync.py @@ -4,11 +4,11 @@ import time import pytest -from splitio.api.client import HttpResponse +from harness_commons.api.client import HttpResponse from splitio.tasks import events_sync from splitio.storage import EventStorage from harness_commons.models.events import Event -from splitio.api.events import EventsAPI +from harness_commons.api.events import EventsAPI from splitio.sync.event import EventSynchronizer, EventSynchronizerAsync from splitio.optional.loaders import asyncio diff --git a/tests/tasks/test_impressions_sync.py b/tests/tasks/test_impressions_sync.py index 2fccae73..7cac868b 100644 --- a/tests/tasks/test_impressions_sync.py +++ b/tests/tasks/test_impressions_sync.py @@ -4,11 +4,11 @@ import time import pytest -from splitio.api.client import HttpResponse +from harness_commons.api.client import HttpResponse from splitio.tasks import impressions_sync from splitio.storage import ImpressionStorage from harness_commons.models.impressions import Impression -from splitio.api.impressions import ImpressionsAPI +from harness_commons.api.impressions import ImpressionsAPI from splitio.sync.impression import ImpressionSynchronizer, ImpressionsCountSynchronizer, ImpressionSynchronizerAsync, ImpressionsCountSynchronizerAsync from splitio.engine.impressions.manager import Counter from splitio.optional.loaders import asyncio diff --git a/tests/tasks/test_segment_sync.py b/tests/tasks/test_segment_sync.py index 24befe5d..a91fa69e 100644 --- a/tests/tasks/test_segment_sync.py +++ b/tests/tasks/test_segment_sync.py @@ -4,7 +4,7 @@ import time import pytest -from splitio.api.commons import FetchOptions +from harness_commons.api.commons import FetchOptions from splitio.tasks import segment_sync from splitio.storage import SegmentStorage, SplitStorage, RuleBasedSegmentsStorage from splitio.models.splits import Split diff --git a/tests/tasks/test_split_sync.py b/tests/tasks/test_split_sync.py index c9a0c692..7945136a 100644 --- a/tests/tasks/test_split_sync.py +++ b/tests/tasks/test_split_sync.py @@ -3,8 +3,8 @@ import time import pytest -from splitio.api import APIException -from splitio.api.commons import FetchOptions +from harness_commons.api import APIException +from harness_commons.api.commons import FetchOptions from splitio.tasks import split_sync from splitio.storage import SplitStorage, RuleBasedSegmentsStorage from splitio.models.splits import Split diff --git a/tests/tasks/test_telemetry_sync.py b/tests/tasks/test_telemetry_sync.py index 21a887d0..532e086a 100644 --- a/tests/tasks/test_telemetry_sync.py +++ b/tests/tasks/test_telemetry_sync.py @@ -2,9 +2,9 @@ import pytest import threading import time -from splitio.api.client import HttpResponse +from harness_commons.api.client import HttpResponse from splitio.tasks.telemetry_sync import TelemetrySyncTask, TelemetrySyncTaskAsync -from splitio.api.telemetry import TelemetryAPI, TelemetryAPIAsync +from harness_commons.api.telemetry import TelemetryAPI, TelemetryAPIAsync from splitio.sync.telemetry import TelemetrySynchronizer, TelemetrySynchronizerAsync, InMemoryTelemetrySubmitter, InMemoryTelemetrySubmitterAsync from splitio.storage.inmemmory import InMemoryTelemetryStorage, InMemoryTelemetryStorageAsync from splitio.engine.telemetry import TelemetryStorageConsumer, TelemetryStorageConsumerAsync diff --git a/tests/tasks/test_unique_keys_sync.py b/tests/tasks/test_unique_keys_sync.py index d04f9271..0cdd4d8b 100644 --- a/tests/tasks/test_unique_keys_sync.py +++ b/tests/tasks/test_unique_keys_sync.py @@ -4,10 +4,10 @@ import time import pytest -from splitio.api.client import HttpResponse +from harness_commons.api.client import HttpResponse from splitio.tasks.unique_keys_sync import UniqueKeysSyncTask, ClearFilterSyncTask,\ ClearFilterSyncTaskAsync, UniqueKeysSyncTaskAsync -from splitio.api.telemetry import TelemetryAPI +from harness_commons.api.telemetry import TelemetryAPI from splitio.sync.unique_keys import UniqueKeysSynchronizer, ClearFilterSynchronizer,\ UniqueKeysSynchronizerAsync, ClearFilterSynchronizerAsync from splitio.engine.impressions.unique_keys_tracker import UniqueKeysTracker, UniqueKeysTrackerAsync