Merged from trunk

This commit is contained in:
gholt 2011-06-13 20:51:06 +00:00
commit 024df7c0f2
15 changed files with 262 additions and 70 deletions

View File

@ -547,6 +547,10 @@ error_suppression_limit 10 Error count to consider a
node error limited node error limited
allow_account_management false Whether account PUTs and DELETEs allow_account_management false Whether account PUTs and DELETEs
are even callable are even callable
account_autocreate false If set to 'true' authorized
accounts that do not yet exist
within the Swift cluster will
be automatically created.
============================ =============== ============================= ============================ =============== =============================
[tempauth] [tempauth]

View File

@ -19,11 +19,11 @@ the proxy log output to an hourly log file. For example, a proxy request that
is made on August 4, 2010 at 12:37 gets logged in a file named 2010080412. is made on August 4, 2010 at 12:37 gets logged in a file named 2010080412.
This allows easy log rotation and easy per-hour log processing. This allows easy log rotation and easy per-hour log processing.
****************** *********************************
Account stats logs Account / Container DB stats logs
****************** *********************************
Account stats logs are generated by a stats system process. DB stats logs are generated by a stats system process.
swift-account-stats-logger runs on each account server (via cron) and walks swift-account-stats-logger runs on each account server (via cron) and walks
the filesystem looking for account databases. When an account database is the filesystem looking for account databases. When an account database is
found, the logger selects the account hash, bytes_used, container_count, and found, the logger selects the account hash, bytes_used, container_count, and
@ -34,7 +34,8 @@ runs the account stats logger every hour. Therefore, in a cluster of ten
account servers, ten csv files are produced every hour. Also, every account account servers, ten csv files are produced every hour. Also, every account
will have one entry for every replica in the system. On average, there will be will have one entry for every replica in the system. On average, there will be
three copies of each account in the aggregate of all account stat csv files three copies of each account in the aggregate of all account stat csv files
created in one system-wide run. created in one system-wide run. The swift-container-stats-logger runs in a
similar fashion, scanning the container dbs.
---------------------- ----------------------
Log Processing plugins Log Processing plugins

View File

@ -54,3 +54,4 @@ processable = false
# devices = /srv/node # devices = /srv/node
# mount_check = true # mount_check = true
# user = swift # user = swift
# metadata_keys = comma separated list of user metadata keys to be collected

View File

@ -40,6 +40,9 @@ use = egg:swift#proxy
# If set to 'true' any authorized user may create and delete accounts; if # If set to 'true' any authorized user may create and delete accounts; if
# 'false' no one, even authorized, can. # 'false' no one, even authorized, can.
# allow_account_management = false # allow_account_management = false
# If set to 'true' authorized accounts that do not yet exist within the Swift
# cluster will be automatically created.
# account_autocreate = false
[filter:tempauth] [filter:tempauth]
use = egg:swift#tempauth use = egg:swift#tempauth

View File

@ -43,7 +43,7 @@ class Bench(object):
self.user = conf.user self.user = conf.user
self.key = conf.key self.key = conf.key
self.auth_url = conf.auth self.auth_url = conf.auth
self.use_proxy = conf.use_proxy in TRUE_VALUES self.use_proxy = conf.use_proxy.lower() in TRUE_VALUES
if self.use_proxy: if self.use_proxy:
url, token = client.get_auth(self.auth_url, self.user, self.key) url, token = client.get_auth(self.auth_url, self.user, self.key)
self.token = token self.token = token
@ -125,7 +125,7 @@ class BenchController(object):
self.logger = logger self.logger = logger
self.conf = conf self.conf = conf
self.names = [] self.names = []
self.delete = conf.delete in TRUE_VALUES self.delete = conf.delete.lower() in TRUE_VALUES
self.gets = int(conf.num_gets) self.gets = int(conf.num_gets)
def run(self): def run(self):

View File

@ -75,7 +75,8 @@ def run_daemon(klass, conf_file, section_name='', once=False, **kwargs):
log_name=kwargs.get('log_name')) log_name=kwargs.get('log_name'))
# once on command line (i.e. daemonize=false) will over-ride config # once on command line (i.e. daemonize=false) will over-ride config
once = once or conf.get('daemonize', 'true') not in utils.TRUE_VALUES once = once or \
conf.get('daemonize', 'true').lower() not in utils.TRUE_VALUES
# pre-configure logger # pre-configure logger
if 'logger' in kwargs: if 'logger' in kwargs:

View File

@ -881,15 +881,17 @@ class ContainerBroker(DatabaseBroker):
return (row['object_count'] in (None, '', 0, '0')) and \ return (row['object_count'] in (None, '', 0, '0')) and \
(float(row['delete_timestamp']) > float(row['put_timestamp'])) (float(row['delete_timestamp']) > float(row['put_timestamp']))
def get_info(self): def get_info(self, include_metadata=False):
""" """
Get global data for the container. Get global data for the container.
:returns: sqlite.row of (account, container, created_at, put_timestamp, :returns: dict with keys: account, container, created_at,
delete_timestamp, object_count, bytes_used, put_timestamp, delete_timestamp, object_count, bytes_used,
reported_put_timestamp, reported_delete_timestamp, reported_put_timestamp, reported_delete_timestamp,
reported_object_count, reported_bytes_used, hash, id, reported_object_count, reported_bytes_used, hash, id,
x_container_sync_point1, x_container_sync_point2) x_container_sync_point1, and x_container_sync_point2.
If include_metadata is set, metadata is included as a key
pointing to a dict of tuples of the metadata
""" """
try: try:
self._commit_puts() self._commit_puts()
@ -897,27 +899,36 @@ class ContainerBroker(DatabaseBroker):
if not self.stale_reads_ok: if not self.stale_reads_ok:
raise raise
with self.get() as conn: with self.get() as conn:
try: data = None
return conn.execute(''' trailing1 = 'metadata'
SELECT account, container, created_at, put_timestamp, trailing2 = 'x_container_sync_point1, x_container_sync_point2'
delete_timestamp, object_count, bytes_used, while not data:
reported_put_timestamp, reported_delete_timestamp, try:
reported_object_count, reported_bytes_used, hash, id, data = conn.execute('''
x_container_sync_point1, x_container_sync_point2 SELECT account, container, created_at, put_timestamp,
FROM container_stat delete_timestamp, object_count, bytes_used,
''').fetchone() reported_put_timestamp, reported_delete_timestamp,
except sqlite3.OperationalError, err: reported_object_count, reported_bytes_used, hash,
if 'no such column: x_container_sync_point' not in str(err): id, %s, %s
raise FROM container_stat
return conn.execute(''' ''' % (trailing1, trailing2)).fetchone()
SELECT account, container, created_at, put_timestamp, except sqlite3.OperationalError, err:
delete_timestamp, object_count, bytes_used, if 'no such column: metadata' in str(err):
reported_put_timestamp, reported_delete_timestamp, trailing1 = "'' as metadata"
reported_object_count, reported_bytes_used, hash, id, elif 'no such column: x_container_sync_point' in str(err):
-1 AS x_container_sync_point1, trailing2 = '-1 AS x_container_sync_point1, ' \
-1 AS x_container_sync_point2 '-1 AS x_container_sync_point2'
FROM container_stat else:
''').fetchone() raise
data = dict(data)
if include_metadata:
try:
data['metadata'] = json.loads(data.get('metadata', ''))
except ValueError:
data['metadata'] = {}
elif 'metadata' in data:
del data['metadata']
return data
def set_x_container_sync_points(self, sync_point1, sync_point2): def set_x_container_sync_points(self, sync_point1, sync_point2):
with self.get() as conn: with self.get() as conn:
@ -1449,9 +1460,9 @@ class AccountBroker(DatabaseBroker):
""" """
Get global data for the account. Get global data for the account.
:returns: sqlite.row of (account, created_at, put_timestamp, :returns: dict with keys: account, created_at, put_timestamp,
delete_timestamp, container_count, object_count, delete_timestamp, container_count, object_count,
bytes_used, hash, id) bytes_used, hash, id
""" """
try: try:
self._commit_puts() self._commit_puts()
@ -1459,11 +1470,11 @@ class AccountBroker(DatabaseBroker):
if not self.stale_reads_ok: if not self.stale_reads_ok:
raise raise
with self.get() as conn: with self.get() as conn:
return conn.execute(''' return dict(conn.execute('''
SELECT account, created_at, put_timestamp, delete_timestamp, SELECT account, created_at, put_timestamp, delete_timestamp,
container_count, object_count, bytes_used, hash, id container_count, object_count, bytes_used, hash, id
FROM account_stat FROM account_stat
''').fetchone() ''').fetchone())
def list_containers_iter(self, limit, marker, end_marker, prefix, def list_containers_iter(self, limit, marker, end_marker, prefix,
delimiter): delimiter):

View File

@ -270,7 +270,7 @@ class StaticWeb(object):
:param start_response: The original WSGI start_response hook. :param start_response: The original WSGI start_response hook.
:param prefix: Any prefix desired for the container listing. :param prefix: Any prefix desired for the container listing.
""" """
if self._listings not in TRUE_VALUES: if self._listings.lower() not in TRUE_VALUES:
resp = HTTPNotFound()(env, self._start_response) resp = HTTPNotFound()(env, self._start_response)
return self._error_response(resp, env, start_response) return self._error_response(resp, env, start_response)
tmp_env = self._get_escalated_env(env) tmp_env = self._get_escalated_env(env)

View File

@ -72,7 +72,7 @@ if hash_conf.read('/etc/swift/swift.conf'):
pass pass
# Used when reading config values # Used when reading config values
TRUE_VALUES = set(('true', '1', 'yes', 'True', 'Yes', 'on', 'On', 't', 'y')) TRUE_VALUES = set(('true', '1', 'yes', 'on', 't', 'y'))
def validate_configuration(): def validate_configuration():

View File

@ -41,8 +41,8 @@ from webob.exc import HTTPAccepted, HTTPBadRequest, HTTPMethodNotAllowed, \
from webob import Request, Response from webob import Request, Response
from swift.common.ring import Ring from swift.common.ring import Ring
from swift.common.utils import get_logger, normalize_timestamp, split_path, \ from swift.common.utils import cache_from_env, ContextPool, get_logger, \
cache_from_env, ContextPool, get_remote_client get_remote_client, normalize_timestamp, split_path, TRUE_VALUES
from swift.common.bufferedhttp import http_connect from swift.common.bufferedhttp import http_connect
from swift.common.constraints import check_metadata, check_object_creation, \ from swift.common.constraints import check_metadata, check_object_creation, \
check_utf8, CONTAINER_LISTING_LIMIT, MAX_ACCOUNT_NAME_LENGTH, \ check_utf8, CONTAINER_LISTING_LIMIT, MAX_ACCOUNT_NAME_LENGTH, \
@ -338,7 +338,7 @@ class Controller(object):
node['errors'] = self.app.error_suppression_limit + 1 node['errors'] = self.app.error_suppression_limit + 1
node['last_error'] = time.time() node['last_error'] = time.time()
def account_info(self, account): def account_info(self, account, autocreate=False):
""" """
Get account information, and also verify that the account exists. Get account information, and also verify that the account exists.
@ -353,7 +353,7 @@ class Controller(object):
result_code = self.app.memcache.get(cache_key) result_code = self.app.memcache.get(cache_key)
if result_code == 200: if result_code == 200:
return partition, nodes return partition, nodes
elif result_code == 404: elif result_code == 404 and not autocreate:
return None, None return None, None
result_code = 0 result_code = 0
attempts_left = self.app.account_ring.replica_count attempts_left = self.app.account_ring.replica_count
@ -386,6 +386,17 @@ class Controller(object):
except (Exception, TimeoutError): except (Exception, TimeoutError):
self.exception_occurred(node, _('Account'), self.exception_occurred(node, _('Account'),
_('Trying to get account info for %s') % path) _('Trying to get account info for %s') % path)
if result_code == 404 and autocreate:
if len(account) > MAX_ACCOUNT_NAME_LENGTH:
return None, None
headers = {'X-Timestamp': normalize_timestamp(time.time()),
'X-Trans-Id': self.trans_id}
resp = self.make_requests(Request.blank('/v1' + path),
self.app.account_ring, partition, 'PUT',
path, [headers] * len(nodes))
if resp.status_int // 100 != 2:
raise Exception('Could not autocreate account %r' % path)
result_code = 200
if self.app.memcache and result_code in (200, 404): if self.app.memcache and result_code in (200, 404):
if result_code == 200: if result_code == 200:
cache_timeout = self.app.recheck_account_existence cache_timeout = self.app.recheck_account_existence
@ -397,7 +408,7 @@ class Controller(object):
return partition, nodes return partition, nodes
return None, None return None, None
def container_info(self, account, container): def container_info(self, account, container, account_autocreate=False):
""" """
Get container information and thusly verify container existance. Get container information and thusly verify container existance.
This will also make a call to account_info to verify that the This will also make a call to account_info to verify that the
@ -424,7 +435,7 @@ class Controller(object):
return partition, nodes, read_acl, write_acl, sync_key return partition, nodes, read_acl, write_acl, sync_key
elif status == 404: elif status == 404:
return None, None, None, None, None return None, None, None, None, None
if not self.account_info(account)[1]: if not self.account_info(account, autocreate=account_autocreate)[1]:
return None, None, None, None, None return None, None, None, None, None
result_code = 0 result_code = 0
read_acl = None read_acl = None
@ -864,7 +875,8 @@ class ObjectController(Controller):
if error_response: if error_response:
return error_response return error_response
container_partition, containers, _junk, req.acl, _junk = \ container_partition, containers, _junk, req.acl, _junk = \
self.container_info(self.account_name, self.container_name) self.container_info(self.account_name, self.container_name,
account_autocreate=self.app.account_autocreate)
if 'swift.authorize' in req.environ: if 'swift.authorize' in req.environ:
aresp = req.environ['swift.authorize'](req) aresp = req.environ['swift.authorize'](req)
if aresp: if aresp:
@ -922,7 +934,8 @@ class ObjectController(Controller):
"""HTTP PUT request handler.""" """HTTP PUT request handler."""
(container_partition, containers, _junk, req.acl, (container_partition, containers, _junk, req.acl,
req.environ['swift_sync_key']) = \ req.environ['swift_sync_key']) = \
self.container_info(self.account_name, self.container_name) self.container_info(self.account_name, self.container_name,
account_autocreate=self.app.account_autocreate)
if 'swift.authorize' in req.environ: if 'swift.authorize' in req.environ:
aresp = req.environ['swift.authorize'](req) aresp = req.environ['swift.authorize'](req)
if aresp: if aresp:
@ -1268,7 +1281,8 @@ class ContainerController(Controller):
resp.body = 'Container name length of %d longer than %d' % \ resp.body = 'Container name length of %d longer than %d' % \
(len(self.container_name), MAX_CONTAINER_NAME_LENGTH) (len(self.container_name), MAX_CONTAINER_NAME_LENGTH)
return resp return resp
account_partition, accounts = self.account_info(self.account_name) account_partition, accounts = self.account_info(self.account_name,
autocreate=self.app.account_autocreate)
if not accounts: if not accounts:
return HTTPNotFound(request=req) return HTTPNotFound(request=req)
container_partition, containers = self.app.container_ring.get_nodes( container_partition, containers = self.app.container_ring.get_nodes(
@ -1298,7 +1312,8 @@ class ContainerController(Controller):
self.clean_acls(req) or check_metadata(req, 'container') self.clean_acls(req) or check_metadata(req, 'container')
if error_response: if error_response:
return error_response return error_response
account_partition, accounts = self.account_info(self.account_name) account_partition, accounts = self.account_info(self.account_name,
autocreate=self.app.account_autocreate)
if not accounts: if not accounts:
return HTTPNotFound(request=req) return HTTPNotFound(request=req)
container_partition, containers = self.app.container_ring.get_nodes( container_partition, containers = self.app.container_ring.get_nodes(
@ -1440,7 +1455,7 @@ class BaseApplication(object):
self.put_queue_depth = int(conf.get('put_queue_depth', 10)) self.put_queue_depth = int(conf.get('put_queue_depth', 10))
self.object_chunk_size = int(conf.get('object_chunk_size', 65536)) self.object_chunk_size = int(conf.get('object_chunk_size', 65536))
self.client_chunk_size = int(conf.get('client_chunk_size', 65536)) self.client_chunk_size = int(conf.get('client_chunk_size', 65536))
self.log_headers = conf.get('log_headers') == 'True' self.log_headers = conf.get('log_headers', 'no').lower() in TRUE_VALUES
self.error_suppression_interval = \ self.error_suppression_interval = \
int(conf.get('error_suppression_interval', 60)) int(conf.get('error_suppression_interval', 60))
self.error_suppression_limit = \ self.error_suppression_limit = \
@ -1450,7 +1465,7 @@ class BaseApplication(object):
self.recheck_account_existence = \ self.recheck_account_existence = \
int(conf.get('recheck_account_existence', 60)) int(conf.get('recheck_account_existence', 60))
self.allow_account_management = \ self.allow_account_management = \
conf.get('allow_account_management', 'false').lower() == 'true' conf.get('allow_account_management', 'no').lower() in TRUE_VALUES
self.resellers_conf = ConfigParser() self.resellers_conf = ConfigParser()
self.resellers_conf.read(os.path.join(swift_dir, 'resellers.conf')) self.resellers_conf.read(os.path.join(swift_dir, 'resellers.conf'))
self.object_ring = object_ring or \ self.object_ring = object_ring or \
@ -1462,6 +1477,8 @@ class BaseApplication(object):
self.memcache = memcache self.memcache = memcache
mimetypes.init(mimetypes.knownfiles + mimetypes.init(mimetypes.knownfiles +
[os.path.join(swift_dir, 'mime.types')]) [os.path.join(swift_dir, 'mime.types')])
self.account_autocreate = \
conf.get('account_autocreate', 'no').lower() in TRUE_VALUES
def get_controller(self, path): def get_controller(self, path):
""" """

View File

@ -58,7 +58,10 @@ class DatabaseStatsCollector(Daemon):
(self.stats_type, (time.time() - start) / 60)) (self.stats_type, (time.time() - start) / 60))
def get_data(self): def get_data(self):
raise Exception('Not Implemented') raise NotImplementedError('Subclasses must override')
def get_header(self):
raise NotImplementedError('Subclasses must override')
def find_and_process(self): def find_and_process(self):
src_filename = time.strftime(self.filename_format) src_filename = time.strftime(self.filename_format)
@ -70,6 +73,7 @@ class DatabaseStatsCollector(Daemon):
hasher = hashlib.md5() hasher = hashlib.md5()
try: try:
with open(tmp_filename, 'wb') as statfile: with open(tmp_filename, 'wb') as statfile:
statfile.write(self.get_header())
for device in os.listdir(self.devices): for device in os.listdir(self.devices):
if self.mount_check and not check_mount(self.devices, if self.mount_check and not check_mount(self.devices,
device): device):
@ -122,6 +126,9 @@ class AccountStatsCollector(DatabaseStatsCollector):
info['bytes_used']) info['bytes_used'])
return line_data return line_data
def get_header(self):
return ''
class ContainerStatsCollector(DatabaseStatsCollector): class ContainerStatsCollector(DatabaseStatsCollector):
""" """
@ -133,20 +140,38 @@ class ContainerStatsCollector(DatabaseStatsCollector):
super(ContainerStatsCollector, self).__init__(stats_conf, 'container', super(ContainerStatsCollector, self).__init__(stats_conf, 'container',
container_server_data_dir, container_server_data_dir,
'container-stats-%Y%m%d%H_') 'container-stats-%Y%m%d%H_')
# webob calls title on all the header keys
self.metadata_keys = ['X-Container-Meta-%s' % mkey.strip().title()
for mkey in stats_conf.get('metadata_keys', '').split(',')
if mkey.strip()]
def get_header(self):
header = 'Account Hash,Container Name,Object Count,Bytes Used'
if self.metadata_keys:
xtra_headers = ','.join(self.metadata_keys)
header += ',%s' % xtra_headers
header += '\n'
return header
def get_data(self, db_path): def get_data(self, db_path):
""" """
Data for generated csv has the following columns: Data for generated csv has the following columns:
Account Hash, Container Name, Object Count, Bytes Used Account Hash, Container Name, Object Count, Bytes Used
This will just collect whether or not the metadata is set
using a 1 or ''.
""" """
line_data = None line_data = None
broker = ContainerBroker(db_path) broker = ContainerBroker(db_path)
if not broker.is_deleted(): if not broker.is_deleted():
info = broker.get_info() info = broker.get_info(include_metadata=bool(self.metadata_keys))
encoded_container_name = urllib.quote(info['container']) encoded_container_name = urllib.quote(info['container'])
line_data = '"%s","%s",%d,%d\n' % ( line_data = '"%s","%s",%d,%d' % (
info['account'], info['account'], encoded_container_name,
encoded_container_name, info['object_count'], info['bytes_used'])
info['object_count'], if self.metadata_keys:
info['bytes_used']) metadata_results = ','.join(
[info['metadata'].get(mkey) and '1' or ''
for mkey in self.metadata_keys])
line_data += ',%s' % metadata_results
line_data += '\n'
return line_data return line_data

View File

@ -69,7 +69,7 @@ class LogUploader(Daemon):
self.internal_proxy = InternalProxy(proxy_server_conf) self.internal_proxy = InternalProxy(proxy_server_conf)
self.new_log_cutoff = int(cutoff or self.new_log_cutoff = int(cutoff or
uploader_conf.get('new_log_cutoff', '7200')) uploader_conf.get('new_log_cutoff', '7200'))
self.unlink_log = uploader_conf.get('unlink_log', 'True').lower() in \ self.unlink_log = uploader_conf.get('unlink_log', 'true').lower() in \
utils.TRUE_VALUES utils.TRUE_VALUES
self.filename_pattern = regex or \ self.filename_pattern = regex or \
uploader_conf.get('source_filename_pattern', uploader_conf.get('source_filename_pattern',

View File

@ -788,6 +788,10 @@ log_name = yarr'''
['1.1.1.1', '2.2.2.2']), ['1.1.1.1', '2.2.2.2']),
None) None)
def test_TRUE_VALUES(self):
for v in utils.TRUE_VALUES:
self.assertEquals(v, v.lower())
if __name__ == '__main__': if __name__ == '__main__':
unittest.main() unittest.main()

View File

@ -393,6 +393,48 @@ class TestController(unittest.TestCase):
test(404, 507, 503) test(404, 507, 503)
test(503, 503, 503) test(503, 503, 503)
def test_account_info_account_autocreate(self):
with save_globals():
self.memcache.store = {}
proxy_server.http_connect = \
fake_http_connect(404, 404, 404, 201, 201, 201)
partition, nodes = \
self.controller.account_info(self.account, autocreate=False)
self.check_account_info_return(partition, nodes, is_none=True)
self.memcache.store = {}
proxy_server.http_connect = \
fake_http_connect(404, 404, 404, 201, 201, 201)
partition, nodes = \
self.controller.account_info(self.account)
self.check_account_info_return(partition, nodes, is_none=True)
self.memcache.store = {}
proxy_server.http_connect = \
fake_http_connect(404, 404, 404, 201, 201, 201)
partition, nodes = \
self.controller.account_info(self.account, autocreate=True)
self.check_account_info_return(partition, nodes)
self.memcache.store = {}
proxy_server.http_connect = \
fake_http_connect(404, 404, 404, 503, 201, 201)
partition, nodes = \
self.controller.account_info(self.account, autocreate=True)
self.check_account_info_return(partition, nodes)
self.memcache.store = {}
proxy_server.http_connect = \
fake_http_connect(404, 404, 404, 503, 201, 503)
exc = None
try:
partition, nodes = \
self.controller.account_info(self.account, autocreate=True)
except Exception, err:
exc = err
self.assertEquals(str(exc),
"Could not autocreate account '/some_account'")
def check_container_info_return(self, ret, is_none=False): def check_container_info_return(self, ret, is_none=False):
if is_none: if is_none:
partition, nodes, read_acl, write_acl = None, None, None, None partition, nodes, read_acl, write_acl = None, None, None, None
@ -406,7 +448,7 @@ class TestController(unittest.TestCase):
self.assertEqual(write_acl, ret[3]) self.assertEqual(write_acl, ret[3])
def test_container_info_invalid_account(self): def test_container_info_invalid_account(self):
def account_info(self, account): def account_info(self, account, autocreate=False):
return None, None return None, None
with save_globals(): with save_globals():
@ -417,7 +459,7 @@ class TestController(unittest.TestCase):
# tests if 200 is cached and used # tests if 200 is cached and used
def test_container_info_200(self): def test_container_info_200(self):
def account_info(self, account): def account_info(self, account, autocreate=False):
return True, True return True, True
with save_globals(): with save_globals():
@ -443,7 +485,7 @@ class TestController(unittest.TestCase):
# tests if 404 is cached and used # tests if 404 is cached and used
def test_container_info_404(self): def test_container_info_404(self):
def account_info(self, account): def account_info(self, account, autocreate=False):
return True, True return True, True
with save_globals(): with save_globals():

View File

@ -66,6 +66,16 @@ class TestDbStats(unittest.TestCase):
info = stat.get_data("%s/con.db" % self.containers) info = stat.get_data("%s/con.db" % self.containers)
self.assertEquals('''"test_acc","test_con",1,10\n''', info) self.assertEquals('''"test_acc","test_con",1,10\n''', info)
def test_container_stat_get_metadata(self):
stat = db_stats_collector.ContainerStatsCollector(self.conf)
container_db = ContainerBroker("%s/con.db" % self.containers,
account='test_acc', container='test_con')
container_db.initialize()
container_db.put_object('test_obj', time.time(), 10, 'text', 'faketag')
info = stat.get_data("%s/con.db" % self.containers)
self.assertEquals('''"test_acc","test_con",1,10\n''', info)
container_db.update_metadata({'test1': ('val', 1000)})
def _gen_account_stat(self): def _gen_account_stat(self):
stat = db_stats_collector.AccountStatsCollector(self.conf) stat = db_stats_collector.AccountStatsCollector(self.conf)
output_data = set() output_data = set()
@ -83,20 +93,61 @@ class TestDbStats(unittest.TestCase):
self.assertEqual(len(output_data), 10) self.assertEqual(len(output_data), 10)
return stat, output_data return stat, output_data
def _gen_container_stat(self): def _drop_metadata_col(self, broker, acc_name):
broker.conn.execute('''drop table container_stat''')
broker.conn.executescript("""
CREATE TABLE container_stat (
account TEXT DEFAULT '%s',
container TEXT DEFAULT 'test_con',
created_at TEXT,
put_timestamp TEXT DEFAULT '0',
delete_timestamp TEXT DEFAULT '0',
object_count INTEGER,
bytes_used INTEGER,
reported_put_timestamp TEXT DEFAULT '0',
reported_delete_timestamp TEXT DEFAULT '0',
reported_object_count INTEGER DEFAULT 0,
reported_bytes_used INTEGER DEFAULT 0,
hash TEXT default '00000000000000000000000000000000',
id TEXT,
status TEXT DEFAULT '',
status_changed_at TEXT DEFAULT '0'
);
INSERT INTO container_stat (object_count, bytes_used)
VALUES (1, 10);
""" % acc_name)
def _gen_container_stat(self, set_metadata=False, drop_metadata=False):
if set_metadata:
self.conf['metadata_keys'] = 'test1,test2'
# webob runs title on all headers
stat = db_stats_collector.ContainerStatsCollector(self.conf) stat = db_stats_collector.ContainerStatsCollector(self.conf)
output_data = set() output_data = set()
for i in range(10): for i in range(10):
account_db = ContainerBroker( cont_db = ContainerBroker(
"%s/container-stats-201001010%s-%s.db" % (self.containers, i, "%s/container-stats-201001010%s-%s.db" % (self.containers, i,
uuid.uuid4().hex), uuid.uuid4().hex),
account='test_acc_%s' % i, container='test_con') account='test_acc_%s' % i, container='test_con')
account_db.initialize() cont_db.initialize()
account_db.put_object('test_obj', time.time(), 10, 'text', cont_db.put_object('test_obj', time.time(), 10, 'text', 'faketag')
'faketag') metadata_output = ''
if set_metadata:
if i % 2:
cont_db.update_metadata({'X-Container-Meta-Test1': (5, 1)})
metadata_output = ',1,'
else:
cont_db.update_metadata({'X-Container-Meta-Test2': (7, 2)})
metadata_output = ',,1'
# this will "commit" the data # this will "commit" the data
account_db.get_info() cont_db.get_info()
output_data.add('''"test_acc_%s","test_con",1,10''' % i), if drop_metadata:
output_data.add('''"test_acc_%s","test_con",1,10,,''' % i)
else:
output_data.add('''"test_acc_%s","test_con",1,10%s''' %
(i, metadata_output))
if drop_metadata:
self._drop_metadata_col(cont_db, 'test_acc_%s' % i)
self.assertEqual(len(output_data), 10) self.assertEqual(len(output_data), 10)
return stat, output_data return stat, output_data
@ -112,6 +163,35 @@ class TestDbStats(unittest.TestCase):
self.assertEqual(len(output_data), 0) self.assertEqual(len(output_data), 0)
def test_account_stat_run_once_container_metadata(self):
stat, output_data = self._gen_container_stat(set_metadata=True)
stat.run_once()
stat_file = os.listdir(self.log_dir)[0]
with open(os.path.join(self.log_dir, stat_file)) as stat_handle:
headers = stat_handle.readline()
self.assert_(headers.startswith('Account Hash,Container Name,'))
for i in range(10):
data = stat_handle.readline()
output_data.discard(data.strip())
self.assertEqual(len(output_data), 0)
def test_account_stat_run_once_container_no_metadata(self):
stat, output_data = self._gen_container_stat(set_metadata=True,
drop_metadata=True)
stat.run_once()
stat_file = os.listdir(self.log_dir)[0]
with open(os.path.join(self.log_dir, stat_file)) as stat_handle:
headers = stat_handle.readline()
self.assert_(headers.startswith('Account Hash,Container Name,'))
for i in range(10):
data = stat_handle.readline()
output_data.discard(data.strip())
self.assertEqual(len(output_data), 0)
def test_account_stat_run_once_both(self): def test_account_stat_run_once_both(self):
acc_stat, acc_output_data = self._gen_account_stat() acc_stat, acc_output_data = self._gen_account_stat()
con_stat, con_output_data = self._gen_container_stat() con_stat, con_output_data = self._gen_container_stat()
@ -128,6 +208,8 @@ class TestDbStats(unittest.TestCase):
con_stat.run_once() con_stat.run_once()
stat_file = [f for f in os.listdir(self.log_dir) if f != stat_file][0] stat_file = [f for f in os.listdir(self.log_dir) if f != stat_file][0]
with open(os.path.join(self.log_dir, stat_file)) as stat_handle: with open(os.path.join(self.log_dir, stat_file)) as stat_handle:
headers = stat_handle.readline()
self.assert_(headers.startswith('Account Hash,Container Name,'))
for i in range(10): for i in range(10):
data = stat_handle.readline() data = stat_handle.readline()
con_output_data.discard(data.strip()) con_output_data.discard(data.strip())
@ -143,7 +225,8 @@ class TestDbStats(unittest.TestCase):
def test_not_implemented(self): def test_not_implemented(self):
db_stat = db_stats_collector.DatabaseStatsCollector(self.conf, db_stat = db_stats_collector.DatabaseStatsCollector(self.conf,
'account', 'test_dir', 'stats-%Y%m%d%H_') 'account', 'test_dir', 'stats-%Y%m%d%H_')
self.assertRaises(Exception, db_stat.get_data) self.assertRaises(NotImplementedError, db_stat.get_data)
self.assertRaises(NotImplementedError, db_stat.get_header)
def test_not_not_mounted(self): def test_not_not_mounted(self):
self.conf['mount_check'] = 'true' self.conf['mount_check'] = 'true'