Skip to content

Commit

Permalink
Release v1.1.0
Browse files Browse the repository at this point in the history
  • Loading branch information
andrew-vant committed Jul 22, 2014
2 parents 7482de7 + 46bdf69 commit 302ac47
Show file tree
Hide file tree
Showing 7 changed files with 39 additions and 134 deletions.
54 changes: 2 additions & 52 deletions controllers/health_controller.py
Original file line number Diff line number Diff line change
Expand Up @@ -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 *
Expand All @@ -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

Expand All @@ -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."
Expand Down Expand Up @@ -196,24 +157,13 @@ 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,
"retry_rate": 0 if (self.shared.id_totalcnt == 0 or self.shared.id_retrycnt == 0) else float(self.shared.id_retrycnt) / self.shared.id_totalcnt
}
}
},
"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": {
Expand Down
54 changes: 9 additions & 45 deletions controllers/main_controller.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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.

Expand All @@ -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"""
Expand Down
5 changes: 0 additions & 5 deletions controllers/shared.py
Original file line number Diff line number Diff line change
Expand Up @@ -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")
3 changes: 3 additions & 0 deletions requirements.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
pymongo
git+git://github.com/lichray/moecache.git
git+git://github.com/rackerlabs/rse-util.git
12 changes: 4 additions & 8 deletions rse.default.conf
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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
38 changes: 18 additions & 20 deletions rse.py
Original file line number Diff line number Diff line change
Expand Up @@ -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 *
Expand All @@ -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',
Expand Down Expand Up @@ -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):
Expand Down
7 changes: 3 additions & 4 deletions rse.qa.conf
Original file line number Diff line number Diff line change
Expand Up @@ -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

0 comments on commit 302ac47

Please sign in to comment.