Support last modified on listing containers
For now, last modified timestamp is supported only on object listing. (i.e. GET container) For example: GET container with json format results in like as: [{"hash": "d41d8cd98f00b204e9800998ecf8427e", "last_modified": "2015-06-10T04:58:23.460230", "bytes": 0, "name": "object", "content_type": "application/octet-stream"}] However, container listing (i.e. GET account) shows just a dict consists of ("name", "bytes", "name") for each container. For example: GET accounts with json format result in like as: [{"count": 0, "bytes": 0, "name": "container"}] This patch is for supporting last_modified key in the container listing results as well as object listing like as: [{"count": 0, "bytes": 0, "name": "container", "last_modified": "2015-06-10T04:58:23.460230"}] This patch is changing just output for listing. The original timestamp to show the last modified is already in container table of account.db as a "put_timestamp" column. Note that this patch *DOESN'T* change the put_timestamp semantics. i.e. the last_modified timestamp will be changed only at both PUT container and POST container. (PUT object doesn't affect the timestamp) Note that the tuple format of returning value from swift.account.backend.AccountBroker.list_containers is now (name, object_count, bytes_used, put_timestamp, 0) * put_timestamp is added * Original discussion was in working session at Vancouver Summit. Etherpads are around here: https://etherpad.openstack.org/p/liberty-swift-contributors-meetup https://etherpad.openstack.org/p/liberty-container-listing-update DocImpact Change-Id: Iba0503916f1481a20c59ae9136436f40183e4c5b
This commit is contained in:
parent
436374e66c
commit
652276fea6
@ -2,11 +2,13 @@
|
|||||||
{
|
{
|
||||||
"count": 0,
|
"count": 0,
|
||||||
"bytes": 0,
|
"bytes": 0,
|
||||||
"name": "janeausten"
|
"name": "janeausten",
|
||||||
|
"last_modified": "2013-11-19T20:08:13.283452"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"count": 1,
|
"count": 1,
|
||||||
"bytes": 14,
|
"bytes": 14,
|
||||||
"name": "marktwain"
|
"name": "marktwain",
|
||||||
|
"last_modified": "2016-04-29T16:23:50.460230"
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
|
@ -4,10 +4,12 @@
|
|||||||
<name>janeausten</name>
|
<name>janeausten</name>
|
||||||
<count>0</count>
|
<count>0</count>
|
||||||
<bytes>0</bytes>
|
<bytes>0</bytes>
|
||||||
|
<last_modified>2013-11-19T20:08:13.283452</last_modified>
|
||||||
</container>
|
</container>
|
||||||
<container>
|
<container>
|
||||||
<name>marktwain</name>
|
<name>marktwain</name>
|
||||||
<count>1</count>
|
<count>1</count>
|
||||||
<bytes>14</bytes>
|
<bytes>14</bytes>
|
||||||
|
<last_modified>2016-04-29T16:23:50.460230</last_modified>
|
||||||
</container>
|
</container>
|
||||||
</account>
|
</account>
|
||||||
|
@ -379,7 +379,8 @@ class AccountBroker(DatabaseBroker):
|
|||||||
:param delimiter: delimiter for query
|
:param delimiter: delimiter for query
|
||||||
:param reverse: reverse the result order.
|
:param reverse: reverse the result order.
|
||||||
|
|
||||||
:returns: list of tuples of (name, object_count, bytes_used, 0)
|
:returns: list of tuples of (name, object_count, bytes_used,
|
||||||
|
put_timestamp, 0)
|
||||||
"""
|
"""
|
||||||
delim_force_gte = False
|
delim_force_gte = False
|
||||||
(marker, end_marker, prefix, delimiter) = utf8encode(
|
(marker, end_marker, prefix, delimiter) = utf8encode(
|
||||||
@ -397,7 +398,7 @@ class AccountBroker(DatabaseBroker):
|
|||||||
results = []
|
results = []
|
||||||
while len(results) < limit:
|
while len(results) < limit:
|
||||||
query = """
|
query = """
|
||||||
SELECT name, object_count, bytes_used, 0
|
SELECT name, object_count, bytes_used, put_timestamp, 0
|
||||||
FROM container
|
FROM container
|
||||||
WHERE """
|
WHERE """
|
||||||
query_args = []
|
query_args = []
|
||||||
@ -459,7 +460,7 @@ class AccountBroker(DatabaseBroker):
|
|||||||
delim_force_gte = True
|
delim_force_gte = True
|
||||||
dir_name = name[:end + 1]
|
dir_name = name[:end + 1]
|
||||||
if dir_name != orig_marker:
|
if dir_name != orig_marker:
|
||||||
results.append([dir_name, 0, 0, 1])
|
results.append([dir_name, 0, 0, '0', 1])
|
||||||
curs.close()
|
curs.close()
|
||||||
break
|
break
|
||||||
results.append(row)
|
results.append(row)
|
||||||
|
@ -268,7 +268,7 @@ class AccountReaper(Daemon):
|
|||||||
if not containers:
|
if not containers:
|
||||||
break
|
break
|
||||||
try:
|
try:
|
||||||
for (container, _junk, _junk, _junk) in containers:
|
for (container, _junk, _junk, _junk, _junk) in containers:
|
||||||
this_shard = int(md5(container).hexdigest(), 16) % \
|
this_shard = int(md5(container).hexdigest(), 16) % \
|
||||||
len(nodes)
|
len(nodes)
|
||||||
if container_shard not in (this_shard, None):
|
if container_shard not in (this_shard, None):
|
||||||
|
@ -81,24 +81,30 @@ def account_listing_response(account, req, response_content_type, broker=None,
|
|||||||
prefix, delimiter, reverse)
|
prefix, delimiter, reverse)
|
||||||
if response_content_type == 'application/json':
|
if response_content_type == 'application/json':
|
||||||
data = []
|
data = []
|
||||||
for (name, object_count, bytes_used, is_subdir) in account_list:
|
for (name, object_count, bytes_used, put_timestamp, is_subdir) \
|
||||||
|
in account_list:
|
||||||
if is_subdir:
|
if is_subdir:
|
||||||
data.append({'subdir': name})
|
data.append({'subdir': name})
|
||||||
else:
|
else:
|
||||||
data.append({'name': name, 'count': object_count,
|
data.append(
|
||||||
'bytes': bytes_used})
|
{'name': name, 'count': object_count,
|
||||||
|
'bytes': bytes_used,
|
||||||
|
'last_modified': Timestamp(put_timestamp).isoformat})
|
||||||
account_list = json.dumps(data)
|
account_list = json.dumps(data)
|
||||||
elif response_content_type.endswith('/xml'):
|
elif response_content_type.endswith('/xml'):
|
||||||
output_list = ['<?xml version="1.0" encoding="UTF-8"?>',
|
output_list = ['<?xml version="1.0" encoding="UTF-8"?>',
|
||||||
'<account name=%s>' % saxutils.quoteattr(account)]
|
'<account name=%s>' % saxutils.quoteattr(account)]
|
||||||
for (name, object_count, bytes_used, is_subdir) in account_list:
|
for (name, object_count, bytes_used, put_timestamp, is_subdir) \
|
||||||
|
in account_list:
|
||||||
if is_subdir:
|
if is_subdir:
|
||||||
output_list.append(
|
output_list.append(
|
||||||
'<subdir name=%s />' % saxutils.quoteattr(name))
|
'<subdir name=%s />' % saxutils.quoteattr(name))
|
||||||
else:
|
else:
|
||||||
item = '<container><name>%s</name><count>%s</count>' \
|
item = '<container><name>%s</name><count>%s</count>' \
|
||||||
'<bytes>%s</bytes></container>' % \
|
'<bytes>%s</bytes><last_modified>%s</last_modified>' \
|
||||||
(saxutils.escape(name), object_count, bytes_used)
|
'</container>' % \
|
||||||
|
(saxutils.escape(name), object_count,
|
||||||
|
bytes_used, Timestamp(put_timestamp).isoformat)
|
||||||
output_list.append(item)
|
output_list.append(item)
|
||||||
output_list.append('</account>')
|
output_list.append('</account>')
|
||||||
account_list = '\n'.join(output_list)
|
account_list = '\n'.join(output_list)
|
||||||
|
@ -439,7 +439,7 @@ class Account(Base):
|
|||||||
tree = minidom.parseString(self.conn.response.read())
|
tree = minidom.parseString(self.conn.response.read())
|
||||||
for x in tree.getElementsByTagName('container'):
|
for x in tree.getElementsByTagName('container'):
|
||||||
cont = {}
|
cont = {}
|
||||||
for key in ['name', 'count', 'bytes']:
|
for key in ['name', 'count', 'bytes', 'last_modified']:
|
||||||
cont[key] = x.getElementsByTagName(key)[0].\
|
cont[key] = x.getElementsByTagName(key)[0].\
|
||||||
childNodes[0].nodeValue
|
childNodes[0].nodeValue
|
||||||
conts.append(cont)
|
conts.append(cont)
|
||||||
|
@ -28,6 +28,7 @@ from copy import deepcopy
|
|||||||
import eventlet
|
import eventlet
|
||||||
from unittest2 import SkipTest
|
from unittest2 import SkipTest
|
||||||
from swift.common.http import is_success, is_client_error
|
from swift.common.http import is_success, is_client_error
|
||||||
|
from email.utils import parsedate
|
||||||
|
|
||||||
from test.functional import normalized_urls, load_constraint, cluster_info
|
from test.functional import normalized_urls, load_constraint, cluster_info
|
||||||
from test.functional import check_response, retry
|
from test.functional import check_response, retry
|
||||||
@ -299,6 +300,33 @@ class TestAccount(Base):
|
|||||||
results = [r for r in results if r in expected]
|
results = [r for r in results if r in expected]
|
||||||
self.assertEqual(expected, results)
|
self.assertEqual(expected, results)
|
||||||
|
|
||||||
|
def testContainerListingLastModified(self):
|
||||||
|
expected = {}
|
||||||
|
for container in self.env.containers:
|
||||||
|
res = container.info()
|
||||||
|
expected[container.name] = time.mktime(
|
||||||
|
parsedate(res['last_modified']))
|
||||||
|
|
||||||
|
for format_type in ['json', 'xml']:
|
||||||
|
actual = {}
|
||||||
|
containers = self.env.account.containers(
|
||||||
|
parms={'format': format_type})
|
||||||
|
if isinstance(containers[0], dict):
|
||||||
|
for container in containers:
|
||||||
|
self.assertIn('name', container) # sanity
|
||||||
|
self.assertIn('last_modified', container) # sanity
|
||||||
|
# ceil by hand (wants easier way!)
|
||||||
|
datetime_str, micro_sec_str = \
|
||||||
|
container['last_modified'].split('.')
|
||||||
|
timestamp = time.mktime(
|
||||||
|
time.strptime(datetime_str,
|
||||||
|
"%Y-%m-%dT%H:%M:%S"))
|
||||||
|
if int(micro_sec_str):
|
||||||
|
timestamp += 1
|
||||||
|
actual[container['name']] = timestamp
|
||||||
|
|
||||||
|
self.assertEqual(expected, actual)
|
||||||
|
|
||||||
def testInvalidAuthToken(self):
|
def testInvalidAuthToken(self):
|
||||||
hdrs = {'X-Auth-Token': 'bogus_auth_token'}
|
hdrs = {'X-Auth-Token': 'bogus_auth_token'}
|
||||||
self.assertRaises(ResponseError, self.env.account.info, hdrs=hdrs)
|
self.assertRaises(ResponseError, self.env.account.info, hdrs=hdrs)
|
||||||
|
@ -1225,18 +1225,19 @@ class TestAccountBrokerBeforeSPI(TestAccountBroker):
|
|||||||
'from container table!')
|
'from container table!')
|
||||||
|
|
||||||
# manually insert an existing row to avoid migration
|
# manually insert an existing row to avoid migration
|
||||||
|
timestamp = Timestamp(time()).internal
|
||||||
with broker.get() as conn:
|
with broker.get() as conn:
|
||||||
conn.execute('''
|
conn.execute('''
|
||||||
INSERT INTO container (name, put_timestamp,
|
INSERT INTO container (name, put_timestamp,
|
||||||
delete_timestamp, object_count, bytes_used,
|
delete_timestamp, object_count, bytes_used,
|
||||||
deleted)
|
deleted)
|
||||||
VALUES (?, ?, ?, ?, ?, ?)
|
VALUES (?, ?, ?, ?, ?, ?)
|
||||||
''', ('test_name', Timestamp(time()).internal, 0, 1, 2, 0))
|
''', ('test_name', timestamp, 0, 1, 2, 0))
|
||||||
conn.commit()
|
conn.commit()
|
||||||
|
|
||||||
# make sure we can iter containers without the migration
|
# make sure we can iter containers without the migration
|
||||||
for c in broker.list_containers_iter(1, None, None, None, None):
|
for c in broker.list_containers_iter(1, None, None, None, None):
|
||||||
self.assertEqual(c, ('test_name', 1, 2, 0))
|
self.assertEqual(c, ('test_name', 1, 2, timestamp, 0))
|
||||||
|
|
||||||
# stats table is mysteriously empty...
|
# stats table is mysteriously empty...
|
||||||
stats = broker.get_policy_stats()
|
stats = broker.get_policy_stats()
|
||||||
@ -1363,6 +1364,7 @@ class TestAccountBrokerBeforeSPI(TestAccountBroker):
|
|||||||
new_broker = AccountBroker(os.path.join(tempdir, 'new_account.db'),
|
new_broker = AccountBroker(os.path.join(tempdir, 'new_account.db'),
|
||||||
account='a')
|
account='a')
|
||||||
new_broker.initialize(next(ts).internal)
|
new_broker.initialize(next(ts).internal)
|
||||||
|
timestamp = next(ts).internal
|
||||||
|
|
||||||
# manually insert an existing row to avoid migration for old database
|
# manually insert an existing row to avoid migration for old database
|
||||||
with old_broker.get() as conn:
|
with old_broker.get() as conn:
|
||||||
@ -1371,7 +1373,7 @@ class TestAccountBrokerBeforeSPI(TestAccountBroker):
|
|||||||
delete_timestamp, object_count, bytes_used,
|
delete_timestamp, object_count, bytes_used,
|
||||||
deleted)
|
deleted)
|
||||||
VALUES (?, ?, ?, ?, ?, ?)
|
VALUES (?, ?, ?, ?, ?, ?)
|
||||||
''', ('test_name', next(ts).internal, 0, 1, 2, 0))
|
''', ('test_name', timestamp, 0, 1, 2, 0))
|
||||||
conn.commit()
|
conn.commit()
|
||||||
|
|
||||||
# get replication info and rows form old database
|
# get replication info and rows form old database
|
||||||
@ -1384,7 +1386,7 @@ class TestAccountBrokerBeforeSPI(TestAccountBroker):
|
|||||||
# make sure "test_name" container in new database
|
# make sure "test_name" container in new database
|
||||||
self.assertEqual(new_broker.get_info()['container_count'], 1)
|
self.assertEqual(new_broker.get_info()['container_count'], 1)
|
||||||
for c in new_broker.list_containers_iter(1, None, None, None, None):
|
for c in new_broker.list_containers_iter(1, None, None, None, None):
|
||||||
self.assertEqual(c, ('test_name', 1, 2, 0))
|
self.assertEqual(c, ('test_name', 1, 2, timestamp, 0))
|
||||||
|
|
||||||
# full migration successful
|
# full migration successful
|
||||||
with new_broker.get() as conn:
|
with new_broker.get() as conn:
|
||||||
|
@ -87,7 +87,7 @@ class FakeAccountBroker(object):
|
|||||||
|
|
||||||
def list_containers_iter(self, *args):
|
def list_containers_iter(self, *args):
|
||||||
for cont in self.containers:
|
for cont in self.containers:
|
||||||
yield cont, None, None, None
|
yield cont, None, None, None, None
|
||||||
|
|
||||||
def is_status_deleted(self):
|
def is_status_deleted(self):
|
||||||
return True
|
return True
|
||||||
@ -749,7 +749,7 @@ class TestReaper(unittest.TestCase):
|
|||||||
if container in self.containers_yielded:
|
if container in self.containers_yielded:
|
||||||
continue
|
continue
|
||||||
|
|
||||||
yield container, None, None, None
|
yield container, None, None, None, None
|
||||||
self.containers_yielded.append(container)
|
self.containers_yielded.append(container)
|
||||||
|
|
||||||
def fake_reap_container(self, account, account_partition,
|
def fake_reap_container(self, account, account_partition,
|
||||||
|
@ -34,7 +34,7 @@ from swift.common.swob import (Request, WsgiBytesIO, HTTPNoContent)
|
|||||||
from swift.common import constraints
|
from swift.common import constraints
|
||||||
from swift.account.server import AccountController
|
from swift.account.server import AccountController
|
||||||
from swift.common.utils import (normalize_timestamp, replication, public,
|
from swift.common.utils import (normalize_timestamp, replication, public,
|
||||||
mkdirs, storage_directory)
|
mkdirs, storage_directory, Timestamp)
|
||||||
from swift.common.request_helpers import get_sys_meta_prefix
|
from swift.common.request_helpers import get_sys_meta_prefix
|
||||||
from test.unit import patch_policies, debug_logger
|
from test.unit import patch_policies, debug_logger
|
||||||
from swift.common.storage_policy import StoragePolicy, POLICIES
|
from swift.common.storage_policy import StoragePolicy, POLICIES
|
||||||
@ -847,18 +847,21 @@ class TestAccountController(unittest.TestCase):
|
|||||||
self.assertEqual(resp.charset, 'utf-8')
|
self.assertEqual(resp.charset, 'utf-8')
|
||||||
|
|
||||||
def test_GET_with_containers_json(self):
|
def test_GET_with_containers_json(self):
|
||||||
|
put_timestamps = {}
|
||||||
req = Request.blank('/sda1/p/a', environ={'REQUEST_METHOD': 'PUT',
|
req = Request.blank('/sda1/p/a', environ={'REQUEST_METHOD': 'PUT',
|
||||||
'HTTP_X_TIMESTAMP': '0'})
|
'HTTP_X_TIMESTAMP': '0'})
|
||||||
req.get_response(self.controller)
|
req.get_response(self.controller)
|
||||||
|
put_timestamps['c1'] = normalize_timestamp(1)
|
||||||
req = Request.blank('/sda1/p/a/c1', environ={'REQUEST_METHOD': 'PUT'},
|
req = Request.blank('/sda1/p/a/c1', environ={'REQUEST_METHOD': 'PUT'},
|
||||||
headers={'X-Put-Timestamp': '1',
|
headers={'X-Put-Timestamp': put_timestamps['c1'],
|
||||||
'X-Delete-Timestamp': '0',
|
'X-Delete-Timestamp': '0',
|
||||||
'X-Object-Count': '0',
|
'X-Object-Count': '0',
|
||||||
'X-Bytes-Used': '0',
|
'X-Bytes-Used': '0',
|
||||||
'X-Timestamp': normalize_timestamp(0)})
|
'X-Timestamp': normalize_timestamp(0)})
|
||||||
req.get_response(self.controller)
|
req.get_response(self.controller)
|
||||||
|
put_timestamps['c2'] = normalize_timestamp(2)
|
||||||
req = Request.blank('/sda1/p/a/c2', environ={'REQUEST_METHOD': 'PUT'},
|
req = Request.blank('/sda1/p/a/c2', environ={'REQUEST_METHOD': 'PUT'},
|
||||||
headers={'X-Put-Timestamp': '2',
|
headers={'X-Put-Timestamp': put_timestamps['c2'],
|
||||||
'X-Delete-Timestamp': '0',
|
'X-Delete-Timestamp': '0',
|
||||||
'X-Object-Count': '0',
|
'X-Object-Count': '0',
|
||||||
'X-Bytes-Used': '0',
|
'X-Bytes-Used': '0',
|
||||||
@ -868,18 +871,23 @@ class TestAccountController(unittest.TestCase):
|
|||||||
environ={'REQUEST_METHOD': 'GET'})
|
environ={'REQUEST_METHOD': 'GET'})
|
||||||
resp = req.get_response(self.controller)
|
resp = req.get_response(self.controller)
|
||||||
self.assertEqual(resp.status_int, 200)
|
self.assertEqual(resp.status_int, 200)
|
||||||
self.assertEqual(json.loads(resp.body),
|
self.assertEqual(
|
||||||
[{'count': 0, 'bytes': 0, 'name': 'c1'},
|
json.loads(resp.body),
|
||||||
{'count': 0, 'bytes': 0, 'name': 'c2'}])
|
[{'count': 0, 'bytes': 0, 'name': 'c1',
|
||||||
|
'last_modified': Timestamp(put_timestamps['c1']).isoformat},
|
||||||
|
{'count': 0, 'bytes': 0, 'name': 'c2',
|
||||||
|
'last_modified': Timestamp(put_timestamps['c2']).isoformat}])
|
||||||
|
put_timestamps['c1'] = normalize_timestamp(3)
|
||||||
req = Request.blank('/sda1/p/a/c1', environ={'REQUEST_METHOD': 'PUT'},
|
req = Request.blank('/sda1/p/a/c1', environ={'REQUEST_METHOD': 'PUT'},
|
||||||
headers={'X-Put-Timestamp': '1',
|
headers={'X-Put-Timestamp': put_timestamps['c1'],
|
||||||
'X-Delete-Timestamp': '0',
|
'X-Delete-Timestamp': '0',
|
||||||
'X-Object-Count': '1',
|
'X-Object-Count': '1',
|
||||||
'X-Bytes-Used': '2',
|
'X-Bytes-Used': '2',
|
||||||
'X-Timestamp': normalize_timestamp(0)})
|
'X-Timestamp': normalize_timestamp(0)})
|
||||||
req.get_response(self.controller)
|
req.get_response(self.controller)
|
||||||
|
put_timestamps['c2'] = normalize_timestamp(4)
|
||||||
req = Request.blank('/sda1/p/a/c2', environ={'REQUEST_METHOD': 'PUT'},
|
req = Request.blank('/sda1/p/a/c2', environ={'REQUEST_METHOD': 'PUT'},
|
||||||
headers={'X-Put-Timestamp': '2',
|
headers={'X-Put-Timestamp': put_timestamps['c2'],
|
||||||
'X-Delete-Timestamp': '0',
|
'X-Delete-Timestamp': '0',
|
||||||
'X-Object-Count': '3',
|
'X-Object-Count': '3',
|
||||||
'X-Bytes-Used': '4',
|
'X-Bytes-Used': '4',
|
||||||
@ -889,25 +897,31 @@ class TestAccountController(unittest.TestCase):
|
|||||||
environ={'REQUEST_METHOD': 'GET'})
|
environ={'REQUEST_METHOD': 'GET'})
|
||||||
resp = req.get_response(self.controller)
|
resp = req.get_response(self.controller)
|
||||||
self.assertEqual(resp.status_int, 200)
|
self.assertEqual(resp.status_int, 200)
|
||||||
self.assertEqual(json.loads(resp.body),
|
self.assertEqual(
|
||||||
[{'count': 1, 'bytes': 2, 'name': 'c1'},
|
json.loads(resp.body),
|
||||||
{'count': 3, 'bytes': 4, 'name': 'c2'}])
|
[{'count': 1, 'bytes': 2, 'name': 'c1',
|
||||||
|
'last_modified': Timestamp(put_timestamps['c1']).isoformat},
|
||||||
|
{'count': 3, 'bytes': 4, 'name': 'c2',
|
||||||
|
'last_modified': Timestamp(put_timestamps['c2']).isoformat}])
|
||||||
self.assertEqual(resp.content_type, 'application/json')
|
self.assertEqual(resp.content_type, 'application/json')
|
||||||
self.assertEqual(resp.charset, 'utf-8')
|
self.assertEqual(resp.charset, 'utf-8')
|
||||||
|
|
||||||
def test_GET_with_containers_xml(self):
|
def test_GET_with_containers_xml(self):
|
||||||
|
put_timestamps = {}
|
||||||
req = Request.blank('/sda1/p/a', environ={'REQUEST_METHOD': 'PUT',
|
req = Request.blank('/sda1/p/a', environ={'REQUEST_METHOD': 'PUT',
|
||||||
'HTTP_X_TIMESTAMP': '0'})
|
'HTTP_X_TIMESTAMP': '0'})
|
||||||
req.get_response(self.controller)
|
req.get_response(self.controller)
|
||||||
|
put_timestamps['c1'] = normalize_timestamp(1)
|
||||||
req = Request.blank('/sda1/p/a/c1', environ={'REQUEST_METHOD': 'PUT'},
|
req = Request.blank('/sda1/p/a/c1', environ={'REQUEST_METHOD': 'PUT'},
|
||||||
headers={'X-Put-Timestamp': '1',
|
headers={'X-Put-Timestamp': put_timestamps['c1'],
|
||||||
'X-Delete-Timestamp': '0',
|
'X-Delete-Timestamp': '0',
|
||||||
'X-Object-Count': '0',
|
'X-Object-Count': '0',
|
||||||
'X-Bytes-Used': '0',
|
'X-Bytes-Used': '0',
|
||||||
'X-Timestamp': normalize_timestamp(0)})
|
'X-Timestamp': normalize_timestamp(0)})
|
||||||
req.get_response(self.controller)
|
req.get_response(self.controller)
|
||||||
|
put_timestamps['c2'] = normalize_timestamp(2)
|
||||||
req = Request.blank('/sda1/p/a/c2', environ={'REQUEST_METHOD': 'PUT'},
|
req = Request.blank('/sda1/p/a/c2', environ={'REQUEST_METHOD': 'PUT'},
|
||||||
headers={'X-Put-Timestamp': '2',
|
headers={'X-Put-Timestamp': put_timestamps['c2'],
|
||||||
'X-Delete-Timestamp': '0',
|
'X-Delete-Timestamp': '0',
|
||||||
'X-Object-Count': '0',
|
'X-Object-Count': '0',
|
||||||
'X-Bytes-Used': '0',
|
'X-Bytes-Used': '0',
|
||||||
@ -926,24 +940,30 @@ class TestAccountController(unittest.TestCase):
|
|||||||
self.assertEqual(listing[0].nodeName, 'container')
|
self.assertEqual(listing[0].nodeName, 'container')
|
||||||
container = [n for n in listing[0].childNodes if n.nodeName != '#text']
|
container = [n for n in listing[0].childNodes if n.nodeName != '#text']
|
||||||
self.assertEqual(sorted([n.nodeName for n in container]),
|
self.assertEqual(sorted([n.nodeName for n in container]),
|
||||||
['bytes', 'count', 'name'])
|
['bytes', 'count', 'last_modified', 'name'])
|
||||||
node = [n for n in container if n.nodeName == 'name'][0]
|
node = [n for n in container if n.nodeName == 'name'][0]
|
||||||
self.assertEqual(node.firstChild.nodeValue, 'c1')
|
self.assertEqual(node.firstChild.nodeValue, 'c1')
|
||||||
node = [n for n in container if n.nodeName == 'count'][0]
|
node = [n for n in container if n.nodeName == 'count'][0]
|
||||||
self.assertEqual(node.firstChild.nodeValue, '0')
|
self.assertEqual(node.firstChild.nodeValue, '0')
|
||||||
node = [n for n in container if n.nodeName == 'bytes'][0]
|
node = [n for n in container if n.nodeName == 'bytes'][0]
|
||||||
self.assertEqual(node.firstChild.nodeValue, '0')
|
self.assertEqual(node.firstChild.nodeValue, '0')
|
||||||
|
node = [n for n in container if n.nodeName == 'last_modified'][0]
|
||||||
|
self.assertEqual(node.firstChild.nodeValue,
|
||||||
|
Timestamp(put_timestamps['c1']).isoformat)
|
||||||
self.assertEqual(listing[-1].nodeName, 'container')
|
self.assertEqual(listing[-1].nodeName, 'container')
|
||||||
container = \
|
container = \
|
||||||
[n for n in listing[-1].childNodes if n.nodeName != '#text']
|
[n for n in listing[-1].childNodes if n.nodeName != '#text']
|
||||||
self.assertEqual(sorted([n.nodeName for n in container]),
|
self.assertEqual(sorted([n.nodeName for n in container]),
|
||||||
['bytes', 'count', 'name'])
|
['bytes', 'count', 'last_modified', 'name'])
|
||||||
node = [n for n in container if n.nodeName == 'name'][0]
|
node = [n for n in container if n.nodeName == 'name'][0]
|
||||||
self.assertEqual(node.firstChild.nodeValue, 'c2')
|
self.assertEqual(node.firstChild.nodeValue, 'c2')
|
||||||
node = [n for n in container if n.nodeName == 'count'][0]
|
node = [n for n in container if n.nodeName == 'count'][0]
|
||||||
self.assertEqual(node.firstChild.nodeValue, '0')
|
self.assertEqual(node.firstChild.nodeValue, '0')
|
||||||
node = [n for n in container if n.nodeName == 'bytes'][0]
|
node = [n for n in container if n.nodeName == 'bytes'][0]
|
||||||
self.assertEqual(node.firstChild.nodeValue, '0')
|
self.assertEqual(node.firstChild.nodeValue, '0')
|
||||||
|
node = [n for n in container if n.nodeName == 'last_modified'][0]
|
||||||
|
self.assertEqual(node.firstChild.nodeValue,
|
||||||
|
Timestamp(put_timestamps['c2']).isoformat)
|
||||||
req = Request.blank('/sda1/p/a/c1', environ={'REQUEST_METHOD': 'PUT'},
|
req = Request.blank('/sda1/p/a/c1', environ={'REQUEST_METHOD': 'PUT'},
|
||||||
headers={'X-Put-Timestamp': '1',
|
headers={'X-Put-Timestamp': '1',
|
||||||
'X-Delete-Timestamp': '0',
|
'X-Delete-Timestamp': '0',
|
||||||
@ -970,24 +990,30 @@ class TestAccountController(unittest.TestCase):
|
|||||||
self.assertEqual(listing[0].nodeName, 'container')
|
self.assertEqual(listing[0].nodeName, 'container')
|
||||||
container = [n for n in listing[0].childNodes if n.nodeName != '#text']
|
container = [n for n in listing[0].childNodes if n.nodeName != '#text']
|
||||||
self.assertEqual(sorted([n.nodeName for n in container]),
|
self.assertEqual(sorted([n.nodeName for n in container]),
|
||||||
['bytes', 'count', 'name'])
|
['bytes', 'count', 'last_modified', 'name'])
|
||||||
node = [n for n in container if n.nodeName == 'name'][0]
|
node = [n for n in container if n.nodeName == 'name'][0]
|
||||||
self.assertEqual(node.firstChild.nodeValue, 'c1')
|
self.assertEqual(node.firstChild.nodeValue, 'c1')
|
||||||
node = [n for n in container if n.nodeName == 'count'][0]
|
node = [n for n in container if n.nodeName == 'count'][0]
|
||||||
self.assertEqual(node.firstChild.nodeValue, '1')
|
self.assertEqual(node.firstChild.nodeValue, '1')
|
||||||
node = [n for n in container if n.nodeName == 'bytes'][0]
|
node = [n for n in container if n.nodeName == 'bytes'][0]
|
||||||
self.assertEqual(node.firstChild.nodeValue, '2')
|
self.assertEqual(node.firstChild.nodeValue, '2')
|
||||||
|
node = [n for n in container if n.nodeName == 'last_modified'][0]
|
||||||
|
self.assertEqual(node.firstChild.nodeValue,
|
||||||
|
Timestamp(put_timestamps['c1']).isoformat)
|
||||||
self.assertEqual(listing[-1].nodeName, 'container')
|
self.assertEqual(listing[-1].nodeName, 'container')
|
||||||
container = [
|
container = [
|
||||||
n for n in listing[-1].childNodes if n.nodeName != '#text']
|
n for n in listing[-1].childNodes if n.nodeName != '#text']
|
||||||
self.assertEqual(sorted([n.nodeName for n in container]),
|
self.assertEqual(sorted([n.nodeName for n in container]),
|
||||||
['bytes', 'count', 'name'])
|
['bytes', 'count', 'last_modified', 'name'])
|
||||||
node = [n for n in container if n.nodeName == 'name'][0]
|
node = [n for n in container if n.nodeName == 'name'][0]
|
||||||
self.assertEqual(node.firstChild.nodeValue, 'c2')
|
self.assertEqual(node.firstChild.nodeValue, 'c2')
|
||||||
node = [n for n in container if n.nodeName == 'count'][0]
|
node = [n for n in container if n.nodeName == 'count'][0]
|
||||||
self.assertEqual(node.firstChild.nodeValue, '3')
|
self.assertEqual(node.firstChild.nodeValue, '3')
|
||||||
node = [n for n in container if n.nodeName == 'bytes'][0]
|
node = [n for n in container if n.nodeName == 'bytes'][0]
|
||||||
self.assertEqual(node.firstChild.nodeValue, '4')
|
self.assertEqual(node.firstChild.nodeValue, '4')
|
||||||
|
node = [n for n in container if n.nodeName == 'last_modified'][0]
|
||||||
|
self.assertEqual(node.firstChild.nodeValue,
|
||||||
|
Timestamp(put_timestamps['c2']).isoformat)
|
||||||
self.assertEqual(resp.charset, 'utf-8')
|
self.assertEqual(resp.charset, 'utf-8')
|
||||||
|
|
||||||
def test_GET_xml_escapes_account_name(self):
|
def test_GET_xml_escapes_account_name(self):
|
||||||
@ -1054,15 +1080,16 @@ class TestAccountController(unittest.TestCase):
|
|||||||
req = Request.blank('/sda1/p/a', environ={'REQUEST_METHOD': 'PUT',
|
req = Request.blank('/sda1/p/a', environ={'REQUEST_METHOD': 'PUT',
|
||||||
'HTTP_X_TIMESTAMP': '0'})
|
'HTTP_X_TIMESTAMP': '0'})
|
||||||
req.get_response(self.controller)
|
req.get_response(self.controller)
|
||||||
|
put_timestamp = normalize_timestamp(0)
|
||||||
for c in range(5):
|
for c in range(5):
|
||||||
req = Request.blank(
|
req = Request.blank(
|
||||||
'/sda1/p/a/c%d' % c,
|
'/sda1/p/a/c%d' % c,
|
||||||
environ={'REQUEST_METHOD': 'PUT'},
|
environ={'REQUEST_METHOD': 'PUT'},
|
||||||
headers={'X-Put-Timestamp': str(c + 1),
|
headers={'X-Put-Timestamp': put_timestamp,
|
||||||
'X-Delete-Timestamp': '0',
|
'X-Delete-Timestamp': '0',
|
||||||
'X-Object-Count': '2',
|
'X-Object-Count': '2',
|
||||||
'X-Bytes-Used': '3',
|
'X-Bytes-Used': '3',
|
||||||
'X-Timestamp': normalize_timestamp(0)})
|
'X-Timestamp': put_timestamp})
|
||||||
req.get_response(self.controller)
|
req.get_response(self.controller)
|
||||||
req = Request.blank('/sda1/p/a?limit=3',
|
req = Request.blank('/sda1/p/a?limit=3',
|
||||||
environ={'REQUEST_METHOD': 'GET'})
|
environ={'REQUEST_METHOD': 'GET'})
|
||||||
@ -1079,31 +1106,38 @@ class TestAccountController(unittest.TestCase):
|
|||||||
req = Request.blank('/sda1/p/a', environ={'REQUEST_METHOD': 'PUT',
|
req = Request.blank('/sda1/p/a', environ={'REQUEST_METHOD': 'PUT',
|
||||||
'HTTP_X_TIMESTAMP': '0'})
|
'HTTP_X_TIMESTAMP': '0'})
|
||||||
req.get_response(self.controller)
|
req.get_response(self.controller)
|
||||||
|
put_timestamp = normalize_timestamp(0)
|
||||||
for c in range(5):
|
for c in range(5):
|
||||||
req = Request.blank(
|
req = Request.blank(
|
||||||
'/sda1/p/a/c%d' % c,
|
'/sda1/p/a/c%d' % c,
|
||||||
environ={'REQUEST_METHOD': 'PUT'},
|
environ={'REQUEST_METHOD': 'PUT'},
|
||||||
headers={'X-Put-Timestamp': str(c + 1),
|
headers={'X-Put-Timestamp': put_timestamp,
|
||||||
'X-Delete-Timestamp': '0',
|
'X-Delete-Timestamp': '0',
|
||||||
'X-Object-Count': '2',
|
'X-Object-Count': '2',
|
||||||
'X-Bytes-Used': '3',
|
'X-Bytes-Used': '3',
|
||||||
'X-Timestamp': normalize_timestamp(0)})
|
'X-Timestamp': put_timestamp})
|
||||||
req.get_response(self.controller)
|
req.get_response(self.controller)
|
||||||
req = Request.blank('/sda1/p/a?limit=3&format=json',
|
req = Request.blank('/sda1/p/a?limit=3&format=json',
|
||||||
environ={'REQUEST_METHOD': 'GET'})
|
environ={'REQUEST_METHOD': 'GET'})
|
||||||
resp = req.get_response(self.controller)
|
resp = req.get_response(self.controller)
|
||||||
self.assertEqual(resp.status_int, 200)
|
self.assertEqual(resp.status_int, 200)
|
||||||
self.assertEqual(json.loads(resp.body),
|
timestamp_str = Timestamp(put_timestamp).isoformat
|
||||||
[{'count': 2, 'bytes': 3, 'name': 'c0'},
|
expected = [{'count': 2, 'bytes': 3, 'name': 'c0',
|
||||||
{'count': 2, 'bytes': 3, 'name': 'c1'},
|
'last_modified': timestamp_str},
|
||||||
{'count': 2, 'bytes': 3, 'name': 'c2'}])
|
{'count': 2, 'bytes': 3, 'name': 'c1',
|
||||||
|
'last_modified': timestamp_str},
|
||||||
|
{'count': 2, 'bytes': 3, 'name': 'c2',
|
||||||
|
'last_modified': timestamp_str}]
|
||||||
|
self.assertEqual(json.loads(resp.body), expected)
|
||||||
req = Request.blank('/sda1/p/a?limit=3&marker=c2&format=json',
|
req = Request.blank('/sda1/p/a?limit=3&marker=c2&format=json',
|
||||||
environ={'REQUEST_METHOD': 'GET'})
|
environ={'REQUEST_METHOD': 'GET'})
|
||||||
resp = req.get_response(self.controller)
|
resp = req.get_response(self.controller)
|
||||||
self.assertEqual(resp.status_int, 200)
|
self.assertEqual(resp.status_int, 200)
|
||||||
self.assertEqual(json.loads(resp.body),
|
expected = [{'count': 2, 'bytes': 3, 'name': 'c3',
|
||||||
[{'count': 2, 'bytes': 3, 'name': 'c3'},
|
'last_modified': timestamp_str},
|
||||||
{'count': 2, 'bytes': 3, 'name': 'c4'}])
|
{'count': 2, 'bytes': 3, 'name': 'c4',
|
||||||
|
'last_modified': timestamp_str}]
|
||||||
|
self.assertEqual(json.loads(resp.body), expected)
|
||||||
|
|
||||||
def test_GET_limit_marker_xml(self):
|
def test_GET_limit_marker_xml(self):
|
||||||
req = Request.blank('/sda1/p/a', environ={'REQUEST_METHOD': 'PUT',
|
req = Request.blank('/sda1/p/a', environ={'REQUEST_METHOD': 'PUT',
|
||||||
@ -1131,24 +1165,31 @@ class TestAccountController(unittest.TestCase):
|
|||||||
self.assertEqual(listing[0].nodeName, 'container')
|
self.assertEqual(listing[0].nodeName, 'container')
|
||||||
container = [n for n in listing[0].childNodes if n.nodeName != '#text']
|
container = [n for n in listing[0].childNodes if n.nodeName != '#text']
|
||||||
self.assertEqual(sorted([n.nodeName for n in container]),
|
self.assertEqual(sorted([n.nodeName for n in container]),
|
||||||
['bytes', 'count', 'name'])
|
['bytes', 'count', 'last_modified', 'name'])
|
||||||
node = [n for n in container if n.nodeName == 'name'][0]
|
node = [n for n in container if n.nodeName == 'name'][0]
|
||||||
self.assertEqual(node.firstChild.nodeValue, 'c0')
|
self.assertEqual(node.firstChild.nodeValue, 'c0')
|
||||||
node = [n for n in container if n.nodeName == 'count'][0]
|
node = [n for n in container if n.nodeName == 'count'][0]
|
||||||
self.assertEqual(node.firstChild.nodeValue, '2')
|
self.assertEqual(node.firstChild.nodeValue, '2')
|
||||||
node = [n for n in container if n.nodeName == 'bytes'][0]
|
node = [n for n in container if n.nodeName == 'bytes'][0]
|
||||||
self.assertEqual(node.firstChild.nodeValue, '3')
|
self.assertEqual(node.firstChild.nodeValue, '3')
|
||||||
|
node = [n for n in container if n.nodeName == 'last_modified'][0]
|
||||||
|
self.assertEqual(node.firstChild.nodeValue,
|
||||||
|
Timestamp('1').isoformat)
|
||||||
self.assertEqual(listing[-1].nodeName, 'container')
|
self.assertEqual(listing[-1].nodeName, 'container')
|
||||||
container = [
|
container = [
|
||||||
n for n in listing[-1].childNodes if n.nodeName != '#text']
|
n for n in listing[-1].childNodes if n.nodeName != '#text']
|
||||||
self.assertEqual(sorted([n.nodeName for n in container]),
|
self.assertEqual(sorted([n.nodeName for n in container]),
|
||||||
['bytes', 'count', 'name'])
|
['bytes', 'count', 'last_modified', 'name'])
|
||||||
|
node = [n for n in container if n.nodeName == 'name'][0]
|
||||||
node = [n for n in container if n.nodeName == 'name'][0]
|
node = [n for n in container if n.nodeName == 'name'][0]
|
||||||
self.assertEqual(node.firstChild.nodeValue, 'c2')
|
self.assertEqual(node.firstChild.nodeValue, 'c2')
|
||||||
node = [n for n in container if n.nodeName == 'count'][0]
|
node = [n for n in container if n.nodeName == 'count'][0]
|
||||||
self.assertEqual(node.firstChild.nodeValue, '2')
|
self.assertEqual(node.firstChild.nodeValue, '2')
|
||||||
node = [n for n in container if n.nodeName == 'bytes'][0]
|
node = [n for n in container if n.nodeName == 'bytes'][0]
|
||||||
self.assertEqual(node.firstChild.nodeValue, '3')
|
self.assertEqual(node.firstChild.nodeValue, '3')
|
||||||
|
node = [n for n in container if n.nodeName == 'last_modified'][0]
|
||||||
|
self.assertEqual(node.firstChild.nodeValue,
|
||||||
|
Timestamp('3').isoformat)
|
||||||
req = Request.blank('/sda1/p/a?limit=3&marker=c2&format=xml',
|
req = Request.blank('/sda1/p/a?limit=3&marker=c2&format=xml',
|
||||||
environ={'REQUEST_METHOD': 'GET'})
|
environ={'REQUEST_METHOD': 'GET'})
|
||||||
resp = req.get_response(self.controller)
|
resp = req.get_response(self.controller)
|
||||||
@ -1161,18 +1202,21 @@ class TestAccountController(unittest.TestCase):
|
|||||||
self.assertEqual(listing[0].nodeName, 'container')
|
self.assertEqual(listing[0].nodeName, 'container')
|
||||||
container = [n for n in listing[0].childNodes if n.nodeName != '#text']
|
container = [n for n in listing[0].childNodes if n.nodeName != '#text']
|
||||||
self.assertEqual(sorted([n.nodeName for n in container]),
|
self.assertEqual(sorted([n.nodeName for n in container]),
|
||||||
['bytes', 'count', 'name'])
|
['bytes', 'count', 'last_modified', 'name'])
|
||||||
node = [n for n in container if n.nodeName == 'name'][0]
|
node = [n for n in container if n.nodeName == 'name'][0]
|
||||||
self.assertEqual(node.firstChild.nodeValue, 'c3')
|
self.assertEqual(node.firstChild.nodeValue, 'c3')
|
||||||
node = [n for n in container if n.nodeName == 'count'][0]
|
node = [n for n in container if n.nodeName == 'count'][0]
|
||||||
self.assertEqual(node.firstChild.nodeValue, '2')
|
self.assertEqual(node.firstChild.nodeValue, '2')
|
||||||
node = [n for n in container if n.nodeName == 'bytes'][0]
|
node = [n for n in container if n.nodeName == 'bytes'][0]
|
||||||
self.assertEqual(node.firstChild.nodeValue, '3')
|
self.assertEqual(node.firstChild.nodeValue, '3')
|
||||||
|
node = [n for n in container if n.nodeName == 'last_modified'][0]
|
||||||
|
self.assertEqual(node.firstChild.nodeValue,
|
||||||
|
Timestamp('4').isoformat)
|
||||||
self.assertEqual(listing[-1].nodeName, 'container')
|
self.assertEqual(listing[-1].nodeName, 'container')
|
||||||
container = [
|
container = [
|
||||||
n for n in listing[-1].childNodes if n.nodeName != '#text']
|
n for n in listing[-1].childNodes if n.nodeName != '#text']
|
||||||
self.assertEqual(sorted([n.nodeName for n in container]),
|
self.assertEqual(sorted([n.nodeName for n in container]),
|
||||||
['bytes', 'count', 'name'])
|
['bytes', 'count', 'last_modified', 'name'])
|
||||||
node = [n for n in container if n.nodeName == 'name'][0]
|
node = [n for n in container if n.nodeName == 'name'][0]
|
||||||
self.assertEqual(node.firstChild.nodeValue, 'c4')
|
self.assertEqual(node.firstChild.nodeValue, 'c4')
|
||||||
node = [n for n in container if n.nodeName == 'count'][0]
|
node = [n for n in container if n.nodeName == 'count'][0]
|
||||||
|
Loading…
x
Reference in New Issue
Block a user