diff --git a/controllers/health_controller.py b/controllers/health_controller.py index b0c29f9..f7dfab1 100644 --- a/controllers/health_controller.py +++ b/controllers/health_controller.py @@ -10,10 +10,8 @@ import time import httplib -# Requires python 2.6 or better import json -# These need to be installed (easy_install) import pymongo from rax.http.exceptions import * @@ -37,43 +35,13 @@ class HealthController(rawr.Controller): """Provides web service health info""" """@todo Move this class into a separate file""" - def __init__(self, accountsvc_host, accountsvc_https, accountsvc_timeout, mongo_db, test_mode, shared): - # Account services host for authenticating requests - self.accountsvc_host = accountsvc_host - # Whether to use HTTPS for account services - self.accountsvc_https = accountsvc_https - # Timeout for requests to Account services - self.accountsvc_timeout = accountsvc_timeout + def __init__(self, mongo_db, test_mode, shared): self.mongo_db = mongo_db # MongoDB database for storing events # MongoDB connection for storing events self.mongo_db_connection = mongo_db.connection self.test_mode = test_mode # If true, relax auth/uuid requirements self.shared = shared # Shared performance counters, logging, etc. - def _auth_service_is_healthy(self): - try: - accountsvc = httplib.HTTPSConnection(self.accountsvc_host, timeout=self.accountsvc_timeout) if self.accountsvc_https else httplib.HTTPConnection( - self.accountsvc_host, timeout=self.accountsvc_timeout) - accountsvc.request('GET', self.shared.AUTH_HEALTH_ENDPOINT) - auth_response = accountsvc.getresponse() - - if auth_response.status != 200: - error_message = "Auth endpoint returned HTTP %d instead of HTTP 200" % auth_response.status - return (False, error_message) - - auth_service_health = json.loads(auth_response.read()) - if auth_service_health['Status'] != 'OK': - error_message = "Auth endpoint returned %s instead of OK" % auth_service_health[ - 'Status'] - return (False, error_message) - - except Exception as ex: - error_message = "Exception while checking auth service health: %s" % str_utf8( - ex) - return (False, error_message) - - return (True, "No errors") - def _basic_health_check(self): db_ok = False @@ -92,19 +60,12 @@ def _basic_health_check(self): 'Could not count events collection: ' + str_utf8(ex)) break - auth_ok, msg = self._auth_service_is_healthy() - if not auth_ok: - self.shared.logger.error(msg) - - return auth_ok and db_ok + return db_ok def _create_report(self, profile_db, validate_db): """@todo Build up the report piecemeal so we can get partial results on errors.""" """@todo Return a non-200 HTTP error code on error""" - # Check our auth endpoint - auth_online, auth_error_message = self._auth_service_is_healthy() - validation_info = "N/A. Pass validate_db=true to enable." profile_info = "N/A. Pass profile_db=true to enable." server_info = "N/A." @@ -196,11 +157,6 @@ def _create_report(self, profile_db, validate_db): "test_mode": self.test_mode, "events": active_events, "pp_stats": { - "auth_token_cache": { - "lookups": self.shared.cache_token_totalcnt, - "hits": self.shared.cache_token_hitcnt, - "hit_rate": 0 if self.shared.cache_token_totalcnt == 0 else float(self.shared.cache_token_hitcnt) / self.shared.cache_token_totalcnt - }, "id_generator": { "attempts": self.shared.id_totalcnt, "retries": self.shared.id_retrycnt, @@ -208,12 +164,6 @@ def _create_report(self, profile_db, validate_db): } } }, - "auth": { - "url": "%s://%s%s" % ("https" if self.accountsvc_https else "http", self.accountsvc_host, self.shared.AUTH_ENDPOINT), - "url_health": "%s://%s%s" % ("https" if self.accountsvc_https else "http", self.accountsvc_host, self.shared.AUTH_HEALTH_ENDPOINT), - "online": auth_online, - "error": auth_error_message - }, "mongodb": { "stats": { "background_flushing": { diff --git a/controllers/main_controller.py b/controllers/main_controller.py index a2cb00d..e14a8fc 100644 --- a/controllers/main_controller.py +++ b/controllers/main_controller.py @@ -10,10 +10,8 @@ import datetime import time import re -import httplib import random -# These need to be installed (easy_install) import pymongo # We got this off the web somewhere - put in the same dir as rse.py @@ -39,13 +37,9 @@ def format_datetime(dt): class MainController(rawr.Controller): """Provides all RSE functionality""" - def __init__(self, accountsvc_host, accountsvc_https, accountsvc_timeout, mongo_db, shared, test_mode=False): - # Account services host for authenticating requests - self.accountsvc_host = accountsvc_host - # Whether to use HTTPS for account services - self.accountsvc_https = accountsvc_https - self.accountsvc_timeout = accountsvc_timeout + def __init__(self, mongo_db, shared, authtoken_prefix, test_mode=False): self.mongo_db = mongo_db # MongoDB database for storing events + self.authtoken_prefix = authtoken_prefix self.test_mode = test_mode # If true, relax auth/uuid requirements self.shared = shared # Shared performance counters, logging, etc. @@ -61,48 +55,18 @@ def prepare(self): "Missing X-Auth-Token header (required in live mode)") raise HttpUnauthorized() - # Incement token cache access counter - self.shared.cache_token_totalcnt += 1 - - # See if auth is cached - if self.shared.authtoken_cache.is_cached(auth_token): - # They are OK for the moment. Update hit counter for posterity. - self.shared.cache_token_hitcnt += 1 - return - - # We don't have a record of this token, so proxy authentication to the - # Account Services API + # See if auth is cached by API try: - accountsvc = httplib.HTTPSConnection(self.accountsvc_host, timeout=self.accountsvc_timeout) if self.accountsvc_https else httplib.HTTPConnection( - self.accountsvc_host, timeout=self.accountsvc_timeout) - accountsvc.request( - 'GET', self.shared.AUTH_ENDPOINT, None, {'X-Auth-Token': auth_token}) - response = accountsvc.getresponse() - except Exception as ex: - auth_url = 'http%s://%s%s' % ( - 's' if self.accountsvc_https else '', self.accountsvc_host, self.shared.AUTH_ENDPOINT) - - self.shared.logger.error( - 'Error while attempting to validate token via GET %s - %s' % - (auth_url, str_utf8(ex))) - - raise HttpServiceUnavailable() + if (self.shared.authtoken_cache.get( + self.authtoken_prefix + auth_token) is None): + raise HttpUnauthorized() - # Check whether the auth token was good - if response.status != 200: - self.shared.logger.warning( - 'Could not authorize request. Server returned HTTP %d for "%s".' % (response.status, auth_token)) - if (response.status / 100) == 4: - raise HttpError(response.status) - else: - raise HttpServiceUnavailable() + except HttpError: + raise - try: - # Cache good token to reduce latency, and to reduce the load on - # Account Services - self.shared.authtoken_cache.cache(auth_token) except Exception as ex: self.shared.logger.error(str_utf8(ex)) + raise HttpServiceUnavailable() def _is_safe_user_agent(self, user_agent): """Quick heuristic to tell whether we can embed the given user_agent string in a JSON document""" diff --git a/controllers/shared.py b/controllers/shared.py index c077cd5..dcc4545 100644 --- a/controllers/shared.py +++ b/controllers/shared.py @@ -16,13 +16,8 @@ def __init__(self, logger, authtoken_cache): self.authtoken_cache = authtoken_cache self.logger = logger - self.cache_token_hitcnt = 0 - self.cache_token_totalcnt = 0 self.id_totalcnt = 0 self.id_retrycnt = 0 - self.AUTH_ENDPOINT = '/v1.0/auth/isauthenticated' - self.AUTH_HEALTH_ENDPOINT = '/v1.0/help/apihealth' - # Precompiled regex for validating JSONP callback name self.JSONP_CALLBACK_PATTERN = re.compile("\A[a-zA-Z0-9_]+\Z") diff --git a/requirements.txt b/requirements.txt new file mode 100644 index 0000000..07b05e3 --- /dev/null +++ b/requirements.txt @@ -0,0 +1,3 @@ +pymongo +git+git://github.com/lichray/moecache.git +git+git://github.com/rackerlabs/rse-util.git diff --git a/rse.default.conf b/rse.default.conf index bec2268..b6a7935 100644 --- a/rse.default.conf +++ b/rse.default.conf @@ -8,9 +8,10 @@ replica-set = [none] test = yes event-ttl = 120 -[fastcache] -authtoken-retention-period = 30 -authtoken-slice-size = 2 +[authcache] +authtoken-prefix = QuattroAPI_Login_Ticket_ +memcached-shards = 127.0.0.1:11211 +memcached-timeout = 5 [logging] console = yes @@ -19,8 +20,3 @@ filelog-path = ./rse.log syslog = no syslog-address = /dev/log verbose = yes - -[account-services] -host = api.drivesrvr-dev.com -https = no -timeout = 5 diff --git a/rse.py b/rse.py index d2ebdef..4118beb 100755 --- a/rse.py +++ b/rse.py @@ -25,12 +25,11 @@ import os.path import ConfigParser -# These need to be installed (easy_install) import pymongo import argparse +import moecache from rax.http import rawr -from rax.fastcache.fastcache import * from controllers.shared import * from controllers.health_controller import * @@ -53,8 +52,7 @@ def __init__(self): config = ConfigParser.ConfigParser( defaults={ 'timeout': '5', - 'authtoken-retention-period': '30', - 'authtoken-slice-size': '2', + 'authtoken-prefix': '', 'replica-set': '[none]', 'filelog': 'yes', 'console': 'no', @@ -97,33 +95,33 @@ def __init__(self): logger.addHandler(handler) # FastCache for Auth Token - retention_period = config.getint( - 'fastcache', 'authtoken-retention-period') - slice_size = config.getint('fastcache', 'authtoken-slice-size') - authtoken_cache = FastCache(retention_period, slice_size) + authtoken_prefix = config.get('authcache', 'authtoken-prefix') + memcached_shards = [(host, int(port)) for host, port in + [addr.split(':') for addr in + config.get('authcache', + 'memcached-shards').split(',')]] + memcached_timeout = config.getint('authcache', 'memcached-timeout') + authtoken_cache = moecache.Client(memcached_shards, + timeout=memcached_timeout) # Connnect to MongoDB mongo_db, mongo_db_master = self.init_database(logger, config) - # Get account services options - accountsvc_host = config.get('account-services', 'host') - accountsvc_https = config.getboolean('account-services', 'https') - accountsvc_timeout = config.getint('account-services', 'timeout') + # Get auth requirements test_mode = config.getboolean('rse', 'test') # Setup routes shared = Shared(logger, authtoken_cache) - health_options = dict(shared=shared, accountsvc_host=accountsvc_host, - accountsvc_https=accountsvc_https, - accountsvc_timeout=accountsvc_timeout, - mongo_db=mongo_db_master, test_mode=test_mode) + health_options = dict(shared=shared, + mongo_db=mongo_db_master, + test_mode=test_mode) self.add_route(r"/health$", HealthController, health_options) - main_options = dict(shared=shared, accountsvc_host=accountsvc_host, - accountsvc_https=accountsvc_https, - accountsvc_timeout=accountsvc_timeout, - mongo_db=mongo_db, test_mode=test_mode) + main_options = dict(shared=shared, + mongo_db=mongo_db, + authtoken_prefix=authtoken_prefix, + test_mode=test_mode) self.add_route(r"/.+", MainController, main_options) def init_database(self, logger, config): diff --git a/rse.qa.conf b/rse.qa.conf index 71f83d1..28c9fe9 100644 --- a/rse.qa.conf +++ b/rse.qa.conf @@ -7,13 +7,12 @@ replica-set = rse [rse] test = yes +[authcache] +authtoken-prefix = CloudBackup_API_QAQuattroAPI_Login_Ticket_ + [logging] filelog = yes filelog-path = ./rse.log syslog = no syslog-address = /dev/log verbose = yes - -[account-services] -host = controlpanelsvc.drivesrvr-qa.com -https = no