summaryrefslogtreecommitdiffstats
path: root/test/unit
diff options
context:
space:
mode:
authorThiago da Silva <thiago@redhat.com>2014-04-22 14:15:02 -0400
committerPrashanth Pai <ppai@redhat.com>2016-01-06 07:53:12 -0800
commit2a8f9f0f530327039c32e444b6a27130b12666bd (patch)
treee24e38b5b3c0245a0acafc63fc50bacbf7de718a /test/unit
parent4c6ca1db931377b75583f61a7bca262cfc27b0fa (diff)
Update repo
This is a squashed commit imported from this repo: https://github.com/openstack/swiftonfile/tree/icehouse Contains the follwing commits from above mentioned repo: eb50236 Merge "Backport: Fix metadata overall limits bug" into icehouse 79ea52a Backport: Fix metadata overall limits bug bc43f0b Fix inconsistent data being returned on GET ad0bb79 Import HTTPBadRequest from swift's module 74d02e6 Exclude .trashcan dir from container listing b2dbc15 Catch ESTALE in addition to ENOENT 8d60b48 Properly handle read_metadata() exceptions 6762fc6 Fix object server leaking file descriptors 2842e82 Fix API incompatibility in update_metadata() 2beeef6 Merge "Remove swiftkerbauth code" into icehouse 93dbcb5 Update object-expirer.conf with explanations c9d2f09 Merge "Check if /etc/swift exists in ring builder" into icehouse d66c14c Remove swiftkerbauth code 3142ed2 Add object expiration functests 97153d1 Merge "Cleanup functest and undo old patch" into icehouse bc234d0 Remove old travis config file and fix typo 260c8ef Check if /etc/swift exists in ring builder 637dac9 Cleanup functest and undo old patch 051e068 Merge pull request #35 from prashanthpai/backport-1 be104a3 Merge pull request #36 from prashanthpai/backport-2 ff76f42 fix issue with GET on large object (icehouse-backport) 04d0a99 Fix unlink call after successful rename 4c6ca1d updating README file with project name change 10b2680 Merge pull request #18 from thiagol11/icehouse 5bcab8f Updating version on __init__ file 5c2cba2 Merge pull request #15 from thiagol11/update_spec 52b00a8 updating spec file to add dependency on swift icehouse ae7c93b Merge pull request #6 from prashanthpai/rebase 191e55b Revert: allow non-root user to run functests cb7e968 Modify unit tests and func tests d23fd1b Sync with OpenStack Swift v1.13.1 b6d1671 Merge pull request #12 from pushpesh/functionalnosetestremove 962622b Merge pull request #8 from thiagol11/update_readme 4560857 Merge pull request #9 from prashanthpai/spec-expirer be0ae7e Minor update 65000f1 Removing functionalnosetests 8ab1069 Fix object-expirer.conf-gluster RPM build error afee30f added new support filesystem section 527b01f updated README.md to Swift-On-File 9a240c7 Merge pull request #3 from thiagol11/add_jenkins_to_travis 34b5a8b removing blank lines 3568b64 fixing missing fi d8f5b0f adding support to run jenkins triggered by travis 6f4a88c Removing functionalnosetests 8041944 Update README.md c015148 Merge pull request #2 from thiagol11/master 3ddd952 fixing travis file to run correct unit test c582669 adding travis status badge to README 8093096 adding py26 unit testing to travis 37835fd trigger travis build cb6332a adding travis ci testing All tests have been run sucessfully against this. tox -e p2p8,py27,functest Change-Id: I096b611da852d3eb3913844034b443b8272c2ac4 Signed-off-by: Prashanth Pai <ppai@redhat.com> Reviewed-on: http://review.gluster.org/13188
Diffstat (limited to 'test/unit')
-rw-r--r--test/unit/__init__.py39
-rw-r--r--test/unit/common/middleware/swiftkerbauth/__init__.py0
-rw-r--r--test/unit/common/middleware/swiftkerbauth/test_kerbauth.py478
-rw-r--r--test/unit/common/middleware/swiftkerbauth/test_kerbauth_utils.py77
-rw-r--r--test/unit/common/test_constraints.py73
-rw-r--r--test/unit/common/test_diskdir.py14
-rw-r--r--test/unit/common/test_utils.py24
-rw-r--r--test/unit/obj/test_diskfile.py55
-rw-r--r--test/unit/obj/test_expirer.py14
-rwxr-xr-xtest/unit/proxy/controllers/test_obj.py98
-rw-r--r--test/unit/proxy/test_server.py794
11 files changed, 721 insertions, 945 deletions
diff --git a/test/unit/__init__.py b/test/unit/__init__.py
index 847479a..a1bfef8 100644
--- a/test/unit/__init__.py
+++ b/test/unit/__init__.py
@@ -33,6 +33,7 @@ from hashlib import md5
from eventlet import sleep, Timeout
import logging.handlers
from httplib import HTTPException
+from numbers import Number
class FakeRing(object):
@@ -248,6 +249,7 @@ class FakeLogger(logging.Logger):
if 'facility' in kwargs:
self.facility = kwargs['facility']
self.statsd_client = None
+ self.thread_locals = None
def _clear(self):
self.log_dict = defaultdict(list)
@@ -465,8 +467,11 @@ def fake_http_connect(*code_iter, **kwargs):
self.body = body
self.headers = headers or {}
self.timestamp = timestamp
- if kwargs.get('slow') and isinstance(kwargs['slow'], list):
- kwargs['slow'][0] -= 1
+ if 'slow' in kwargs and isinstance(kwargs['slow'], list):
+ try:
+ self._next_sleep = kwargs['slow'].pop(0)
+ except IndexError:
+ self._next_sleep = None
def getresponse(self):
if kwargs.get('raise_exc'):
@@ -482,6 +487,8 @@ def fake_http_connect(*code_iter, **kwargs):
return FakeConn(507)
if self.expect_status == -4:
return FakeConn(201)
+ if self.expect_status == 412:
+ return FakeConn(412)
return FakeConn(100)
def getheaders(self):
@@ -510,31 +517,39 @@ def fake_http_connect(*code_iter, **kwargs):
headers['x-container-timestamp'] = '1'
except StopIteration:
pass
- if self.am_slow():
+ am_slow, value = self.get_slow()
+ if am_slow:
headers['content-length'] = '4'
headers.update(self.headers)
return headers.items()
- def am_slow(self):
- if kwargs.get('slow') and isinstance(kwargs['slow'], list):
- return kwargs['slow'][0] >= 0
- return bool(kwargs.get('slow'))
+ def get_slow(self):
+ if 'slow' in kwargs and isinstance(kwargs['slow'], list):
+ if self._next_sleep is not None:
+ return True, self._next_sleep
+ else:
+ return False, 0.01
+ if kwargs.get('slow') and isinstance(kwargs['slow'], Number):
+ return True, kwargs['slow']
+ return bool(kwargs.get('slow')), 0.1
def read(self, amt=None):
- if self.am_slow():
+ am_slow, value = self.get_slow()
+ if am_slow:
if self.sent < 4:
self.sent += 1
- sleep(0.1)
+ sleep(value)
return ' '
rv = self.body[:amt]
self.body = self.body[amt:]
return rv
def send(self, amt=None):
- if self.am_slow():
+ am_slow, value = self.get_slow()
+ if am_slow:
if self.received < 4:
self.received += 1
- sleep(0.1)
+ sleep(value)
def getheader(self, name, default=None):
return dict(self.getheaders()).get(name.lower(), default)
@@ -584,4 +599,6 @@ def fake_http_connect(*code_iter, **kwargs):
return FakeConn(status, etag, body=body, timestamp=timestamp,
expect_status=expect_status, headers=headers)
+ connect.code_iter = code_iter
+
return connect
diff --git a/test/unit/common/middleware/swiftkerbauth/__init__.py b/test/unit/common/middleware/swiftkerbauth/__init__.py
deleted file mode 100644
index e69de29..0000000
--- a/test/unit/common/middleware/swiftkerbauth/__init__.py
+++ /dev/null
diff --git a/test/unit/common/middleware/swiftkerbauth/test_kerbauth.py b/test/unit/common/middleware/swiftkerbauth/test_kerbauth.py
deleted file mode 100644
index 537b8d3..0000000
--- a/test/unit/common/middleware/swiftkerbauth/test_kerbauth.py
+++ /dev/null
@@ -1,478 +0,0 @@
-# Copyright (c) 2013 Red Hat, Inc.
-#
-# Licensed under the Apache License, Version 2.0 (the "License");
-# you may not use this file except in compliance with the License.
-# You may obtain a copy of the License at
-#
-# http://www.apache.org/licenses/LICENSE-2.0
-#
-# Unless required by applicable law or agreed to in writing, software
-# distributed under the License is distributed on an "AS IS" BASIS,
-# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
-# implied.
-# See the License for the specific language governing permissions and
-# limitations under the License.
-
-import os
-import errno
-import unittest
-from time import time
-from mock import patch, Mock
-from test.unit import FakeMemcache
-from swift.common.swob import Request, Response
-from gluster.swift.common.middleware.swiftkerbauth import kerbauth as auth
-
-EXT_AUTHENTICATION_URL = "127.0.0.1"
-REDIRECT_STATUS = 303 # HTTPSeeOther
-
-
-def my_filter_factory(global_conf, **local_conf):
- if 'ext_authentication_url' not in global_conf:
- global_conf['ext_authentication_url'] = EXT_AUTHENTICATION_URL
- conf = global_conf.copy()
- conf.update(local_conf)
-
- def auth_filter(app):
- return auth.KerbAuth(app, conf)
- return auth_filter
-
-# Monkey patching filter_factory to always pass ext_authentication_url
-# as a parameter. Absence of ext_authentication_url raises a RuntimeError
-
-
-def patch_filter_factory():
- auth.filter_factory = my_filter_factory
-
-
-def unpatch_filter_factory():
- reload(auth)
-
-
-class FakeApp(object):
-
- def __init__(self, status_headers_body_iter=None, acl=None, sync_key=None):
- self.calls = 0
- self.status_headers_body_iter = status_headers_body_iter
- if not self.status_headers_body_iter:
- self.status_headers_body_iter = iter([('404 Not Found', {}, '')])
- self.acl = acl
- self.sync_key = sync_key
-
- def __call__(self, env, start_response):
- self.calls += 1
- self.request = Request.blank('', environ=env)
- if self.acl:
- self.request.acl = self.acl
- if self.sync_key:
- self.request.environ['swift_sync_key'] = self.sync_key
- if 'swift.authorize' in env:
- resp = env['swift.authorize'](self.request)
- if resp:
- return resp(env, start_response)
- status, headers, body = self.status_headers_body_iter.next()
- return Response(status=status, headers=headers,
- body=body)(env, start_response)
-
-
-class TestKerbAuth(unittest.TestCase):
-
- # Patch auth.filter_factory()
- patch_filter_factory()
-
- def setUp(self):
- self.test_auth = \
- auth.filter_factory({'auth_method': 'active'})(FakeApp())
- self.test_auth_passive = \
- auth.filter_factory({'auth_method': 'passive'})(FakeApp())
-
- def _make_request(self, path, **kwargs):
- req = Request.blank(path, **kwargs)
- req.environ['swift.cache'] = FakeMemcache()
- return req
-
- def test_no_ext_authentication_url(self):
- app = FakeApp()
- try:
- # Use original auth.filter_factory and NOT monkey patched version
- unpatch_filter_factory()
- auth.filter_factory({})(app)
- except RuntimeError as e:
- # Restore monkey patched version
- patch_filter_factory()
- self.assertTrue(e.args[0].startswith("Missing filter parameter "
- "ext_authentication_url"))
-
- def test_reseller_prefix_init(self):
- app = FakeApp()
- ath = auth.filter_factory({})(app)
- self.assertEquals(ath.reseller_prefix, 'AUTH_')
- ath = auth.filter_factory({'reseller_prefix': 'TEST'})(app)
- self.assertEquals(ath.reseller_prefix, 'TEST_')
- ath = auth.filter_factory({'reseller_prefix': 'TEST_'})(app)
- self.assertEquals(ath.reseller_prefix, 'TEST_')
-
- def test_auth_prefix_init(self):
- app = FakeApp()
- ath = auth.filter_factory({})(app)
- self.assertEquals(ath.auth_prefix, '/auth/')
- ath = auth.filter_factory({'auth_prefix': ''})(app)
- self.assertEquals(ath.auth_prefix, '/auth/')
- ath = auth.filter_factory({'auth_prefix': '/'})(app)
- self.assertEquals(ath.auth_prefix, '/auth/')
- ath = auth.filter_factory({'auth_prefix': '/test/'})(app)
- self.assertEquals(ath.auth_prefix, '/test/')
- ath = auth.filter_factory({'auth_prefix': '/test'})(app)
- self.assertEquals(ath.auth_prefix, '/test/')
- ath = auth.filter_factory({'auth_prefix': 'test/'})(app)
- self.assertEquals(ath.auth_prefix, '/test/')
- ath = auth.filter_factory({'auth_prefix': 'test'})(app)
- self.assertEquals(ath.auth_prefix, '/test/')
-
- def test_top_level_redirect(self):
- req = self._make_request('/')
- resp = req.get_response(self.test_auth)
- self.assertEquals(resp.status_int, REDIRECT_STATUS)
- self.assertEquals(req.environ['swift.authorize'],
- self.test_auth.denied_response)
-
- def test_passive_top_level_deny(self):
- req = self._make_request('/')
- resp = req.get_response(self.test_auth_passive)
- self.assertEquals(resp.status_int, 401)
- self.assertEquals(req.environ['swift.authorize'],
- self.test_auth_passive.denied_response)
-
- def test_passive_deny_invalid_token(self):
- req = self._make_request('/v1/AUTH_account',
- headers={'X-Auth-Token': 'AUTH_t'})
- resp = req.get_response(self.test_auth_passive)
- self.assertEquals(resp.status_int, 401)
-
- def test_override_asked_for_and_allowed(self):
- self.test_auth = \
- auth.filter_factory({'allow_overrides': 'true'})(FakeApp())
- req = self._make_request('/v1/AUTH_account',
- environ={'swift.authorize_override': True})
- resp = req.get_response(self.test_auth)
- self.assertEquals(resp.status_int, 404)
- self.assertTrue('swift.authorize' not in req.environ)
-
- def test_override_default_allowed(self):
- req = self._make_request('/v1/AUTH_account',
- environ={'swift.authorize_override': True})
- resp = req.get_response(self.test_auth)
- self.assertEquals(resp.status_int, 404)
- self.assertTrue('swift.authorize' not in req.environ)
-
- def test_options_call(self):
- req = self._make_request('/v1/AUTH_cfa/c/o',
- environ={'REQUEST_METHOD': 'OPTIONS'})
- resp = self.test_auth.authorize(req)
- self.assertEquals(resp, None)
-
- def test_auth_deny_non_reseller_prefix_no_override(self):
- fake_authorize = lambda x: Response(status='500 Fake')
- req = self._make_request('/v1/BLAH_account',
- headers={'X-Auth-Token': 'BLAH_t'},
- environ={'swift.authorize': fake_authorize}
- )
- resp = req.get_response(self.test_auth)
- self.assertEquals(resp.status_int, 500)
- self.assertEquals(req.environ['swift.authorize'], fake_authorize)
-
- def test_authorize_acl_group_access(self):
- req = self._make_request('/v1/AUTH_cfa')
- req.remote_user = 'act:usr,act'
- resp = self.test_auth.authorize(req)
- self.assertEquals(resp.status_int, 403)
- req = self._make_request('/v1/AUTH_cfa')
- req.remote_user = 'act:usr,act'
- req.acl = 'act'
- self.assertEquals(self.test_auth.authorize(req), None)
- req = self._make_request('/v1/AUTH_cfa')
- req.remote_user = 'act:usr,act'
- req.acl = 'act:usr'
- self.assertEquals(self.test_auth.authorize(req), None)
- req = self._make_request('/v1/AUTH_cfa')
- req.remote_user = 'act:usr,act'
-
- def test_deny_cross_reseller(self):
- # Tests that cross-reseller is denied, even if ACLs/group names match
- req = self._make_request('/v1/OTHER_cfa')
- req.remote_user = 'act:usr,act,AUTH_cfa'
- req.acl = 'act'
- resp = self.test_auth.authorize(req)
- self.assertEquals(resp.status_int, 403)
-
- def test_authorize_acl_referer_after_user_groups(self):
- req = self._make_request('/v1/AUTH_cfa/c')
- req.remote_user = 'act:usr'
- req.acl = '.r:*,act:usr'
- self.assertEquals(self.test_auth.authorize(req), None)
-
- def test_detect_reseller_request(self):
- req = self._make_request('/v1/AUTH_admin',
- headers={'X-Auth-Token': 'AUTH_t'})
- cache_key = 'AUTH_/token/AUTH_t'
- cache_entry = (time() + 3600, '.reseller_admin')
- req.environ['swift.cache'].set(cache_key, cache_entry)
- req.get_response(self.test_auth)
- self.assertTrue(req.environ.get('reseller_request', False))
-
- def test_regular_is_not_owner(self):
- orig_authorize = self.test_auth.authorize
- owner_values = []
-
- def mitm_authorize(req):
- rv = orig_authorize(req)
- owner_values.append(req.environ.get('swift_owner', False))
- return rv
-
- self.test_auth.authorize = mitm_authorize
-
- req = self._make_request(
- '/v1/AUTH_cfa/c',
- headers={'X-Auth-Token': 'AUTH_t'})
- req.remote_user = 'act:usr'
- self.test_auth.authorize(req)
- self.assertEquals(owner_values, [False])
-
- def test_no_memcache(self):
- env = {'swift.cache': None}
- try:
- self.test_auth.get_groups(env, None)
- except Exception as e:
- self.assertTrue(e.args[0].startswith("Memcache required"))
-
- def test_handle_request(self):
- req = self._make_request('/auth/v1.0')
- resp = self.test_auth.handle_request(req)
- self.assertEquals(resp.status_int, REDIRECT_STATUS)
-
- def test_handle_request_bad_request(self):
- req = self._make_request('////')
- resp = self.test_auth.handle_request(req)
- self.assertEquals(resp.status_int, 404)
-
- def test_handle_request_no_handler(self):
- req = self._make_request('/blah/blah/blah/blah')
- resp = self.test_auth.handle_request(req)
- self.assertEquals(resp.status_int, 400)
-
- def test_handle_get_token_bad_request(self):
- req = self._make_request('/blah/blah')
- resp = self.test_auth.handle_get_token(req)
- self.assertEquals(resp.status_int, 400)
- req = self._make_request('/////')
- resp = self.test_auth.handle_get_token(req)
- self.assertEquals(resp.status_int, 404)
-
- def test_passive_handle_get_token_no_user_or_key(self):
- #No user and key
- req = self._make_request('/auth/v1.0')
- resp = self.test_auth_passive.handle_get_token(req)
- self.assertEquals(resp.status_int, REDIRECT_STATUS)
- #User given but no key
- req = self._make_request('/auth/v1.0',
- headers={'X-Auth-User': 'test:user'})
- resp = self.test_auth_passive.handle_get_token(req)
- self.assertEquals(resp.status_int, 401)
-
- def test_passive_handle_get_token_account_in_req_path(self):
- req = self._make_request('/v1/test/auth',
- headers={'X-Auth-User': 'test:user',
- 'X-Auth-Key': 'password'})
- _mock_run_kinit = Mock(return_value=0)
- _mock_get_groups = Mock(return_value="user,auth_test")
- with patch('gluster.swift.common.middleware.swiftkerbauth.kerbauth.run_kinit', _mock_run_kinit):
- with patch('gluster.swift.common.middleware.swiftkerbauth.kerbauth.get_groups_from_username',
- _mock_get_groups):
- resp = self.test_auth_passive.handle_get_token(req)
- _mock_run_kinit.assert_called_once_with('user', 'password')
- self.assertEquals(_mock_get_groups.call_count, 2)
- self.assertEquals(resp.status_int, 200)
- self.assertTrue(resp.headers['X-Auth-Token'] is not None)
- self.assertTrue(resp.headers['X-Storage-Token'] is not None)
- self.assertTrue(resp.headers['X-Storage-Url'] is not None)
-
- def test_passive_handle_get_token_user_invalid_or_no__account(self):
- #X-Auth-User not in acc:user format
- req = self._make_request('/auth/v1.0',
- headers={'X-Auth-User': 'user'})
- resp = self.test_auth_passive.handle_get_token(req)
- self.assertEquals(resp.status_int, 401)
- req = self._make_request('/v1/test/auth',
- headers={'X-Auth-User': 'user'})
- resp = self.test_auth_passive.handle_get_token(req)
- self.assertEquals(resp.status_int, 401)
- # Account name mismatch
- req = self._make_request('/v1/test/auth',
- headers={'X-Auth-User': 'wrongacc:user'})
- resp = self.test_auth_passive.handle_get_token(req)
- self.assertEquals(resp.status_int, 401)
-
- def test_passive_handle_get_token_no_kinit(self):
- req = self._make_request('/auth/v1.0',
- headers={'X-Auth-User': 'test:user',
- 'X-Auth-Key': 'password'})
- _mock_run_kinit = Mock(side_effect=OSError(errno.ENOENT,
- os.strerror(errno.ENOENT)))
- with patch('gluster.swift.common.middleware.swiftkerbauth.kerbauth.run_kinit', _mock_run_kinit):
- resp = self.test_auth_passive.handle_get_token(req)
- self.assertEquals(resp.status_int, 500)
- self.assertTrue("kinit command not found" in resp.body)
- _mock_run_kinit.assert_called_once_with('user', 'password')
-
- def test_passive_handle_get_token_kinit_fail(self):
- req = self._make_request('/auth/v1.0',
- headers={'X-Auth-User': 'test:user',
- 'X-Auth-Key': 'password'})
- _mock_run_kinit = Mock(return_value=1)
- with patch('gluster.swift.common.middleware.swiftkerbauth.kerbauth.run_kinit', _mock_run_kinit):
- resp = self.test_auth_passive.handle_get_token(req)
- self.assertEquals(resp.status_int, 401)
- _mock_run_kinit.assert_called_once_with('user', 'password')
-
- def test_passive_handle_get_token_kinit_success_token_not_present(self):
- req = self._make_request('/auth/v1.0',
- headers={'X-Auth-User': 'test:user',
- 'X-Auth-Key': 'password'})
- _mock_run_kinit = Mock(return_value=0)
- _mock_get_groups = Mock(return_value="user,auth_test")
- with patch('gluster.swift.common.middleware.swiftkerbauth.kerbauth.run_kinit', _mock_run_kinit):
- with patch('gluster.swift.common.middleware.swiftkerbauth.kerbauth.get_groups_from_username',
- _mock_get_groups):
- resp = self.test_auth_passive.handle_get_token(req)
- _mock_run_kinit.assert_called_once_with('user', 'password')
- self.assertEquals(_mock_get_groups.call_count, 2)
- self.assertEquals(resp.status_int, 200)
- self.assertTrue(resp.headers['X-Auth-Token'] is not None)
- self.assertTrue(resp.headers['X-Storage-Token'] is not None)
- self.assertTrue(resp.headers['X-Storage-Url'] is not None)
-
- def test_passive_handle_get_token_kinit_realm_and_memcache(self):
- req = self._make_request('/auth/v1.0',
- headers={'X-Auth-User': 'test:user',
- 'X-Auth-Key': 'password'})
- req.environ['swift.cache'] = None
- _auth_passive = \
- auth.filter_factory({'auth_method': 'passive',
- 'realm_name': 'EXAMPLE.COM'})(FakeApp())
- _mock_run_kinit = Mock(return_value=0)
- _mock_get_groups = Mock(return_value="user,auth_test")
- with patch('gluster.swift.common.middleware.swiftkerbauth.kerbauth.run_kinit', _mock_run_kinit):
- with patch('gluster.swift.common.middleware.swiftkerbauth.kerbauth.get_groups_from_username',
- _mock_get_groups):
- try:
- _auth_passive.handle_get_token(req)
- except Exception as e:
- self.assertTrue(e.args[0].startswith("Memcache "
- "required"))
- else:
- self.fail("Expected Exception - Memcache required")
- _mock_run_kinit.assert_called_once_with('user@EXAMPLE.COM', 'password')
- _mock_get_groups.assert_called_once_with('user')
-
- def test_passive_handle_get_token_user_in_any__account(self):
- req = self._make_request('/auth/v1.0',
- headers={'X-Auth-User': 'test:user',
- 'X-Auth-Key': 'password'})
- _mock_run_kinit = Mock(return_value=0)
- _mock_get_groups = Mock(return_value="user,auth_blah")
- with patch('gluster.swift.common.middleware.swiftkerbauth.kerbauth.run_kinit', _mock_run_kinit):
- with patch('gluster.swift.common.middleware.swiftkerbauth.kerbauth.get_groups_from_username',
- _mock_get_groups):
- resp = self.test_auth_passive.handle_get_token(req)
- self.assertEquals(resp.status_int, 401)
- _mock_run_kinit.assert_called_once_with('user', 'password')
- _mock_get_groups.assert_called_once_with('user')
-
- def test_handle(self):
- req = self._make_request('/auth/v1.0')
- resp = req.get_response(self.test_auth)
- self.assertEquals(resp.status_int, REDIRECT_STATUS)
-
- def test_authorize_invalid_req(self):
- req = self._make_request('/')
- resp = self.test_auth.authorize(req)
- self.assertEquals(resp.status_int, 404)
-
- def test_authorize_set_swift_owner(self):
- req = self._make_request('/v1/AUTH_test/c1/o1')
- req.remote_user = 'test,auth_reseller_admin'
- resp = self.test_auth.authorize(req)
- self.assertEquals(req.environ['swift_owner'], True)
- self.assertTrue(resp is None)
- req = self._make_request('/v1/AUTH_test/c1/o1')
- req.remote_user = 'test,auth_test'
- resp = self.test_auth.authorize(req)
- self.assertEquals(req.environ['swift_owner'], True)
- self.assertTrue(resp is None)
-
- def test_authorize_swift_sync_key(self):
- req = self._make_request(
- '/v1/AUTH_cfa/c/o',
- environ={'swift_sync_key': 'secret'},
- headers={'x-container-sync-key': 'secret',
- 'x-timestamp': '123.456'})
- resp = self.test_auth.authorize(req)
- self.assertTrue(resp is None)
-
- def test_authorize_acl_referrer_access(self):
- req = self._make_request('/v1/AUTH_cfa/c')
- req.remote_user = 'act:usr,act'
- resp = self.test_auth.authorize(req)
- self.assertEquals(resp.status_int, 403)
- req = self._make_request('/v1/AUTH_cfa/c')
- req.remote_user = 'act:usr,act'
- req.acl = '.r:*,.rlistings'
- self.assertEquals(self.test_auth.authorize(req), None)
- req = self._make_request('/v1/AUTH_cfa/c')
- req.remote_user = 'act:usr,act'
- req.acl = '.r:*' # No listings allowed
- resp = self.test_auth.authorize(req)
- self.assertEquals(resp.status_int, 403)
- req = self._make_request('/v1/AUTH_cfa/c')
- req.remote_user = 'act:usr,act'
- req.acl = '.r:.example.com,.rlistings'
- resp = self.test_auth.authorize(req)
- self.assertEquals(resp.status_int, 403)
- req = self._make_request('/v1/AUTH_cfa/c')
- req.remote_user = 'act:usr,act'
- req.referer = 'http://www.example.com/index.html'
- req.acl = '.r:.example.com,.rlistings'
- self.assertEquals(self.test_auth.authorize(req), None)
- req = self._make_request('/v1/AUTH_cfa/c')
- resp = self.test_auth.authorize(req)
- self.assertEquals(resp.status_int, REDIRECT_STATUS)
- req = self._make_request('/v1/AUTH_cfa/c')
- req.acl = '.r:*,.rlistings'
- self.assertEquals(self.test_auth.authorize(req), None)
- req = self._make_request('/v1/AUTH_cfa/c')
- req.acl = '.r:*' # No listings allowed
- resp = self.test_auth.authorize(req)
- self.assertEquals(resp.status_int, REDIRECT_STATUS)
- req = self._make_request('/v1/AUTH_cfa/c')
- req.acl = '.r:.example.com,.rlistings'
- resp = self.test_auth.authorize(req)
- self.assertEquals(resp.status_int, REDIRECT_STATUS)
- req = self._make_request('/v1/AUTH_cfa/c')
- req.referer = 'http://www.example.com/index.html'
- req.acl = '.r:.example.com,.rlistings'
- self.assertEquals(self.test_auth.authorize(req), None)
-
- def test_handle_x_storage_token(self):
- req = self._make_request(
- '/auth/v1.0',
- headers={'x-storage-token': 'blahblah', })
- resp = req.get_response(self.test_auth)
- self.assertEquals(resp.status_int, REDIRECT_STATUS)
-
- def test_invalid_token(self):
- req = self._make_request('/k1/test')
- req.environ['HTTP_X_AUTH_TOKEN'] = 'AUTH_blahblahblah'
- resp = req.get_response(self.test_auth)
- self.assertEquals(resp.status_int, REDIRECT_STATUS)
-
-if __name__ == '__main__':
- unittest.main()
diff --git a/test/unit/common/middleware/swiftkerbauth/test_kerbauth_utils.py b/test/unit/common/middleware/swiftkerbauth/test_kerbauth_utils.py
deleted file mode 100644
index 2a4e90b..0000000
--- a/test/unit/common/middleware/swiftkerbauth/test_kerbauth_utils.py
+++ /dev/null
@@ -1,77 +0,0 @@
-# Copyright (c) 2013 Red Hat, Inc.
-#
-# Licensed under the Apache License, Version 2.0 (the "License");
-# you may not use this file except in compliance with the License.
-# You may obtain a copy of the License at
-#
-# http://www.apache.org/licenses/LICENSE-2.0
-#
-# Unless required by applicable law or agreed to in writing, software
-# distributed under the License is distributed on an "AS IS" BASIS,
-# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
-# implied.
-# See the License for the specific language governing permissions and
-# limitations under the License.
-
-import unittest
-import re
-from time import time
-from test.unit import FakeMemcache
-from gluster.swift.common.middleware.swiftkerbauth import kerbauth_utils as ku
-
-
-class TestKerbUtils(unittest.TestCase):
-
- def test_get_remote_user(self):
- env = {'REMOTE_USER': "auth_admin@EXAMPLE.COM"}
- result = ku.get_remote_user(env)
- self.assertEqual(result, "auth_admin")
-
- def test_get_remote_user_err(self):
- env = {'REMOTE_USER': "auth_admin"}
- try:
- ku.get_remote_user(env)
- except RuntimeError as err:
- self.assertTrue(err.args[0].startswith("Malformed REMOTE_USER"))
- else:
- self.fail("Expected RuntimeError")
-
- def test_get_auth_data(self):
- mc = FakeMemcache()
- expiry = time() + 100
- ku.set_auth_data(mc, "root", "AUTH_tk", expiry, "root,admin")
- (token, expires, groups) = ku.get_auth_data(mc, "root")
- self.assertEqual(("AUTH_tk", expiry, "root,admin"),
- (token, expires, groups))
-
- def test_get_auth_data_err(self):
- mc = FakeMemcache()
- (token, expires, groups) = ku.get_auth_data(mc, "root")
- self.assertEqual((token, expires, groups), (None, None, None))
-
- expiry = time() - 1
- ku.set_auth_data(mc, "root", "AUTH_tk", expiry, "root,admin")
- (token, expires, groups) = ku.get_auth_data(mc, "root")
- self.assertEqual((token, expires, groups), (None, None, None))
-
- def test_set_auth_data(self):
- mc = FakeMemcache()
- expiry = time() + 100
- ku.set_auth_data(mc, "root", "AUTH_tk", expiry, "root,admin")
-
- def test_generate_token(self):
- token = ku.generate_token()
- matches = re.match('AUTH_tk[a-f0-9]{32}', token)
- self.assertTrue(matches is not None)
-
- def test_get_groups_from_username(self):
- groups = ku.get_groups_from_username("root")
- self.assertTrue("root" in groups)
-
- def test_get_groups_from_username_err(self):
- try:
- ku.get_groups_from_username("Zroot")
- except RuntimeError as err:
- self.assertTrue(err.args[0].startswith("Failure running id -G"))
- else:
- self.fail("Expected RuntimeError")
diff --git a/test/unit/common/test_constraints.py b/test/unit/common/test_constraints.py
index 6c78d75..e8ddd69 100644
--- a/test/unit/common/test_constraints.py
+++ b/test/unit/common/test_constraints.py
@@ -57,10 +57,10 @@ class TestConstraints(unittest.TestCase):
cnt.set_object_name_component_length()
self.assertEqual(len, cnt.get_object_name_component_length())
- with patch('swift.common.constraints.constraints_conf_int',
- mock_constraints_conf_int):
- cnt.set_object_name_component_length()
- self.assertEqual(cnt.get_object_name_component_length(), 1000)
+ with patch('swift.common.constraints.constraints_conf_int',
+ mock_constraints_conf_int):
+ cnt.set_object_name_component_length()
+ self.assertEqual(cnt.get_object_name_component_length(), 1000)
def test_validate_obj_name_component(self):
max_obj_len = cnt.get_object_name_component_length()
@@ -75,65 +75,6 @@ class TestConstraints(unittest.TestCase):
self.assertTrue(cnt.validate_obj_name_component('..'))
self.assertTrue(cnt.validate_obj_name_component(''))
- def test_validate_headers(self):
- req = Mock()
- req.headers = []
- self.assertEqual(cnt.validate_headers(req), '')
- req.headers = ['x-some-header']
- self.assertEqual(cnt.validate_headers(req), '')
- #TODO: Although we now support x-delete-at and x-delete-after,
- #retained this test case as we may add some other header to
- #unsupported list in future
- raise SkipTest
- req.headers = ['x-delete-at', 'x-some-header']
- self.assertNotEqual(cnt.validate_headers(req), '')
- req.headers = ['x-delete-after', 'x-some-header']
- self.assertNotEqual(cnt.validate_headers(req), '')
- req.headers = ['x-delete-at', 'x-delete-after', 'x-some-header']
- self.assertNotEqual(cnt.validate_headers(req), '')
-
- def test_validate_headers_ignoring_config_set(self):
- with patch('gluster.swift.common.constraints.'
- 'Glusterfs._ignore_unsupported_headers', True):
- req = Mock()
- req.headers = []
- self.assertEqual(cnt.validate_headers(req), '')
- req.headers = ['x-some-header']
- self.assertEqual(cnt.validate_headers(req), '')
- #TODO: Although we now support x-delete-at and x-delete-after,
- #retained this test case as we may add some other header to
- #unsupported list in future
- raise SkipTest
- req.headers = ['x-delete-at', 'x-some-header']
- self.assertEqual(cnt.validate_headers(req), '')
- req.headers = ['x-delete-after', 'x-some-header']
- self.assertEqual(cnt.validate_headers(req), '')
- req.headers = ['x-delete-at', 'x-delete-after', 'x-some-header']
- self.assertEqual(cnt.validate_headers(req), '')
-
- def test_gluster_check_metadata(self):
- mock_check_metadata = Mock()
- with patch('gluster.swift.common.constraints.__check_metadata',
- mock_check_metadata):
- req = Mock()
- req.headers = []
- cnt.gluster_check_metadata(req, 'object')
- self.assertTrue(1, mock_check_metadata.call_count)
- cnt.gluster_check_metadata(req, 'object', POST=False)
- self.assertTrue(1, mock_check_metadata.call_count)
- req.headers = ['x-some-header']
- self.assertEqual(cnt.gluster_check_metadata(req, 'object', POST=False), None)
- #TODO: Although we now support x-delete-at and x-delete-after,
- #retained this test case as we may add some other header to
- #unsupported list in future
- raise SkipTest
- req.headers = ['x-delete-at', 'x-some-header']
- self.assertNotEqual(cnt.gluster_check_metadata(req, 'object', POST=False), None)
- req.headers = ['x-delete-after', 'x-some-header']
- self.assertNotEqual(cnt.gluster_check_metadata(req, 'object', POST=False), None)
- req.headers = ['x-delete-at', 'x-delete-after', 'x-some-header']
- self.assertNotEqual(cnt.gluster_check_metadata(req, 'object', POST=False), None)
-
def test_gluster_check_object_creation(self):
with patch('gluster.swift.common.constraints.__check_object_creation',
mock_check_object_creation):
@@ -147,9 +88,3 @@ class TestConstraints(unittest.TestCase):
req = Mock()
req.headers = []
self.assertTrue(cnt.gluster_check_object_creation(req, 'dir/.'))
- #TODO: Although we now support x-delete-at and x-delete-after,
- #retained this test case as we may add some other header to
- #unsupported list in future
- raise SkipTest
- req.headers = ['x-delete-at']
- self.assertTrue(cnt.gluster_check_object_creation(req, 'dir/z'))
diff --git a/test/unit/common/test_diskdir.py b/test/unit/common/test_diskdir.py
index f32c3ad..ebc554a 100644
--- a/test/unit/common/test_diskdir.py
+++ b/test/unit/common/test_diskdir.py
@@ -408,7 +408,7 @@ class TestContainerBroker(unittest.TestCase):
self.assertEqual(os.path.basename(broker.db_file), 'db_file.db')
broker.initialize(self.initial_ts)
self.assertTrue(os.path.isdir(self.container))
- self.assertEquals(self.initial_ts, broker.metadata[utils.X_TIMESTAMP])
+ self.assertEquals(self.initial_ts, broker.metadata[utils.X_TIMESTAMP][0])
self.assertFalse(broker.is_deleted())
def test_creation_existing(self):
@@ -419,7 +419,7 @@ class TestContainerBroker(unittest.TestCase):
self.assertEqual(os.path.basename(broker.db_file), 'db_file.db')
broker.initialize(self.initial_ts)
self.assertTrue(os.path.isdir(self.container))
- self.assertEquals(self.initial_ts, broker.metadata[utils.X_TIMESTAMP])
+ self.assertEquals(self.initial_ts, broker.metadata[utils.X_TIMESTAMP][0])
self.assertFalse(broker.is_deleted())
def test_creation_existing_bad_metadata(self):
@@ -432,7 +432,7 @@ class TestContainerBroker(unittest.TestCase):
self.assertEqual(os.path.basename(broker.db_file), 'db_file.db')
broker.initialize(self.initial_ts)
self.assertTrue(os.path.isdir(self.container))
- self.assertEquals(self.initial_ts, broker.metadata[utils.X_TIMESTAMP])
+ self.assertEquals(self.initial_ts, broker.metadata[utils.X_TIMESTAMP][0])
self.assertFalse(broker.is_deleted())
def test_empty(self):
@@ -954,7 +954,7 @@ class TestContainerBroker(unittest.TestCase):
self.assertEqual(os.path.basename(broker.db_file), 'db_file.db')
broker.initialize(self.initial_ts)
self.assertTrue(os.path.isdir(self.container))
- self.assertEquals(self.initial_ts, broker.metadata[utils.X_TIMESTAMP])
+ self.assertEquals(self.initial_ts, broker.metadata[utils.X_TIMESTAMP][0])
self.assertFalse(broker.is_deleted())
broker.delete_db(normalize_timestamp(time()))
self.assertTrue(broker.is_deleted())
@@ -1005,7 +1005,7 @@ class TestAccountBroker(unittest.TestCase):
self.assertEqual(os.path.basename(broker.db_file), 'db_file.db')
broker.initialize(self.initial_ts)
self.assertTrue(os.path.isdir(self.drive_fullpath))
- self.assertEquals(self.initial_ts, broker.metadata[utils.X_TIMESTAMP])
+ self.assertEquals(self.initial_ts, broker.metadata[utils.X_TIMESTAMP][0])
self.assertFalse(broker.is_deleted())
def test_creation_bad_metadata(self):
@@ -1016,7 +1016,7 @@ class TestAccountBroker(unittest.TestCase):
self.assertEqual(os.path.basename(broker.db_file), 'db_file.db')
broker.initialize(self.initial_ts)
self.assertTrue(os.path.isdir(self.drive_fullpath))
- self.assertEquals(self.initial_ts, broker.metadata[utils.X_TIMESTAMP])
+ self.assertEquals(self.initial_ts, broker.metadata[utils.X_TIMESTAMP][0])
self.assertFalse(broker.is_deleted())
def test_empty(self):
@@ -1219,7 +1219,7 @@ class TestAccountBroker(unittest.TestCase):
self.assertEqual(os.path.basename(broker.db_file), 'db_file.db')
broker.initialize(self.initial_ts)
self.assertTrue(os.path.isdir(self.drive_fullpath))
- self.assertEquals(self.initial_ts, broker.metadata[utils.X_TIMESTAMP])
+ self.assertEquals(self.initial_ts, broker.metadata[utils.X_TIMESTAMP][0])
self.assertFalse(broker.is_deleted())
broker.delete_db(normalize_timestamp(time()))
# Deleting the "db" should be a NOOP
diff --git a/test/unit/common/test_utils.py b/test/unit/common/test_utils.py
index dd03bd8..55bd0cf 100644
--- a/test/unit/common/test_utils.py
+++ b/test/unit/common/test_utils.py
@@ -25,9 +25,10 @@ import hashlib
import tarfile
import shutil
from collections import defaultdict
-from mock import patch
+from mock import patch, Mock
from gluster.swift.common import utils, Glusterfs
-from gluster.swift.common.exceptions import GlusterFileSystemOSError
+from gluster.swift.common.exceptions import GlusterFileSystemOSError,\
+ GlusterFileSystemIOError
from swift.common.exceptions import DiskFileNoSpace
#
@@ -712,6 +713,18 @@ class TestUtils(unittest.TestCase):
ret = utils.validate_object(md)
assert ret
+ def test_validate_object_with_stat(self):
+ md = {utils.X_TIMESTAMP: 'na',
+ utils.X_CONTENT_TYPE: 'na',
+ utils.X_ETAG: 'bad',
+ utils.X_CONTENT_LENGTH: '12345',
+ utils.X_TYPE: utils.OBJECT,
+ utils.X_OBJECT_TYPE: 'na'}
+ fake_stat = Mock(st_size=12346)
+ self.assertFalse(utils.validate_object(md, fake_stat))
+ fake_stat = Mock(st_size=12345)
+ self.assertTrue(utils.validate_object(md, fake_stat))
+
class TestUtilsDirObjects(unittest.TestCase):
@@ -794,7 +807,8 @@ class TestUtilsDirObjects(unittest.TestCase):
def _mock_rm(path):
print "_mock_rm-metadata_enoent(%s)" % path
shutil.rmtree(path)
- raise OSError(errno.ENOENT, os.strerror(errno.ENOENT))
+ raise GlusterFileSystemIOError(errno.ENOENT,
+ os.strerror(errno.ENOENT))
# Remove the files
for f in self.files:
@@ -805,8 +819,8 @@ class TestUtilsDirObjects(unittest.TestCase):
try:
try:
self.assertTrue(utils.rmobjdir(self.rootdir))
- except OSError:
- self.fail("Unexpected OSError")
+ except IOError:
+ self.fail("Unexpected IOError")
else:
pass
finally:
diff --git a/test/unit/obj/test_diskfile.py b/test/unit/obj/test_diskfile.py
index f8c26db..1fe0904 100644
--- a/test/unit/obj/test_diskfile.py
+++ b/test/unit/obj/test_diskfile.py
@@ -223,7 +223,7 @@ class TestDiskFile(unittest.TestCase):
ini_md = {
'X-Type': 'Object',
'X-Object-Type': 'file',
- 'Content-Length': 5,
+ 'Content-Length': 4,
'ETag': 'etag',
'X-Timestamp': 'ts',
'Content-Type': 'application/loctet-stream'}
@@ -283,9 +283,8 @@ class TestDiskFile(unittest.TestCase):
with gdf.open():
assert gdf._is_dir
assert gdf._data_file == the_dir
- assert gdf._metadata == exp_md
- def _create_and_get_diskfile(self, dev, par, acc, con, obj):
+ def _create_and_get_diskfile(self, dev, par, acc, con, obj, fsize=256):
# FIXME: assumes account === volume
the_path = os.path.join(self.td, dev, con)
the_file = os.path.join(the_path, obj)
@@ -293,7 +292,7 @@ class TestDiskFile(unittest.TestCase):
base_dir = os.path.dirname(the_file)
os.makedirs(base_dir)
with open(the_file, "wb") as fd:
- fd.write("y" * 256)
+ fd.write("y" * fsize)
gdf = self._get_diskfile(dev, par, acc, con, obj)
assert gdf._obj == base_obj
assert not gdf._is_dir
@@ -353,6 +352,26 @@ class TestDiskFile(unittest.TestCase):
assert len(chunks) == 1, repr(chunks)
assert called[0] == 1, called
+ def test_reader_larger_file(self):
+ closed = [False]
+ fd = [-1]
+
+ def mock_close(*args, **kwargs):
+ closed[0] = True
+ os.close(fd[0])
+
+ with mock.patch("gluster.swift.obj.diskfile.do_close", mock_close):
+ gdf = self._create_and_get_diskfile("vol0", "p57", "ufo47", "bar", "z", fsize=1024*1024*2)
+ with gdf.open():
+ assert gdf._fd is not None
+ assert gdf._data_file == os.path.join(self.td, "vol0", "bar", "z")
+ reader = gdf.reader()
+ assert reader._fd is not None
+ fd[0] = reader._fd
+ chunks = [ck for ck in reader]
+ assert reader._fd is None
+ assert closed[0]
+
def test_reader_dir_object(self):
called = [False]
@@ -926,3 +945,31 @@ class TestDiskFile(unittest.TestCase):
dw.write("123")
os.unlink(saved_tmppath)
assert not os.path.exists(saved_tmppath)
+
+ def test_unlink_not_called_after_rename(self):
+ the_obj_path = os.path.join("b", "a")
+ the_file = os.path.join(the_obj_path, "z")
+ gdf = self._get_diskfile("vol0", "p57", "ufo47", "bar", the_file)
+
+ body = '1234\n'
+ etag = md5(body).hexdigest()
+ metadata = {
+ 'X-Timestamp': '1234',
+ 'Content-Type': 'file',
+ 'ETag': etag,
+ 'Content-Length': '5',
+ }
+
+ _mock_do_unlink = Mock() # Shouldn't be called
+ with patch("gluster.swift.obj.diskfile.do_unlink", _mock_do_unlink):
+ with gdf.create() as dw:
+ assert dw._tmppath is not None
+ tmppath = dw._tmppath
+ dw.write(body)
+ dw.put(metadata)
+ # do_unlink is not called if dw._tmppath is set to None
+ assert dw._tmppath is None
+ self.assertFalse(_mock_do_unlink.called)
+
+ assert os.path.exists(gdf._data_file) # Real file exists
+ assert not os.path.exists(tmppath) # Temp file does not exist
diff --git a/test/unit/obj/test_expirer.py b/test/unit/obj/test_expirer.py
index 4329eef..236775e 100644
--- a/test/unit/obj/test_expirer.py
+++ b/test/unit/obj/test_expirer.py
@@ -46,7 +46,7 @@ class TestObjectExpirer(TestCase):
self.old_loadapp = internal_client.loadapp
self.old_sleep = internal_client.sleep
- internal_client.loadapp = lambda x: None
+ internal_client.loadapp = lambda *a, **kw: None
internal_client.sleep = not_sleep
def teardown(self):
@@ -618,7 +618,7 @@ class TestObjectExpirer(TestCase):
start_response('204 No Content', [('Content-Length', '0')])
return []
- internal_client.loadapp = lambda x: fake_app
+ internal_client.loadapp = lambda *a, **kw: fake_app
x = expirer.ObjectExpirer({})
ts = '1234'
@@ -635,7 +635,7 @@ class TestObjectExpirer(TestCase):
start_response('204 No Content', [('Content-Length', '0')])
return []
- internal_client.loadapp = lambda x: fake_app
+ internal_client.loadapp = lambda *a, **kw: fake_app
x = expirer.ObjectExpirer({})
ts = '1234'
@@ -649,7 +649,7 @@ class TestObjectExpirer(TestCase):
start_response('404 Not Found', [('Content-Length', '0')])
return []
- internal_client.loadapp = lambda x: fake_app
+ internal_client.loadapp = lambda *a, **kw: fake_app
x = expirer.ObjectExpirer({})
x.delete_actual_object('/path/to/object', '1234')
@@ -661,7 +661,7 @@ class TestObjectExpirer(TestCase):
[('Content-Length', '0')])
return []
- internal_client.loadapp = lambda x: fake_app
+ internal_client.loadapp = lambda *a, **kw: fake_app
x = expirer.ObjectExpirer({})
x.delete_actual_object('/path/to/object', '1234')
@@ -674,7 +674,7 @@ class TestObjectExpirer(TestCase):
[('Content-Length', '0')])
return []
- internal_client.loadapp = lambda x: fake_app
+ internal_client.loadapp = lambda *a, **kw: fake_app
x = expirer.ObjectExpirer({})
exc = None
@@ -692,7 +692,7 @@ class TestObjectExpirer(TestCase):
x = expirer.ObjectExpirer({})
x.swift.make_request = mock.MagicMock()
x.delete_actual_object(name, timestamp)
- x.swift.make_request.assert_called_once()
+ self.assertTrue(x.swift.make_request.called)
self.assertEqual(x.swift.make_request.call_args[0][1],
'/v1/' + urllib.quote(name))
diff --git a/test/unit/proxy/controllers/test_obj.py b/test/unit/proxy/controllers/test_obj.py
index aada616..4942691 100755
--- a/test/unit/proxy/controllers/test_obj.py
+++ b/test/unit/proxy/controllers/test_obj.py
@@ -22,7 +22,7 @@ import mock
import swift
from swift.proxy import server as proxy_server
from swift.common.swob import HTTPException
-from test.unit import FakeRing, FakeMemcache, fake_http_connect
+from test.unit import FakeRing, FakeMemcache, fake_http_connect, debug_logger
@contextmanager
@@ -90,26 +90,96 @@ class TestObjControllerWriteAffinity(unittest.TestCase):
class TestObjController(unittest.TestCase):
+ def setUp(self):
+ logger = debug_logger('proxy-server')
+ logger.thread_locals = ('txn1', '127.0.0.2')
+ self.app = proxy_server.Application(
+ None, FakeMemcache(), account_ring=FakeRing(),
+ container_ring=FakeRing(), object_ring=FakeRing(),
+ logger=logger)
+ self.controller = proxy_server.ObjectController(self.app,
+ 'a', 'c', 'o')
+ self.controller.container_info = mock.MagicMock(return_value={
+ 'partition': 1,
+ 'nodes': [
+ {'ip': '127.0.0.1', 'port': '1', 'device': 'sda'},
+ {'ip': '127.0.0.1', 'port': '2', 'device': 'sda'},
+ {'ip': '127.0.0.1', 'port': '3', 'device': 'sda'},
+ ],
+ 'write_acl': None,
+ 'read_acl': None,
+ 'sync_key': None,
+ 'versions': None})
+
+ def test_PUT_simple(self):
+ req = swift.common.swob.Request.blank('/v1/a/c/o')
+ req.headers['content-length'] = '0'
+ with set_http_connect(201, 201, 201):
+ resp = self.controller.PUT(req)
+ self.assertEquals(resp.status_int, 201)
+
+ def test_PUT_if_none_match(self):
+ req = swift.common.swob.Request.blank('/v1/a/c/o')
+ req.headers['if-none-match'] = '*'
+ req.headers['content-length'] = '0'
+ with set_http_connect(201, 201, 201):
+ resp = self.controller.PUT(req)
+ self.assertEquals(resp.status_int, 201)
+
+ def test_PUT_if_none_match_denied(self):
+ req = swift.common.swob.Request.blank('/v1/a/c/o')
+ req.headers['if-none-match'] = '*'
+ req.headers['content-length'] = '0'
+ with set_http_connect(201, (412, 412), 201):
+ resp = self.controller.PUT(req)
+ self.assertEquals(resp.status_int, 412)
+
+ def test_PUT_if_none_match_not_star(self):
+ req = swift.common.swob.Request.blank('/v1/a/c/o')
+ req.headers['if-none-match'] = 'somethingelse'
+ req.headers['content-length'] = '0'
+ with set_http_connect(201, 201, 201):
+ resp = self.controller.PUT(req)
+ self.assertEquals(resp.status_int, 400)
+
+ def test_GET_simple(self):
+ req = swift.common.swob.Request.blank('/v1/a/c/o')
+ with set_http_connect(200):
+ resp = self.controller.GET(req)
+ self.assertEquals(resp.status_int, 200)
+
+ def test_DELETE_simple(self):
+ req = swift.common.swob.Request.blank('/v1/a/c/o')
+ with set_http_connect(204, 204, 204):
+ resp = self.controller.DELETE(req)
+ self.assertEquals(resp.status_int, 204)
+
+ def test_POST_simple(self):
+ req = swift.common.swob.Request.blank('/v1/a/c/o')
+ with set_http_connect(200, 200, 200, 201, 201, 201):
+ resp = self.controller.POST(req)
+ self.assertEquals(resp.status_int, 202)
+
+ def test_COPY_simple(self):
+ req = swift.common.swob.Request.blank('/v1/a/c/o')
+ with set_http_connect(200, 200, 200, 201, 201, 201):
+ resp = self.controller.POST(req)
+ self.assertEquals(resp.status_int, 202)
+
+ def test_HEAD_simple(self):
+ req = swift.common.swob.Request.blank('/v1/a/c/o')
+ with set_http_connect(200, 200, 200, 201, 201, 201):
+ resp = self.controller.POST(req)
+ self.assertEquals(resp.status_int, 202)
def test_PUT_log_info(self):
# mock out enough to get to the area of the code we want to test
with mock.patch('swift.proxy.controllers.obj.check_object_creation',
mock.MagicMock(return_value=None)):
- app = mock.MagicMock()
- app.container_ring.get_nodes.return_value = (1, [2])
- app.object_ring.get_nodes.return_value = (1, [2])
- controller = proxy_server.ObjectController(app, 'a', 'c', 'o')
- controller.container_info = mock.MagicMock(return_value={
- 'partition': 1,
- 'nodes': [{}],
- 'write_acl': None,
- 'sync_key': None,
- 'versions': None})
- # and now test that we add the header to log_info
req = swift.common.swob.Request.blank('/v1/a/c/o')
req.headers['x-copy-from'] = 'somewhere'
try:
- controller.PUT(req)
+ self.controller.PUT(req)
except HTTPException:
pass
self.assertEquals(
@@ -119,7 +189,7 @@ class TestObjController(unittest.TestCase):
req.method = 'POST'
req.headers['x-copy-from'] = 'elsewhere'
try:
- controller.PUT(req)
+ self.controller.PUT(req)
except HTTPException:
pass
self.assertEquals(req.environ.get('swift.log_info'), None)
diff --git a/test/unit/proxy/test_server.py b/test/unit/proxy/test_server.py
index 0cb2278..1a59016 100644
--- a/test/unit/proxy/test_server.py
+++ b/test/unit/proxy/test_server.py
@@ -30,6 +30,7 @@ from urllib import quote
from hashlib import md5
from tempfile import mkdtemp
import weakref
+import re
import mock
from eventlet import sleep, spawn, wsgi, listen
@@ -60,7 +61,8 @@ from swift.proxy.controllers.base import get_container_memcache_key, \
get_account_memcache_key, cors_validation
import swift.proxy.controllers
from swift.common.request_helpers import get_sys_meta_prefix
-from swift.common.swob import Request, Response, HTTPUnauthorized
+from swift.common.swob import Request, Response, HTTPUnauthorized, \
+ HTTPException
# mocks
logging.getLogger().addHandler(logging.StreamHandler(sys.stdout))
@@ -245,6 +247,7 @@ def set_http_connect(*args, **kwargs):
swift.proxy.controllers.obj.http_connect = new_connect
swift.proxy.controllers.account.http_connect = new_connect
swift.proxy.controllers.container.http_connect = new_connect
+ return new_connect
# tests
@@ -692,10 +695,10 @@ class TestObjectController(unittest.TestCase):
def setUp(self):
self.app = proxy_server.Application(None, FakeMemcache(),
+ logger=debug_logger('proxy-ut'),
account_ring=FakeRing(),
container_ring=FakeRing(),
object_ring=FakeRing())
- monkey_patch_mimetools()
def tearDown(self):
self.app.account_ring.set_replicas(3)
@@ -802,6 +805,7 @@ class TestObjectController(unittest.TestCase):
self.app.update_request(req)
self.app.memcache.store = {}
res = controller.PUT(req)
+ self.assertEqual(test_errors, [])
self.assertTrue(res.status.startswith('201 '))
def test_PUT_respects_write_affinity(self):
@@ -1609,7 +1613,7 @@ class TestObjectController(unittest.TestCase):
dev['ip'] = '127.0.0.1'
dev['port'] = 1
- class SlowBody():
+ class SlowBody(object):
def __init__(self):
self.sent = 0
@@ -1658,7 +1662,7 @@ class TestObjectController(unittest.TestCase):
dev['ip'] = '127.0.0.1'
dev['port'] = 1
- class SlowBody():
+ class SlowBody(object):
def __init__(self):
self.sent = 0
@@ -1693,7 +1697,7 @@ class TestObjectController(unittest.TestCase):
dev['port'] = 1
req = Request.blank('/v1/a/c/o', environ={'REQUEST_METHOD': 'GET'})
self.app.update_request(req)
- set_http_connect(200, 200, 200, slow=True)
+ set_http_connect(200, 200, 200, slow=0.1)
req.sent_size = 0
resp = req.get_response(self.app)
got_exc = False
@@ -1703,7 +1707,7 @@ class TestObjectController(unittest.TestCase):
got_exc = True
self.assert_(not got_exc)
self.app.recoverable_node_timeout = 0.1
- set_http_connect(200, 200, 200, slow=True)
+ set_http_connect(200, 200, 200, slow=1.0)
resp = req.get_response(self.app)
got_exc = False
try:
@@ -1718,16 +1722,17 @@ class TestObjectController(unittest.TestCase):
self.app.update_request(req)
self.app.recoverable_node_timeout = 0.1
- set_http_connect(200, 200, 200, slow=[3])
+ set_http_connect(200, 200, 200, slow=[1.0, 1.0, 1.0])
resp = req.get_response(self.app)
got_exc = False
try:
- resp.body
+ self.assertEquals('', resp.body)
except ChunkReadTimeout:
got_exc = True
self.assert_(got_exc)
- set_http_connect(200, 200, 200, body='lalala', slow=[2])
+ set_http_connect(200, 200, 200, body='lalala',
+ slow=[1.0, 1.0])
resp = req.get_response(self.app)
got_exc = False
try:
@@ -1736,8 +1741,8 @@ class TestObjectController(unittest.TestCase):
got_exc = True
self.assert_(not got_exc)
- set_http_connect(200, 200, 200, body='lalala', slow=[2],
- etags=['a', 'a', 'a'])
+ set_http_connect(200, 200, 200, body='lalala',
+ slow=[1.0, 1.0], etags=['a', 'a', 'a'])
resp = req.get_response(self.app)
got_exc = False
try:
@@ -1746,8 +1751,8 @@ class TestObjectController(unittest.TestCase):
got_exc = True
self.assert_(not got_exc)
- set_http_connect(200, 200, 200, body='lalala', slow=[2],
- etags=['a', 'b', 'a'])
+ set_http_connect(200, 200, 200, body='lalala',
+ slow=[1.0, 1.0], etags=['a', 'b', 'a'])
resp = req.get_response(self.app)
got_exc = False
try:
@@ -1757,8 +1762,8 @@ class TestObjectController(unittest.TestCase):
self.assert_(not got_exc)
req = Request.blank('/v1/a/c/o', environ={'REQUEST_METHOD': 'GET'})
- set_http_connect(200, 200, 200, body='lalala', slow=[2],
- etags=['a', 'b', 'b'])
+ set_http_connect(200, 200, 200, body='lalala',
+ slow=[1.0, 1.0], etags=['a', 'b', 'b'])
resp = req.get_response(self.app)
got_exc = False
try:
@@ -1787,17 +1792,17 @@ class TestObjectController(unittest.TestCase):
'Content-Type': 'text/plain'},
body=' ')
self.app.update_request(req)
- set_http_connect(200, 200, 201, 201, 201, slow=True)
+ set_http_connect(200, 200, 201, 201, 201, slow=0.1)
resp = req.get_response(self.app)
self.assertEquals(resp.status_int, 201)
self.app.node_timeout = 0.1
- set_http_connect(201, 201, 201, slow=True)
req = Request.blank('/v1/a/c/o',
environ={'REQUEST_METHOD': 'PUT'},
headers={'Content-Length': '4',
'Content-Type': 'text/plain'},
body=' ')
self.app.update_request(req)
+ set_http_connect(201, 201, 201, slow=1.0)
resp = req.get_response(self.app)
self.assertEquals(resp.status_int, 503)
@@ -2227,307 +2232,322 @@ class TestObjectController(unittest.TestCase):
resp = controller.PUT(req)
self.assertEquals(resp.status_int, 400)
- def test_copy_from(self):
+ @contextmanager
+ def controller_context(self, req, *args, **kwargs):
+ _v, account, container, obj = utils.split_path(req.path, 4, 4, True)
+ controller = proxy_server.ObjectController(self.app, account,
+ container, obj)
+ self.app.update_request(req)
+ self.app.memcache.store = {}
with save_globals():
- controller = proxy_server.ObjectController(self.app, 'account',
- 'container', 'object')
- # initial source object PUT
- req = Request.blank('/v1/a/c/o', environ={'REQUEST_METHOD': 'PUT'},
- headers={'Content-Length': '0'})
- self.app.update_request(req)
- set_http_connect(200, 200, 201, 201, 201)
- # acct cont obj obj obj
+ new_connect = set_http_connect(*args, **kwargs)
+ yield controller
+ unused_status_list = []
+ while True:
+ try:
+ unused_status_list.append(new_connect.code_iter.next())
+ except StopIteration:
+ break
+ if unused_status_list:
+ raise self.fail('UN-USED STATUS CODES: %r' %
+ unused_status_list)
+
+ def test_basic_put_with_x_copy_from(self):
+ req = Request.blank('/v1/a/c/o', environ={'REQUEST_METHOD': 'PUT'},
+ headers={'Content-Length': '0',
+ 'X-Copy-From': 'c/o'})
+ status_list = (200, 200, 200, 200, 200, 201, 201, 201)
+ # acct cont objc objc objc obj obj obj
+ with self.controller_context(req, *status_list) as controller:
resp = controller.PUT(req)
- self.assertEquals(resp.status_int, 201)
+ self.assertEquals(resp.status_int, 201)
+ self.assertEquals(resp.headers['x-copied-from'], 'c/o')
- # basic copy
- req = Request.blank('/v1/a/c/o', environ={'REQUEST_METHOD': 'PUT'},
- headers={'Content-Length': '0',
- 'X-Copy-From': 'c/o'})
- self.app.update_request(req)
- set_http_connect(200, 200, 200, 200, 200, 200, 200, 201, 201, 201)
- # acct cont acct cont objc objc objc obj obj obj
- self.app.memcache.store = {}
+ def test_basic_put_with_x_copy_from_across_container(self):
+ req = Request.blank('/v1/a/c/o', environ={'REQUEST_METHOD': 'PUT'},
+ headers={'Content-Length': '0',
+ 'X-Copy-From': 'c2/o'})
+ status_list = (200, 200, 200, 200, 200, 200, 201, 201, 201)
+ # acct cont conc objc objc objc obj obj obj
+ with self.controller_context(req, *status_list) as controller:
resp = controller.PUT(req)
- self.assertEquals(resp.status_int, 201)
- self.assertEquals(resp.headers['x-copied-from'], 'c/o')
+ self.assertEquals(resp.status_int, 201)
+ self.assertEquals(resp.headers['x-copied-from'], 'c2/o')
- # non-zero content length
- req = Request.blank('/v1/a/c/o', environ={'REQUEST_METHOD': 'PUT'},
- headers={'Content-Length': '5',
- 'X-Copy-From': 'c/o'})
- self.app.update_request(req)
- set_http_connect(200, 200, 200, 200, 200, 200, 200)
- # acct cont acct cont objc objc objc
- self.app.memcache.store = {}
+ def test_copy_non_zero_content_length(self):
+ req = Request.blank('/v1/a/c/o', environ={'REQUEST_METHOD': 'PUT'},
+ headers={'Content-Length': '5',
+ 'X-Copy-From': 'c/o'})
+ status_list = (200, 200)
+ # acct cont
+ with self.controller_context(req, *status_list) as controller:
resp = controller.PUT(req)
- self.assertEquals(resp.status_int, 400)
+ self.assertEquals(resp.status_int, 400)
- # extra source path parsing
- req = Request.blank('/v1/a/c/o', environ={'REQUEST_METHOD': 'PUT'},
- headers={'Content-Length': '0',
- 'X-Copy-From': 'c/o/o2'})
- req.account = 'a'
- set_http_connect(200, 200, 200, 200, 200, 200, 200, 201, 201, 201)
- # acct cont acct cont objc objc objc obj obj obj
- self.app.memcache.store = {}
+ def test_copy_with_slashes_in_x_copy_from(self):
+ # extra source path parsing
+ req = Request.blank('/v1/a/c/o', environ={'REQUEST_METHOD': 'PUT'},
+ headers={'Content-Length': '0',
+ 'X-Copy-From': 'c/o/o2'})
+ status_list = (200, 200, 200, 200, 200, 201, 201, 201)
+ # acct cont objc objc objc obj obj obj
+ with self.controller_context(req, *status_list) as controller:
resp = controller.PUT(req)
- self.assertEquals(resp.status_int, 201)
- self.assertEquals(resp.headers['x-copied-from'], 'c/o/o2')
+ self.assertEquals(resp.status_int, 201)
+ self.assertEquals(resp.headers['x-copied-from'], 'c/o/o2')
- # space in soure path
- req = Request.blank('/v1/a/c/o', environ={'REQUEST_METHOD': 'PUT'},
- headers={'Content-Length': '0',
- 'X-Copy-From': 'c/o%20o2'})
- req.account = 'a'
- set_http_connect(200, 200, 200, 200, 200, 200, 200, 201, 201, 201)
- # acct cont acct cont objc objc objc obj obj obj
- self.app.memcache.store = {}
+ def test_copy_with_spaces_in_x_copy_from(self):
+ # space in soure path
+ req = Request.blank('/v1/a/c/o', environ={'REQUEST_METHOD': 'PUT'},
+ headers={'Content-Length': '0',
+ 'X-Copy-From': 'c/o%20o2'})
+ status_list = (200, 200, 200, 200, 200, 201, 201, 201)
+ # acct cont objc objc objc obj obj obj
+ with self.controller_context(req, *status_list) as controller:
resp = controller.PUT(req)
- self.assertEquals(resp.status_int, 201)
- self.assertEquals(resp.headers['x-copied-from'], 'c/o%20o2')
+ self.assertEquals(resp.status_int, 201)
+ self.assertEquals(resp.headers['x-copied-from'], 'c/o%20o2')
- # repeat tests with leading /
- req = Request.blank('/v1/a/c/o', environ={'REQUEST_METHOD': 'PUT'},
- headers={'Content-Length': '0',
- 'X-Copy-From': '/c/o'})
- self.app.update_request(req)
- set_http_connect(200, 200, 200, 200, 200, 200, 200, 201, 201, 201)
- # acct cont acct cont objc objc objc obj obj obj
- self.app.memcache.store = {}
+ def test_copy_with_leading_slash_in_x_copy_from(self):
+ # repeat tests with leading /
+ req = Request.blank('/v1/a/c/o', environ={'REQUEST_METHOD': 'PUT'},
+ headers={'Content-Length': '0',
+ 'X-Copy-From': '/c/o'})
+ status_list = (200, 200, 200, 200, 200, 201, 201, 201)
+ # acct cont objc objc objc obj obj obj
+ with self.controller_context(req, *status_list) as controller:
resp = controller.PUT(req)
- self.assertEquals(resp.status_int, 201)
- self.assertEquals(resp.headers['x-copied-from'], 'c/o')
+ self.assertEquals(resp.status_int, 201)
+ self.assertEquals(resp.headers['x-copied-from'], 'c/o')
- req = Request.blank('/v1/a/c/o', environ={'REQUEST_METHOD': 'PUT'},
- headers={'Content-Length': '0',
- 'X-Copy-From': '/c/o/o2'})
- req.account = 'a'
- set_http_connect(200, 200, 200, 200, 200, 200, 200, 201, 201, 201)
- # acct cont acct cont objc objc objc obj obj obj
- self.app.memcache.store = {}
+ def test_copy_with_leading_slash_and_slashes_in_x_copy_from(self):
+ req = Request.blank('/v1/a/c/o', environ={'REQUEST_METHOD': 'PUT'},
+ headers={'Content-Length': '0',
+ 'X-Copy-From': '/c/o/o2'})
+ status_list = (200, 200, 200, 200, 200, 201, 201, 201)
+ # acct cont objc objc objc obj obj obj
+ with self.controller_context(req, *status_list) as controller:
resp = controller.PUT(req)
- self.assertEquals(resp.status_int, 201)
- self.assertEquals(resp.headers['x-copied-from'], 'c/o/o2')
+ self.assertEquals(resp.status_int, 201)
+ self.assertEquals(resp.headers['x-copied-from'], 'c/o/o2')
- # negative tests
+ def test_copy_with_no_object_in_x_copy_from(self):
+ req = Request.blank('/v1/a/c/o', environ={'REQUEST_METHOD': 'PUT'},
+ headers={'Content-Length': '0',
+ 'X-Copy-From': '/c'})
+ status_list = (200, 200)
+ # acct cont
+ with self.controller_context(req, *status_list) as controller:
+ try:
+ controller.PUT(req)
+ except HTTPException as resp:
+ self.assertEquals(resp.status_int // 100, 4) # client error
+ else:
+ raise self.fail('Invalid X-Copy-From did not raise '
+ 'client error')
- # invalid x-copy-from path
- req = Request.blank('/v1/a/c/o', environ={'REQUEST_METHOD': 'PUT'},
- headers={'Content-Length': '0',
- 'X-Copy-From': '/c'})
- self.app.update_request(req)
- self.app.memcache.store = {}
+ def test_copy_server_error_reading_source(self):
+ req = Request.blank('/v1/a/c/o', environ={'REQUEST_METHOD': 'PUT'},
+ headers={'Content-Length': '0',
+ 'X-Copy-From': '/c/o'})
+ status_list = (200, 200, 503, 503, 503)
+ # acct cont objc objc objc
+ with self.controller_context(req, *status_list) as controller:
resp = controller.PUT(req)
- self.assertEquals(resp.status_int // 100, 4) # client error
+ self.assertEquals(resp.status_int, 503)
- # server error
- req = Request.blank('/v1/a/c/o', environ={'REQUEST_METHOD': 'PUT'},
- headers={'Content-Length': '0',
- 'X-Copy-From': '/c/o'})
- self.app.update_request(req)
- set_http_connect(200, 200, 503, 503, 503)
- # acct cont objc objc objc
- self.app.memcache.store = {}
+ def test_copy_not_found_reading_source(self):
+ req = Request.blank('/v1/a/c/o', environ={'REQUEST_METHOD': 'PUT'},
+ headers={'Content-Length': '0',
+ 'X-Copy-From': '/c/o'})
+ # not found
+ status_list = (200, 200, 404, 404, 404)
+ # acct cont objc objc objc
+ with self.controller_context(req, *status_list) as controller:
resp = controller.PUT(req)
- self.assertEquals(resp.status_int, 503)
+ self.assertEquals(resp.status_int, 404)
- # not found
- req = Request.blank('/v1/a/c/o', environ={'REQUEST_METHOD': 'PUT'},
- headers={'Content-Length': '0',
- 'X-Copy-From': '/c/o'})
- self.app.update_request(req)
- set_http_connect(200, 200, 404, 404, 404)
- # acct cont objc objc objc
- self.app.memcache.store = {}
+ def test_copy_with_some_missing_sources(self):
+ req = Request.blank('/v1/a/c/o', environ={'REQUEST_METHOD': 'PUT'},
+ headers={'Content-Length': '0',
+ 'X-Copy-From': '/c/o'})
+ status_list = (200, 200, 404, 404, 200, 201, 201, 201)
+ # acct cont objc objc objc obj obj obj
+ with self.controller_context(req, *status_list) as controller:
resp = controller.PUT(req)
- self.assertEquals(resp.status_int, 404)
+ self.assertEquals(resp.status_int, 201)
- # some missing containers
- req = Request.blank('/v1/a/c/o', environ={'REQUEST_METHOD': 'PUT'},
- headers={'Content-Length': '0',
- 'X-Copy-From': '/c/o'})
- self.app.update_request(req)
- set_http_connect(200, 200, 404, 404, 200, 201, 201, 201)
- # acct cont objc objc objc obj obj obj
- self.app.memcache.store = {}
+ def test_copy_with_object_metadata(self):
+ req = Request.blank('/v1/a/c/o', environ={'REQUEST_METHOD': 'PUT'},
+ headers={'Content-Length': '0',
+ 'X-Copy-From': '/c/o',
+ 'X-Object-Meta-Ours': 'okay'})
+ # test object metadata
+ status_list = (200, 200, 200, 200, 200, 201, 201, 201)
+ # acct cont objc objc objc obj obj obj
+ with self.controller_context(req, *status_list) as controller:
resp = controller.PUT(req)
- self.assertEquals(resp.status_int, 201)
+ self.assertEquals(resp.status_int, 201)
+ self.assertEquals(resp.headers.get('x-object-meta-test'), 'testing')
+ self.assertEquals(resp.headers.get('x-object-meta-ours'), 'okay')
+ self.assertEquals(resp.headers.get('x-delete-at'), '9876543210')
- # test object meta data
- req = Request.blank('/v1/a/c/o', environ={'REQUEST_METHOD': 'PUT'},
- headers={'Content-Length': '0',
- 'X-Copy-From': '/c/o',
- 'X-Object-Meta-Ours': 'okay'})
- self.app.update_request(req)
- set_http_connect(200, 200, 200, 200, 200, 201, 201, 201)
- # acct cont objc objc objc obj obj obj
- self.app.memcache.store = {}
- resp = controller.PUT(req)
- self.assertEquals(resp.status_int, 201)
- self.assertEquals(resp.headers.get('x-object-meta-test'),
- 'testing')
- self.assertEquals(resp.headers.get('x-object-meta-ours'), 'okay')
- self.assertEquals(resp.headers.get('x-delete-at'), '9876543210')
+ def test_copy_source_larger_than_max_file_size(self):
+ req = Request.blank('/v1/a/c/o', environ={'REQUEST_METHOD': 'PUT'},
+ headers={'Content-Length': '0',
+ 'X-Copy-From': '/c/o'})
- # copy-from object is too large to fit in target object
- req = Request.blank('/v1/a/c/o', environ={'REQUEST_METHOD': 'PUT'},
- headers={'Content-Length': '0',
- 'X-Copy-From': '/c/o'})
- self.app.update_request(req)
+ # copy-from object is too large to fit in target object
+ class LargeResponseBody(object):
- class LargeResponseBody(object):
+ def __len__(self):
+ return MAX_FILE_SIZE + 1
- def __len__(self):
- return MAX_FILE_SIZE + 1
+ def __getitem__(self, key):
+ return ''
- def __getitem__(self, key):
- return ''
+ copy_from_obj_body = LargeResponseBody()
+ status_list = (200, 200, 200, 200, 200)
+ # acct cont objc objc objc
+ kwargs = dict(body=copy_from_obj_body)
+ with self.controller_context(req, *status_list,
+ **kwargs) as controller:
+ self.app.update_request(req)
- copy_from_obj_body = LargeResponseBody()
- set_http_connect(200, 200, 200, 200, 200, 201, 201, 201,
- body=copy_from_obj_body)
self.app.memcache.store = {}
resp = controller.PUT(req)
self.assertEquals(resp.status_int, 413)
- def test_COPY(self):
- with save_globals():
- controller = proxy_server.ObjectController(self.app, 'a', 'c', 'o')
- req = Request.blank('/v1/a/c/o', environ={'REQUEST_METHOD': 'PUT'},
- headers={'Content-Length': '0'})
- req.account = 'a'
- set_http_connect(200, 200, 201, 201, 201)
- # acct cont obj obj obj
- resp = controller.PUT(req)
- self.assertEquals(resp.status_int, 201)
-
- req = Request.blank('/v1/a/c/o',
- environ={'REQUEST_METHOD': 'COPY'},
- headers={'Destination': 'c/o'})
- req.account = 'a'
- set_http_connect(200, 200, 200, 200, 200, 201, 201, 201, 200, 200)
- # acct cont acct cont objc objc objc obj obj obj
- self.app.memcache.store = {}
+ def test_basic_COPY(self):
+ req = Request.blank('/v1/a/c/o',
+ environ={'REQUEST_METHOD': 'COPY'},
+ headers={'Destination': 'c/o2'})
+ status_list = (200, 200, 200, 200, 200, 201, 201, 201)
+ # acct cont objc objc objc obj obj obj
+ with self.controller_context(req, *status_list) as controller:
resp = controller.COPY(req)
- self.assertEquals(resp.status_int, 201)
- self.assertEquals(resp.headers['x-copied-from'], 'c/o')
+ self.assertEquals(resp.status_int, 201)
+ self.assertEquals(resp.headers['x-copied-from'], 'c/o')
- req = Request.blank('/v1/a/c/o/o2',
- environ={'REQUEST_METHOD': 'COPY'},
- headers={'Destination': 'c/o'})
- req.account = 'a'
- controller.object_name = 'o/o2'
- set_http_connect(200, 200, 200, 200, 200, 201, 201, 201, 200, 200)
- # acct cont acct cont objc objc objc obj obj obj
- self.app.memcache.store = {}
+ def test_COPY_across_containers(self):
+ req = Request.blank('/v1/a/c/o',
+ environ={'REQUEST_METHOD': 'COPY'},
+ headers={'Destination': 'c2/o'})
+ status_list = (200, 200, 200, 200, 200, 200, 201, 201, 201)
+ # acct cont c2 objc objc objc obj obj obj
+ with self.controller_context(req, *status_list) as controller:
resp = controller.COPY(req)
- self.assertEquals(resp.status_int, 201)
- self.assertEquals(resp.headers['x-copied-from'], 'c/o/o2')
-
- req = Request.blank('/v1/a/c/o',
- environ={'REQUEST_METHOD': 'COPY'},
- headers={'Destination': '/c/o'})
- req.account = 'a'
- controller.object_name = 'o'
- set_http_connect(200, 200, 200, 200, 200, 201, 201, 201, 200, 200)
- # acct cont acct cont objc objc objc obj obj obj
- self.app.memcache.store = {}
+ self.assertEquals(resp.status_int, 201)
+ self.assertEquals(resp.headers['x-copied-from'], 'c/o')
+
+ def test_COPY_source_with_slashes_in_name(self):
+ req = Request.blank('/v1/a/c/o/o2',
+ environ={'REQUEST_METHOD': 'COPY'},
+ headers={'Destination': 'c/o'})
+ status_list = (200, 200, 200, 200, 200, 201, 201, 201)
+ # acct cont objc objc objc obj obj obj
+ with self.controller_context(req, *status_list) as controller:
resp = controller.COPY(req)
- self.assertEquals(resp.status_int, 201)
- self.assertEquals(resp.headers['x-copied-from'], 'c/o')
+ self.assertEquals(resp.status_int, 201)
+ self.assertEquals(resp.headers['x-copied-from'], 'c/o/o2')
- req = Request.blank('/v1/a/c/o/o2',
- environ={'REQUEST_METHOD': 'COPY'},
- headers={'Destination': '/c/o'})
- req.account = 'a'
- controller.object_name = 'o/o2'
- set_http_connect(200, 200, 200, 200, 200, 201, 201, 201, 200, 200)
- # acct cont acct cont objc objc objc obj obj obj
- self.app.memcache.store = {}
+ def test_COPY_destination_leading_slash(self):
+ req = Request.blank('/v1/a/c/o',
+ environ={'REQUEST_METHOD': 'COPY'},
+ headers={'Destination': '/c/o'})
+ status_list = (200, 200, 200, 200, 200, 201, 201, 201)
+ # acct cont objc objc objc obj obj obj
+ with self.controller_context(req, *status_list) as controller:
resp = controller.COPY(req)
- self.assertEquals(resp.status_int, 201)
- self.assertEquals(resp.headers['x-copied-from'], 'c/o/o2')
+ self.assertEquals(resp.status_int, 201)
+ self.assertEquals(resp.headers['x-copied-from'], 'c/o')
+
+ def test_COPY_source_with_slashes_destination_leading_slash(self):
+ req = Request.blank('/v1/a/c/o/o2',
+ environ={'REQUEST_METHOD': 'COPY'},
+ headers={'Destination': '/c/o'})
+ status_list = (200, 200, 200, 200, 200, 201, 201, 201)
+ # acct cont objc objc objc obj obj obj
+ with self.controller_context(req, *status_list) as controller:
+ resp = controller.COPY(req)
+ self.assertEquals(resp.status_int, 201)
+ self.assertEquals(resp.headers['x-copied-from'], 'c/o/o2')
- req = Request.blank('/v1/a/c/o',
- environ={'REQUEST_METHOD': 'COPY'},
- headers={'Destination': 'c_o'})
- req.account = 'a'
- controller.object_name = 'o'
- set_http_connect(200, 200)
- # acct cont
- self.app.memcache.store = {}
+ def test_COPY_no_object_in_destination(self):
+ req = Request.blank('/v1/a/c/o',
+ environ={'REQUEST_METHOD': 'COPY'},
+ headers={'Destination': 'c_o'})
+ status_list = [] # no requests needed
+ with self.controller_context(req, *status_list) as controller:
resp = controller.COPY(req)
- self.assertEquals(resp.status_int, 412)
+ self.assertEquals(resp.status_int, 412)
- req = Request.blank('/v1/a/c/o',
- environ={'REQUEST_METHOD': 'COPY'},
- headers={'Destination': '/c/o'})
- req.account = 'a'
- controller.object_name = 'o'
- set_http_connect(200, 200, 503, 503, 503)
- # acct cont objc objc objc
- self.app.memcache.store = {}
+ def test_COPY_server_error_reading_source(self):
+ req = Request.blank('/v1/a/c/o',
+ environ={'REQUEST_METHOD': 'COPY'},
+ headers={'Destination': '/c/o'})
+ status_list = (200, 200, 503, 503, 503)
+ # acct cont objc objc objc
+ with self.controller_context(req, *status_list) as controller:
resp = controller.COPY(req)
- self.assertEquals(resp.status_int, 503)
+ self.assertEquals(resp.status_int, 503)
- req = Request.blank('/v1/a/c/o',
- environ={'REQUEST_METHOD': 'COPY'},
- headers={'Destination': '/c/o'})
- req.account = 'a'
- controller.object_name = 'o'
- set_http_connect(200, 200, 404, 404, 404)
- # acct cont objc objc objc
- self.app.memcache.store = {}
+ def test_COPY_not_found_reading_source(self):
+ req = Request.blank('/v1/a/c/o',
+ environ={'REQUEST_METHOD': 'COPY'},
+ headers={'Destination': '/c/o'})
+ status_list = (200, 200, 404, 404, 404)
+ # acct cont objc objc objc
+ with self.controller_context(req, *status_list) as controller:
resp = controller.COPY(req)
- self.assertEquals(resp.status_int, 404)
+ self.assertEquals(resp.status_int, 404)
- req = Request.blank('/v1/a/c/o',
- environ={'REQUEST_METHOD': 'COPY'},
- headers={'Destination': '/c/o'})
- req.account = 'a'
- controller.object_name = 'o'
- set_http_connect(200, 200, 404, 404, 200, 201, 201, 201)
- # acct cont objc objc objc obj obj obj
- self.app.memcache.store = {}
+ def test_COPY_with_some_missing_sources(self):
+ req = Request.blank('/v1/a/c/o',
+ environ={'REQUEST_METHOD': 'COPY'},
+ headers={'Destination': '/c/o'})
+ status_list = (200, 200, 404, 404, 200, 201, 201, 201)
+ # acct cont objc objc objc obj obj obj
+ with self.controller_context(req, *status_list) as controller:
resp = controller.COPY(req)
- self.assertEquals(resp.status_int, 201)
+ self.assertEquals(resp.status_int, 201)
- req = Request.blank('/v1/a/c/o',
- environ={'REQUEST_METHOD': 'COPY'},
- headers={'Destination': '/c/o',
- 'X-Object-Meta-Ours': 'okay'})
- req.account = 'a'
- controller.object_name = 'o'
- set_http_connect(200, 200, 200, 200, 200, 201, 201, 201)
- # acct cont objc objc objc obj obj obj
- self.app.memcache.store = {}
+ def test_COPY_with_metadata(self):
+ req = Request.blank('/v1/a/c/o',
+ environ={'REQUEST_METHOD': 'COPY'},
+ headers={'Destination': '/c/o',
+ 'X-Object-Meta-Ours': 'okay'})
+ status_list = (200, 200, 200, 200, 200, 201, 201, 201)
+ # acct cont objc objc objc obj obj obj
+ with self.controller_context(req, *status_list) as controller:
resp = controller.COPY(req)
- self.assertEquals(resp.status_int, 201)
- self.assertEquals(resp.headers.get('x-object-meta-test'),
- 'testing')
- self.assertEquals(resp.headers.get('x-object-meta-ours'), 'okay')
- self.assertEquals(resp.headers.get('x-delete-at'), '9876543210')
+ self.assertEquals(resp.status_int, 201)
+ self.assertEquals(resp.headers.get('x-object-meta-test'),
+ 'testing')
+ self.assertEquals(resp.headers.get('x-object-meta-ours'), 'okay')
+ self.assertEquals(resp.headers.get('x-delete-at'), '9876543210')
- req = Request.blank('/v1/a/c/o',
- environ={'REQUEST_METHOD': 'COPY'},
- headers={'Destination': '/c/o'})
- self.app.update_request(req)
+ def test_COPY_source_larger_than_max_file_size(self):
+ req = Request.blank('/v1/a/c/o',
+ environ={'REQUEST_METHOD': 'COPY'},
+ headers={'Destination': '/c/o'})
- class LargeResponseBody(object):
+ class LargeResponseBody(object):
- def __len__(self):
- return MAX_FILE_SIZE + 1
+ def __len__(self):
+ return MAX_FILE_SIZE + 1
- def __getitem__(self, key):
- return ''
+ def __getitem__(self, key):
+ return ''
- copy_from_obj_body = LargeResponseBody()
- set_http_connect(200, 200, 200, 200, 200, 201, 201, 201,
- body=copy_from_obj_body)
- self.app.memcache.store = {}
+ copy_from_obj_body = LargeResponseBody()
+ status_list = (200, 200, 200, 200, 200)
+ # acct cont objc objc objc
+ kwargs = dict(body=copy_from_obj_body)
+ with self.controller_context(req, *status_list,
+ **kwargs) as controller:
resp = controller.COPY(req)
- self.assertEquals(resp.status_int, 413)
+ self.assertEquals(resp.status_int, 413)
def test_COPY_newest(self):
with save_globals():
@@ -2574,7 +2594,7 @@ class TestObjectController(unittest.TestCase):
def test_chunked_put(self):
- class ChunkedFile():
+ class ChunkedFile(object):
def __init__(self, bytes):
self.bytes = bytes
@@ -3824,9 +3844,7 @@ class TestObjectController(unittest.TestCase):
req.content_length = 0
resp = controller.OPTIONS(req)
self.assertEquals(200, resp.status_int)
- self.assertEquals(
- 'https://bar.baz',
- resp.headers['access-control-allow-origin'])
+ self.assertEquals('*', resp.headers['access-control-allow-origin'])
for verb in 'OPTIONS COPY GET POST PUT DELETE HEAD'.split():
self.assertTrue(
verb in resp.headers['access-control-allow-methods'])
@@ -3842,10 +3860,11 @@ class TestObjectController(unittest.TestCase):
def stubContainerInfo(*args):
return {
'cors': {
- 'allow_origin': 'http://foo.bar'
+ 'allow_origin': 'http://not.foo.bar'
}
}
controller.container_info = stubContainerInfo
+ controller.app.strict_cors_mode = False
def objectGET(controller, req):
return Response(headers={
@@ -3876,6 +3895,50 @@ class TestObjectController(unittest.TestCase):
'x-trans-id', 'x-object-meta-color'])
self.assertEquals(expected_exposed, exposed)
+ controller.app.strict_cors_mode = True
+ req = Request.blank(
+ '/v1/a/c/o.jpg',
+ {'REQUEST_METHOD': 'GET'},
+ headers={'Origin': 'http://foo.bar'})
+
+ resp = cors_validation(objectGET)(controller, req)
+
+ self.assertEquals(200, resp.status_int)
+ self.assertTrue('access-control-allow-origin' not in resp.headers)
+
+ def test_CORS_valid_with_obj_headers(self):
+ with save_globals():
+ controller = proxy_server.ObjectController(self.app, 'a', 'c', 'o')
+
+ def stubContainerInfo(*args):
+ return {
+ 'cors': {
+ 'allow_origin': 'http://foo.bar'
+ }
+ }
+ controller.container_info = stubContainerInfo
+
+ def objectGET(controller, req):
+ return Response(headers={
+ 'X-Object-Meta-Color': 'red',
+ 'X-Super-Secret': 'hush',
+ 'Access-Control-Allow-Origin': 'http://obj.origin',
+ 'Access-Control-Expose-Headers': 'x-trans-id'
+ })
+
+ req = Request.blank(
+ '/v1/a/c/o.jpg',
+ {'REQUEST_METHOD': 'GET'},
+ headers={'Origin': 'http://foo.bar'})
+
+ resp = cors_validation(objectGET)(controller, req)
+
+ self.assertEquals(200, resp.status_int)
+ self.assertEquals('http://obj.origin',
+ resp.headers['access-control-allow-origin'])
+ self.assertEquals('x-trans-id',
+ resp.headers['access-control-expose-headers'])
+
def _gather_x_container_headers(self, controller_call, req, *connect_args,
**kwargs):
header_list = kwargs.pop('header_list', ['X-Container-Device',
@@ -4092,7 +4155,8 @@ class TestContainerController(unittest.TestCase):
self.app = proxy_server.Application(None, FakeMemcache(),
account_ring=FakeRing(),
container_ring=FakeRing(),
- object_ring=FakeRing())
+ object_ring=FakeRing(),
+ logger=FakeLogger())
def test_transfer_headers(self):
src_headers = {'x-remove-versions-location': 'x',
@@ -4843,9 +4907,7 @@ class TestContainerController(unittest.TestCase):
req.content_length = 0
resp = controller.OPTIONS(req)
self.assertEquals(200, resp.status_int)
- self.assertEquals(
- 'https://bar.baz',
- resp.headers['access-control-allow-origin'])
+ self.assertEquals('*', resp.headers['access-control-allow-origin'])
for verb in 'OPTIONS GET POST PUT DELETE HEAD'.split():
self.assertTrue(
verb in resp.headers['access-control-allow-methods'])
@@ -5016,11 +5078,60 @@ class TestContainerController(unittest.TestCase):
'X-Account-Device': 'sdc'}
])
+ def test_PUT_backed_x_timestamp_header(self):
+ timestamps = []
+
+ def capture_timestamps(*args, **kwargs):
+ headers = kwargs['headers']
+ timestamps.append(headers.get('X-Timestamp'))
+
+ req = Request.blank('/v1/a/c', method='PUT', headers={'': ''})
+ with save_globals():
+ new_connect = set_http_connect(200, # account existance check
+ 201, 201, 201,
+ give_connect=capture_timestamps)
+ resp = self.app.handle_request(req)
+
+ # sanity
+ self.assertRaises(StopIteration, new_connect.code_iter.next)
+ self.assertEqual(2, resp.status_int // 100)
+
+ timestamps.pop(0) # account existance check
+ self.assertEqual(3, len(timestamps))
+ for timestamp in timestamps:
+ self.assertEqual(timestamp, timestamps[0])
+ self.assert_(re.match('[0-9]{10}\.[0-9]{5}', timestamp))
+
+ def test_DELETE_backed_x_timestamp_header(self):
+ timestamps = []
+
+ def capture_timestamps(*args, **kwargs):
+ headers = kwargs['headers']
+ timestamps.append(headers.get('X-Timestamp'))
+
+ req = Request.blank('/v1/a/c', method='DELETE', headers={'': ''})
+ self.app.update_request(req)
+ with save_globals():
+ new_connect = set_http_connect(200, # account existance check
+ 201, 201, 201,
+ give_connect=capture_timestamps)
+ resp = self.app.handle_request(req)
+
+ # sanity
+ self.assertRaises(StopIteration, new_connect.code_iter.next)
+ self.assertEqual(2, resp.status_int // 100)
+
+ timestamps.pop(0) # account existance check
+ self.assertEqual(3, len(timestamps))
+ for timestamp in timestamps:
+ self.assertEqual(timestamp, timestamps[0])
+ self.assert_(re.match('[0-9]{10}\.[0-9]{5}', timestamp))
+
def test_node_read_timeout_retry_to_container(self):
with save_globals():
req = Request.blank('/v1/a/c', environ={'REQUEST_METHOD': 'GET'})
self.app.node_timeout = 0.1
- set_http_connect(200, 200, 200, body='abcdef', slow=[2])
+ set_http_connect(200, 200, 200, body='abcdef', slow=[1.0, 1.0])
resp = req.get_response(self.app)
got_exc = False
try:
@@ -5547,6 +5658,143 @@ class TestAccountControllerFakeGetResponse(unittest.TestCase):
resp = req.get_response(self.app)
self.assertEqual(400, resp.status_int)
+ def test_account_acl_header_access(self):
+ acl = {
+ 'admin': ['AUTH_alice'],
+ 'read-write': ['AUTH_bob'],
+ 'read-only': ['AUTH_carol'],
+ }
+ prefix = get_sys_meta_prefix('account')
+ privileged_headers = {(prefix + 'core-access-control'): format_acl(
+ version=2, acl_dict=acl)}
+
+ app = proxy_server.Application(
+ None, FakeMemcache(), account_ring=FakeRing(),
+ container_ring=FakeRing(), object_ring=FakeRing())
+
+ with save_globals():
+ # Mock account server will provide privileged information (ACLs)
+ set_http_connect(200, 200, 200, headers=privileged_headers)
+ req = Request.blank('/v1/a', environ={'REQUEST_METHOD': 'GET'})
+ resp = app.handle_request(req)
+
+ # Not a swift_owner -- ACLs should NOT be in response
+ header = 'X-Account-Access-Control'
+ self.assert_(header not in resp.headers, '%r was in %r' % (
+ header, resp.headers))
+
+ # Same setup -- mock acct server will provide ACLs
+ set_http_connect(200, 200, 200, headers=privileged_headers)
+ req = Request.blank('/v1/a', environ={'REQUEST_METHOD': 'GET',
+ 'swift_owner': True})
+ resp = app.handle_request(req)
+
+ # For a swift_owner, the ACLs *should* be in response
+ self.assert_(header in resp.headers, '%r not in %r' % (
+ header, resp.headers))
+
+ def test_account_acls_through_delegation(self):
+
+ # Define a way to grab the requests sent out from the AccountController
+ # to the Account Server, and a way to inject responses we'd like the
+ # Account Server to return.
+ resps_to_send = []
+
+ @contextmanager
+ def patch_account_controller_method(verb):
+ old_method = getattr(proxy_server.AccountController, verb)
+ new_method = lambda self, req, *_, **__: resps_to_send.pop(0)
+ try:
+ setattr(proxy_server.AccountController, verb, new_method)
+ yield
+ finally:
+ setattr(proxy_server.AccountController, verb, old_method)
+
+ def make_test_request(http_method, swift_owner=True):
+ env = {
+ 'REQUEST_METHOD': http_method,
+ 'swift_owner': swift_owner,
+ }
+ acl = {
+ 'admin': ['foo'],
+ 'read-write': ['bar'],
+ 'read-only': ['bas'],
+ }
+ headers = {} if http_method in ('GET', 'HEAD') else {
+ 'x-account-access-control': format_acl(version=2, acl_dict=acl)
+ }
+
+ return Request.blank('/v1/a', environ=env, headers=headers)
+
+ # Our AccountController will invoke methods to communicate with the
+ # Account Server, and they will return responses like these:
+ def make_canned_response(http_method):
+ acl = {
+ 'admin': ['foo'],
+ 'read-write': ['bar'],
+ 'read-only': ['bas'],
+ }
+ headers = {'x-account-sysmeta-core-access-control': format_acl(
+ version=2, acl_dict=acl)}
+ canned_resp = Response(headers=headers)
+ canned_resp.environ = {
+ 'PATH_INFO': '/acct',
+ 'REQUEST_METHOD': http_method,
+ }
+ resps_to_send.append(canned_resp)
+
+ app = proxy_server.Application(
+ None, FakeMemcache(), account_ring=FakeRing(),
+ container_ring=FakeRing(), object_ring=FakeRing())
+ app.allow_account_management = True
+
+ ext_header = 'x-account-access-control'
+ with patch_account_controller_method('GETorHEAD_base'):
+ # GET/HEAD requests should remap sysmeta headers from acct server
+ for verb in ('GET', 'HEAD'):
+ make_canned_response(verb)
+ req = make_test_request(verb)
+ resp = app.handle_request(req)
+ h = parse_acl(version=2, data=resp.headers.get(ext_header))
+ self.assertEqual(h['admin'], ['foo'])
+ self.assertEqual(h['read-write'], ['bar'])
+ self.assertEqual(h['read-only'], ['bas'])
+
+ # swift_owner = False: GET/HEAD shouldn't return sensitive info
+ make_canned_response(verb)
+ req = make_test_request(verb, swift_owner=False)
+ resp = app.handle_request(req)
+ h = resp.headers
+ self.assertEqual(None, h.get(ext_header))
+
+ # swift_owner unset: GET/HEAD shouldn't return sensitive info
+ make_canned_response(verb)
+ req = make_test_request(verb, swift_owner=False)
+ del req.environ['swift_owner']
+ resp = app.handle_request(req)
+ h = resp.headers
+ self.assertEqual(None, h.get(ext_header))
+
+ # Verify that PUT/POST requests remap sysmeta headers from acct server
+ with patch_account_controller_method('make_requests'):
+ make_canned_response('PUT')
+ req = make_test_request('PUT')
+ resp = app.handle_request(req)
+
+ h = parse_acl(version=2, data=resp.headers.get(ext_header))
+ self.assertEqual(h['admin'], ['foo'])
+ self.assertEqual(h['read-write'], ['bar'])
+ self.assertEqual(h['read-only'], ['bas'])
+
+ make_canned_response('POST')
+ req = make_test_request('POST')
+ resp = app.handle_request(req)
+
+ h = parse_acl(version=2, data=resp.headers.get(ext_header))
+ self.assertEqual(h['admin'], ['foo'])
+ self.assertEqual(h['read-write'], ['bar'])
+ self.assertEqual(h['read-only'], ['bas'])
+
class FakeObjectController(object):