From e764b9036d18a231f06cd691264425085f0de047 Mon Sep 17 00:00:00 2001 From: Mario de Frutos Date: Tue, 16 Feb 2016 13:22:45 +0100 Subject: [PATCH] Quota check for the routing feature We have refactor all the quota logic and extracted to a new QuotaChecker class in order to have it ready to create a factory when needed Added the logic for the routing quota check --- .../cdb_dataservices_server--0.3.0--0.4.0.sql | 25 +++--- .../cdb_dataservices_server--0.4.0--0.3.0.sql | 2 +- .../cdb_dataservices_server--0.4.0.sql | 27 +++--- .../extension/sql/0.4.0/15_config_helper.sql | 10 +-- .../extension/sql/0.4.0/80_routing_helper.sql | 9 +- server/extension/sql/0.4.0/85_isodistance.sql | 4 +- server/extension/sql/0.4.0/90_isochrone.sql | 4 +- .../cartodb_services/metrics/__init__.py | 2 +- .../cartodb_services/metrics/config.py | 70 ++++++++++++++-- .../cartodb_services/metrics/quota.py | 83 ++++++++++++++----- .../cartodb_services/metrics/user.py | 30 +++++-- 11 files changed, 191 insertions(+), 75 deletions(-) diff --git a/server/extension/cdb_dataservices_server--0.3.0--0.4.0.sql b/server/extension/cdb_dataservices_server--0.3.0--0.4.0.sql index 7af507e..221898d 100644 --- a/server/extension/cdb_dataservices_server--0.3.0--0.4.0.sql +++ b/server/extension/cdb_dataservices_server--0.3.0--0.4.0.sql @@ -1,12 +1,12 @@ -- Get the Redis configuration from the _conf table -- -CREATE OR REPLACE FUNCTION cdb_dataservices_server._get_routing_config(username text, orgname text) +CREATE OR REPLACE FUNCTION cdb_dataservices_server._get_isolines_routing_config(username text, orgname text) RETURNS boolean AS $$ - cache_key = "user_routing_config_{0}".format(username) + cache_key = "user_isolines_routing_config_{0}".format(username) if cache_key in GD: return False else: import json - from cartodb_services.metrics import RoutingConfig + from cartodb_services.metrics import IsolinesRoutingConfig plpy.execute("SELECT cdb_dataservices_server._connect_to_redis('{0}')".format(username)) redis_conn = GD["redis_connection_{0}".format(username)]['redis_metadata_connection'] heremaps_conf_json = plpy.execute("SELECT cartodb.CDB_Conf_GetConf('heremaps_conf') as heremaps_conf", 1)[0]['heremaps_conf'] @@ -17,7 +17,7 @@ RETURNS boolean AS $$ heremaps_conf = json.loads(heremaps_conf_json) heremaps_app_id = heremaps_conf['app_id'] heremaps_app_code = heremaps_conf['app_code'] - routing_config = RoutingConfig(redis_conn, username, orgname, heremaps_app_id, heremaps_app_code) + routing_config = IsolinesRoutingConfig(redis_conn, username, orgname, heremaps_app_id, heremaps_app_code) # --Think about the security concerns with this kind of global cache, it should be only available # --for this user session but... GD[cache_key] = routing_config @@ -34,12 +34,15 @@ RETURNS SETOF cdb_dataservices_server.isoline AS $$ from cartodb_services.here.types import geo_polyline_to_multipolygon redis_conn = GD["redis_connection_{0}".format(username)]['redis_metrics_connection'] - user_routing_config = GD["user_routing_config_{0}".format(username)] + user_isolines_routing_config = GD["user_isolines_routing_config_{0}".format(username)] - quota_service = QuotaService(user_routing_config, redis_conn) + # -- Check the quota + quota_service = QuotaService(user_isolines_routing_config, redis_conn) + if not quota_service.check_user_quota(): + plpy.error('You have reach the limit of your quota') try: - client = HereMapsRoutingIsoline(user_routing_config.heremaps_app_id, user_routing_config.heremaps_app_code, base_url = HereMapsRoutingIsoline.PRODUCTION_ROUTING_BASE_URL) + client = HereMapsRoutingIsoline(user_isolines_routing_config.heremaps_app_id, user_isolines_routing_config.heremaps_app_code, base_url = HereMapsRoutingIsoline.PRODUCTION_ROUTING_BASE_URL) if source: lat = plpy.execute("SELECT ST_Y('%s') AS lat" % source)[0]['lat'] @@ -80,8 +83,8 @@ CREATE OR REPLACE FUNCTION cdb_dataservices_server.cdb_isodistance(username TEXT RETURNS SETOF cdb_dataservices_server.isoline AS $$ plpy.execute("SELECT cdb_dataservices_server._connect_to_redis('{0}')".format(username)) redis_conn = GD["redis_connection_{0}".format(username)]['redis_metrics_connection'] - plpy.execute("SELECT cdb_dataservices_server._get_routing_config({0}, {1})".format(plpy.quote_nullable(username), plpy.quote_nullable(orgname))) - user_isolines_config = GD["user_routing_config_{0}".format(username)] + plpy.execute("SELECT cdb_dataservices_server._get_isolines_routing_config({0}, {1})".format(plpy.quote_nullable(username), plpy.quote_nullable(orgname))) + user_isolines_config = GD["user_isolines_routing_config_{0}".format(username)] type = 'isodistance' here_plan = plpy.prepare("SELECT cdb_dataservices_server._cdb_here_routing_isolines($1, $2, $3, $4, $5, $6, $7) as isoline; ", ["text", "text", "text", "geometry(Geometry, 4326)", "text", "integer[]", "text[]"]) @@ -99,8 +102,8 @@ CREATE OR REPLACE FUNCTION cdb_dataservices_server.cdb_isochrone(username TEXT, RETURNS SETOF cdb_dataservices_server.isoline AS $$ plpy.execute("SELECT cdb_dataservices_server._connect_to_redis('{0}')".format(username)) redis_conn = GD["redis_connection_{0}".format(username)]['redis_metrics_connection'] - plpy.execute("SELECT cdb_dataservices_server._get_routing_config({0}, {1})".format(plpy.quote_nullable(username), plpy.quote_nullable(orgname))) - user_isolines_config = GD["user_routing_config_{0}".format(username)] + plpy.execute("SELECT cdb_dataservices_server._get_isolines_routing_config({0}, {1})".format(plpy.quote_nullable(username), plpy.quote_nullable(orgname))) + user_isolines_config = GD["user_isolines_routing_config_{0}".format(username)] type = 'isochrone' here_plan = plpy.prepare("SELECT cdb_dataservices_server._cdb_here_routing_isolines($1, $2, $3, $4, $5, $6, $7) as isoline; ", ["text", "text", "text", "geometry(Geometry, 4326)", "text", "integer[]", "text[]"]) diff --git a/server/extension/cdb_dataservices_server--0.4.0--0.3.0.sql b/server/extension/cdb_dataservices_server--0.4.0--0.3.0.sql index bb764a1..a67074f 100644 --- a/server/extension/cdb_dataservices_server--0.4.0--0.3.0.sql +++ b/server/extension/cdb_dataservices_server--0.4.0--0.3.0.sql @@ -1,5 +1,5 @@ DROP FUNCTION IF EXISTS cdb_dataservices_server.cdb_isochrone(TEXT, TEXT, geometry(Geometry, 4326), TEXT, integer[], text[]); DROP FUNCTION IF EXISTS cdb_dataservices_server.cdb_isodistance(TEXT, TEXT, geometry(Geometry, 4326), TEXT, integer[], text[]); DROP FUNCTION IF EXISTS cdb_dataservices_server._cdb_here_routing_isolines(TEXT, TEXT, TEXT, geometry(Geometry, 4326), TEXT, integer[], text[]); -DROP FUNCTION IF EXISTS cdb_dataservices_server._get_routing_config(text, text); +DROP FUNCTION IF EXISTS cdb_dataservices_server._get_isolines_routing_config(text, text); DROP TYPE IF EXISTS cdb_dataservices_server.isoline; \ No newline at end of file diff --git a/server/extension/cdb_dataservices_server--0.4.0.sql b/server/extension/cdb_dataservices_server--0.4.0.sql index df953c3..85cffae 100644 --- a/server/extension/cdb_dataservices_server--0.4.0.sql +++ b/server/extension/cdb_dataservices_server--0.4.0.sql @@ -85,14 +85,14 @@ RETURNS boolean AS $$ $$ LANGUAGE plpythonu SECURITY DEFINER; -- Get the Redis configuration from the _conf table -- -CREATE OR REPLACE FUNCTION cdb_dataservices_server._get_routing_config(username text, orgname text) +CREATE OR REPLACE FUNCTION cdb_dataservices_server._get_isolines_routing_config(username text, orgname text) RETURNS boolean AS $$ - cache_key = "user_routing_config_{0}".format(username) + cache_key = "user_isolines_routing_config_{0}".format(username) if cache_key in GD: return False else: import json - from cartodb_services.metrics import RoutingConfig + from cartodb_services.metrics import IsolinesRoutingConfig plpy.execute("SELECT cdb_dataservices_server._connect_to_redis('{0}')".format(username)) redis_conn = GD["redis_connection_{0}".format(username)]['redis_metadata_connection'] heremaps_conf_json = plpy.execute("SELECT cartodb.CDB_Conf_GetConf('heremaps_conf') as heremaps_conf", 1)[0]['heremaps_conf'] @@ -103,10 +103,10 @@ RETURNS boolean AS $$ heremaps_conf = json.loads(heremaps_conf_json) heremaps_app_id = heremaps_conf['app_id'] heremaps_app_code = heremaps_conf['app_code'] - routing_config = RoutingConfig(redis_conn, username, orgname, heremaps_app_id, heremaps_app_code) + isolines_routing_config = IsolinesRoutingConfig(redis_conn, username, orgname, heremaps_app_id, heremaps_app_code) # --Think about the security concerns with this kind of global cache, it should be only available # --for this user session but... - GD[cache_key] = routing_config + GD[cache_key] = isolines_routing_config return True $$ LANGUAGE plpythonu SECURITY DEFINER; -- Geocodes a street address given a searchtext and a state and/or country @@ -814,12 +814,15 @@ RETURNS SETOF cdb_dataservices_server.isoline AS $$ from cartodb_services.here.types import geo_polyline_to_multipolygon redis_conn = GD["redis_connection_{0}".format(username)]['redis_metrics_connection'] - user_routing_config = GD["user_routing_config_{0}".format(username)] + user_isolines_routing_config = GD["user_isolines_routing_config_{0}".format(username)] - quota_service = QuotaService(user_routing_config, redis_conn) + # -- Check the quota + quota_service = QuotaService(user_isolines_routing_config, redis_conn) + if not quota_service.check_user_quota(): + plpy.error('You have reach the limit of your quota') try: - client = HereMapsRoutingIsoline(user_routing_config.heremaps_app_id, user_routing_config.heremaps_app_code, base_url = HereMapsRoutingIsoline.STAGING_ROUTING_BASE_URL ) + client = HereMapsRoutingIsoline(user_isolines_routing_config.heremaps_app_id, user_isolines_routing_config.heremaps_app_code, base_url = HereMapsRoutingIsoline.PRODUCTION_ROUTING_BASE_URL) if source: lat = plpy.execute("SELECT ST_Y('%s') AS lat" % source)[0]['lat'] @@ -859,8 +862,8 @@ CREATE OR REPLACE FUNCTION cdb_dataservices_server.cdb_isodistance(username TEXT RETURNS SETOF cdb_dataservices_server.isoline AS $$ plpy.execute("SELECT cdb_dataservices_server._connect_to_redis('{0}')".format(username)) redis_conn = GD["redis_connection_{0}".format(username)]['redis_metrics_connection'] - plpy.execute("SELECT cdb_dataservices_server._get_routing_config({0}, {1})".format(plpy.quote_nullable(username), plpy.quote_nullable(orgname))) - user_isolines_config = GD["user_routing_config_{0}".format(username)] + plpy.execute("SELECT cdb_dataservices_server._get_isolines_routing_config({0}, {1})".format(plpy.quote_nullable(username), plpy.quote_nullable(orgname))) + user_isolines_config = GD["user_isolines_routing_config_{0}".format(username)] type = 'isodistance' here_plan = plpy.prepare("SELECT cdb_dataservices_server._cdb_here_routing_isolines($1, $2, $3, $4, $5, $6, $7) as isoline; ", ["text", "text", "text", "geometry(Geometry, 4326)", "text", "integer[]", "text[]"]) @@ -877,8 +880,8 @@ CREATE OR REPLACE FUNCTION cdb_dataservices_server.cdb_isochrone(username TEXT, RETURNS SETOF cdb_dataservices_server.isoline AS $$ plpy.execute("SELECT cdb_dataservices_server._connect_to_redis('{0}')".format(username)) redis_conn = GD["redis_connection_{0}".format(username)]['redis_metrics_connection'] - plpy.execute("SELECT cdb_dataservices_server._get_routing_config({0}, {1})".format(plpy.quote_nullable(username), plpy.quote_nullable(orgname))) - user_isolines_config = GD["user_routing_config_{0}".format(username)] + plpy.execute("SELECT cdb_dataservices_server._get_isolines_routing_config({0}, {1})".format(plpy.quote_nullable(username), plpy.quote_nullable(orgname))) + user_isolines_config = GD["user_isolines_routing_config_{0}".format(username)] type = 'isochrone' here_plan = plpy.prepare("SELECT cdb_dataservices_server._cdb_here_routing_isolines($1, $2, $3, $4, $5, $6, $7) as isoline; ", ["text", "text", "text", "geometry(Geometry, 4326)", "text", "integer[]", "text[]"]) diff --git a/server/extension/sql/0.4.0/15_config_helper.sql b/server/extension/sql/0.4.0/15_config_helper.sql index 47aad42..c49ef50 100644 --- a/server/extension/sql/0.4.0/15_config_helper.sql +++ b/server/extension/sql/0.4.0/15_config_helper.sql @@ -25,14 +25,14 @@ RETURNS boolean AS $$ $$ LANGUAGE plpythonu SECURITY DEFINER; -- Get the Redis configuration from the _conf table -- -CREATE OR REPLACE FUNCTION cdb_dataservices_server._get_routing_config(username text, orgname text) +CREATE OR REPLACE FUNCTION cdb_dataservices_server._get_isolines_routing_config(username text, orgname text) RETURNS boolean AS $$ - cache_key = "user_routing_config_{0}".format(username) + cache_key = "user_isolines_routing_config_{0}".format(username) if cache_key in GD: return False else: import json - from cartodb_services.metrics import RoutingConfig + from cartodb_services.metrics import IsolinesRoutingConfig plpy.execute("SELECT cdb_dataservices_server._connect_to_redis('{0}')".format(username)) redis_conn = GD["redis_connection_{0}".format(username)]['redis_metadata_connection'] heremaps_conf_json = plpy.execute("SELECT cartodb.CDB_Conf_GetConf('heremaps_conf') as heremaps_conf", 1)[0]['heremaps_conf'] @@ -43,9 +43,9 @@ RETURNS boolean AS $$ heremaps_conf = json.loads(heremaps_conf_json) heremaps_app_id = heremaps_conf['app_id'] heremaps_app_code = heremaps_conf['app_code'] - routing_config = RoutingConfig(redis_conn, username, orgname, heremaps_app_id, heremaps_app_code) + isolines_routing_config = IsolinesRoutingConfig(redis_conn, username, orgname, heremaps_app_id, heremaps_app_code) # --Think about the security concerns with this kind of global cache, it should be only available # --for this user session but... - GD[cache_key] = routing_config + GD[cache_key] = isolines_routing_config return True $$ LANGUAGE plpythonu SECURITY DEFINER; diff --git a/server/extension/sql/0.4.0/80_routing_helper.sql b/server/extension/sql/0.4.0/80_routing_helper.sql index 634c2d7..eb15963 100644 --- a/server/extension/sql/0.4.0/80_routing_helper.sql +++ b/server/extension/sql/0.4.0/80_routing_helper.sql @@ -8,12 +8,15 @@ RETURNS SETOF cdb_dataservices_server.isoline AS $$ from cartodb_services.here.types import geo_polyline_to_multipolygon redis_conn = GD["redis_connection_{0}".format(username)]['redis_metrics_connection'] - user_routing_config = GD["user_routing_config_{0}".format(username)] + user_isolines_routing_config = GD["user_isolines_routing_config_{0}".format(username)] - quota_service = QuotaService(user_routing_config, redis_conn) + # -- Check the quota + quota_service = QuotaService(user_isolines_routing_config, redis_conn) + if not quota_service.check_user_quota(): + plpy.error('You have reach the limit of your quota') try: - client = HereMapsRoutingIsoline(user_routing_config.heremaps_app_id, user_routing_config.heremaps_app_code, base_url = HereMapsRoutingIsoline.PRODUCTION_ROUTING_BASE_URL) + client = HereMapsRoutingIsoline(user_isolines_routing_config.heremaps_app_id, user_isolines_routing_config.heremaps_app_code, base_url = HereMapsRoutingIsoline.PRODUCTION_ROUTING_BASE_URL) if source: lat = plpy.execute("SELECT ST_Y('%s') AS lat" % source)[0]['lat'] diff --git a/server/extension/sql/0.4.0/85_isodistance.sql b/server/extension/sql/0.4.0/85_isodistance.sql index b5b553c..c6d99ae 100644 --- a/server/extension/sql/0.4.0/85_isodistance.sql +++ b/server/extension/sql/0.4.0/85_isodistance.sql @@ -2,8 +2,8 @@ CREATE OR REPLACE FUNCTION cdb_dataservices_server.cdb_isodistance(username TEXT RETURNS SETOF cdb_dataservices_server.isoline AS $$ plpy.execute("SELECT cdb_dataservices_server._connect_to_redis('{0}')".format(username)) redis_conn = GD["redis_connection_{0}".format(username)]['redis_metrics_connection'] - plpy.execute("SELECT cdb_dataservices_server._get_routing_config({0}, {1})".format(plpy.quote_nullable(username), plpy.quote_nullable(orgname))) - user_isolines_config = GD["user_routing_config_{0}".format(username)] + plpy.execute("SELECT cdb_dataservices_server._get_isolines_routing_config({0}, {1})".format(plpy.quote_nullable(username), plpy.quote_nullable(orgname))) + user_isolines_config = GD["user_isolines_routing_config_{0}".format(username)] type = 'isodistance' here_plan = plpy.prepare("SELECT cdb_dataservices_server._cdb_here_routing_isolines($1, $2, $3, $4, $5, $6, $7) as isoline; ", ["text", "text", "text", "geometry(Geometry, 4326)", "text", "integer[]", "text[]"]) diff --git a/server/extension/sql/0.4.0/90_isochrone.sql b/server/extension/sql/0.4.0/90_isochrone.sql index 31f9d3a..5ca7272 100644 --- a/server/extension/sql/0.4.0/90_isochrone.sql +++ b/server/extension/sql/0.4.0/90_isochrone.sql @@ -2,8 +2,8 @@ CREATE OR REPLACE FUNCTION cdb_dataservices_server.cdb_isochrone(username TEXT, RETURNS SETOF cdb_dataservices_server.isoline AS $$ plpy.execute("SELECT cdb_dataservices_server._connect_to_redis('{0}')".format(username)) redis_conn = GD["redis_connection_{0}".format(username)]['redis_metrics_connection'] - plpy.execute("SELECT cdb_dataservices_server._get_routing_config({0}, {1})".format(plpy.quote_nullable(username), plpy.quote_nullable(orgname))) - user_isolines_config = GD["user_routing_config_{0}".format(username)] + plpy.execute("SELECT cdb_dataservices_server._get_isolines_routing_config({0}, {1})".format(plpy.quote_nullable(username), plpy.quote_nullable(orgname))) + user_isolines_config = GD["user_isolines_routing_config_{0}".format(username)] type = 'isochrone' here_plan = plpy.prepare("SELECT cdb_dataservices_server._cdb_here_routing_isolines($1, $2, $3, $4, $5, $6, $7) as isoline; ", ["text", "text", "text", "geometry(Geometry, 4326)", "text", "integer[]", "text[]"]) diff --git a/server/lib/python/cartodb_services/cartodb_services/metrics/__init__.py b/server/lib/python/cartodb_services/cartodb_services/metrics/__init__.py index 51147f8..208b2be 100644 --- a/server/lib/python/cartodb_services/cartodb_services/metrics/__init__.py +++ b/server/lib/python/cartodb_services/cartodb_services/metrics/__init__.py @@ -1,3 +1,3 @@ -from config import GeocoderConfig, RoutingConfig, InternalGeocoderConfig, ConfigException +from config import GeocoderConfig, IsolinesRoutingConfig, InternalGeocoderConfig, ConfigException from quota import QuotaService from user import UserMetricsService diff --git a/server/lib/python/cartodb_services/cartodb_services/metrics/config.py b/server/lib/python/cartodb_services/cartodb_services/metrics/config.py index 2b8c51b..484cbb7 100644 --- a/server/lib/python/cartodb_services/cartodb_services/metrics/config.py +++ b/server/lib/python/cartodb_services/cartodb_services/metrics/config.py @@ -28,18 +28,76 @@ class ServiceConfig(object): return self._orgname -class RoutingConfig(ServiceConfig): +class IsolinesRoutingConfig(ServiceConfig): + + ROUTING_CONFIG_KEYS = ['here_isolines_quota', 'soft_here_isolines_limit', + 'period_end_date', 'username', 'orgname', + 'heremaps_app_id', 'heremaps_app_code'] + NOKIA_APP_ID_KEY = 'heremaps_app_id' + NOKIA_APP_CODE_KEY = 'heremaps_app_code' + QUOTA_KEY = 'here_isolines_quota' + SOFT_LIMIT_KEY = 'soft_here_isolines_limit' + USERNAME_KEY = 'username' + ORGNAME_KEY = 'orgname' + PERIOD_END_DATE = 'period_end_date' def __init__(self, redis_connection, username, orgname=None, heremaps_app_id=None, heremaps_app_code=None): - super(RoutingConfig, self).__init__(redis_connection, - username, orgname) - self._heremaps_app_id = heremaps_app_id - self._heremaps_app_code = heremaps_app_code + super(IsolinesRoutingConfig, self).__init__(redis_connection, username, + orgname) + config = self.__get_user_config(username, orgname, heremaps_app_id, + heremaps_app_code) + filtered_config = {key: config[key] for key in self.ROUTING_CONFIG_KEYS if key in config.keys()} + self.__parse_config(filtered_config) + + def __get_user_config(self, username, orgname=None, heremaps_app_id=None, + heremaps_app_code=None): + user_config = self._redis_connection.hgetall( + "rails:users:{0}".format(username)) + if not user_config: + raise ConfigException("""There is no user config available. Please check your configuration.'""") + else: + user_config[self.NOKIA_APP_ID_KEY] = heremaps_app_id + user_config[self.NOKIA_APP_CODE_KEY] = heremaps_app_code + if orgname: + self.__get_organization_config(orgname, user_config) + + return user_config + + def __get_organization_config(self, orgname, user_config): + org_config = self._redis_connection.hgetall( + "rails:orgs:{0}".format(orgname)) + if not org_config: + raise ConfigException("""There is no organization config available. Please check your configuration.'""") + else: + user_config[self.QUOTA_KEY] = org_config[self.QUOTA_KEY] + user_config[self.PERIOD_END_DATE] = org_config[self.PERIOD_END_DATE] + + def __parse_config(self, filtered_config): + self._isolines_quota = float(filtered_config[self.QUOTA_KEY]) + self._period_end_date = date_parse(filtered_config[self.PERIOD_END_DATE]) + if filtered_config[self.SOFT_LIMIT_KEY].lower() == 'true': + self._soft_isolines_limit = True + else: + self._soft_isolines_limit = False + self._heremaps_app_id = filtered_config[self.NOKIA_APP_ID_KEY] + self._heremaps_app_code = filtered_config[self.NOKIA_APP_CODE_KEY] @property def service_type(self): - return 'routing_here' + return 'here_isolines' + + @property + def isolines_quota(self): + return self._isolines_quota + + @property + def soft_isolines_limit(self): + return self._soft_isolines_limit + + @property + def period_end_date(self): + return self._period_end_date @property def heremaps_app_id(self): diff --git a/server/lib/python/cartodb_services/cartodb_services/metrics/quota.py b/server/lib/python/cartodb_services/cartodb_services/metrics/quota.py index 340642c..aba6135 100644 --- a/server/lib/python/cartodb_services/cartodb_services/metrics/quota.py +++ b/server/lib/python/cartodb_services/cartodb_services/metrics/quota.py @@ -1,32 +1,22 @@ from user import UserMetricsService from datetime import date +import re class QuotaService: """ Class to manage all the quota operation for the Geocoder SQL API Extension """ - def __init__(self, user_geocoder_config, redis_connection): - self._user_geocoder_config = user_geocoder_config + def __init__(self, user_service_config, redis_connection): + self._user_service_config = user_service_config + # TODO First step to extract to a factory if needed in the future + self._quota_checker = QuotaChecker(user_service_config, + redis_connection) self._user_service = UserMetricsService( - self._user_geocoder_config, redis_connection) + self._user_service_config, redis_connection) def check_user_quota(self): - """ Check if the current user quota surpasses the current quota """ - # We don't have quota check for google geocoder - if self._user_geocoder_config.google_geocoder: - return True - - user_quota = self._user_geocoder_config.geocoding_quota - today = date.today() - service_type = self._user_geocoder_config.service_type - current_used = self._user_service.used_quota(service_type, today) - soft_geocoding_limit = self._user_geocoder_config.soft_geocoding_limit - - if soft_geocoding_limit or current_used <= user_quota: - return True - else: - return False + return self._quota_checker.check() # TODO # We are going to change this class to be the generic one and @@ -36,25 +26,72 @@ class QuotaService: def increment_success_geocoder_use(self, amount=1): self._user_service.increment_service_use( - self._user_geocoder_config.service_type, "success_responses", + self._user_service_config.service_type, "success_responses", amount=amount) def increment_empty_geocoder_use(self, amount=1): self._user_service.increment_service_use( - self._user_geocoder_config.service_type, "empty_responses", + self._user_service_config.service_type, "empty_responses", amount=amount) def increment_failed_geocoder_use(self, amount=1): self._user_service.increment_service_use( - self._user_geocoder_config.service_type, "fail_responses", + self._user_service_config.service_type, "fail_responses", amount=amount) def increment_total_geocoder_use(self, amount=1): self._user_service.increment_service_use( - self._user_geocoder_config.service_type, "total_requests", + self._user_service_config.service_type, "total_requests", amount=amount) def increment_isolines_service_use(self, amount=1): self._user_service.increment_service_use( - self._user_geocoder_config.service_type, "isolines_generated", + self._user_service_config.service_type, "isolines_generated", amount=amount) + + +class QuotaChecker: + + def __init__(self, user_service_config, redis_connection): + self._user_service_config = user_service_config + self._user_service = UserMetricsService( + self._user_service_config, redis_connection) + + def check(self): + """ Check if the current user quota surpasses the current quota """ + if re.match('geocoder_*', + self._user_service_config.service_type) is not None: + return self.__check_geocoder_quota() + elif re.match('here_isolines', + self._user_service_config.service_type) is not None: + return self.__check_isolines_quota() + else: + return False + + def __check_geocoder_quota(self): + # We don't have quota check for google geocoder + if self._user_service_config.google_geocoder: + return True + + user_quota = self._user_service_config.geocoding_quota + today = date.today() + service_type = self._user_service_config.service_type + current_used = self._user_service.used_quota(service_type, today) + soft_geocoding_limit = self._user_service_config.soft_geocoding_limit + + if soft_geocoding_limit or (user_quota > 0 and current_used <= user_quota): + return True + else: + return False + + def __check_isolines_quota(self): + user_quota = self._user_service_config.isolines_quota + today = date.today() + service_type = self._user_service_config.service_type + current_used = self._user_service.used_quota(service_type, today) + soft_isolines_limit = self._user_service_config.soft_isolines_limit + + if soft_isolines_limit or (user_quota > 0 and current_used <= user_quota): + return True + else: + return False diff --git a/server/lib/python/cartodb_services/cartodb_services/metrics/user.py b/server/lib/python/cartodb_services/cartodb_services/metrics/user.py index 947f782..782746d 100644 --- a/server/lib/python/cartodb_services/cartodb_services/metrics/user.py +++ b/server/lib/python/cartodb_services/cartodb_services/metrics/user.py @@ -6,16 +6,8 @@ class UserMetricsService: """ Class to manage all the user info """ SERVICE_GEOCODER_NOKIA = 'geocoder_here' - SERVICE_GEOCODER_GOOGLE = 'geocoder_google' SERVICE_GEOCODER_CACHE = 'geocoder_cache' - - GEOCODING_QUOTA_KEY = "geocoding_quota" - GEOCODING_SOFT_LIMIT_KEY = "soft_geocoder_limit" - - REDIS_CONNECTION_KEY = "redis_connection" - REDIS_CONNECTION_HOST = "redis_host" - REDIS_CONNECTION_PORT = "redis_port" - REDIS_CONNECTION_DB = "redis_db" + SERVICE_HERE_ISOLINES = 'here_isolines' def __init__(self, user_geocoder_config, redis_connection): self._user_geocoder_config = user_geocoder_config @@ -24,6 +16,12 @@ class UserMetricsService: self._orgname = user_geocoder_config.organization def used_quota(self, service_type, date): + if service_type == self.SERVICE_HERE_ISOLINES: + return self.__used_isolines_quota(service_type, date) + else: + return self.__used_geocoding_quota(service_type, date) + + def __used_geocoding_quota(self, service_type, date): """ Recover the used quota for the user in the current month """ date_from, date_to = self.__current_billing_cycle() current_use = 0 @@ -42,6 +40,20 @@ class UserMetricsService: return current_use + def __used_isolines_quota(self, service_type, date): + date_from, date_to = self.__current_billing_cycle() + current_use = 0 + isolines_generated = self.get_metrics(service_type, + 'isolines_generated', date_from, + date_to) + empty_responses = self.get_metrics(service_type, + 'empty_responses', date_from, + date_to) + current_use += (isolines_generated + empty_responses) + + return current_use + + def increment_service_use(self, service_type, metric, date=date.today(), amount=1): """ Increment the services uses in monthly and daily basis""" self.__increment_user_uses(service_type, metric, date, amount)