From 1cf2787aaa33c0584c4fa2dfe39c8bbeb9155021 Mon Sep 17 00:00:00 2001 From: Zhihao Yuan Date: Tue, 22 Apr 2014 16:45:47 -0400 Subject: [PATCH 1/3] feat(deps): pip install -r requirements.txt RSE is not pip-installable, and I don't have a plan to change this. This change make the third-party dependency easier to install. The memcached client we are going to use, moecache, is also pulled int. --- controllers/health_controller.py | 2 -- controllers/main_controller.py | 1 - requirements.txt | 3 +++ rse.py | 1 - 4 files changed, 3 insertions(+), 4 deletions(-) create mode 100644 requirements.txt diff --git a/controllers/health_controller.py b/controllers/health_controller.py index b0c29f9..9dbc9e9 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 * diff --git a/controllers/main_controller.py b/controllers/main_controller.py index a2cb00d..58cbed3 100644 --- a/controllers/main_controller.py +++ b/controllers/main_controller.py @@ -13,7 +13,6 @@ 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 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.py b/rse.py index d2ebdef..85b0fc0 100755 --- a/rse.py +++ b/rse.py @@ -25,7 +25,6 @@ import os.path import ConfigParser -# These need to be installed (easy_install) import pymongo import argparse From dd5347cbeac43356a02f5b407541def48c217c60 Mon Sep 17 00:00:00 2001 From: Zhihao Yuan Date: Mon, 28 Apr 2014 17:43:25 -0400 Subject: [PATCH 2/3] feat(auth): query API's memcached servers With this change, using the API to authenticate an agent for RSE is no longer needed. If the auth token is not cached by API, then authentication fails. --- controllers/health_controller.py | 5 --- controllers/main_controller.py | 53 ++++++-------------------------- controllers/shared.py | 2 -- rse.default.conf | 7 +++-- rse.py | 25 ++++++++------- rse.qa.conf | 3 ++ 6 files changed, 30 insertions(+), 65 deletions(-) diff --git a/controllers/health_controller.py b/controllers/health_controller.py index 9dbc9e9..67e7547 100644 --- a/controllers/health_controller.py +++ b/controllers/health_controller.py @@ -194,11 +194,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, diff --git a/controllers/main_controller.py b/controllers/main_controller.py index 58cbed3..e14a8fc 100644 --- a/controllers/main_controller.py +++ b/controllers/main_controller.py @@ -10,7 +10,6 @@ import datetime import time import re -import httplib import random import pymongo @@ -38,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. @@ -60,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..2449b10 100644 --- a/controllers/shared.py +++ b/controllers/shared.py @@ -16,8 +16,6 @@ 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 diff --git a/rse.default.conf b/rse.default.conf index bec2268..ca79deb 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 diff --git a/rse.py b/rse.py index 85b0fc0..b40f992 100755 --- a/rse.py +++ b/rse.py @@ -27,9 +27,9 @@ 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 * @@ -52,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', @@ -96,10 +95,14 @@ 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) @@ -119,10 +122,10 @@ def __init__(self): 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..881e0c9 100644 --- a/rse.qa.conf +++ b/rse.qa.conf @@ -7,6 +7,9 @@ replica-set = rse [rse] test = yes +[authcache] +authtoken-prefix = CloudBackup_API_QAQuattroAPI_Login_Ticket_ + [logging] filelog = yes filelog-path = ./rse.log From e120d97d1b66fdf4522fe5b13c7a6ec6f483ecc4 Mon Sep 17 00:00:00 2001 From: Zhihao Yuan Date: Tue, 29 Apr 2014 14:39:44 -0400 Subject: [PATCH 3/3] feat(auth): no longer talks to auth API 1. Health endpoint no longer reports auth health; 2. 'account-services' section in the configuration file is dropped. --- controllers/health_controller.py | 47 ++------------------------------ controllers/shared.py | 3 -- rse.default.conf | 5 ---- rse.py | 12 +++----- rse.qa.conf | 4 --- 5 files changed, 6 insertions(+), 65 deletions(-) diff --git a/controllers/health_controller.py b/controllers/health_controller.py index 67e7547..f7dfab1 100644 --- a/controllers/health_controller.py +++ b/controllers/health_controller.py @@ -35,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 @@ -90,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." @@ -201,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/shared.py b/controllers/shared.py index 2449b10..dcc4545 100644 --- a/controllers/shared.py +++ b/controllers/shared.py @@ -19,8 +19,5 @@ def __init__(self, logger, authtoken_cache): 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/rse.default.conf b/rse.default.conf index ca79deb..b6a7935 100644 --- a/rse.default.conf +++ b/rse.default.conf @@ -20,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 b40f992..4118beb 100755 --- a/rse.py +++ b/rse.py @@ -107,19 +107,15 @@ def __init__(self): # 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, diff --git a/rse.qa.conf b/rse.qa.conf index 881e0c9..28c9fe9 100644 --- a/rse.qa.conf +++ b/rse.qa.conf @@ -16,7 +16,3 @@ filelog-path = ./rse.log syslog = no syslog-address = /dev/log verbose = yes - -[account-services] -host = controlpanelsvc.drivesrvr-qa.com -https = no