Skip to content

Instantly share code, notes, and snippets.

Show Gist options
  • Save psachin/5b8e049aa635ff96e7af362c15c289f6 to your computer and use it in GitHub Desktop.
Save psachin/5b8e049aa635ff96e7af362c15c289f6 to your computer and use it in GitHub Desktop.
From 16c36ce82259fdf82686492d3447dc84b2070ee7 Mon Sep 17 00:00:00 2001
From: Sachin Patil <psachin@redhat.com>
Date: Wed, 28 Dec 2016 22:28:19 +0000
Subject: [PATCH] Proxy server controller tests should be reorganized
Following tests were moved:
- @patch_policies within test_server.TestObjectController -> controllers/test_obj.py
- @patch_policies within test_server.TestContainerController -> controllers/test_container.py
- @patch_policies within test_server.TestAccountController -> controllers/test_account.py
- Lots of refactor and cleanup
Closes-Bug: #1646963
Change-Id: Ib7affafcf7c13193b523e983711461f153a6bc93
Signed-off-by: Sachin Patil <psachin@redhat.com>
---
test/unit/common/middleware/test_copy.py | 16 +-
test/unit/proxy/__init__.py | 71 +
test/unit/proxy/controllers/test_account.py | 714 ++++++-
test/unit/proxy/controllers/test_container.py | 1314 ++++++++++++-
test/unit/proxy/controllers/test_obj.py | 2477 +++++++++++++++++++++++--
test/unit/proxy/test_mem_server.py | 8 +-
test/unit/proxy/test_server.py | 2186 +---------------------
7 files changed, 4436 insertions(+), 2350 deletions(-)
diff --git a/test/unit/common/middleware/test_copy.py b/test/unit/common/middleware/test_copy.py
index 2dba7194ad73..695bbe8d769d 100644
--- a/test/unit/common/middleware/test_copy.py
+++ b/test/unit/common/middleware/test_copy.py
@@ -29,8 +29,8 @@ from swift.common.storage_policy import POLICIES
from swift.common.swob import Request, HTTPException
from test.unit import patch_policies, debug_logger, FakeMemcache, FakeRing
from test.unit.common.middleware.helpers import FakeSwift
-from test.unit.proxy.controllers.test_obj import set_http_connect, \
- PatchedObjControllerApp
+from test.unit.proxy.controllers.test_obj import (
+ set_http_connect_contextmgr, PatchedObjControllerApp)
class TestCopyConstraints(unittest.TestCase):
@@ -1570,9 +1570,10 @@ class TestServerSideCopyMiddlewareWithEC(unittest.TestCase):
if method == 'PUT':
put_hdrs.append(args[0])
- with set_http_connect(*status_codes, body_iter=body_iter,
- headers=headers, expect_headers=expect_headers,
- give_connect=capture_conn):
+ with set_http_connect_contextmgr(
+ *status_codes, body_iter=body_iter,
+ headers=headers, expect_headers=expect_headers,
+ give_connect=capture_conn):
resp = req.get_response(self.ssc)
self.assertEqual(resp.status_int, 201)
@@ -1627,8 +1628,9 @@ class TestServerSideCopyMiddlewareWithEC(unittest.TestCase):
'X-Obj-Multiphase-Commit': 'yes'
}
# TODO possibly use FakeApp here
- with set_http_connect(*status_codes, body_iter=body_iter,
- headers=headers, expect_headers=expect_headers):
+ with set_http_connect_contextmgr(
+ *status_codes, body_iter=body_iter,
+ headers=headers, expect_headers=expect_headers):
resp = req.get_response(self.ssc)
self.assertEqual(resp.status_int, 416)
self.assertEqual(resp.content_length, len(range_not_satisfiable_body))
diff --git a/test/unit/proxy/__init__.py b/test/unit/proxy/__init__.py
index e69de29bb2d1..42a4d83850ab 100644
--- a/test/unit/proxy/__init__.py
+++ b/test/unit/proxy/__init__.py
@@ -0,0 +1,71 @@
+# Copyright (c) 2010-2016 OpenStack Foundation
+#
+# 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.
+
+""" Swift tests """
+
+from __future__ import print_function
+from contextlib import contextmanager
+
+from swift.proxy import controllers as proxy_controllers
+
+from test.unit import (FakeMemcache, fake_http_connect)
+
+
+def _make_callback_func(calls):
+ def callback(ipaddr, port, device, partition, method, path,
+ headers=None, query_string=None, ssl=False):
+ context = {}
+ context['method'] = method
+ context['path'] = path
+ context['headers'] = headers or {}
+ calls.append(context)
+ return callback
+
+
+def set_http_connect(*args, **kwargs):
+ new_connect = fake_http_connect(*args, **kwargs)
+ proxy_controllers.base.http_connect = new_connect
+ proxy_controllers.obj.http_connect = new_connect
+ proxy_controllers.account.http_connect = new_connect
+ proxy_controllers.container.http_connect = new_connect
+ return new_connect
+
+
+@contextmanager
+def save_globals():
+ orig_http_connect = getattr(proxy_controllers.base, 'http_connect',
+ None)
+ orig_account_info = getattr(proxy_controllers.Controller,
+ 'account_info', None)
+ orig_container_info = getattr(proxy_controllers.Controller,
+ 'container_info', None)
+
+ try:
+ yield True
+ finally:
+ proxy_controllers.Controller.account_info = orig_account_info
+ proxy_controllers.base.http_connect = orig_http_connect
+ proxy_controllers.obj.http_connect = orig_http_connect
+ proxy_controllers.account.http_connect = orig_http_connect
+ proxy_controllers.container.http_connect = orig_http_connect
+ proxy_controllers.Controller.container_info = orig_container_info
+
+
+class FakeMemcacheReturnsNone(FakeMemcache):
+
+ def get(self, key):
+ # Returns None as the timestamp of the container; assumes we're only
+ # using the FakeMemcache for container existence checks.
+ return None
diff --git a/test/unit/proxy/controllers/test_account.py b/test/unit/proxy/controllers/test_account.py
index a46dcc90f1b3..d9d598cbf03c 100644
--- a/test/unit/proxy/controllers/test_account.py
+++ b/test/unit/proxy/controllers/test_account.py
@@ -15,27 +15,29 @@
import mock
import unittest
+from abc import ABCMeta
+from contextlib import contextmanager
+from six import add_metaclass
from swift.common.swob import Request, Response
-from swift.common.middleware.acl import format_acl
+from swift.common.middleware.acl import (
+ parse_acl, format_acl)
from swift.proxy import server as proxy_server
from swift.proxy.controllers.base import headers_to_account_info
from swift.common import constraints
-from test.unit import fake_http_connect, FakeRing, FakeMemcache
+from test.unit import (
+ fake_http_connect, FakeRing, FakeMemcache, patch_policies)
+from test.unit.proxy import (
+ _make_callback_func, save_globals,
+ set_http_connect, FakeMemcacheReturnsNone)
from swift.common.storage_policy import StoragePolicy
from swift.common.request_helpers import get_sys_meta_prefix
import swift.proxy.controllers.base
from swift.proxy.controllers.base import get_account_info
-from test.unit import patch_policies
-
-@patch_policies([StoragePolicy(0, 'zero', True, object_ring=FakeRing())])
-class TestAccountController(unittest.TestCase):
- def setUp(self):
- self.app = proxy_server.Application(
- None, FakeMemcache(),
- account_ring=FakeRing(), container_ring=FakeRing())
+@add_metaclass(ABCMeta)
+class AccountControllerMeta(object):
def _make_callback_func(self, context):
def callback(ipaddr, port, device, partition, method, path,
@@ -62,6 +64,470 @@ class TestAccountController(unittest.TestCase):
'Expected %s but got %s. Failed case: %s' %
(expected, resp.status_int, str(responses)))
+ def assert_status_map(self, method, statuses, expected, env_expected=None,
+ headers=None, **kwargs):
+ headers = headers or {}
+ with save_globals():
+ set_http_connect(*statuses, **kwargs)
+ req = Request.blank('/v1/a', {}, headers=headers)
+ self.app.update_request(req)
+ res = method(req)
+ self.assertEqual(res.status_int, expected)
+ infocache = res.environ.get('swift.infocache', {})
+ if env_expected:
+ self.assertEqual(infocache['account/a']['status'],
+ env_expected)
+ set_http_connect(*statuses)
+ req = Request.blank('/v1/a/', {})
+ self.app.update_request(req)
+ res = method(req)
+ infocache = res.environ.get('swift.infocache', {})
+ self.assertEqual(res.status_int, expected)
+ if env_expected:
+ self.assertEqual(infocache['account/a']['status'],
+ env_expected)
+
+
+@patch_policies([StoragePolicy(0, 'zero', True, object_ring=FakeRing())])
+class TestAccountController(AccountControllerMeta, unittest.TestCase):
+ def setUp(self):
+ self.app = proxy_server.Application(
+ None, FakeMemcache(),
+ account_ring=FakeRing(), container_ring=FakeRing())
+
+ def test_OPTIONS(self):
+ with save_globals():
+ self.app.allow_account_management = False
+ controller = proxy_server.AccountController(self.app, 'account')
+ req = Request.blank('/v1/account', {'REQUEST_METHOD': 'OPTIONS'})
+ req.content_length = 0
+ resp = controller.OPTIONS(req)
+ self.assertEqual(200, resp.status_int)
+ for verb in 'OPTIONS GET POST HEAD'.split():
+ self.assertIn(verb, resp.headers['Allow'])
+ self.assertEqual(len(resp.headers['Allow'].split(', ')), 4)
+
+ # Test a CORS OPTIONS request (i.e. including Origin and
+ # Access-Control-Request-Method headers)
+ self.app.allow_account_management = False
+ controller = proxy_server.AccountController(self.app, 'account')
+ req = Request.blank(
+ '/v1/account', {'REQUEST_METHOD': 'OPTIONS'},
+ headers={'Origin': 'http://foo.com',
+ 'Access-Control-Request-Method': 'GET'})
+ req.content_length = 0
+ resp = controller.OPTIONS(req)
+ self.assertEqual(200, resp.status_int)
+ for verb in 'OPTIONS GET POST HEAD'.split():
+ self.assertIn(verb, resp.headers['Allow'])
+ self.assertEqual(len(resp.headers['Allow'].split(', ')), 4)
+
+ self.app.allow_account_management = True
+ controller = proxy_server.AccountController(self.app, 'account')
+ req = Request.blank('/v1/account', {'REQUEST_METHOD': 'OPTIONS'})
+ req.content_length = 0
+ resp = controller.OPTIONS(req)
+ self.assertEqual(200, resp.status_int)
+ for verb in 'OPTIONS GET POST PUT DELETE HEAD'.split():
+ self.assertIn(verb, resp.headers['Allow'])
+ self.assertEqual(len(resp.headers['Allow'].split(', ')), 6)
+
+ def test_GET(self):
+ with save_globals():
+ controller = proxy_server.AccountController(self.app, 'a')
+ # GET returns after the first successful call to an Account Server
+ self.assert_status_map(controller.GET, (200,), 200, 200)
+ self.assert_status_map(controller.GET, (503, 200), 200, 200)
+ self.assert_status_map(controller.GET, (503, 503, 200), 200, 200)
+ self.assert_status_map(controller.GET, (204,), 204, 204)
+ self.assert_status_map(controller.GET, (503, 204), 204, 204)
+ self.assert_status_map(controller.GET, (503, 503, 204), 204, 204)
+ self.assert_status_map(controller.GET, (404, 200), 200, 200)
+ self.assert_status_map(controller.GET, (404, 404, 200), 200, 200)
+ self.assert_status_map(controller.GET, (404, 503, 204), 204, 204)
+ # If Account servers fail, if autocreate = False, return majority
+ # response
+ self.assert_status_map(controller.GET, (404, 404, 404), 404, 404)
+ self.assert_status_map(controller.GET, (404, 404, 503), 404, 404)
+ self.assert_status_map(controller.GET, (404, 503, 503), 503)
+
+ self.app.memcache = FakeMemcacheReturnsNone()
+ self.assert_status_map(controller.GET, (404, 404, 404), 404, 404)
+
+ def test_GET_autocreate(self):
+ with save_globals():
+ controller = proxy_server.AccountController(self.app, 'a')
+ self.app.memcache = FakeMemcacheReturnsNone()
+ self.assertFalse(self.app.account_autocreate)
+ # Repeat the test for autocreate = False and 404 by all
+ self.assert_status_map(controller.GET,
+ (404, 404, 404), 404)
+ self.assert_status_map(controller.GET,
+ (404, 503, 404), 404)
+ # When autocreate is True, if none of the nodes respond 2xx
+ # And quorum of the nodes responded 404,
+ # ALL nodes are asked to create the account
+ # If successful, the GET request is repeated.
+ controller.app.account_autocreate = True
+ self.assert_status_map(controller.GET,
+ (404, 404, 404), 204)
+ self.assert_status_map(controller.GET,
+ (404, 503, 404), 204)
+
+ # We always return 503 if no majority between 4xx, 3xx or 2xx found
+ self.assert_status_map(controller.GET,
+ (500, 500, 400), 503)
+
+ def test_HEAD(self):
+ # Same behaviour as GET
+ with save_globals():
+ controller = proxy_server.AccountController(self.app, 'a')
+ self.assert_status_map(controller.HEAD, (200,), 200, 200)
+ self.assert_status_map(controller.HEAD, (503, 200), 200, 200)
+ self.assert_status_map(controller.HEAD, (503, 503, 200), 200, 200)
+ self.assert_status_map(controller.HEAD, (204,), 204, 204)
+ self.assert_status_map(controller.HEAD, (503, 204), 204, 204)
+ self.assert_status_map(controller.HEAD, (204, 503, 503), 204, 204)
+ self.assert_status_map(controller.HEAD, (204,), 204, 204)
+ self.assert_status_map(controller.HEAD, (404, 404, 404), 404, 404)
+ self.assert_status_map(controller.HEAD, (404, 404, 200), 200, 200)
+ self.assert_status_map(controller.HEAD, (404, 200), 200, 200)
+ self.assert_status_map(controller.HEAD, (404, 404, 503), 404, 404)
+ self.assert_status_map(controller.HEAD, (404, 503, 503), 503)
+ self.assert_status_map(controller.HEAD, (404, 503, 204), 204, 204)
+
+ def test_HEAD_autocreate(self):
+ # Same behaviour as GET
+ with save_globals():
+ controller = proxy_server.AccountController(self.app, 'a')
+ self.app.memcache = FakeMemcacheReturnsNone()
+ self.assertFalse(self.app.account_autocreate)
+ self.assert_status_map(controller.HEAD,
+ (404, 404, 404), 404)
+ controller.app.account_autocreate = True
+ self.assert_status_map(controller.HEAD,
+ (404, 404, 404), 204)
+ self.assert_status_map(controller.HEAD,
+ (500, 404, 404), 204)
+ # We always return 503 if no majority between 4xx, 3xx or 2xx found
+ self.assert_status_map(controller.HEAD,
+ (500, 500, 400), 503)
+
+ def test_POST_autocreate(self):
+ with save_globals():
+ controller = proxy_server.AccountController(self.app, 'a')
+ self.app.memcache = FakeMemcacheReturnsNone()
+ # first test with autocreate being False
+ self.assertFalse(self.app.account_autocreate)
+ self.assert_status_map(controller.POST,
+ (404, 404, 404), 404)
+ # next turn it on and test account being created than updated
+ controller.app.account_autocreate = True
+ self.assert_status_map(
+ controller.POST,
+ (404, 404, 404, 202, 202, 202, 201, 201, 201), 201)
+ # account_info PUT account POST account
+ self.assert_status_map(
+ controller.POST,
+ (404, 404, 503, 201, 201, 503, 204, 204, 504), 204)
+ # what if create fails
+ self.assert_status_map(
+ controller.POST,
+ (404, 404, 404, 403, 403, 403, 400, 400, 400), 400)
+
+ def test_POST_autocreate_with_sysmeta(self):
+ with save_globals():
+ controller = proxy_server.AccountController(self.app, 'a')
+ self.app.memcache = FakeMemcacheReturnsNone()
+ # first test with autocreate being False
+ self.assertFalse(self.app.account_autocreate)
+ self.assert_status_map(controller.POST,
+ (404, 404, 404), 404)
+ # next turn it on and test account being created than updated
+ controller.app.account_autocreate = True
+ calls = []
+ callback = _make_callback_func(calls)
+ key, value = 'X-Account-Sysmeta-Blah', 'something'
+ headers = {key: value}
+ self.assert_status_map(
+ controller.POST,
+ (404, 404, 404, 202, 202, 202, 201, 201, 201), 201,
+ # POST , autocreate PUT, POST again
+ headers=headers,
+ give_connect=callback)
+ self.assertEqual(9, len(calls))
+ for call in calls:
+ self.assertIn(key, call['headers'],
+ '%s call, key %s missing in headers %s' %
+ (call['method'], key, call['headers']))
+ self.assertEqual(value, call['headers'][key])
+
+ def test_connection_refused(self):
+ self.app.account_ring.get_nodes('account')
+ for dev in self.app.account_ring.devs:
+ dev['ip'] = '127.0.0.1'
+ dev['port'] = 1 # can't connect on this port
+ controller = proxy_server.AccountController(self.app, 'account')
+ req = Request.blank('/v1/account', environ={'REQUEST_METHOD': 'HEAD'})
+ self.app.update_request(req)
+ resp = controller.HEAD(req)
+ self.assertEqual(resp.status_int, 503)
+
+ def test_other_socket_error(self):
+ self.app.account_ring.get_nodes('account')
+ for dev in self.app.account_ring.devs:
+ dev['ip'] = '127.0.0.1'
+ dev['port'] = -1 # invalid port number
+ controller = proxy_server.AccountController(self.app, 'account')
+ req = Request.blank('/v1/account', environ={'REQUEST_METHOD': 'HEAD'})
+ self.app.update_request(req)
+ resp = controller.HEAD(req)
+ self.assertEqual(resp.status_int, 503)
+
+ def test_response_get_accept_ranges_header(self):
+ with save_globals():
+ set_http_connect(200, 200, body='{}')
+ controller = proxy_server.AccountController(self.app, 'account')
+ req = Request.blank('/v1/a?format=json')
+ self.app.update_request(req)
+ res = controller.GET(req)
+ self.assertIn('accept-ranges', res.headers)
+ self.assertEqual(res.headers['accept-ranges'], 'bytes')
+
+ def test_response_head_accept_ranges_header(self):
+ with save_globals():
+ set_http_connect(200, 200, body='{}')
+ controller = proxy_server.AccountController(self.app, 'account')
+ req = Request.blank('/v1/a?format=json')
+ self.app.update_request(req)
+ res = controller.HEAD(req)
+ res.body
+ self.assertIn('accept-ranges', res.headers)
+ self.assertEqual(res.headers['accept-ranges'], 'bytes')
+
+ def test_PUT(self):
+ with save_globals():
+ controller = proxy_server.AccountController(self.app, 'account')
+
+ def test_status_map(statuses, expected, **kwargs):
+ set_http_connect(*statuses, **kwargs)
+ self.app.memcache.store = {}
+ req = Request.blank('/v1/a', {})
+ req.content_length = 0
+ self.app.update_request(req)
+ res = controller.PUT(req)
+ expected = str(expected)
+ self.assertEqual(res.status[:len(expected)], expected)
+ test_status_map((201, 201, 201), 405)
+ self.app.allow_account_management = True
+ test_status_map((201, 201, 201), 201)
+ test_status_map((201, 201, 500), 201)
+ test_status_map((201, 500, 500), 503)
+ test_status_map((204, 500, 404), 503)
+
+ def test_PUT_max_account_name_length(self):
+ with save_globals():
+ self.app.allow_account_management = True
+ limit = constraints.MAX_ACCOUNT_NAME_LENGTH
+ controller = proxy_server.AccountController(self.app, '1' * limit)
+ self.assert_status_map(controller.PUT, (201, 201, 201), 201)
+ controller = proxy_server.AccountController(
+ self.app, '2' * (limit + 1))
+ self.assert_status_map(controller.PUT, (201, 201, 201), 400)
+
+ def test_PUT_connect_exceptions(self):
+ with save_globals():
+ self.app.allow_account_management = True
+ controller = proxy_server.AccountController(self.app, 'account')
+ self.assert_status_map(controller.PUT, (201, 201, -1), 201)
+ self.assert_status_map(controller.PUT, (201, -1, -1), 503)
+ self.assert_status_map(controller.PUT, (503, 503, -1), 503)
+
+ def test_PUT_status(self):
+ with save_globals():
+ self.app.allow_account_management = True
+ controller = proxy_server.AccountController(self.app, 'account')
+ self.assert_status_map(controller.PUT, (201, 201, 202), 202)
+
+ def metadata_helper(self, method):
+ for test_header, test_value in (
+ ('X-Account-Meta-TestHeader', 'TestValue'),
+ ('X-Account-Meta-TestHeader', ''),
+ ('X-Remove-Account-Meta-TestHeader', 'anything')):
+ test_errors = []
+
+ def test_connect(ipaddr, port, device, partition, method, path,
+ headers=None, query_string=None):
+ if path == '/a':
+ find_header = test_header
+ find_value = test_value
+ if find_header.lower().startswith('x-remove-'):
+ find_header = \
+ find_header.lower().replace('-remove', '', 1)
+ find_value = ''
+ for k, v in headers.items():
+ if k.lower() == find_header.lower() and \
+ v == find_value:
+ break
+ else:
+ test_errors.append('%s: %s not in %s' %
+ (find_header, find_value, headers))
+ with save_globals():
+ self.app.allow_account_management = True
+ controller = \
+ proxy_server.AccountController(self.app, 'a')
+ set_http_connect(201, 201, 201, give_connect=test_connect)
+ req = Request.blank('/v1/a/c',
+ environ={'REQUEST_METHOD': method},
+ headers={test_header: test_value})
+ self.app.update_request(req)
+ getattr(controller, method)(req)
+ self.assertEqual(test_errors, [])
+
+ def test_PUT_metadata(self):
+ self.metadata_helper('PUT')
+
+ def test_POST_metadata(self):
+ self.metadata_helper('POST')
+
+ def bad_metadata_helper(self, method):
+ with save_globals():
+ self.app.allow_account_management = True
+ controller = proxy_server.AccountController(self.app, 'a')
+ set_http_connect(200, 201, 201, 201)
+ req = Request.blank('/v1/a/c', environ={'REQUEST_METHOD': method})
+ self.app.update_request(req)
+ resp = getattr(controller, method)(req)
+ self.assertEqual(resp.status_int, 201)
+
+ set_http_connect(201, 201, 201)
+ req = Request.blank('/v1/a/c', environ={'REQUEST_METHOD': method},
+ headers={'X-Account-Meta-' +
+ ('a' * constraints.MAX_META_NAME_LENGTH): 'v'})
+ self.app.update_request(req)
+ resp = getattr(controller, method)(req)
+ self.assertEqual(resp.status_int, 201)
+ set_http_connect(201, 201, 201)
+ req = Request.blank(
+ '/v1/a/c', environ={'REQUEST_METHOD': method},
+ headers={'X-Account-Meta-' +
+ ('a' * (constraints.MAX_META_NAME_LENGTH + 1)): 'v'})
+ self.app.update_request(req)
+ resp = getattr(controller, method)(req)
+ self.assertEqual(resp.status_int, 400)
+
+ set_http_connect(201, 201, 201)
+ req = Request.blank('/v1/a/c', environ={'REQUEST_METHOD': method},
+ headers={'X-Account-Meta-Too-Long':
+ 'a' * constraints.MAX_META_VALUE_LENGTH})
+ self.app.update_request(req)
+ resp = getattr(controller, method)(req)
+ self.assertEqual(resp.status_int, 201)
+ set_http_connect(201, 201, 201)
+ req = Request.blank('/v1/a/c', environ={'REQUEST_METHOD': method},
+ headers={'X-Account-Meta-Too-Long':
+ 'a' * (constraints.MAX_META_VALUE_LENGTH + 1)})
+ self.app.update_request(req)
+ resp = getattr(controller, method)(req)
+ self.assertEqual(resp.status_int, 400)
+
+ set_http_connect(201, 201, 201)
+ headers = {}
+ for x in range(constraints.MAX_META_COUNT):
+ headers['X-Account-Meta-%d' % x] = 'v'
+ req = Request.blank('/v1/a/c', environ={'REQUEST_METHOD': method},
+ headers=headers)
+ self.app.update_request(req)
+ resp = getattr(controller, method)(req)
+ self.assertEqual(resp.status_int, 201)
+ set_http_connect(201, 201, 201)
+ headers = {}
+ for x in range(constraints.MAX_META_COUNT + 1):
+ headers['X-Account-Meta-%d' % x] = 'v'
+ req = Request.blank('/v1/a/c', environ={'REQUEST_METHOD': method},
+ headers=headers)
+ self.app.update_request(req)
+ resp = getattr(controller, method)(req)
+ self.assertEqual(resp.status_int, 400)
+
+ set_http_connect(201, 201, 201)
+ headers = {}
+ header_value = 'a' * constraints.MAX_META_VALUE_LENGTH
+ size = 0
+ x = 0
+ while size < (constraints.MAX_META_OVERALL_SIZE - 4
+ - constraints.MAX_META_VALUE_LENGTH):
+ size += 4 + constraints.MAX_META_VALUE_LENGTH
+ headers['X-Account-Meta-%04d' % x] = header_value
+ x += 1
+ if constraints.MAX_META_OVERALL_SIZE - size > 1:
+ headers['X-Account-Meta-a'] = \
+ 'a' * (constraints.MAX_META_OVERALL_SIZE - size - 1)
+ req = Request.blank('/v1/a/c', environ={'REQUEST_METHOD': method},
+ headers=headers)
+ self.app.update_request(req)
+ resp = getattr(controller, method)(req)
+ self.assertEqual(resp.status_int, 201)
+ set_http_connect(201, 201, 201)
+ headers['X-Account-Meta-a'] = \
+ 'a' * (constraints.MAX_META_OVERALL_SIZE - size)
+ req = Request.blank('/v1/a/c', environ={'REQUEST_METHOD': method},
+ headers=headers)
+ self.app.update_request(req)
+ resp = getattr(controller, method)(req)
+ self.assertEqual(resp.status_int, 400)
+
+ def test_PUT_bad_metadata(self):
+ self.bad_metadata_helper('PUT')
+
+ def test_POST_bad_metadata(self):
+ self.bad_metadata_helper('POST')
+
+ def test_DELETE(self):
+ with save_globals():
+ controller = proxy_server.AccountController(self.app, 'account')
+
+ def test_status_map(statuses, expected, **kwargs):
+ set_http_connect(*statuses, **kwargs)
+ self.app.memcache.store = {}
+ req = Request.blank('/v1/a', {'REQUEST_METHOD': 'DELETE'})
+ req.content_length = 0
+ self.app.update_request(req)
+ res = controller.DELETE(req)
+ expected = str(expected)
+ self.assertEqual(res.status[:len(expected)], expected)
+ test_status_map((201, 201, 201), 405)
+ self.app.allow_account_management = True
+ test_status_map((201, 201, 201), 201)
+ test_status_map((201, 201, 500), 201)
+ test_status_map((201, 500, 500), 503)
+ test_status_map((204, 500, 404), 503)
+
+ def test_DELETE_with_query_string(self):
+ # Extra safety in case someone typos a query string for an
+ # account-level DELETE request that was really meant to be caught by
+ # some middleware.
+ with save_globals():
+ controller = proxy_server.AccountController(self.app, 'account')
+
+ def test_status_map(statuses, expected, **kwargs):
+ set_http_connect(*statuses, **kwargs)
+ self.app.memcache.store = {}
+ req = Request.blank('/v1/a?whoops',
+ environ={'REQUEST_METHOD': 'DELETE'})
+ req.content_length = 0
+ self.app.update_request(req)
+ res = controller.DELETE(req)
+ expected = str(expected)
+ self.assertEqual(res.status[:len(expected)], expected)
+ test_status_map((201, 201, 201), 400)
+ self.app.allow_account_management = True
+ test_status_map((201, 201, 201), 400)
+ test_status_map((201, 201, 500), 400)
+ test_status_map((201, 500, 500), 400)
+ test_status_map((204, 500, 404), 400)
+
+ # Original functions start from here
def test_account_info_in_response_env(self):
controller = proxy_server.AccountController(self.app, 'AUTH_bob')
with mock.patch('swift.proxy.controllers.base.http_connect',
@@ -316,7 +782,7 @@ class TestAccountController(unittest.TestCase):
@patch_policies(
[StoragePolicy(0, 'zero', True, object_ring=FakeRing(replicas=4))])
-class TestAccountController4Replicas(TestAccountController):
+class TestAccountController4Replicas(AccountControllerMeta, unittest.TestCase):
def setUp(self):
self.app = proxy_server.Application(
None,
@@ -402,5 +868,231 @@ class TestGetAccountInfo(unittest.TestCase):
self.assertEqual(410, info.get('status'))
+@patch_policies([StoragePolicy(0, 'zero', True, object_ring=FakeRing())])
+class TestAccountControllerFakeGetResponse(unittest.TestCase):
+ """
+ Test all the faked-out GET responses for accounts that don't exist. They
+ have to match the responses for empty accounts that really exist.
+ """
+ def setUp(self):
+ conf = {'account_autocreate': 'yes'}
+ self.app = proxy_server.Application(conf, FakeMemcache(),
+ account_ring=FakeRing(),
+ container_ring=FakeRing())
+ self.app.memcache = FakeMemcacheReturnsNone()
+
+ def test_GET_autocreate_accept_json(self):
+ with save_globals():
+ set_http_connect(*([404] * 100)) # nonexistent: all backends 404
+ req = Request.blank(
+ '/v1/a', headers={'Accept': 'application/json'},
+ environ={'REQUEST_METHOD': 'GET',
+ 'PATH_INFO': '/v1/a'})
+ resp = req.get_response(self.app)
+ self.assertEqual(200, resp.status_int)
+ self.assertEqual('application/json; charset=utf-8',
+ resp.headers['Content-Type'])
+ self.assertEqual("[]", resp.body)
+
+ def test_GET_autocreate_format_json(self):
+ with save_globals():
+ set_http_connect(*([404] * 100)) # nonexistent: all backends 404
+ req = Request.blank('/v1/a?format=json',
+ environ={'REQUEST_METHOD': 'GET',
+ 'PATH_INFO': '/v1/a',
+ 'QUERY_STRING': 'format=json'})
+ resp = req.get_response(self.app)
+ self.assertEqual(200, resp.status_int)
+ self.assertEqual('application/json; charset=utf-8',
+ resp.headers['Content-Type'])
+ self.assertEqual("[]", resp.body)
+
+ def test_GET_autocreate_accept_xml(self):
+ with save_globals():
+ set_http_connect(*([404] * 100)) # nonexistent: all backends 404
+ req = Request.blank('/v1/a', headers={"Accept": "text/xml"},
+ environ={'REQUEST_METHOD': 'GET',
+ 'PATH_INFO': '/v1/a'})
+
+ resp = req.get_response(self.app)
+ self.assertEqual(200, resp.status_int)
+
+ self.assertEqual('text/xml; charset=utf-8',
+ resp.headers['Content-Type'])
+ empty_xml_listing = ('<?xml version="1.0" encoding="UTF-8"?>\n'
+ '<account name="a">\n</account>')
+ self.assertEqual(empty_xml_listing, resp.body)
+
+ def test_GET_autocreate_format_xml(self):
+ with save_globals():
+ set_http_connect(*([404] * 100)) # nonexistent: all backends 404
+ req = Request.blank('/v1/a?format=xml',
+ environ={'REQUEST_METHOD': 'GET',
+ 'PATH_INFO': '/v1/a',
+ 'QUERY_STRING': 'format=xml'})
+ resp = req.get_response(self.app)
+ self.assertEqual(200, resp.status_int)
+ self.assertEqual('application/xml; charset=utf-8',
+ resp.headers['Content-Type'])
+ empty_xml_listing = ('<?xml version="1.0" encoding="UTF-8"?>\n'
+ '<account name="a">\n</account>')
+ self.assertEqual(empty_xml_listing, resp.body)
+
+ def test_GET_autocreate_accept_unknown(self):
+ with save_globals():
+ set_http_connect(*([404] * 100)) # nonexistent: all backends 404
+ req = Request.blank('/v1/a', headers={"Accept": "mystery/meat"},
+ environ={'REQUEST_METHOD': 'GET',
+ 'PATH_INFO': '/v1/a'})
+ resp = req.get_response(self.app)
+ self.assertEqual(406, resp.status_int)
+
+ def test_GET_autocreate_format_invalid_utf8(self):
+ with save_globals():
+ set_http_connect(*([404] * 100)) # nonexistent: all backends 404
+ req = Request.blank('/v1/a?format=\xff\xfe',
+ environ={'REQUEST_METHOD': 'GET',
+ 'PATH_INFO': '/v1/a',
+ 'QUERY_STRING': 'format=\xff\xfe'})
+ 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())
+
+ 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.assertNotIn(header, 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.assertIn(header, 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())
+ 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.assertIsNone(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.assertIsNone(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'])
+
if __name__ == '__main__':
unittest.main()
diff --git a/test/unit/proxy/controllers/test_container.py b/test/unit/proxy/controllers/test_container.py
index 1cc0d1986fd4..239ccd76592d 100644
--- a/test/unit/proxy/controllers/test_container.py
+++ b/test/unit/proxy/controllers/test_container.py
@@ -15,35 +15,54 @@
import mock
import unittest
-
+from abc import ABCMeta
from eventlet import Timeout
+import random
+import re
+from contextlib import contextmanager
+from six import add_metaclass
+import time
+from swift.common import constraints
from swift.common.swob import Request
from swift.proxy import server as proxy_server
-from swift.proxy.controllers.base import headers_to_container_info
+import swift.proxy.controllers
+from swift.proxy.controllers import base as proxy_base
+from swift.proxy.controllers.base import (
+ cors_validation, headers_to_container_info)
from test.unit import fake_http_connect, FakeRing, FakeMemcache
-from swift.common.storage_policy import StoragePolicy
+from swift.common.exceptions import ChunkReadTimeout
from swift.common.request_helpers import get_sys_meta_prefix
+from swift.common.storage_policy import StoragePolicy, POLICIES
+from swift.common.swob import (
+ HTTPException, HTTPUnauthorized, Response)
-from test.unit import patch_policies, mocked_http_conn, debug_logger
+from test.unit import (
+ patch_policies, mocked_http_conn, debug_logger)
from test.unit.common.ring.test_ring import TestRingBase
-from test.unit.proxy.test_server import node_error_count
+from test.unit.proxy import (
+ _make_callback_func, save_globals,
+ set_http_connect, FakeMemcacheReturnsNone)
+from test.unit.proxy.test_server import (
+ node_error_count, node_last_error,
+ set_node_errors, sortHeaderNames)
-@patch_policies([StoragePolicy(0, 'zero', True, object_ring=FakeRing())])
-class TestContainerController(TestRingBase):
+@add_metaclass(ABCMeta)
+class ContainerControllerMeta(object):
CONTAINER_REPLICAS = 3
def setUp(self):
TestRingBase.setUp(self)
- self.logger = debug_logger()
- self.container_ring = FakeRing(replicas=self.CONTAINER_REPLICAS,
- max_more_nodes=9)
- self.app = proxy_server.Application(None, FakeMemcache(),
- logger=self.logger,
- account_ring=FakeRing(),
- container_ring=self.container_ring)
+ self.container_ring = FakeRing(
+ replicas=self.CONTAINER_REPLICAS,
+ max_more_nodes=9)
+ self.app = proxy_server.Application(
+ None, FakeMemcache(),
+ account_ring=FakeRing(),
+ container_ring=self.container_ring,
+ logger=debug_logger())
self.account_info = {
'status': 200,
@@ -95,6 +114,10 @@ class TestContainerController(TestRingBase):
'Expected %s but got %s. Failed case: %s' %
(expected, resp.status_int, str(responses)))
+
+@patch_policies([StoragePolicy(0, 'zero', True, object_ring=FakeRing())])
+class TestContainerController(ContainerControllerMeta, TestRingBase):
+
def test_container_info_got_cached(self):
controller = proxy_server.ContainerController(self.app, 'a', 'c')
with mock.patch('swift.proxy.controllers.base.http_connect',
@@ -305,9 +328,1270 @@ class TestContainerController(TestRingBase):
self._assert_responses('POST', POST_TEST_CASES)
+@patch_policies([
+ StoragePolicy(0, 'zero', True, object_ring=FakeRing(base_port=3000)),
+ StoragePolicy(1, 'one', False, object_ring=FakeRing(base_port=3000)),
+ StoragePolicy(2, 'two', False, True, object_ring=FakeRing(base_port=3000))
+])
+class TestContainerControllerBasePort(unittest.TestCase):
+ def setUp(self):
+ self.app = proxy_server.Application(
+ None, FakeMemcache(),
+ account_ring=FakeRing(),
+ container_ring=FakeRing(base_port=2000),
+ logger=debug_logger())
+
+ def test_convert_policy_to_index(self):
+ controller = swift.proxy.controllers.ContainerController(self.app,
+ 'a', 'c')
+ expected = {
+ 'zero': 0,
+ 'ZeRo': 0,
+ 'one': 1,
+ 'OnE': 1,
+ }
+ for name, index in expected.items():
+ req = Request.blank('/a/c', headers={'Content-Length': '0',
+ 'Content-Type': 'text/plain',
+ 'X-Storage-Policy': name})
+ self.assertEqual(controller._convert_policy_to_index(req), index)
+ # default test
+ req = Request.blank('/a/c', headers={'Content-Length': '0',
+ 'Content-Type': 'text/plain'})
+ self.assertIsNone(controller._convert_policy_to_index(req))
+ # negative test
+ req = Request.blank('/a/c',
+ headers={'Content-Length': '0',
+ 'Content-Type': 'text/plain',
+ 'X-Storage-Policy': 'nada'})
+ self.assertRaises(HTTPException, controller._convert_policy_to_index,
+ req)
+ # storage policy two is deprecated
+ req = Request.blank('/a/c', headers={'Content-Length': '0',
+ 'Content-Type': 'text/plain',
+ 'X-Storage-Policy': 'two'})
+ self.assertRaises(HTTPException, controller._convert_policy_to_index,
+ req)
+
+ def test_convert_index_to_name(self):
+ policy = random.choice(list(POLICIES))
+ req = Request.blank('/v1/a/c')
+ with mocked_http_conn(
+ 200, 200,
+ headers={'X-Backend-Storage-Policy-Index': int(policy)},
+ ) as fake_conn:
+ resp = req.get_response(self.app)
+ self.assertRaises(StopIteration, fake_conn.code_iter.next)
+ self.assertEqual(resp.status_int, 200)
+ self.assertEqual(resp.headers['X-Storage-Policy'], policy.name)
+
+ def test_no_convert_index_to_name_when_container_not_found(self):
+ policy = random.choice(list(POLICIES))
+ req = Request.blank('/v1/a/c')
+ with mocked_http_conn(
+ 200, 404, 404, 404,
+ headers={'X-Backend-Storage-Policy-Index':
+ int(policy)}) as fake_conn:
+ resp = req.get_response(self.app)
+ self.assertRaises(StopIteration, fake_conn.code_iter.next)
+ self.assertEqual(resp.status_int, 404)
+ self.assertIsNone(resp.headers['X-Storage-Policy'])
+
+ def test_error_convert_index_to_name(self):
+ req = Request.blank('/v1/a/c')
+ with mocked_http_conn(
+ 200, 200,
+ headers={'X-Backend-Storage-Policy-Index': '-1'}) as fake_conn:
+ resp = req.get_response(self.app)
+ self.assertRaises(StopIteration, fake_conn.code_iter.next)
+ self.assertEqual(resp.status_int, 200)
+ self.assertIsNone(resp.headers['X-Storage-Policy'])
+ error_lines = self.app.logger.get_lines_for_level('error')
+ self.assertEqual(2, len(error_lines))
+ for msg in error_lines:
+ expected = "Could not translate " \
+ "X-Backend-Storage-Policy-Index ('-1')"
+ self.assertIn(expected, msg)
+
+ def test_transfer_headers(self):
+ src_headers = {'x-remove-versions-location': 'x',
+ 'x-container-read': '*:user',
+ 'x-remove-container-sync-key': 'x'}
+ dst_headers = {'x-versions-location': 'backup'}
+ controller = swift.proxy.controllers.ContainerController(self.app,
+ 'a', 'c')
+ controller.transfer_headers(src_headers, dst_headers)
+ expected_headers = {'x-versions-location': '',
+ 'x-container-read': '*:user',
+ 'x-container-sync-key': ''}
+ self.assertEqual(dst_headers, expected_headers)
+
+ def assert_status_map(self, method, statuses, expected,
+ raise_exc=False, missing_container=False):
+ with save_globals():
+ kwargs = {}
+ if raise_exc:
+ kwargs['raise_exc'] = raise_exc
+ kwargs['missing_container'] = missing_container
+ set_http_connect(*statuses, **kwargs)
+ self.app.memcache.store = {}
+ req = Request.blank('/v1/a/c', headers={'Content-Length': '0',
+ 'Content-Type': 'text/plain'})
+ self.app.update_request(req)
+ res = method(req)
+ self.assertEqual(res.status_int, expected)
+ set_http_connect(*statuses, **kwargs)
+ self.app.memcache.store = {}
+ req = Request.blank('/v1/a/c/', headers={'Content-Length': '0',
+ 'Content-Type': 'text/plain'})
+ self.app.update_request(req)
+ res = method(req)
+ self.assertEqual(res.status_int, expected)
+
+ def test_HEAD_GET(self):
+ with save_globals():
+ controller = proxy_server.ContainerController(self.app, 'a', 'c')
+
+ def test_status_map(statuses, expected,
+ c_expected=None, a_expected=None, **kwargs):
+ set_http_connect(*statuses, **kwargs)
+ self.app.memcache.store = {}
+ req = Request.blank('/v1/a/c', {})
+ self.app.update_request(req)
+ res = controller.HEAD(req)
+ self.assertEqual(res.status[:len(str(expected))],
+ str(expected))
+ infocache = res.environ.get('swift.infocache', {})
+ if expected < 400:
+ self.assertIn('x-works', res.headers)
+ self.assertEqual(res.headers['x-works'], 'yes')
+ if expected < 300:
+ self.assertIn('last-modified', res.headers)
+ self.assertEqual(res.headers['last-modified'], '1')
+ if c_expected:
+ self.assertIn('container/a/c', infocache)
+ self.assertEqual(
+ infocache['container/a/c']['status'],
+ c_expected)
+ else:
+ self.assertNotIn('container/a/c', infocache)
+ if a_expected:
+ self.assertIn('account/a', infocache)
+ self.assertEqual(infocache['account/a']['status'],
+ a_expected)
+ else:
+ self.assertNotIn('account/a', res.environ)
+
+ set_http_connect(*statuses, **kwargs)
+ self.app.memcache.store = {}
+ req = Request.blank('/v1/a/c', {})
+ self.app.update_request(req)
+ res = controller.GET(req)
+ self.assertEqual(res.status[:len(str(expected))],
+ str(expected))
+ infocache = res.environ.get('swift.infocache', {})
+ if expected < 400:
+ self.assertIn('x-works', res.headers)
+ self.assertEqual(res.headers['x-works'], 'yes')
+ if expected < 300:
+ self.assertIn('last-modified', res.headers)
+ self.assertEqual(res.headers['last-modified'], '1')
+ if c_expected:
+ self.assertIn('container/a/c', infocache)
+ self.assertEqual(
+ infocache['container/a/c']['status'],
+ c_expected)
+ else:
+ self.assertNotIn('container/a/c', infocache)
+ if a_expected:
+ self.assertIn('account/a', infocache)
+ self.assertEqual(infocache['account/a']['status'],
+ a_expected)
+ else:
+ self.assertNotIn('account/a', infocache)
+ # In all the following tests cache 200 for account
+ # return and cache vary for container
+ # return 200 and cache 200 for account and container
+ test_status_map((200, 200, 404, 404), 200, 200, 200)
+ test_status_map((200, 200, 500, 404), 200, 200, 200)
+ # return 304 don't cache container
+ test_status_map((200, 304, 500, 404), 304, None, 200)
+ # return 404 and cache 404 for container
+ test_status_map((200, 404, 404, 404), 404, 404, 200)
+ test_status_map((200, 404, 404, 500), 404, 404, 200)
+ # return 503, don't cache container
+ test_status_map((200, 500, 500, 500), 503, None, 200)
+ self.assertFalse(self.app.account_autocreate)
+
+ # return 404 (as account is not found) and don't cache container
+ test_status_map((404, 404, 404), 404, None, 404)
+
+ # cache a 204 for the account because it's sort of like it
+ # exists
+ self.app.account_autocreate = True
+ test_status_map((404, 404, 404), 404, None, 204)
+
+ def test_PUT_policy_headers(self):
+ backend_requests = []
+
+ def capture_requests(ipaddr, port, device, partition, method,
+ path, headers=None, query_string=None):
+ if method == 'PUT':
+ backend_requests.append(headers)
+
+ def test_policy(requested_policy):
+ with save_globals():
+ mock_conn = set_http_connect(200, 201, 201, 201,
+ give_connect=capture_requests)
+ self.app.memcache.store = {}
+ req = Request.blank('/v1/a/test', method='PUT',
+ headers={'Content-Length': 0})
+ if requested_policy:
+ expected_policy = requested_policy
+ req.headers['X-Storage-Policy'] = policy.name
+ else:
+ expected_policy = POLICIES.default
+ res = req.get_response(self.app)
+ if expected_policy.is_deprecated:
+ self.assertEqual(res.status_int, 400)
+ self.assertEqual(0, len(backend_requests))
+ expected = 'is deprecated'
+ self.assertIn(expected, res.body,
+ '%r did not include %r' % (
+ res.body, expected))
+ return
+ self.assertEqual(res.status_int, 201)
+ self.assertEqual(
+ expected_policy.object_ring.replicas,
+ len(backend_requests))
+ for headers in backend_requests:
+ if not requested_policy:
+ self.assertNotIn('X-Backend-Storage-Policy-Index',
+ headers)
+ self.assertIn('X-Backend-Storage-Policy-Default',
+ headers)
+ self.assertEqual(
+ int(expected_policy),
+ int(headers['X-Backend-Storage-Policy-Default']))
+ else:
+ self.assertIn('X-Backend-Storage-Policy-Index',
+ headers)
+ self.assertEqual(int(headers
+ ['X-Backend-Storage-Policy-Index']),
+ int(policy))
+ # make sure all mocked responses are consumed
+ self.assertRaises(StopIteration, mock_conn.code_iter.next)
+
+ test_policy(None) # no policy header
+ for policy in POLICIES:
+ backend_requests = [] # reset backend requests
+ test_policy(policy)
+
+ def test_PUT(self):
+ with save_globals():
+ controller = proxy_server.ContainerController(self.app, 'account',
+ 'container')
+
+ def test_status_map(statuses, expected, **kwargs):
+ set_http_connect(*statuses, **kwargs)
+ self.app.memcache.store = {}
+ req = Request.blank('/v1/a/c', {})
+ req.content_length = 0
+ self.app.update_request(req)
+ res = controller.PUT(req)
+ expected = str(expected)
+ self.assertEqual(res.status[:len(expected)], expected)
+
+ test_status_map((200, 201, 201, 201), 201, missing_container=True)
+ test_status_map((200, 201, 201, 500), 201, missing_container=True)
+ test_status_map((200, 204, 404, 404), 404, missing_container=True)
+ test_status_map((200, 204, 500, 404), 503, missing_container=True)
+ self.assertFalse(self.app.account_autocreate)
+ test_status_map((404, 404, 404), 404, missing_container=True)
+ self.app.account_autocreate = True
+ # fail to retrieve account info
+ test_status_map(
+ (503, 503, 503), # account_info fails on 503
+ 404, missing_container=True)
+ # account fail after creation
+ test_status_map(
+ (404, 404, 404, # account_info fails on 404
+ 201, 201, 201, # PUT account
+ 404, 404, 404), # account_info fail
+ 404, missing_container=True)
+ test_status_map(
+ (503, 503, 404, # account_info fails on 404
+ 503, 503, 503, # PUT account
+ 503, 503, 404), # account_info fail
+ 404, missing_container=True)
+ # put fails
+ test_status_map(
+ (404, 404, 404, # account_info fails on 404
+ 201, 201, 201, # PUT account
+ 200, # account_info success
+ 503, 503, 201), # put container fail
+ 503, missing_container=True)
+ # all goes according to plan
+ test_status_map(
+ (404, 404, 404, # account_info fails on 404
+ 201, 201, 201, # PUT account
+ 200, # account_info success
+ 201, 201, 201), # put container success
+ 201, missing_container=True)
+ test_status_map(
+ (503, 404, 404, # account_info fails on 404
+ 503, 201, 201, # PUT account
+ 503, 200, # account_info success
+ 503, 201, 201), # put container success
+ 201, missing_container=True)
+
+ def test_PUT_autocreate_account_with_sysmeta(self):
+ # x-account-sysmeta headers in a container PUT request should be
+ # transferred to the account autocreate PUT request
+ with save_globals():
+ controller = proxy_server.ContainerController(self.app, 'account',
+ 'container')
+
+ def test_status_map(statuses, expected, headers=None, **kwargs):
+ set_http_connect(*statuses, **kwargs)
+ self.app.memcache.store = {}
+ req = Request.blank('/v1/a/c', {}, headers=headers)
+ req.content_length = 0
+ self.app.update_request(req)
+ res = controller.PUT(req)
+ expected = str(expected)
+ self.assertEqual(res.status[:len(expected)], expected)
+
+ self.app.account_autocreate = True
+ calls = []
+ callback = _make_callback_func(calls)
+ key, value = 'X-Account-Sysmeta-Blah', 'something'
+ headers = {key: value}
+
+ # all goes according to plan
+ test_status_map(
+ (404, 404, 404, # account_info fails on 404
+ 201, 201, 201, # PUT account
+ 200, # account_info success
+ 201, 201, 201), # put container success
+ 201, missing_container=True,
+ headers=headers,
+ give_connect=callback)
+
+ self.assertEqual(10, len(calls))
+ for call in calls[3:6]:
+ self.assertEqual('/account', call['path'])
+ self.assertIn(key, call['headers'],
+ '%s call, key %s missing in headers %s' % (
+ call['method'], key, call['headers']))
+ self.assertEqual(value, call['headers'][key])
+
+ def test_POST(self):
+ with save_globals():
+ controller = proxy_server.ContainerController(self.app, 'account',
+ 'container')
+
+ def test_status_map(statuses, expected, **kwargs):
+ set_http_connect(*statuses, **kwargs)
+ self.app.memcache.store = {}
+ req = Request.blank('/v1/a/c', {})
+ req.content_length = 0
+ self.app.update_request(req)
+ res = controller.POST(req)
+ expected = str(expected)
+ self.assertEqual(res.status[:len(expected)], expected)
+
+ test_status_map((200, 201, 201, 201), 201, missing_container=True)
+ test_status_map((200, 201, 201, 500), 201, missing_container=True)
+ test_status_map((200, 204, 404, 404), 404, missing_container=True)
+ test_status_map((200, 204, 500, 404), 503, missing_container=True)
+ self.assertFalse(self.app.account_autocreate)
+ test_status_map((404, 404, 404), 404, missing_container=True)
+ self.app.account_autocreate = True
+ test_status_map((404, 404, 404), 404, missing_container=True)
+
+ def test_PUT_max_containers_per_account(self):
+ with save_globals():
+ self.app.max_containers_per_account = 12346
+ controller = proxy_server.ContainerController(self.app, 'account',
+ 'container')
+ self.assert_status_map(controller.PUT,
+ (200, 201, 201, 201), 201,
+ missing_container=True)
+
+ self.app.max_containers_per_account = 12345
+ controller = proxy_server.ContainerController(self.app, 'account',
+ 'container')
+ self.assert_status_map(controller.PUT,
+ (200, 200, 201, 201, 201), 201,
+ missing_container=True)
+
+ controller = proxy_server.ContainerController(self.app, 'account',
+ 'container_new')
+
+ self.assert_status_map(controller.PUT, (200, 404, 404, 404), 403,
+ missing_container=True)
+
+ self.app.max_containers_per_account = 12345
+ self.app.max_containers_whitelist = ['account']
+ controller = proxy_server.ContainerController(self.app, 'account',
+ 'container')
+ self.assert_status_map(controller.PUT,
+ (200, 201, 201, 201), 201,
+ missing_container=True)
+
+ def test_PUT_max_container_name_length(self):
+ with save_globals():
+ limit = constraints.MAX_CONTAINER_NAME_LENGTH
+ controller = proxy_server.ContainerController(self.app, 'account',
+ '1' * limit)
+ self.assert_status_map(controller.PUT,
+ (200, 201, 201, 201), 201,
+ missing_container=True)
+ controller = proxy_server.ContainerController(self.app, 'account',
+ '2' * (limit + 1))
+ self.assert_status_map(controller.PUT, (201, 201, 201), 400,
+ missing_container=True)
+
+ def test_PUT_connect_exceptions(self):
+ with save_globals():
+ controller = proxy_server.ContainerController(self.app, 'account',
+ 'container')
+ self.assert_status_map(controller.PUT, (200, 201, 201, -1), 201,
+ missing_container=True)
+ self.assert_status_map(controller.PUT, (200, 201, -1, -1), 503,
+ missing_container=True)
+ self.assert_status_map(controller.PUT, (200, 503, 503, -1), 503,
+ missing_container=True)
+
+ def test_acc_missing_returns_404(self):
+ for meth in ('DELETE', 'PUT'):
+ with save_globals():
+ self.app.memcache = FakeMemcacheReturnsNone()
+ self.app._error_limiting = {}
+ controller = proxy_server.ContainerController(self.app,
+ 'account',
+ 'container')
+ if meth == 'PUT':
+ set_http_connect(200, 200, 200, 200, 200, 200,
+ missing_container=True)
+ else:
+ set_http_connect(200, 200, 200, 200)
+ self.app.memcache.store = {}
+ req = Request.blank('/v1/a/c',
+ environ={'REQUEST_METHOD': meth})
+ self.app.update_request(req)
+ resp = getattr(controller, meth)(req)
+ self.assertEqual(resp.status_int, 200)
+
+ set_http_connect(404, 404, 404, 200, 200, 200)
+ # Make sure it is a blank request wthout env caching
+ req = Request.blank('/v1/a/c',
+ environ={'REQUEST_METHOD': meth})
+ resp = getattr(controller, meth)(req)
+ self.assertEqual(resp.status_int, 404)
+
+ set_http_connect(503, 404, 404)
+ # Make sure it is a blank request wthout env caching
+ req = Request.blank('/v1/a/c',
+ environ={'REQUEST_METHOD': meth})
+ resp = getattr(controller, meth)(req)
+ self.assertEqual(resp.status_int, 404)
+
+ set_http_connect(503, 404, raise_exc=True)
+ # Make sure it is a blank request wthout env caching
+ req = Request.blank('/v1/a/c',
+ environ={'REQUEST_METHOD': meth})
+ resp = getattr(controller, meth)(req)
+ self.assertEqual(resp.status_int, 404)
+
+ for dev in self.app.account_ring.devs:
+ set_node_errors(self.app, dev,
+ self.app.error_suppression_limit + 1,
+ time.time())
+ set_http_connect(200, 200, 200, 200, 200, 200)
+ # Make sure it is a blank request wthout env caching
+ req = Request.blank('/v1/a/c',
+ environ={'REQUEST_METHOD': meth})
+ resp = getattr(controller, meth)(req)
+ self.assertEqual(resp.status_int, 404)
+
+ def test_put_locking(self):
+
+ class MockMemcache(FakeMemcache):
+
+ def __init__(self, allow_lock=None):
+ self.allow_lock = allow_lock
+ super(MockMemcache, self).__init__()
+
+ @contextmanager
+ def soft_lock(self, key, timeout=0, retries=5):
+ if self.allow_lock:
+ yield True
+ else:
+ raise NotImplementedError
+
+ with save_globals():
+ controller = proxy_server.ContainerController(self.app, 'account',
+ 'container')
+ self.app.memcache = MockMemcache(allow_lock=True)
+ set_http_connect(200, 201, 201, 201,
+ missing_container=True)
+ req = Request.blank('/v1/a/c', environ={'REQUEST_METHOD': 'PUT'})
+ self.app.update_request(req)
+ res = controller.PUT(req)
+ self.assertEqual(res.status_int, 201)
+
+ def test_error_limiting(self):
+ with save_globals():
+ controller = proxy_server.ContainerController(self.app, 'account',
+ 'container')
+ container_ring = controller.app.container_ring
+ controller.app.sort_nodes = lambda l: l
+ self.assert_status_map(controller.HEAD, (200, 503, 200, 200), 200,
+ missing_container=False)
+
+ self.assertEqual(
+ node_error_count(controller.app, container_ring.devs[0]), 2)
+ self.assertTrue(
+ node_last_error(controller.app, container_ring.devs[0])
+ is not None)
+ for _junk in range(self.app.error_suppression_limit):
+ self.assert_status_map(controller.HEAD,
+ (200, 503, 503, 503), 503)
+ self.assertEqual(
+ node_error_count(controller.app, container_ring.devs[0]),
+ self.app.error_suppression_limit + 1)
+ self.assert_status_map(controller.HEAD, (200, 200, 200, 200), 503)
+ self.assertTrue(
+ node_last_error(controller.app, container_ring.devs[0])
+ is not None)
+ self.assert_status_map(controller.PUT, (200, 201, 201, 201), 503,
+ missing_container=True)
+ self.assert_status_map(controller.DELETE,
+ (200, 204, 204, 204), 503)
+ self.app.error_suppression_interval = -300
+ self.assert_status_map(controller.HEAD, (200, 200, 200, 200), 200)
+ self.assert_status_map(controller.DELETE, (200, 204, 204, 204),
+ 404, raise_exc=True)
+
+ def test_DELETE(self):
+ with save_globals():
+ controller = proxy_server.ContainerController(self.app, 'account',
+ 'container')
+ self.assert_status_map(controller.DELETE,
+ (200, 204, 204, 204), 204)
+ self.assert_status_map(controller.DELETE,
+ (200, 204, 204, 503), 204)
+ self.assert_status_map(controller.DELETE,
+ (200, 204, 503, 503), 503)
+ self.assert_status_map(controller.DELETE,
+ (200, 204, 404, 404), 404)
+ self.assert_status_map(controller.DELETE,
+ (200, 404, 404, 404), 404)
+ self.assert_status_map(controller.DELETE,
+ (200, 204, 503, 404), 503)
+
+ self.app.memcache = FakeMemcacheReturnsNone()
+ # 200: Account check, 404x3: Container check
+ self.assert_status_map(controller.DELETE,
+ (200, 404, 404, 404), 404)
+
+ def test_response_get_accept_ranges_header(self):
+ with save_globals():
+ set_http_connect(200, 200, body='{}')
+ controller = proxy_server.ContainerController(self.app, 'account',
+ 'container')
+ req = Request.blank('/v1/a/c?format=json')
+ self.app.update_request(req)
+ res = controller.GET(req)
+ self.assertIn('accept-ranges', res.headers)
+ self.assertEqual(res.headers['accept-ranges'], 'bytes')
+
+ def test_response_head_accept_ranges_header(self):
+ with save_globals():
+ set_http_connect(200, 200, body='{}')
+ controller = proxy_server.ContainerController(self.app, 'account',
+ 'container')
+ req = Request.blank('/v1/a/c?format=json')
+ self.app.update_request(req)
+ res = controller.HEAD(req)
+ self.assertIn('accept-ranges', res.headers)
+ self.assertEqual(res.headers['accept-ranges'], 'bytes')
+
+ def test_PUT_metadata(self):
+ self.metadata_helper('PUT')
+
+ def test_POST_metadata(self):
+ self.metadata_helper('POST')
+
+ def metadata_helper(self, method):
+ for test_header, test_value in (
+ ('X-Container-Meta-TestHeader', 'TestValue'),
+ ('X-Container-Meta-TestHeader', ''),
+ ('X-Remove-Container-Meta-TestHeader', 'anything'),
+ ('X-Container-Read', '.r:*'),
+ ('X-Remove-Container-Read', 'anything'),
+ ('X-Container-Write', 'anyone'),
+ ('X-Remove-Container-Write', 'anything')):
+ test_errors = []
+
+ def test_connect(ipaddr, port, device, partition, method, path,
+ headers=None, query_string=None):
+ if path == '/a/c':
+ find_header = test_header
+ find_value = test_value
+ if find_header.lower().startswith('x-remove-'):
+ find_header = \
+ find_header.lower().replace('-remove', '', 1)
+ find_value = ''
+ for k, v in headers.items():
+ if k.lower() == find_header.lower() and \
+ v == find_value:
+ break
+ else:
+ test_errors.append('%s: %s not in %s' %
+ (find_header, find_value, headers))
+ with save_globals():
+ controller = \
+ proxy_server.ContainerController(self.app, 'a', 'c')
+ set_http_connect(200, 201, 201, 201, give_connect=test_connect)
+ req = Request.blank(
+ '/v1/a/c',
+ environ={'REQUEST_METHOD': method, 'swift_owner': True},
+ headers={test_header: test_value})
+ self.app.update_request(req)
+ getattr(controller, method)(req)
+ self.assertEqual(test_errors, [])
+
+ def test_PUT_bad_metadata(self):
+ self.bad_metadata_helper('PUT')
+
+ def test_POST_bad_metadata(self):
+ self.bad_metadata_helper('POST')
+
+ def bad_metadata_helper(self, method):
+ with save_globals():
+ controller = proxy_server.ContainerController(self.app, 'a', 'c')
+ set_http_connect(200, 201, 201, 201)
+ req = Request.blank('/v1/a/c', environ={'REQUEST_METHOD': method})
+ self.app.update_request(req)
+ resp = getattr(controller, method)(req)
+ self.assertEqual(resp.status_int, 201)
+
+ set_http_connect(201, 201, 201)
+ req = Request.blank('/v1/a/c', environ={'REQUEST_METHOD': method},
+ headers={'X-Container-Meta-' +
+ ('a' * constraints.MAX_META_NAME_LENGTH): 'v'})
+ self.app.update_request(req)
+ resp = getattr(controller, method)(req)
+ self.assertEqual(resp.status_int, 201)
+ set_http_connect(201, 201, 201)
+ req = Request.blank(
+ '/v1/a/c', environ={'REQUEST_METHOD': method},
+ headers={'X-Container-Meta-' +
+ ('a' * (constraints.MAX_META_NAME_LENGTH + 1)): 'v'})
+ self.app.update_request(req)
+ resp = getattr(controller, method)(req)
+ self.assertEqual(resp.status_int, 400)
+
+ set_http_connect(201, 201, 201)
+ req = Request.blank('/v1/a/c', environ={'REQUEST_METHOD': method},
+ headers={'X-Container-Meta-Too-Long':
+ 'a' * constraints.MAX_META_VALUE_LENGTH})
+ self.app.update_request(req)
+ resp = getattr(controller, method)(req)
+ self.assertEqual(resp.status_int, 201)
+ set_http_connect(201, 201, 201)
+ req = Request.blank('/v1/a/c', environ={'REQUEST_METHOD': method},
+ headers={'X-Container-Meta-Too-Long':
+ 'a' * (constraints.MAX_META_VALUE_LENGTH + 1)})
+ self.app.update_request(req)
+ resp = getattr(controller, method)(req)
+ self.assertEqual(resp.status_int, 400)
+
+ set_http_connect(201, 201, 201)
+ headers = {}
+ for x in range(constraints.MAX_META_COUNT):
+ headers['X-Container-Meta-%d' % x] = 'v'
+ req = Request.blank('/v1/a/c', environ={'REQUEST_METHOD': method},
+ headers=headers)
+ self.app.update_request(req)
+ resp = getattr(controller, method)(req)
+ self.assertEqual(resp.status_int, 201)
+ set_http_connect(201, 201, 201)
+ headers = {}
+ for x in range(constraints.MAX_META_COUNT + 1):
+ headers['X-Container-Meta-%d' % x] = 'v'
+ req = Request.blank('/v1/a/c', environ={'REQUEST_METHOD': method},
+ headers=headers)
+ self.app.update_request(req)
+ resp = getattr(controller, method)(req)
+ self.assertEqual(resp.status_int, 400)
+
+ set_http_connect(201, 201, 201)
+ headers = {}
+ header_value = 'a' * constraints.MAX_META_VALUE_LENGTH
+ size = 0
+ x = 0
+ while size < (constraints.MAX_META_OVERALL_SIZE - 4
+ - constraints.MAX_META_VALUE_LENGTH):
+ size += 4 + constraints.MAX_META_VALUE_LENGTH
+ headers['X-Container-Meta-%04d' % x] = header_value
+ x += 1
+ if constraints.MAX_META_OVERALL_SIZE - size > 1:
+ headers['X-Container-Meta-a'] = \
+ 'a' * (constraints.MAX_META_OVERALL_SIZE - size - 1)
+ req = Request.blank('/v1/a/c', environ={'REQUEST_METHOD': method},
+ headers=headers)
+ self.app.update_request(req)
+ resp = getattr(controller, method)(req)
+ self.assertEqual(resp.status_int, 201)
+ set_http_connect(201, 201, 201)
+ headers['X-Container-Meta-a'] = \
+ 'a' * (constraints.MAX_META_OVERALL_SIZE - size)
+ req = Request.blank('/v1/a/c', environ={'REQUEST_METHOD': method},
+ headers=headers)
+ self.app.update_request(req)
+ resp = getattr(controller, method)(req)
+ self.assertEqual(resp.status_int, 400)
+
+ def test_POST_calls_clean_acl(self):
+ called = [False]
+
+ def clean_acl(header, value):
+ called[0] = True
+ raise ValueError('fake error')
+ with save_globals():
+ set_http_connect(200, 201, 201, 201)
+ controller = proxy_server.ContainerController(self.app, 'account',
+ 'container')
+ req = Request.blank('/v1/a/c', environ={'REQUEST_METHOD': 'POST'},
+ headers={'X-Container-Read': '.r:*'})
+ req.environ['swift.clean_acl'] = clean_acl
+ self.app.update_request(req)
+ controller.POST(req)
+ self.assertTrue(called[0])
+ called[0] = False
+ with save_globals():
+ set_http_connect(200, 201, 201, 201)
+ controller = proxy_server.ContainerController(self.app, 'account',
+ 'container')
+ req = Request.blank('/v1/a/c', environ={'REQUEST_METHOD': 'POST'},
+ headers={'X-Container-Write': '.r:*'})
+ req.environ['swift.clean_acl'] = clean_acl
+ self.app.update_request(req)
+ controller.POST(req)
+ self.assertTrue(called[0])
+
+ def test_PUT_calls_clean_acl(self):
+ called = [False]
+
+ def clean_acl(header, value):
+ called[0] = True
+ raise ValueError('fake error')
+ with save_globals():
+ set_http_connect(200, 201, 201, 201)
+ controller = proxy_server.ContainerController(self.app, 'account',
+ 'container')
+ req = Request.blank('/v1/a/c', environ={'REQUEST_METHOD': 'PUT'},
+ headers={'X-Container-Read': '.r:*'})
+ req.environ['swift.clean_acl'] = clean_acl
+ self.app.update_request(req)
+ controller.PUT(req)
+ self.assertTrue(called[0])
+ called[0] = False
+ with save_globals():
+ set_http_connect(200, 201, 201, 201)
+ controller = proxy_server.ContainerController(self.app, 'account',
+ 'container')
+ req = Request.blank('/v1/a/c', environ={'REQUEST_METHOD': 'PUT'},
+ headers={'X-Container-Write': '.r:*'})
+ req.environ['swift.clean_acl'] = clean_acl
+ self.app.update_request(req)
+ controller.PUT(req)
+ self.assertTrue(called[0])
+
+ def test_GET_no_content(self):
+ with save_globals():
+ set_http_connect(200, 204, 204, 204)
+ controller = proxy_server.ContainerController(self.app, 'a', 'c')
+ req = Request.blank('/v1/a/c')
+ self.app.update_request(req)
+ res = controller.GET(req)
+ self.assertEqual(res.status_int, 204)
+ ic = res.environ['swift.infocache']
+ self.assertEqual(ic['container/a/c']['status'], 204)
+ self.assertEqual(res.content_length, 0)
+ self.assertNotIn('transfer-encoding', res.headers)
+
+ def test_GET_calls_authorize(self):
+ called = [False]
+
+ def authorize(req):
+ called[0] = True
+ return HTTPUnauthorized(request=req)
+ with save_globals():
+ set_http_connect(200, 201, 201, 201)
+ controller = proxy_server.ContainerController(self.app, 'a', 'c')
+ req = Request.blank('/v1/a/c')
+ req.environ['swift.authorize'] = authorize
+ self.app.update_request(req)
+ res = controller.GET(req)
+ self.assertEqual(
+ res.environ['swift.infocache']['container/a/c']['status'],
+ 201)
+ self.assertTrue(called[0])
+
+ def test_HEAD_calls_authorize(self):
+ called = [False]
+
+ def authorize(req):
+ called[0] = True
+ return HTTPUnauthorized(request=req)
+ with save_globals():
+ set_http_connect(200, 201, 201, 201)
+ controller = proxy_server.ContainerController(self.app, 'a', 'c')
+ req = Request.blank('/v1/a/c', {'REQUEST_METHOD': 'HEAD'})
+ req.environ['swift.authorize'] = authorize
+ self.app.update_request(req)
+ controller.HEAD(req)
+ self.assertTrue(called[0])
+
+ def test_unauthorized_requests_when_account_not_found(self):
+ # verify unauthorized container requests always return response
+ # from swift.authorize
+ called = [0, 0]
+
+ def authorize(req):
+ called[0] += 1
+ return HTTPUnauthorized(request=req)
+
+ def account_info(*args):
+ called[1] += 1
+ return None, None, None
+
+ def _do_test(method):
+ with save_globals():
+ swift.proxy.controllers.Controller.account_info = account_info
+ app = proxy_server.Application(None, FakeMemcache(),
+ account_ring=FakeRing(),
+ container_ring=FakeRing())
+ set_http_connect(201, 201, 201)
+ req = Request.blank('/v1/a/c', {'REQUEST_METHOD': method})
+ req.environ['swift.authorize'] = authorize
+ self.app.update_request(req)
+ res = app.handle_request(req)
+ return res
+
+ for method in ('PUT', 'POST', 'DELETE'):
+ # no delay_denial on method, expect one call to authorize
+ called = [0, 0]
+ res = _do_test(method)
+ self.assertEqual(401, res.status_int)
+ self.assertEqual([1, 0], called)
+
+ for method in ('HEAD', 'GET'):
+ # delay_denial on method, expect two calls to authorize
+ called = [0, 0]
+ res = _do_test(method)
+ self.assertEqual(401, res.status_int)
+ self.assertEqual([2, 1], called)
+
+ def test_authorized_requests_when_account_not_found(self):
+ # verify authorized container requests always return 404 when
+ # account not found
+ called = [0, 0]
+
+ def authorize(req):
+ called[0] += 1
+
+ def account_info(*args):
+ called[1] += 1
+ return None, None, None
+
+ def _do_test(method):
+ with save_globals():
+ swift.proxy.controllers.Controller.account_info = account_info
+ app = proxy_server.Application(None, FakeMemcache(),
+ account_ring=FakeRing(),
+ container_ring=FakeRing())
+ set_http_connect(201, 201, 201)
+ req = Request.blank('/v1/a/c', {'REQUEST_METHOD': method})
+ req.environ['swift.authorize'] = authorize
+ self.app.update_request(req)
+ res = app.handle_request(req)
+ return res
+
+ for method in ('PUT', 'POST', 'DELETE', 'HEAD', 'GET'):
+ # expect one call to authorize
+ called = [0, 0]
+ res = _do_test(method)
+ self.assertEqual(404, res.status_int)
+ self.assertEqual([1, 1], called)
+
+ def test_OPTIONS_get_info_drops_origin(self):
+ with save_globals():
+ controller = proxy_server.ContainerController(self.app, 'a', 'c')
+
+ count = [0]
+
+ def my_get_info(app, env, account, container=None,
+ ret_not_found=False, swift_source=None):
+ if count[0] > 11:
+ return {}
+ count[0] += 1
+ if not container:
+ return {'some': 'stuff'}
+ return proxy_base.was_get_info(
+ app, env, account, container, ret_not_found, swift_source)
+
+ proxy_base.was_get_info = proxy_base.get_info
+ with mock.patch.object(proxy_base, 'get_info', my_get_info):
+ proxy_base.get_info = my_get_info
+ req = Request.blank(
+ '/v1/a/c',
+ {'REQUEST_METHOD': 'OPTIONS'},
+ headers={'Origin': 'http://foo.com',
+ 'Access-Control-Request-Method': 'GET'})
+ controller.OPTIONS(req)
+ self.assertLess(count[0], 11)
+
+ def test_OPTIONS(self):
+ with save_globals():
+ controller = proxy_server.ContainerController(self.app, 'a', 'c')
+
+ def my_empty_container_info(*args):
+ return {}
+ controller.container_info = my_empty_container_info
+ req = Request.blank(
+ '/v1/a/c',
+ {'REQUEST_METHOD': 'OPTIONS'},
+ headers={'Origin': 'http://foo.com',
+ 'Access-Control-Request-Method': 'GET'})
+ resp = controller.OPTIONS(req)
+ self.assertEqual(401, resp.status_int)
+
+ def my_empty_origin_container_info(*args):
+ return {'cors': {'allow_origin': None}}
+ controller.container_info = my_empty_origin_container_info
+ req = Request.blank(
+ '/v1/a/c',
+ {'REQUEST_METHOD': 'OPTIONS'},
+ headers={'Origin': 'http://foo.com',
+ 'Access-Control-Request-Method': 'GET'})
+ resp = controller.OPTIONS(req)
+ self.assertEqual(401, resp.status_int)
+
+ def my_container_info(*args):
+ return {
+ 'cors': {
+ 'allow_origin': 'http://foo.bar:8080 https://foo.bar',
+ 'max_age': '999',
+ }
+ }
+ controller.container_info = my_container_info
+ req = Request.blank(
+ '/v1/a/c',
+ {'REQUEST_METHOD': 'OPTIONS'},
+ headers={'Origin': 'https://foo.bar',
+ 'Access-Control-Request-Method': 'GET'})
+ req.content_length = 0
+ resp = controller.OPTIONS(req)
+ self.assertEqual(200, resp.status_int)
+ self.assertEqual(
+ 'https://foo.bar',
+ resp.headers['access-control-allow-origin'])
+ for verb in 'OPTIONS GET POST PUT DELETE HEAD'.split():
+ self.assertIn(verb,
+ resp.headers['access-control-allow-methods'])
+ self.assertEqual(
+ len(resp.headers['access-control-allow-methods'].split(', ')),
+ 6)
+ self.assertEqual('999', resp.headers['access-control-max-age'])
+ req = Request.blank(
+ '/v1/a/c',
+ {'REQUEST_METHOD': 'OPTIONS'},
+ headers={'Origin': 'https://foo.bar'})
+ req.content_length = 0
+ resp = controller.OPTIONS(req)
+ self.assertEqual(401, resp.status_int)
+ req = Request.blank('/v1/a/c', {'REQUEST_METHOD': 'OPTIONS'})
+ req.content_length = 0
+ resp = controller.OPTIONS(req)
+ self.assertEqual(200, resp.status_int)
+ for verb in 'OPTIONS GET POST PUT DELETE HEAD'.split():
+ self.assertIn(verb, resp.headers['Allow'])
+ self.assertEqual(len(resp.headers['Allow'].split(', ')), 6)
+ req = Request.blank(
+ '/v1/a/c',
+ {'REQUEST_METHOD': 'OPTIONS'},
+ headers={'Origin': 'http://foo.bar',
+ 'Access-Control-Request-Method': 'GET'})
+ resp = controller.OPTIONS(req)
+ self.assertEqual(401, resp.status_int)
+ req = Request.blank(
+ '/v1/a/c',
+ {'REQUEST_METHOD': 'OPTIONS'},
+ headers={'Origin': 'http://foo.bar',
+ 'Access-Control-Request-Method': 'GET'})
+ controller.app.cors_allow_origin = ['http://foo.bar', ]
+ resp = controller.OPTIONS(req)
+ self.assertEqual(200, resp.status_int)
+
+ def my_container_info_wildcard(*args):
+ return {
+ 'cors': {
+ 'allow_origin': '*',
+ 'max_age': '999',
+ }
+ }
+ controller.container_info = my_container_info_wildcard
+ req = Request.blank(
+ '/v1/a/c/o.jpg',
+ {'REQUEST_METHOD': 'OPTIONS'},
+ headers={'Origin': 'https://bar.baz',
+ 'Access-Control-Request-Method': 'GET'})
+ req.content_length = 0
+ resp = controller.OPTIONS(req)
+ self.assertEqual(200, resp.status_int)
+ self.assertEqual('*', resp.headers['access-control-allow-origin'])
+ for verb in 'OPTIONS GET POST PUT DELETE HEAD'.split():
+ self.assertIn(verb,
+ resp.headers['access-control-allow-methods'])
+ self.assertEqual(
+ len(resp.headers['access-control-allow-methods'].split(', ')),
+ 6)
+ self.assertEqual('999', resp.headers['access-control-max-age'])
+
+ req = Request.blank(
+ '/v1/a/c/o.jpg',
+ {'REQUEST_METHOD': 'OPTIONS'},
+ headers={'Origin': 'https://bar.baz',
+ 'Access-Control-Request-Headers':
+ 'x-foo, x-bar, x-auth-token',
+ 'Access-Control-Request-Method': 'GET'}
+ )
+ req.content_length = 0
+ resp = controller.OPTIONS(req)
+ self.assertEqual(200, resp.status_int)
+ self.assertEqual(
+ sortHeaderNames('x-foo, x-bar, x-auth-token'),
+ sortHeaderNames(resp.headers['access-control-allow-headers']))
+
+ def test_CORS_valid(self):
+ with save_globals():
+ controller = proxy_server.ContainerController(self.app, 'a', 'c')
+
+ def stubContainerInfo(*args):
+ return {
+ 'cors': {
+ 'allow_origin': 'http://foo.bar'
+ }
+ }
+ controller.container_info = stubContainerInfo
+
+ def containerGET(controller, req):
+ return Response(headers={
+ 'X-Container-Meta-Color': 'red',
+ 'X-Super-Secret': 'hush',
+ })
+
+ req = Request.blank(
+ '/v1/a/c',
+ {'REQUEST_METHOD': 'GET'},
+ headers={'Origin': 'http://foo.bar'})
+
+ resp = cors_validation(containerGET)(controller, req)
+
+ self.assertEqual(200, resp.status_int)
+ self.assertEqual('http://foo.bar',
+ resp.headers['access-control-allow-origin'])
+ self.assertEqual('red', resp.headers['x-container-meta-color'])
+ # X-Super-Secret is in the response, but not "exposed"
+ self.assertEqual('hush', resp.headers['x-super-secret'])
+ self.assertIn('access-control-expose-headers', resp.headers)
+ exposed = set(
+ h.strip() for h in
+ resp.headers['access-control-expose-headers'].split(','))
+ expected_exposed = set([
+ 'cache-control', 'content-language', 'content-type', 'expires',
+ 'last-modified', 'pragma', 'etag', 'x-timestamp', 'x-trans-id',
+ 'x-openstack-request-id', 'x-container-meta-color'])
+ self.assertEqual(expected_exposed, exposed)
+
+ def _gather_x_account_headers(self, controller_call, req, *connect_args,
+ **kwargs):
+ seen_headers = []
+ to_capture = ('X-Account-Partition', 'X-Account-Host',
+ 'X-Account-Device')
+
+ def capture_headers(ipaddr, port, device, partition, method,
+ path, headers=None, query_string=None):
+ captured = {}
+ for header in to_capture:
+ captured[header] = headers.get(header)
+ seen_headers.append(captured)
+
+ with save_globals():
+ self.app.allow_account_management = True
+
+ set_http_connect(*connect_args, give_connect=capture_headers,
+ **kwargs)
+ resp = controller_call(req)
+ self.assertEqual(2, resp.status_int // 100) # sanity check
+
+ # don't care about the account HEAD, so throw away the
+ # first element
+ return sorted(seen_headers[1:],
+ key=lambda d: d['X-Account-Host'] or 'Z')
+
+ def test_PUT_x_account_headers_with_fewer_account_replicas(self):
+ self.app.account_ring.set_replicas(2)
+ req = Request.blank('/v1/a/c', headers={'': ''})
+ controller = proxy_server.ContainerController(self.app, 'a', 'c')
+
+ seen_headers = self._gather_x_account_headers(
+ controller.PUT, req,
+ 200, 201, 201, 201) # HEAD PUT PUT PUT
+ self.assertEqual(seen_headers, [
+ {'X-Account-Host': '10.0.0.0:1000',
+ 'X-Account-Partition': '0',
+ 'X-Account-Device': 'sda'},
+ {'X-Account-Host': '10.0.0.1:1001',
+ 'X-Account-Partition': '0',
+ 'X-Account-Device': 'sdb'},
+ {'X-Account-Host': None,
+ 'X-Account-Partition': None,
+ 'X-Account-Device': None}
+ ])
+
+ def test_PUT_x_account_headers_with_more_account_replicas(self):
+ self.app.account_ring.set_replicas(4)
+ req = Request.blank('/v1/a/c', headers={'': ''})
+ controller = proxy_server.ContainerController(self.app, 'a', 'c')
+
+ seen_headers = self._gather_x_account_headers(
+ controller.PUT, req,
+ 200, 201, 201, 201) # HEAD PUT PUT PUT
+ self.assertEqual(seen_headers, [
+ {'X-Account-Host': '10.0.0.0:1000,10.0.0.3:1003',
+ 'X-Account-Partition': '0',
+ 'X-Account-Device': 'sda,sdd'},
+ {'X-Account-Host': '10.0.0.1:1001',
+ 'X-Account-Partition': '0',
+ 'X-Account-Device': 'sdb'},
+ {'X-Account-Host': '10.0.0.2:1002',
+ 'X-Account-Partition': '0',
+ 'X-Account-Device': 'sdc'}
+ ])
+
+ def test_DELETE_x_account_headers_with_fewer_account_replicas(self):
+ self.app.account_ring.set_replicas(2)
+ req = Request.blank('/v1/a/c', headers={'': ''})
+ controller = proxy_server.ContainerController(self.app, 'a', 'c')
+
+ seen_headers = self._gather_x_account_headers(
+ controller.DELETE, req,
+ 200, 204, 204, 204) # HEAD DELETE DELETE DELETE
+ self.assertEqual(seen_headers, [
+ {'X-Account-Host': '10.0.0.0:1000',
+ 'X-Account-Partition': '0',
+ 'X-Account-Device': 'sda'},
+ {'X-Account-Host': '10.0.0.1:1001',
+ 'X-Account-Partition': '0',
+ 'X-Account-Device': 'sdb'},
+ {'X-Account-Host': None,
+ 'X-Account-Partition': None,
+ 'X-Account-Device': None}
+ ])
+
+ def test_DELETE_x_account_headers_with_more_account_replicas(self):
+ self.app.account_ring.set_replicas(4)
+ req = Request.blank('/v1/a/c', headers={'': ''})
+ controller = proxy_server.ContainerController(self.app, 'a', 'c')
+
+ seen_headers = self._gather_x_account_headers(
+ controller.DELETE, req,
+ 200, 204, 204, 204) # HEAD DELETE DELETE DELETE
+ self.assertEqual(seen_headers, [
+ {'X-Account-Host': '10.0.0.0:1000,10.0.0.3:1003',
+ 'X-Account-Partition': '0',
+ 'X-Account-Device': 'sda,sdd'},
+ {'X-Account-Host': '10.0.0.1:1001',
+ 'X-Account-Partition': '0',
+ 'X-Account-Device': 'sdb'},
+ {'X-Account-Host': '10.0.0.2:1002',
+ 'X-Account-Partition': '0',
+ '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 existence 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 existence check
+ self.assertEqual(3, len(timestamps))
+ for timestamp in timestamps:
+ self.assertEqual(timestamp, timestamps[0])
+ self.assertTrue(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 existence 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 existence check
+ self.assertEqual(3, len(timestamps))
+ for timestamp in timestamps:
+ self.assertEqual(timestamp, timestamps[0])
+ self.assertTrue(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=[1.0, 1.0])
+ resp = req.get_response(self.app)
+ got_exc = False
+ try:
+ resp.body
+ except ChunkReadTimeout:
+ got_exc = True
+ self.assertTrue(got_exc)
+
+
@patch_policies(
[StoragePolicy(0, 'zero', True, object_ring=FakeRing(replicas=4))])
-class TestContainerController4Replicas(TestContainerController):
+class TestContainerController4Replicas(ContainerControllerMeta, TestRingBase):
CONTAINER_REPLICAS = 4
diff --git a/test/unit/proxy/controllers/test_obj.py b/test/unit/proxy/controllers/test_obj.py
index 4ceccb61b0ae..8d3586e0988e 100644
--- a/test/unit/proxy/controllers/test_obj.py
+++ b/test/unit/proxy/controllers/test_obj.py
@@ -17,8 +17,10 @@
import email.parser
import itertools
import math
+import os
import random
import time
+from tempfile import mkdtemp
import unittest
from collections import defaultdict
from contextlib import contextmanager
@@ -26,26 +28,54 @@ import json
from hashlib import md5
import mock
-from eventlet import Timeout
+from eventlet import Timeout, sleep
from six import BytesIO
from six.moves import range
+from shutil import rmtree
import swift
-from swift.common import utils, swob, exceptions
-from swift.common.exceptions import ChunkWriteTimeout
+from swift.common import constraints, exceptions, swob, utils
+from swift.common.exceptions import ChunkReadTimeout, ChunkWriteTimeout
from swift.common.header_key_dict import HeaderKeyDict
from swift.common.utils import Timestamp
-from swift.obj import server
+from swift.obj import server as object_server
from swift.proxy import server as proxy_server
-from swift.proxy.controllers import obj
+from swift.proxy.controllers.obj import (
+ ECObjectController, ReplicatedObjectController)
from swift.proxy.controllers.base import \
- get_container_info as _real_get_container_info
+ get_container_info as _real_get_container_info, cors_validation
from swift.common.storage_policy import POLICIES, ECDriverError, StoragePolicy
+from swift.common.swob import (
+ Request, Response, HTTPUnauthorized, HTTPException)
-from test.unit import FakeRing, FakeMemcache, fake_http_connect, \
- debug_logger, patch_policies, SlowBody, FakeStatus, \
- encode_frag_archive_bodies
-from test.unit.proxy.test_server import node_error_count
+from test.unit import (
+ FakeLogger, FakeRing, FakeMemcache,
+ fake_http_connect, debug_logger, patch_policies, SlowBody,
+ FakeStatus, encode_frag_archive_bodies, mocked_http_conn)
+from test.unit.proxy.test_server import (
+ node_error_count, node_last_error, set_node_errors)
+from test.unit.proxy import (
+ save_globals, set_http_connect, FakeMemcacheReturnsNone)
+
+from test.unit.helpers import setup_servers
+
+
+STATIC_TIME = time.time()
+_test_context = _test_servers = None
+
+
+def do_setup(object_server):
+ """
+ Duplicated in proxy/test_server.py
+ Can this be refactered?
+ """
+ global _test_context, _test_servers
+ _test_context = setup_servers(object_server)
+ _test_servers = _test_context["test_servers"]
+
+
+def setup():
+ do_setup(object_server)
def unchunk_body(chunked_body):
@@ -60,7 +90,7 @@ def unchunk_body(chunked_body):
@contextmanager
-def set_http_connect(*args, **kwargs):
+def set_http_connect_contextmgr(*args, **kwargs):
old_connect = swift.proxy.controllers.base.http_connect
new_connect = fake_http_connect(*args, **kwargs)
try:
@@ -281,7 +311,7 @@ class BaseObjectControllerMixin(object):
self.app, 'a', 'c', 'o')
req = swift.common.swob.Request.blank('/v1/a/c/o')
self.app.conn_timeout = 0.05
- with set_http_connect(slow_connect=True):
+ with set_http_connect_contextmgr(slow_connect=True):
nodes = [dict(ip='', port='', device='')]
res = controller._connect_put_node(nodes, '', req, {}, ('', ''))
self.assertIsNone(res)
@@ -289,7 +319,7 @@ class BaseObjectControllerMixin(object):
def test_DELETE_simple(self):
req = swift.common.swob.Request.blank('/v1/a/c/o', method='DELETE')
codes = [204] * self.replicas()
- with set_http_connect(*codes):
+ with set_http_connect_contextmgr(*codes):
resp = req.get_response(self.app)
self.assertEqual(resp.status_int, 204)
@@ -301,7 +331,7 @@ class BaseObjectControllerMixin(object):
req = swift.common.swob.Request.blank('/v1/a/c/o', method='DELETE')
codes = [404] + [204] * (self.replicas() - 1)
random.shuffle(codes)
- with set_http_connect(*codes):
+ with set_http_connect_contextmgr(*codes):
resp = req.get_response(self.app)
self.assertEqual(resp.status_int, 204)
@@ -312,7 +342,7 @@ class BaseObjectControllerMixin(object):
return
req = swift.common.swob.Request.blank('/v1/a/c/o', method='DELETE')
codes = [404] * (self.replicas() - 1) + [204]
- with set_http_connect(*codes):
+ with set_http_connect_contextmgr(*codes):
resp = req.get_response(self.app)
self.assertEqual(resp.status_int, 404)
@@ -321,7 +351,7 @@ class BaseObjectControllerMixin(object):
mostly_204s = [204] * self.quorum()
codes = mostly_204s + [404] * (self.replicas() - len(mostly_204s))
self.assertEqual(len(codes), self.replicas())
- with set_http_connect(*codes):
+ with set_http_connect_contextmgr(*codes):
resp = req.get_response(self.app)
self.assertEqual(resp.status_int, 204)
@@ -330,7 +360,7 @@ class BaseObjectControllerMixin(object):
mostly_404s = [404] * self.quorum()
codes = mostly_404s + [204] * (self.replicas() - len(mostly_404s))
self.assertEqual(len(codes), self.replicas())
- with set_http_connect(*codes):
+ with set_http_connect_contextmgr(*codes):
resp = req.get_response(self.app)
self.assertEqual(resp.status_int, 404)
@@ -338,7 +368,7 @@ class BaseObjectControllerMixin(object):
self.obj_ring.set_replicas(4)
req = swift.common.swob.Request.blank('/v1/a/c/o', method='DELETE')
- with set_http_connect(404, 204, 404, 204):
+ with set_http_connect_contextmgr(404, 204, 404, 204):
resp = req.get_response(self.app)
self.assertEqual(resp.status_int, 204)
@@ -352,8 +382,8 @@ class BaseObjectControllerMixin(object):
headers = [{}, {}, {'Pick-Me': 'yes'}, {'Pick-Me': 'yes'}]
req = swift.common.swob.Request.blank('/v1/a/c/o', method='DELETE')
- with set_http_connect(*status_codes, body_iter=bodies,
- headers=headers):
+ with set_http_connect_contextmgr(*status_codes, body_iter=bodies,
+ headers=headers):
resp = req.get_response(self.app)
self.assertEqual(resp.status_int, 204)
self.assertEqual(resp.headers.get('Pick-Me'), 'yes')
@@ -362,7 +392,7 @@ class BaseObjectControllerMixin(object):
def test_DELETE_handoff(self):
req = swift.common.swob.Request.blank('/v1/a/c/o', method='DELETE')
codes = [204] * self.replicas()
- with set_http_connect(507, *codes):
+ with set_http_connect_contextmgr(507, *codes):
resp = req.get_response(self.app)
self.assertEqual(resp.status_int, 204)
@@ -380,7 +410,7 @@ class BaseObjectControllerMixin(object):
req = swob.Request.blank('/v1/a/c/o', method='PUT', body='',
headers={'Content-Type': 'foo/bar',
'X-Delete-After': t})
- with set_http_connect():
+ with set_http_connect_contextmgr():
resp = req.get_response(self.app)
self.assertEqual(resp.status_int, 400)
self.assertEqual('Non-integer X-Delete-After', resp.body)
@@ -397,7 +427,7 @@ class BaseObjectControllerMixin(object):
req = swob.Request.blank('/v1/a/c/o', method='PUT', body='',
headers={'Content-Type': 'foo/bar',
'X-Delete-After': '-60'})
- with set_http_connect():
+ with set_http_connect_contextmgr():
resp = req.get_response(self.app)
self.assertEqual(resp.status_int, 400)
self.assertEqual('X-Delete-After in past', resp.body)
@@ -416,7 +446,7 @@ class BaseObjectControllerMixin(object):
req = swob.Request.blank('/v1/a/c/o', method='PUT', body='',
headers={'Content-Type': 'foo/bar',
'X-Delete-At': t})
- with set_http_connect():
+ with set_http_connect_contextmgr():
resp = req.get_response(self.app)
self.assertEqual(resp.status_int, 400)
self.assertEqual('Non-integer X-Delete-At', resp.body)
@@ -435,14 +465,14 @@ class BaseObjectControllerMixin(object):
req = swob.Request.blank('/v1/a/c/o', method='PUT', body='',
headers={'Content-Type': 'foo/bar',
'X-Delete-At': t})
- with set_http_connect():
+ with set_http_connect_contextmgr():
resp = req.get_response(self.app)
self.assertEqual(resp.status_int, 400)
self.assertEqual('X-Delete-At in past', resp.body)
def test_HEAD_simple(self):
req = swift.common.swob.Request.blank('/v1/a/c/o', method='HEAD')
- with set_http_connect(200):
+ with set_http_connect_contextmgr(200):
resp = req.get_response(self.app)
self.assertEqual(resp.status_int, 200)
self.assertIn('Accept-Ranges', resp.headers)
@@ -450,7 +480,7 @@ class BaseObjectControllerMixin(object):
def test_HEAD_x_newest(self):
req = swift.common.swob.Request.blank('/v1/a/c/o', method='HEAD',
headers={'X-Newest': 'true'})
- with set_http_connect(*([200] * self.replicas())):
+ with set_http_connect_contextmgr(*([200] * self.replicas())):
resp = req.get_response(self.app)
self.assertEqual(resp.status_int, 200)
@@ -465,8 +495,8 @@ class BaseObjectControllerMixin(object):
'X-Backend-Timestamp': t.internal,
'X-Timestamp': t.normal
} for t in timestamps]
- with set_http_connect(*([200] * self.replicas()),
- headers=backend_response_headers):
+ with set_http_connect_contextmgr(*([200] * self.replicas()),
+ headers=backend_response_headers):
resp = req.get_response(self.app)
self.assertEqual(resp.status_int, 200)
self.assertEqual(resp.headers['x-timestamp'], newest_timestamp.normal)
@@ -483,8 +513,8 @@ class BaseObjectControllerMixin(object):
'X-Backend-Timestamp': t.internal,
'X-Timestamp': t.normal
} for t in timestamps]
- with set_http_connect(*([200] * self.replicas()),
- headers=backend_response_headers):
+ with set_http_connect_contextmgr(*([200] * self.replicas()),
+ headers=backend_response_headers):
resp = req.get_response(self.app)
self.assertEqual(resp.status_int, 200)
self.assertEqual(resp.headers['x-backend-timestamp'],
@@ -514,9 +544,9 @@ class BaseObjectControllerMixin(object):
'headers': headers,
}
request_log.append(req)
- with set_http_connect(*responses,
- headers=backend_response_headers,
- give_connect=capture_requests):
+ with set_http_connect_contextmgr(*responses,
+ headers=backend_response_headers,
+ give_connect=capture_requests):
resp = req.get_response(self.app)
self.assertEqual(resp.status_int, 200)
for req in request_log:
@@ -532,7 +562,7 @@ class BaseObjectControllerMixin(object):
'X-Timestamp': next(ts).internal})
codes = [409] * self.obj_ring.replicas
ts_iter = itertools.repeat(next(ts).internal)
- with set_http_connect(*codes, timestamps=ts_iter):
+ with set_http_connect_contextmgr(*codes, timestamps=ts_iter):
resp = req.get_response(self.app)
self.assertEqual(resp.status_int, 409)
@@ -582,12 +612,12 @@ class BaseObjectControllerMixin(object):
class TestReplicatedObjController(BaseObjectControllerMixin,
unittest.TestCase):
- controller_cls = obj.ReplicatedObjectController
+ controller_cls = ReplicatedObjectController
def test_PUT_simple(self):
req = swift.common.swob.Request.blank('/v1/a/c/o', method='PUT')
req.headers['content-length'] = '0'
- with set_http_connect(201, 201, 201):
+ with set_http_connect_contextmgr(201, 201, 201):
resp = req.get_response(self.app)
self.assertEqual(resp.status_int, 201)
@@ -602,7 +632,8 @@ class TestReplicatedObjController(BaseObjectControllerMixin,
'X-Obj-Metadata-Footer': 'yes'
}
- with set_http_connect(*codes, expect_headers=expect_headers):
+ with set_http_connect_contextmgr(*codes,
+ expect_headers=expect_headers):
resp = req.get_response(self.app)
self.assertEqual(resp.status_int, 503)
@@ -634,10 +665,12 @@ class TestReplicatedObjController(BaseObjectControllerMixin,
'Some-Header': 'Four',
'Etag': '"%s"' % etag,
}
- with set_http_connect(*codes, expect_headers=expect_headers,
- give_send=capture_body,
- give_connect=capture_headers,
- headers=resp_headers):
+ with set_http_connect_contextmgr(
+ *codes,
+ expect_headers=expect_headers,
+ give_send=capture_body,
+ give_connect=capture_headers,
+ headers=resp_headers):
resp = req.get_response(self.app)
self.assertEqual(resp.status_int, 201)
@@ -714,10 +747,12 @@ class TestReplicatedObjController(BaseObjectControllerMixin,
# NB: ignored!
'Some-Header': 'Four',
}
- with set_http_connect(*codes, expect_headers=expect_headers,
- give_send=capture_body,
- give_connect=capture_headers,
- headers=resp_headers):
+ with set_http_connect_contextmgr(
+ *codes,
+ expect_headers=expect_headers,
+ give_send=capture_body,
+ give_connect=capture_headers,
+ headers=resp_headers):
resp = req.get_response(self.app)
self.assertEqual(resp.status_int, 201)
@@ -788,8 +823,8 @@ class TestReplicatedObjController(BaseObjectControllerMixin,
# we capture stdout since the debug log formatter prints the formatted
# message to stdout
stdout = BytesIO()
- with set_http_connect((100, Timeout()), 503, 503), \
- mock.patch('sys.stdout', stdout):
+ with set_http_connect_contextmgr(
+ (100, Timeout()), 503, 503), mock.patch('sys.stdout', stdout):
resp = req.get_response(self.app)
self.assertEqual(resp.status_int, 503)
for line in stdout.getvalue().splitlines():
@@ -813,7 +848,7 @@ class TestReplicatedObjController(BaseObjectControllerMixin,
codes = [FakeStatus((422, 200))
for _junk in range(self.replicas())]
- with set_http_connect(*codes):
+ with set_http_connect_contextmgr(*codes):
resp = req.get_response(self.app)
self.assertEqual(resp.status_int, 422)
@@ -821,7 +856,7 @@ class TestReplicatedObjController(BaseObjectControllerMixin,
req = swift.common.swob.Request.blank('/v1/a/c/o', method='PUT')
req.headers['if-none-match'] = '*'
req.headers['content-length'] = '0'
- with set_http_connect(201, 201, 201):
+ with set_http_connect_contextmgr(201, 201, 201):
resp = req.get_response(self.app)
self.assertEqual(resp.status_int, 201)
@@ -829,7 +864,7 @@ class TestReplicatedObjController(BaseObjectControllerMixin,
req = swift.common.swob.Request.blank('/v1/a/c/o', method='PUT')
req.headers['if-none-match'] = '*'
req.headers['content-length'] = '0'
- with set_http_connect(201, 412, 201):
+ with set_http_connect_contextmgr(201, 412, 201):
resp = req.get_response(self.app)
self.assertEqual(resp.status_int, 412)
@@ -837,7 +872,7 @@ class TestReplicatedObjController(BaseObjectControllerMixin,
req = swift.common.swob.Request.blank('/v1/a/c/o', method='PUT')
req.headers['if-none-match'] = 'somethingelse'
req.headers['content-length'] = '0'
- with set_http_connect():
+ with set_http_connect_contextmgr():
resp = req.get_response(self.app)
self.assertEqual(resp.status_int, 400)
@@ -849,7 +884,7 @@ class TestReplicatedObjController(BaseObjectControllerMixin,
self.app._error_limiting = {}
req = swob.Request.blank('/v1/a/c/o.jpg', method='PUT',
body='test body')
- with set_http_connect(*statuses):
+ with set_http_connect_contextmgr(*statuses):
resp = req.get_response(self.app)
self.assertEqual(resp.status_int, expected)
@@ -904,7 +939,7 @@ class TestReplicatedObjController(BaseObjectControllerMixin,
method='PUT',
body='life is utf-gr8')
self.app.logger.clear()
- with set_http_connect(*statuses):
+ with set_http_connect_contextmgr(*statuses):
resp = req.get_response(self.app)
self.assertEqual(resp.status_int, expected)
@@ -920,7 +955,7 @@ class TestReplicatedObjController(BaseObjectControllerMixin,
method='PUT',
body='life is utf-gr8')
self.app.logger.clear()
- with set_http_connect(*statuses):
+ with set_http_connect_contextmgr(*statuses):
resp = req.get_response(self.app)
self.assertEqual(resp.status_int, 201)
@@ -948,7 +983,8 @@ class TestReplicatedObjController(BaseObjectControllerMixin,
method='PUT',
body='life is utf-gr8')
self.app.logger.clear()
- with set_http_connect(201, 201, 201, give_send=capture_send):
+ with set_http_connect_contextmgr(201, 201, 201,
+ give_send=capture_send):
resp = req.get_response(self.app)
self.assertEqual(resp.status_int, 201)
@@ -967,7 +1003,7 @@ class TestReplicatedObjController(BaseObjectControllerMixin,
method='PUT',
body='life is utf-gr8')
self.app.logger.clear()
- with set_http_connect(*statuses):
+ with set_http_connect_contextmgr(*statuses):
resp = req.get_response(self.app)
self.assertEqual(resp.status_int, 201)
@@ -1000,7 +1036,7 @@ class TestReplicatedObjController(BaseObjectControllerMixin,
method='DELETE',
body='life is utf-gr8')
self.app.logger.clear()
- with set_http_connect(*statuses):
+ with set_http_connect_contextmgr(*statuses):
resp = req.get_response(self.app)
self.assertEqual(resp.status_int, 201)
@@ -1050,7 +1086,7 @@ class TestReplicatedObjController(BaseObjectControllerMixin,
req.environ['wsgi.input'] = FakeReader()
req.headers['content-length'] = '6'
- with set_http_connect(201, 201, 201):
+ with set_http_connect_contextmgr(201, 201, 201):
resp = req.get_response(self.app)
self.assertEqual(resp.status_int, 499)
@@ -1065,7 +1101,7 @@ class TestReplicatedObjController(BaseObjectControllerMixin,
req.environ['wsgi.input'] = FakeReader()
req.headers['content-length'] = '6'
- with set_http_connect(201, 201, 201):
+ with set_http_connect_contextmgr(201, 201, 201):
resp = req.get_response(self.app)
self.assertEqual(resp.status_int, 408)
@@ -1085,7 +1121,8 @@ class TestReplicatedObjController(BaseObjectControllerMixin,
req.environ['wsgi.input'] = FakeReader()
req.headers['content-length'] = '6'
- with set_http_connect(201, 201, 201, give_expect=capture_expect):
+ with set_http_connect_contextmgr(201, 201, 201,
+ give_expect=capture_expect):
resp = req.get_response(self.app)
self.assertEqual(resp.status_int, 499)
@@ -1103,29 +1140,32 @@ class TestReplicatedObjController(BaseObjectControllerMixin,
req.environ['wsgi.input'] = FakeReader()
req.headers['content-length'] = '6'
- with set_http_connect(201, 201, 201):
+ with set_http_connect_contextmgr(201, 201, 201):
resp = req.get_response(self.app)
self.assertEqual(resp.status_int, 500)
def test_GET_simple(self):
req = swift.common.swob.Request.blank('/v1/a/c/o')
- with set_http_connect(200):
+ with set_http_connect_contextmgr(200):
resp = req.get_response(self.app)
self.assertEqual(resp.status_int, 200)
self.assertIn('Accept-Ranges', resp.headers)
def test_GET_transfer_encoding_chunked(self):
req = swift.common.swob.Request.blank('/v1/a/c/o')
- with set_http_connect(200, headers={'transfer-encoding': 'chunked'}):
+ with set_http_connect_contextmgr(
+ 200,
+ headers={'transfer-encoding': 'chunked'}):
resp = req.get_response(self.app)
self.assertEqual(resp.status_int, 200)
self.assertEqual(resp.headers['Transfer-Encoding'], 'chunked')
def _test_removes_swift_bytes(self, method):
req = swift.common.swob.Request.blank('/v1/a/c/o', method=method)
- with set_http_connect(
- 200, headers={'content-type': 'image/jpeg; swift_bytes=99'}):
+ with set_http_connect_contextmgr(
+ 200,
+ headers={'content-type': 'image/jpeg; swift_bytes=99'}):
resp = req.get_response(self.app)
self.assertEqual(resp.status_int, 200)
self.assertEqual(resp.headers['Content-Type'], 'image/jpeg')
@@ -1140,7 +1180,7 @@ class TestReplicatedObjController(BaseObjectControllerMixin,
req = swift.common.swob.Request.blank('/v1/a/c/o')
self.app.logger.txn_id = req.environ['swift.trans_id'] = 'my-txn-id'
stdout = BytesIO()
- with set_http_connect(503, 200), \
+ with set_http_connect_contextmgr(503, 200), \
mock.patch('sys.stdout', stdout):
resp = req.get_response(self.app)
self.assertEqual(resp.status_int, 200)
@@ -1151,7 +1191,7 @@ class TestReplicatedObjController(BaseObjectControllerMixin,
def test_GET_handoff(self):
req = swift.common.swob.Request.blank('/v1/a/c/o')
codes = [503] * self.obj_ring.replicas + [200]
- with set_http_connect(*codes):
+ with set_http_connect_contextmgr(*codes):
resp = req.get_response(self.app)
self.assertEqual(resp.status_int, 200)
@@ -1159,7 +1199,7 @@ class TestReplicatedObjController(BaseObjectControllerMixin,
req = swift.common.swob.Request.blank('/v1/a/c/o')
codes = [404] * (self.obj_ring.replicas +
self.obj_ring.max_more_nodes)
- with set_http_connect(*codes):
+ with set_http_connect_contextmgr(*codes):
resp = req.get_response(self.app)
self.assertEqual(resp.status_int, 404)
@@ -1175,7 +1215,8 @@ class TestReplicatedObjController(BaseObjectControllerMixin,
if method == 'PUT':
put_headers.append(headers)
codes = [201] * self.obj_ring.replicas
- with set_http_connect(*codes, give_connect=capture_headers):
+ with set_http_connect_contextmgr(*codes,
+ give_connect=capture_headers):
resp = req.get_response(self.app)
self.assertEqual(resp.status_int, 201)
for given_headers in put_headers:
@@ -1197,7 +1238,8 @@ class TestReplicatedObjController(BaseObjectControllerMixin,
put_headers.append(headers)
codes = [201] * self.obj_ring.replicas
t = time.time()
- with set_http_connect(*codes, give_connect=capture_headers):
+ with set_http_connect_contextmgr(*codes,
+ give_connect=capture_headers):
with mock.patch('time.time', lambda: t):
resp = req.get_response(self.app)
self.assertEqual(resp.status_int, 201)
@@ -1220,7 +1262,7 @@ class TestReplicatedObjController(BaseObjectControllerMixin,
'Content-Length': 0,
'X-Timestamp': put_timestamp})
codes = [201] * self.obj_ring.replicas
- with set_http_connect(*codes):
+ with set_http_connect_contextmgr(*codes):
resp = req.get_response(self.app)
self.assertEqual(resp.status_int, 201)
@@ -1235,7 +1277,7 @@ class TestReplicatedObjController(BaseObjectControllerMixin,
'X-Timestamp': put_timestamp})
ts_iter = itertools.repeat(put_timestamp)
codes = [409] * self.obj_ring.replicas
- with set_http_connect(*codes, timestamps=ts_iter):
+ with set_http_connect_contextmgr(*codes, timestamps=ts_iter):
resp = req.get_response(self.app)
self.assertEqual(resp.status_int, 202)
@@ -1250,7 +1292,7 @@ class TestReplicatedObjController(BaseObjectControllerMixin,
'X-Timestamp': next(ts).internal})
ts_iter = itertools.repeat(next(ts).internal)
codes = [409] * self.obj_ring.replicas
- with set_http_connect(*codes, timestamps=ts_iter):
+ with set_http_connect_contextmgr(*codes, timestamps=ts_iter):
resp = req.get_response(self.app)
self.assertEqual(resp.status_int, 202)
@@ -1265,7 +1307,7 @@ class TestReplicatedObjController(BaseObjectControllerMixin,
'X-Timestamp': next(ts).internal})
ts_iter = itertools.repeat(orig_timestamp)
codes = [201] * self.obj_ring.replicas
- with set_http_connect(*codes, timestamps=ts_iter):
+ with set_http_connect_contextmgr(*codes, timestamps=ts_iter):
resp = req.get_response(self.app)
self.assertEqual(resp.status_int, 201)
@@ -1277,7 +1319,7 @@ class TestReplicatedObjController(BaseObjectControllerMixin,
'X-Timestamp': next(ts).internal})
ts_iter = iter([next(ts).internal, None, None])
codes = [409] + [201] * (self.obj_ring.replicas - 1)
- with set_http_connect(*codes, timestamps=ts_iter):
+ with set_http_connect_contextmgr(*codes, timestamps=ts_iter):
resp = req.get_response(self.app)
self.assertEqual(resp.status_int, 202)
@@ -1289,7 +1331,7 @@ class TestReplicatedObjController(BaseObjectControllerMixin,
'X-Timestamp': next(ts).internal})
ts_iter = iter([None, None, None])
codes = [409] * self.obj_ring.replicas
- with set_http_connect(*codes, timestamps=ts_iter):
+ with set_http_connect_contextmgr(*codes, timestamps=ts_iter):
resp = req.get_response(self.app)
self.assertEqual(resp.status_int, 202)
@@ -1301,7 +1343,7 @@ class TestReplicatedObjController(BaseObjectControllerMixin,
'X-Timestamp': next(ts).internal})
ts_iter = iter([next(ts).internal, None, None])
codes = [409] + [(201, 'notused')] * (self.obj_ring.replicas - 1)
- with set_http_connect(*codes, timestamps=ts_iter):
+ with set_http_connect_contextmgr(*codes, timestamps=ts_iter):
resp = req.get_response(self.app)
self.assertEqual(resp.status_int, 202)
@@ -1314,7 +1356,7 @@ class TestReplicatedObjController(BaseObjectControllerMixin,
'X-Timestamp': next(ts).internal})
ts_iter = iter([next(ts).internal, None, None])
codes = [409] + [(412, 'notused')] * (self.obj_ring.replicas - 1)
- with set_http_connect(*codes, timestamps=ts_iter):
+ with set_http_connect_contextmgr(*codes, timestamps=ts_iter):
resp = req.get_response(self.app)
self.assertEqual(resp.status_int, 412)
@@ -1334,7 +1376,7 @@ class TestReplicatedObjController(BaseObjectControllerMixin,
codes = [409] * self.obj_ring.replicas
ts_iter = iter(put_ts)
- with set_http_connect(*codes, timestamps=ts_iter):
+ with set_http_connect_contextmgr(*codes, timestamps=ts_iter):
resp = req.get_response(self.app)
self.assertEqual(resp.status_int, 202)
@@ -1356,7 +1398,7 @@ class TestReplicatedObjController(BaseObjectControllerMixin,
ts_iter = iter(put_ts)
codes = put_resp
- with set_http_connect(*codes, timestamps=ts_iter):
+ with set_http_connect_contextmgr(*codes, timestamps=ts_iter):
resp = req.get_response(self.app)
self.assertEqual(resp.status_int, 202)
@@ -1370,7 +1412,7 @@ class TestReplicatedObjController(BaseObjectControllerMixin,
{'replicas': 1}, {'replicas': 5}, {'replicas': 8}, {'replicas': 15}])
class TestReplicatedObjControllerVariousReplicas(BaseObjectControllerMixin,
unittest.TestCase):
- controller_cls = obj.ReplicatedObjectController
+ controller_cls = ReplicatedObjectController
class StubResponse(object):
@@ -1456,7 +1498,7 @@ class TestECObjController(BaseObjectControllerMixin, unittest.TestCase):
'storage_policy': '0',
}
- controller_cls = obj.ECObjectController
+ controller_cls = ECObjectController
def _add_frag_index(self, index, headers):
# helper method to add a frag index header to an existing header dict
@@ -1508,7 +1550,7 @@ class TestECObjController(BaseObjectControllerMixin, unittest.TestCase):
def test_GET_simple(self):
req = swift.common.swob.Request.blank('/v1/a/c/o')
get_resp = [200] * self.policy.ec_ndata
- with set_http_connect(*get_resp):
+ with set_http_connect_contextmgr(*get_resp):
resp = req.get_response(self.app)
self.assertEqual(resp.status_int, 200)
self.assertIn('Accept-Ranges', resp.headers)
@@ -1526,7 +1568,7 @@ class TestECObjController(BaseObjectControllerMixin, unittest.TestCase):
resp_headers = {'Etag': 'frag_etag',
'X-Object-Sysmeta-Ec-Etag': 'data_etag',
'X-Object-Sysmeta-Alternate-Etag': 'alt_etag'}
- with set_http_connect(*get_resp, headers=resp_headers):
+ with set_http_connect_contextmgr(*get_resp, headers=resp_headers):
resp = req.get_response(self.app)
self.assertEqual('data_etag', resp.headers['Etag'])
return resp
@@ -1577,7 +1619,7 @@ class TestECObjController(BaseObjectControllerMixin, unittest.TestCase):
resp_headers = {'Etag': 'frag_etag',
'X-Object-Sysmeta-Ec-Etag': 'data_etag',
'X-Object-Sysmeta-Alternate-Etag': 'alt_etag'}
- with set_http_connect(*get_resp, headers=resp_headers):
+ with set_http_connect_contextmgr(*get_resp, headers=resp_headers):
resp = req.get_response(self.app)
self.assertEqual('data_etag', resp.headers['Etag'])
return resp
@@ -1619,14 +1661,14 @@ class TestECObjController(BaseObjectControllerMixin, unittest.TestCase):
req = swift.common.swob.Request.blank('/v1/a/c/o',
headers={'X-Newest': 'true'})
codes = [200] * self.policy.ec_ndata
- with set_http_connect(*codes):
+ with set_http_connect_contextmgr(*codes):
resp = req.get_response(self.app)
self.assertEqual(resp.status_int, 200)
def test_GET_error(self):
req = swift.common.swob.Request.blank('/v1/a/c/o')
get_resp = [503] + [200] * self.policy.ec_ndata
- with set_http_connect(*get_resp):
+ with set_http_connect_contextmgr(*get_resp):
resp = req.get_response(self.app)
self.assertEqual(resp.status_int, 200)
@@ -1659,8 +1701,8 @@ class TestECObjController(BaseObjectControllerMixin, unittest.TestCase):
responses = [(200, ''.join(node_fragments[i]), headers)
for i in range(POLICIES.default.ec_ndata)]
status_codes, body_iter, headers = zip(*responses)
- with set_http_connect(*status_codes, body_iter=body_iter,
- headers=headers):
+ with set_http_connect_contextmgr(*status_codes, body_iter=body_iter,
+ headers=headers):
resp = req.get_response(self.app)
self.assertEqual(resp.status_int, 200)
self.assertEqual(len(real_body), len(resp.body))
@@ -1674,7 +1716,8 @@ class TestECObjController(BaseObjectControllerMixin, unittest.TestCase):
'X-Obj-Metadata-Footer': 'yes',
'X-Obj-Multiphase-Commit': 'yes'
}
- with set_http_connect(*codes, expect_headers=expect_headers):
+ with set_http_connect_contextmgr(*codes,
+ expect_headers=expect_headers):
resp = req.get_response(self.app)
self.assertEqual(resp.status_int, 201)
@@ -1697,8 +1740,8 @@ class TestECObjController(BaseObjectControllerMixin, unittest.TestCase):
headers = {'Etag': 'bad etag'}
req = swift.common.swob.Request.blank(
'/v1/a/c/o', method='PUT', headers=headers, body=test_body)
- with set_http_connect(*codes, expect_headers=expect_headers,
- give_expect=capture_expect):
+ with set_http_connect_contextmgr(*codes, expect_headers=expect_headers,
+ give_expect=capture_expect):
resp = req.get_response(self.app)
self.assertEqual(422, resp.status_int)
self.assertEqual(self.replicas(), len(conns))
@@ -1710,7 +1753,8 @@ class TestECObjController(BaseObjectControllerMixin, unittest.TestCase):
env = {'swift.callback.update_footers': footers_callback}
req = swift.common.swob.Request.blank(
'/v1/a/c/o', method='PUT', environ=env, body=test_body)
- with set_http_connect(*codes, expect_headers=expect_headers):
+ with set_http_connect_contextmgr(*codes,
+ expect_headers=expect_headers):
resp = req.get_response(self.app)
self.assertEqual(422, resp.status_int)
@@ -1724,7 +1768,7 @@ class TestECObjController(BaseObjectControllerMixin, unittest.TestCase):
'X-Obj-Metadata-Footer': 'yes',
'X-Obj-Multiphase-Commit': 'yes'
}
- with set_http_connect(*codes, expect_headers=expect_headers), \
+ with set_http_connect_contextmgr(*codes, expect_headers=expect_headers), \
mock.patch('sys.stdout', stdout):
resp = req.get_response(self.app)
self.assertEqual(resp.status_int, 503)
@@ -1741,7 +1785,8 @@ class TestECObjController(BaseObjectControllerMixin, unittest.TestCase):
'X-Obj-Metadata-Footer': 'yes',
'X-Obj-Multiphase-Commit': 'yes'
}
- with set_http_connect(*codes, expect_headers=expect_headers):
+ with set_http_connect_contextmgr(*codes,
+ expect_headers=expect_headers):
resp = req.get_response(self.app)
self.assertEqual(resp.status_int, 201)
@@ -1753,7 +1798,8 @@ class TestECObjController(BaseObjectControllerMixin, unittest.TestCase):
'X-Obj-Metadata-Footer': 'yes',
'X-Obj-Multiphase-Commit': 'yes'
}
- with set_http_connect(*codes, expect_headers=expect_headers):
+ with set_http_connect_contextmgr(*codes,
+ expect_headers=expect_headers):
resp = req.get_response(self.app)
self.assertEqual(resp.status_int, 503)
@@ -1767,7 +1813,8 @@ class TestECObjController(BaseObjectControllerMixin, unittest.TestCase):
'X-Obj-Metadata-Footer': 'yes',
'X-Obj-Multiphase-Commit': 'yes'
}
- with set_http_connect(*codes, expect_headers=expect_headers):
+ with set_http_connect_contextmgr(*codes,
+ expect_headers=expect_headers):
resp = req.get_response(self.app)
self.assertEqual(resp.status_int, 201)
@@ -1779,7 +1826,8 @@ class TestECObjController(BaseObjectControllerMixin, unittest.TestCase):
'X-Obj-Metadata-Footer': 'yes',
'X-Obj-Multiphase-Commit': 'yes'
}
- with set_http_connect(*codes, expect_headers=expect_headers):
+ with set_http_connect_contextmgr(*codes,
+ expect_headers=expect_headers):
resp = req.get_response(self.app)
self.assertEqual(resp.status_int, 503)
@@ -1794,7 +1842,8 @@ class TestECObjController(BaseObjectControllerMixin, unittest.TestCase):
'X-Obj-Metadata-Footer': 'yes',
'X-Obj-Multiphase-Commit': 'yes'
}
- with set_http_connect(*codes, expect_headers=expect_headers):
+ with set_http_connect_contextmgr(*codes,
+ expect_headers=expect_headers):
resp = req.get_response(self.app)
self.assertEqual(resp.status_int, 201)
@@ -1808,7 +1857,8 @@ class TestECObjController(BaseObjectControllerMixin, unittest.TestCase):
'X-Obj-Metadata-Footer': 'yes',
'X-Obj-Multiphase-Commit': 'yes'
}
- with set_http_connect(*codes, expect_headers=expect_headers):
+ with set_http_connect_contextmgr(*codes,
+ expect_headers=expect_headers):
resp = req.get_response(self.app)
self.assertEqual(resp.status_int, 503)
@@ -1821,7 +1871,8 @@ class TestECObjController(BaseObjectControllerMixin, unittest.TestCase):
'X-Obj-Metadata-Footer': 'yes',
'X-Obj-Multiphase-Commit': 'yes'
}
- with set_http_connect(*codes, expect_headers=expect_headers):
+ with set_http_connect_contextmgr(*codes,
+ expect_headers=expect_headers):
resp = req.get_response(self.app)
self.assertEqual(resp.status_int, 201)
@@ -1834,7 +1885,8 @@ class TestECObjController(BaseObjectControllerMixin, unittest.TestCase):
'X-Obj-Metadata-Footer': 'yes',
'X-Obj-Multiphase-Commit': 'yes'
}
- with set_http_connect(*codes, expect_headers=expect_headers):
+ with set_http_connect_contextmgr(*codes,
+ expect_headers=expect_headers):
resp = req.get_response(self.app)
self.assertEqual(resp.status_int, 201)
@@ -1853,7 +1905,8 @@ class TestECObjController(BaseObjectControllerMixin, unittest.TestCase):
'X-Obj-Metadata-Footer': 'yes',
'X-Obj-Multiphase-Commit': 'yes'
}
- with set_http_connect(*codes, expect_headers=expect_headers):
+ with set_http_connect_contextmgr(*codes,
+ expect_headers=expect_headers):
resp = req.get_response(self.app)
self.assertEqual(resp.status_int, 499)
@@ -1873,7 +1926,8 @@ class TestECObjController(BaseObjectControllerMixin, unittest.TestCase):
'X-Obj-Metadata-Footer': 'yes',
'X-Obj-Multiphase-Commit': 'yes'
}
- with set_http_connect(*codes, expect_headers=expect_headers):
+ with set_http_connect_contextmgr(*codes,
+ expect_headers=expect_headers):
resp = req.get_response(self.app)
self.assertEqual(resp.status_int, 408)
@@ -1893,7 +1947,8 @@ class TestECObjController(BaseObjectControllerMixin, unittest.TestCase):
'X-Obj-Metadata-Footer': 'yes',
'X-Obj-Multiphase-Commit': 'yes'
}
- with set_http_connect(*codes, expect_headers=expect_headers):
+ with set_http_connect_contextmgr(*codes,
+ expect_headers=expect_headers):
resp = req.get_response(self.app)
self.assertEqual(resp.status_int, 499)
@@ -1913,7 +1968,8 @@ class TestECObjController(BaseObjectControllerMixin, unittest.TestCase):
'X-Obj-Metadata-Footer': 'yes',
'X-Obj-Multiphase-Commit': 'yes'
}
- with set_http_connect(*codes, expect_headers=expect_headers):
+ with set_http_connect_contextmgr(*codes,
+ expect_headers=expect_headers):
resp = req.get_response(self.app)
self.assertEqual(resp.status_int, 500)
@@ -1956,10 +2012,12 @@ class TestECObjController(BaseObjectControllerMixin, unittest.TestCase):
put_requests[conn_id]['x-timestamp'] = headers[
'X-Timestamp']
- with set_http_connect(*codes, expect_headers=expect_headers,
- give_send=capture_body,
- give_connect=capture_headers,
- headers=resp_headers):
+ with set_http_connect_contextmgr(
+ *codes,
+ expect_headers=expect_headers,
+ give_send=capture_body,
+ give_connect=capture_headers,
+ headers=resp_headers):
resp = req.get_response(self.app)
self.assertEqual(resp.status_int, 201)
@@ -2089,10 +2147,12 @@ class TestECObjController(BaseObjectControllerMixin, unittest.TestCase):
req = swift.common.swob.Request.blank(
'/v1/a/c/o', method='PUT', environ=env, body=test_body)
- with set_http_connect(*codes, expect_headers=expect_headers,
- give_send=capture_body,
- give_connect=capture_headers,
- headers=resp_headers):
+ with set_http_connect_contextmgr(
+ *codes,
+ expect_headers=expect_headers,
+ give_send=capture_body,
+ give_connect=capture_headers,
+ headers=resp_headers):
resp = req.get_response(self.app)
self.assertEqual(resp.status_int, 201)
@@ -2200,7 +2260,8 @@ class TestECObjController(BaseObjectControllerMixin, unittest.TestCase):
# whoops, stupid random
responses = responses[1:] + [responses[0]]
codes, expect_headers = zip(*responses)
- with set_http_connect(*codes, expect_headers=expect_headers):
+ with set_http_connect_contextmgr(*codes,
+ expect_headers=expect_headers):
resp = req.get_response(self.app)
self.assertEqual(resp.status_int, 201)
@@ -2320,7 +2381,7 @@ class TestECObjController(BaseObjectControllerMixin, unittest.TestCase):
'X-Timestamp': obj_stub['timestamp'].normal,
'X-Backend-Data-Timestamp': obj_stub['timestamp'].internal,
'X-Backend-Fragments':
- server._make_backend_fragments_header(ts2frags)
+ object_server._make_backend_fragments_header(ts2frags)
}
if durable_timestamp:
headers['X-Backend-Durable-Timestamp'] = durable_timestamp
@@ -3536,8 +3597,10 @@ class TestECObjController(BaseObjectControllerMixin, unittest.TestCase):
# sanity check responses1
responses = responses1[:self.policy.ec_ndata]
status_codes, body_iter, headers = zip(*responses)
- with set_http_connect(*status_codes, body_iter=body_iter,
- headers=headers):
+ with set_http_connect_contextmgr(
+ *status_codes,
+ body_iter=body_iter,
+ headers=headers):
resp = req.get_response(self.app)
self.assertEqual(resp.status_int, 200)
self.assertEqual(md5(resp.body).hexdigest(), etag1)
@@ -3545,8 +3608,10 @@ class TestECObjController(BaseObjectControllerMixin, unittest.TestCase):
# sanity check responses2
responses = responses2[:self.policy.ec_ndata]
status_codes, body_iter, headers = zip(*responses)
- with set_http_connect(*status_codes, body_iter=body_iter,
- headers=headers):
+ with set_http_connect_contextmgr(
+ *status_codes,
+ body_iter=body_iter,
+ headers=headers):
resp = req.get_response(self.app)
self.assertEqual(resp.status_int, 200)
self.assertEqual(md5(resp.body).hexdigest(), etag2)
@@ -3557,8 +3622,10 @@ class TestECObjController(BaseObjectControllerMixin, unittest.TestCase):
mixed_responses[mix_index] = responses2[mix_index]
status_codes, body_iter, headers = zip(*mixed_responses)
- with set_http_connect(*status_codes, body_iter=body_iter,
- headers=headers):
+ with set_http_connect_contextmgr(
+ *status_codes,
+ body_iter=body_iter,
+ headers=headers):
resp = req.get_response(self.app)
self.assertEqual(resp.status_int, 200)
try:
@@ -3591,8 +3658,10 @@ class TestECObjController(BaseObjectControllerMixin, unittest.TestCase):
status_codes, body_iter, headers = zip(*responses + [
(404, '', {}) for i in range(
self.policy.object_ring.max_more_nodes)])
- with set_http_connect(*status_codes, body_iter=body_iter,
- headers=headers):
+ with set_http_connect_contextmgr(
+ *status_codes,
+ body_iter=body_iter,
+ headers=headers):
resp = req.get_response(self.app)
self.assertEqual(resp.status_int, 200)
# do this inside the fake http context manager, it'll try to
@@ -3624,8 +3693,8 @@ class TestECObjController(BaseObjectControllerMixin, unittest.TestCase):
status_codes, body_iter, headers = zip(
*responses[:self.policy.ec_ndata + 1])
- with set_http_connect(*status_codes, body_iter=body_iter,
- headers=headers):
+ with set_http_connect_contextmgr(*status_codes, body_iter=body_iter,
+ headers=headers):
resp = req.get_response(self.app)
self.assertEqual(resp.status_int, 200)
self.assertTrue(md5(resp.body).hexdigest(), etag)
@@ -3641,8 +3710,8 @@ class TestECObjController(BaseObjectControllerMixin, unittest.TestCase):
responses = [(200, '', headers)]
status_codes, body_iter, headers = zip(*responses)
req = swift.common.swob.Request.blank('/v1/a/c/o', method='HEAD')
- with set_http_connect(*status_codes, body_iter=body_iter,
- headers=headers):
+ with set_http_connect_contextmgr(*status_codes, body_iter=body_iter,
+ headers=headers):
resp = req.get_response(self.app)
self.assertEqual(resp.status_int, 200)
self.assertEqual(resp.body, '')
@@ -3654,8 +3723,8 @@ class TestECObjController(BaseObjectControllerMixin, unittest.TestCase):
responses = [(404, '', {})] * self.replicas() * 2
status_codes, body_iter, headers = zip(*responses)
req = swift.common.swob.Request.blank('/v1/a/c/o', method='HEAD')
- with set_http_connect(*status_codes, body_iter=body_iter,
- headers=headers):
+ with set_http_connect_contextmgr(*status_codes, body_iter=body_iter,
+ headers=headers):
resp = req.get_response(self.app)
self.assertEqual(resp.status_int, 404)
# 404 shows actual response body size (i.e. 0 for HEAD)
@@ -3684,7 +3753,8 @@ class TestECObjController(BaseObjectControllerMixin, unittest.TestCase):
'X-Obj-Metadata-Footer': 'yes',
'X-Obj-Multiphase-Commit': 'yes'
}
- with set_http_connect(*codes, expect_headers=expect_headers):
+ with set_http_connect_contextmgr(*codes,
+ expect_headers=expect_headers):
start = time.time()
resp = req.get_response(self.app)
response_time = time.time() - start
@@ -3703,7 +3773,8 @@ class TestECObjController(BaseObjectControllerMixin, unittest.TestCase):
'X-Obj-Metadata-Footer': 'yes',
'X-Obj-Multiphase-Commit': 'yes'
}
- with set_http_connect(*codes, expect_headers=expect_headers):
+ with set_http_connect_contextmgr(*codes,
+ expect_headers=expect_headers):
resp = req.get_response(self.app)
self.assertEqual(resp.status_int, 201)
@@ -3719,7 +3790,8 @@ class TestECObjController(BaseObjectControllerMixin, unittest.TestCase):
'X-Obj-Metadata-Footer': 'yes',
'X-Obj-Multiphase-Commit': 'yes'
}
- with set_http_connect(*codes, expect_headers=expect_headers):
+ with set_http_connect_contextmgr(*codes,
+ expect_headers=expect_headers):
resp = req.get_response(self.app)
self.assertEqual(resp.status_int, 503)
@@ -3768,8 +3840,11 @@ class TestECObjController(BaseObjectControllerMixin, unittest.TestCase):
'X-Obj-Metadata-Footer': 'yes',
'X-Obj-Multiphase-Commit': 'yes'
}
- with set_http_connect(*status_codes, body_iter=body_iter,
- headers=headers, expect_headers=expect_headers):
+ with set_http_connect_contextmgr(
+ *status_codes,
+ body_iter=body_iter,
+ headers=headers,
+ expect_headers=expect_headers):
resp = req.get_response(self.app)
self.assertEqual(resp.status_int, 416)
self.assertEqual(resp.content_length, len(range_not_satisfiable_body))
@@ -3778,5 +3853,2135 @@ class TestECObjController(BaseObjectControllerMixin, unittest.TestCase):
self.assertEqual(resp.headers['Accept-Ranges'], 'bytes')
+@patch_policies([StoragePolicy(0, 'zero', True,
+ object_ring=FakeRing(base_port=3000))])
+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())
+ # clear proxy logger result for each test
+ _test_servers[0].logger._clear()
+
+ def tearDown(self):
+ self.app.account_ring.set_replicas(3)
+ self.app.container_ring.set_replicas(3)
+ for policy in POLICIES:
+ policy.object_ring = FakeRing(base_port=3000)
+
+ def assert_status_map(self, method, statuses, expected, raise_exc=False):
+ with save_globals():
+ kwargs = {}
+ if raise_exc:
+ kwargs['raise_exc'] = raise_exc
+
+ set_http_connect(*statuses, **kwargs)
+ self.app.memcache.store = {}
+ req = Request.blank('/v1/a/c/o',
+ headers={'Content-Length': '0',
+ 'Content-Type': 'text/plain'})
+ self.app.update_request(req)
+ try:
+ res = method(req)
+ except HTTPException as res:
+ pass
+ self.assertEqual(res.status_int, expected)
+
+ # repeat test
+ set_http_connect(*statuses, **kwargs)
+ self.app.memcache.store = {}
+ req = Request.blank('/v1/a/c/o',
+ headers={'Content-Length': '0',
+ 'Content-Type': 'text/plain'})
+ self.app.update_request(req)
+ try:
+ res = method(req)
+ except HTTPException as res:
+ pass
+ self.assertEqual(res.status_int, expected)
+
+ def _sleep_enough(self, condition):
+ for sleeptime in (0.1, 1.0):
+ sleep(sleeptime)
+ if condition():
+ break
+
+ def test_PUT_expect_header_zero_content_length(self):
+ test_errors = []
+
+ def test_connect(ipaddr, port, device, partition, method, path,
+ headers=None, query_string=None):
+ if path == '/a/c/o.jpg':
+ if 'expect' in headers or 'Expect' in headers:
+ test_errors.append('Expect was in headers for object '
+ 'server!')
+
+ with save_globals():
+ controller = ReplicatedObjectController(
+ self.app, 'account', 'container', 'object')
+ # The (201, Exception('test')) tuples in there have the effect of
+ # changing the status of the initial expect response. The default
+ # expect response from FakeConn for 201 is 100.
+ # But the object server won't send a 100 continue line if the
+ # client doesn't send a expect 100 header (as is the case with
+ # zero byte PUTs as validated by this test), nevertheless the
+ # object controller calls getexpect without prejudice. In this
+ # case the status from the response shows up early in getexpect
+ # instead of having to wait until getresponse. The Exception is
+ # in there to ensure that the object controller also *uses* the
+ # result of getexpect instead of calling getresponse in which case
+ # our FakeConn will blow up.
+ success_codes = [(201, Exception('test'))] * 3
+ set_http_connect(200, 200, *success_codes,
+ give_connect=test_connect)
+ req = Request.blank('/v1/a/c/o.jpg', {})
+ req.content_length = 0
+ self.app.update_request(req)
+ self.app.memcache.store = {}
+ res = controller.PUT(req)
+ self.assertEqual(test_errors, [])
+ self.assertTrue(res.status.startswith('201 '), res.status)
+
+ def test_PUT_expect_header_nonzero_content_length(self):
+ test_errors = []
+
+ def test_connect(ipaddr, port, device, partition, method, path,
+ headers=None, query_string=None):
+ if path == '/a/c/o.jpg':
+ if 'Expect' not in headers:
+ test_errors.append('Expect was not in headers for '
+ 'non-zero byte PUT!')
+
+ with save_globals():
+ controller = ReplicatedObjectController(
+ self.app, 'a', 'c', 'o.jpg')
+ # the (100, 201) tuples in there are just being extra explicit
+ # about the FakeConn returning the 100 Continue status when the
+ # object controller calls getexpect. Which is FakeConn's default
+ # for 201 if no expect_status is specified.
+ success_codes = [(100, 201)] * 3
+ set_http_connect(200, 200, *success_codes,
+ give_connect=test_connect)
+ req = Request.blank('/v1/a/c/o.jpg', {})
+ req.content_length = 1
+ req.body = 'a'
+ 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):
+ written_to = []
+
+ def test_connect(ipaddr, port, device, partition, method, path,
+ headers=None, query_string=None):
+ if path == '/a/c/o.jpg':
+ written_to.append((ipaddr, port, device))
+
+ with save_globals():
+ def is_r0(node):
+ return node['region'] == 0
+
+ object_ring = self.app.get_object_ring(None)
+ object_ring.max_more_nodes = 100
+ self.app.write_affinity_is_local_fn = is_r0
+ self.app.write_affinity_node_count = lambda r: 3
+
+ controller = \
+ ReplicatedObjectController(
+ self.app, 'a', 'c', 'o.jpg')
+ set_http_connect(200, 200, 201, 201, 201,
+ give_connect=test_connect)
+ req = Request.blank('/v1/a/c/o.jpg', {})
+ req.content_length = 1
+ req.body = 'a'
+ self.app.memcache.store = {}
+ res = controller.PUT(req)
+ self.assertTrue(res.status.startswith('201 '))
+
+ self.assertEqual(3, len(written_to))
+ for ip, port, device in written_to:
+ # this is kind of a hokey test, but in FakeRing, the port is even
+ # when the region is 0, and odd when the region is 1, so this test
+ # asserts that we only wrote to nodes in region 0.
+ self.assertEqual(0, port % 2)
+
+ def test_PUT_respects_write_affinity_with_507s(self):
+ written_to = []
+
+ def test_connect(ipaddr, port, device, partition, method, path,
+ headers=None, query_string=None):
+ if path == '/a/c/o.jpg':
+ written_to.append((ipaddr, port, device))
+
+ with save_globals():
+ def is_r0(node):
+ return node['region'] == 0
+
+ object_ring = self.app.get_object_ring(None)
+ object_ring.max_more_nodes = 100
+ self.app.write_affinity_is_local_fn = is_r0
+ self.app.write_affinity_node_count = lambda r: 3
+
+ controller = \
+ ReplicatedObjectController(
+ self.app, 'a', 'c', 'o.jpg')
+ self.app.error_limit(
+ object_ring.get_part_nodes(1)[0], 'test')
+ set_http_connect(200, 200, # account, container
+ 201, 201, 201, # 3 working backends
+ give_connect=test_connect)
+ req = Request.blank('/v1/a/c/o.jpg', {})
+ req.content_length = 1
+ req.body = 'a'
+ self.app.memcache.store = {}
+ res = controller.PUT(req)
+ self.assertTrue(res.status.startswith('201 '))
+
+ self.assertEqual(3, len(written_to))
+ # this is kind of a hokey test, but in FakeRing, the port is even when
+ # the region is 0, and odd when the region is 1, so this test asserts
+ # that we wrote to 2 nodes in region 0, then went to 1 non-r0 node.
+ self.assertEqual(0, written_to[0][1] % 2) # it's (ip, port, device)
+ self.assertEqual(0, written_to[1][1] % 2)
+ self.assertNotEqual(0, written_to[2][1] % 2)
+
+ def test_PUT_auto_content_type(self):
+ with save_globals():
+ controller = ReplicatedObjectController(
+ self.app, 'account', 'container', 'object')
+
+ def test_content_type(filename, expected):
+ # The three responses here are for account_info() (HEAD to
+ # account server), container_info() (HEAD to container server)
+ # and three calls to _connect_put_node() (PUT to three object
+ # servers)
+ set_http_connect(201, 201, 201, 201, 201,
+ give_content_type=lambda content_type:
+ self.assertEqual(content_type,
+ next(expected)))
+ # We need into include a transfer-encoding to get past
+ # constraints.check_object_creation()
+ req = Request.blank('/v1/a/c/%s' % filename, {},
+ headers={'transfer-encoding': 'chunked'})
+ self.app.update_request(req)
+ self.app.memcache.store = {}
+ res = controller.PUT(req)
+ # If we don't check the response here we could miss problems
+ # in PUT()
+ self.assertEqual(res.status_int, 201)
+
+ test_content_type('test.jpg', iter(['', '', 'image/jpeg',
+ 'image/jpeg', 'image/jpeg']))
+ test_content_type('test.html', iter(['', '', 'text/html',
+ 'text/html', 'text/html']))
+ test_content_type('test.css', iter(['', '', 'text/css',
+ 'text/css', 'text/css']))
+
+ def test_custom_mime_types_files(self):
+ swift_dir = mkdtemp()
+ try:
+ with open(os.path.join(swift_dir, 'mime.types'), 'w') as fp:
+ fp.write('foo/bar foo\n')
+ proxy_server.Application({'swift_dir': swift_dir},
+ FakeMemcache(), FakeLogger(),
+ FakeRing(), FakeRing())
+ self.assertEqual(proxy_server.mimetypes.guess_type('blah.foo')[0],
+ 'foo/bar')
+ self.assertEqual(proxy_server.mimetypes.guess_type('blah.jpg')[0],
+ 'image/jpeg')
+ finally:
+ rmtree(swift_dir, ignore_errors=True)
+
+ def test_PUT(self):
+ with save_globals():
+ controller = ReplicatedObjectController(
+ self.app, 'account', 'container', 'object')
+
+ def test_status_map(statuses, expected):
+ set_http_connect(*statuses)
+ req = Request.blank('/v1/a/c/o.jpg', {})
+ req.content_length = 0
+ self.app.update_request(req)
+ self.app.memcache.store = {}
+ res = controller.PUT(req)
+ expected = str(expected)
+ self.assertEqual(res.status[:len(expected)], expected)
+ test_status_map((200, 200, 201, 201, 201), 201)
+ test_status_map((200, 200, 201, 201, 500), 201)
+ test_status_map((200, 200, 204, 404, 404), 404)
+ test_status_map((200, 200, 204, 500, 404), 503)
+ test_status_map((200, 200, 202, 202, 204), 204)
+
+ def test_PUT_connect_exceptions(self):
+ with save_globals():
+ controller = ReplicatedObjectController(
+ self.app, 'account', 'container', 'object')
+
+ def test_status_map(statuses, expected):
+ set_http_connect(*statuses)
+ self.app.memcache.store = {}
+ req = Request.blank('/v1/a/c/o.jpg', {})
+ req.content_length = 0
+ self.app.update_request(req)
+ try:
+ res = controller.PUT(req)
+ except HTTPException as res:
+ pass
+ expected = str(expected)
+ self.assertEqual(res.status[:len(expected)], expected)
+ test_status_map((200, 200, 201, 201, -1), 201) # connect exc
+ # connect errors
+ test_status_map((200, 200, Timeout(), 201, 201, ), 201)
+ test_status_map((200, 200, 201, 201, Exception()), 201)
+ # expect errors
+ test_status_map((200, 200, (Timeout(), None), 201, 201), 201)
+ test_status_map((200, 200, (Exception(), None), 201, 201), 201)
+ # response errors
+ test_status_map((200, 200, (100, Timeout()), 201, 201), 201)
+ test_status_map((200, 200, (100, Exception()), 201, 201), 201)
+ test_status_map((200, 200, 507, 201, 201), 201) # error limited
+ test_status_map((200, 200, -1, 201, -1), 503)
+ test_status_map((200, 200, 503, -1, 503), 503)
+
+ def test_PUT_send_exceptions(self):
+ with save_globals():
+ controller = ReplicatedObjectController(
+ self.app, 'account', 'container', 'object')
+
+ def test_status_map(statuses, expected):
+ self.app.memcache.store = {}
+ set_http_connect(*statuses)
+ req = Request.blank('/v1/a/c/o.jpg',
+ environ={'REQUEST_METHOD': 'PUT'},
+ body='some data')
+ self.app.update_request(req)
+ try:
+ res = controller.PUT(req)
+ except HTTPException as res:
+ pass
+ expected = str(expected)
+ self.assertEqual(res.status[:len(expected)], expected)
+ test_status_map((200, 200, 201, -1, 201), 201)
+ test_status_map((200, 200, 201, -1, -1), 503)
+ test_status_map((200, 200, 503, 503, -1), 503)
+
+ def test_PUT_max_size(self):
+ with save_globals():
+ set_http_connect(201, 201, 201)
+ controller = ReplicatedObjectController(
+ self.app, 'account', 'container', 'object')
+ req = Request.blank('/v1/a/c/o', {}, headers={
+ 'Content-Length': str(constraints.MAX_FILE_SIZE + 1),
+ 'Content-Type': 'foo/bar'})
+ self.app.update_request(req)
+ res = controller.PUT(req)
+ self.assertEqual(res.status_int, 413)
+
+ def test_PUT_bad_content_type(self):
+ with save_globals():
+ set_http_connect(201, 201, 201)
+ controller = ReplicatedObjectController(
+ self.app, 'account', 'container', 'object')
+ req = Request.blank('/v1/a/c/o', {}, headers={
+ 'Content-Length': 0, 'Content-Type': 'foo/bar;swift_hey=45'})
+ self.app.update_request(req)
+ res = controller.PUT(req)
+ self.assertEqual(res.status_int, 400)
+
+ def test_PUT_getresponse_exceptions(self):
+
+ with save_globals():
+ controller = ReplicatedObjectController(
+ self.app, 'account', 'container', 'object')
+
+ def test_status_map(statuses, expected):
+ self.app.memcache.store = {}
+ set_http_connect(*statuses)
+ req = Request.blank('/v1/a/c/o.jpg', {})
+ req.content_length = 0
+ self.app.update_request(req)
+ try:
+ res = controller.PUT(req)
+ except HTTPException as res:
+ pass
+ expected = str(expected)
+ self.assertEqual(res.status[:len(str(expected))],
+ str(expected))
+ test_status_map((200, 200, 201, 201, -1), 201)
+ test_status_map((200, 200, 201, -1, -1), 503)
+ test_status_map((200, 200, 503, 503, -1), 503)
+
+ def test_POST(self):
+ with save_globals():
+ self.app.object_post_as_copy = False
+
+ def test_status_map(statuses, expected):
+ set_http_connect(*statuses)
+ self.app.memcache.store = {}
+ req = Request.blank('/v1/a/c/o', {}, method='POST',
+ headers={'Content-Type': 'foo/bar'})
+ self.app.update_request(req)
+ res = req.get_response(self.app)
+ expected = str(expected)
+ self.assertEqual(res.status[:len(expected)], expected)
+ test_status_map((200, 200, 202, 202, 202), 202)
+ test_status_map((200, 200, 202, 202, 500), 202)
+ test_status_map((200, 200, 202, 500, 500), 503)
+ test_status_map((200, 200, 202, 404, 500), 503)
+ test_status_map((200, 200, 202, 404, 404), 404)
+ test_status_map((200, 200, 404, 500, 500), 503)
+ test_status_map((200, 200, 404, 404, 404), 404)
+
+ @patch_policies([
+ StoragePolicy(0, 'zero', is_default=True, object_ring=FakeRing()),
+ StoragePolicy(1, 'one', object_ring=FakeRing()),
+ ])
+ def test_POST_backend_headers(self):
+ # reset the router post patch_policies
+ self.app.obj_controller_router = proxy_server.ObjectControllerRouter()
+ self.app.object_post_as_copy = False
+ self.app.sort_nodes = lambda nodes: nodes
+ backend_requests = []
+
+ def capture_requests(ip, port, method, path, headers, *args,
+ **kwargs):
+ backend_requests.append((method, path, headers))
+
+ req = Request.blank('/v1/a/c/o', {}, method='POST',
+ headers={'X-Object-Meta-Color': 'Blue',
+ 'Content-Type': 'text/plain'})
+
+ # we want the container_info response to says a policy index of 1
+ resp_headers = {'X-Backend-Storage-Policy-Index': 1}
+ with mocked_http_conn(
+ 200, 200, 202, 202, 202,
+ headers=resp_headers, give_connect=capture_requests
+ ) as fake_conn:
+ resp = req.get_response(self.app)
+ self.assertRaises(StopIteration, fake_conn.code_iter.next)
+
+ self.assertEqual(resp.status_int, 202)
+ self.assertEqual(len(backend_requests), 5)
+
+ def check_request(req, method, path, headers=None):
+ req_method, req_path, req_headers = req
+ self.assertEqual(method, req_method)
+ # caller can ignore leading path parts
+ self.assertTrue(req_path.endswith(path),
+ 'expected path to end with %s, it was %s' % (
+ path, req_path))
+ headers = headers or {}
+ # caller can ignore some headers
+ for k, v in headers.items():
+ self.assertEqual(req_headers[k], v)
+ account_request = backend_requests.pop(0)
+ check_request(account_request, method='HEAD', path='/sda/0/a')
+ container_request = backend_requests.pop(0)
+ check_request(container_request, method='HEAD', path='/sda/0/a/c')
+ # make sure backend requests included expected container headers
+ container_headers = {}
+ for request in backend_requests:
+ req_headers = request[2]
+ device = req_headers['x-container-device']
+ host = req_headers['x-container-host']
+ container_headers[device] = host
+ expectations = {
+ 'method': 'POST',
+ 'path': '/0/a/c/o',
+ 'headers': {
+ 'X-Container-Partition': '0',
+ 'Connection': 'close',
+ 'User-Agent': 'proxy-server %s' % os.getpid(),
+ 'Host': 'localhost:80',
+ 'Referer': 'POST http://localhost/v1/a/c/o',
+ 'X-Object-Meta-Color': 'Blue',
+ 'X-Backend-Storage-Policy-Index': '1'
+ },
+ }
+ check_request(request, **expectations)
+
+ expected = {}
+ for i, device in enumerate(['sda', 'sdb', 'sdc']):
+ expected[device] = '10.0.0.%d:100%d' % (i, i)
+ self.assertEqual(container_headers, expected)
+
+ # and again with policy override
+ self.app.memcache.store = {}
+ backend_requests = []
+ req = Request.blank('/v1/a/c/o', {}, method='POST',
+ headers={'X-Object-Meta-Color': 'Blue',
+ 'Content-Type': 'text/plain',
+ 'X-Backend-Storage-Policy-Index': 0})
+ with mocked_http_conn(
+ 200, 200, 202, 202, 202,
+ headers=resp_headers, give_connect=capture_requests
+ ) as fake_conn:
+ resp = req.get_response(self.app)
+ self.assertRaises(StopIteration, fake_conn.code_iter.next)
+ self.assertEqual(resp.status_int, 202)
+ self.assertEqual(len(backend_requests), 5)
+ for request in backend_requests[2:]:
+ expectations = {
+ 'method': 'POST',
+ 'path': '/0/a/c/o', # ignore device bit
+ 'headers': {
+ 'X-Object-Meta-Color': 'Blue',
+ 'X-Backend-Storage-Policy-Index': '0',
+ }
+ }
+ check_request(request, **expectations)
+
+ def test_DELETE(self):
+ # import pdb; pdb.set_trace()
+ with save_globals():
+ def test_status_map(statuses, expected):
+ set_http_connect(*statuses)
+ self.app.memcache.store = {}
+ req = Request.blank('/v1/a/c/o', {'REQUEST_METHOD': 'DELETE'})
+ self.app.update_request(req)
+ res = req.get_response(self.app)
+ self.assertEqual(res.status[:len(str(expected))],
+ str(expected))
+ test_status_map((200, 200, 204, 204, 204), 204)
+ test_status_map((200, 200, 204, 204, 500), 204)
+ test_status_map((200, 200, 204, 404, 404), 404)
+ test_status_map((200, 204, 500, 500, 404), 503)
+ test_status_map((200, 200, 404, 404, 404), 404)
+ test_status_map((200, 200, 400, 400, 400), 400)
+
+ def test_HEAD(self):
+ with save_globals():
+ def test_status_map(statuses, expected):
+ set_http_connect(*statuses)
+ self.app.memcache.store = {}
+ req = Request.blank('/v1/a/c/o', {'REQUEST_METHOD': 'HEAD'})
+ self.app.update_request(req)
+ res = req.get_response(self.app)
+ self.assertEqual(res.status[:len(str(expected))],
+ str(expected))
+ if expected < 400:
+ self.assertIn('x-works', res.headers)
+ self.assertEqual(res.headers['x-works'], 'yes')
+ self.assertIn('accept-ranges', res.headers)
+ self.assertEqual(res.headers['accept-ranges'], 'bytes')
+
+ test_status_map((200, 200, 200, 404, 404), 200)
+ test_status_map((200, 200, 200, 500, 404), 200)
+ test_status_map((200, 200, 304, 500, 404), 304)
+ test_status_map((200, 200, 404, 404, 404), 404)
+ test_status_map((200, 200, 404, 404, 500), 404)
+ test_status_map((200, 200, 500, 500, 500), 503)
+
+ def test_HEAD_newest(self):
+ with save_globals():
+ def test_status_map(statuses, expected, timestamps,
+ expected_timestamp):
+ set_http_connect(*statuses, timestamps=timestamps)
+ self.app.memcache.store = {}
+ req = Request.blank('/v1/a/c/o', {'REQUEST_METHOD': 'HEAD'},
+ headers={'x-newest': 'true'})
+ self.app.update_request(req)
+ res = req.get_response(self.app)
+ self.assertEqual(res.status[:len(str(expected))],
+ str(expected))
+ self.assertEqual(res.headers.get('last-modified'),
+ expected_timestamp)
+
+ # acct cont obj obj obj
+ test_status_map((200, 200, 200, 200, 200), 200, ('0', '0', '1',
+ '2', '3'), '3')
+ test_status_map((200, 200, 200, 200, 200), 200, ('0', '0', '1',
+ '3', '2'), '3')
+ test_status_map((200, 200, 200, 200, 200), 200, ('0', '0', '1',
+ '3', '1'), '3')
+ test_status_map((200, 200, 200, 200, 200), 200, ('0', '0', '3',
+ '3', '1'), '3')
+ test_status_map((200, 200, 200, 200, 200), 200, ('0', '0', None,
+ None, None), None)
+ test_status_map((200, 200, 200, 200, 200), 200, ('0', '0', None,
+ None, '1'), '1')
+ test_status_map((200, 200, 404, 404, 200), 200, ('0', '0', None,
+ None, '1'), '1')
+
+ def test_GET_newest(self):
+ with save_globals():
+ def test_status_map(statuses, expected, timestamps,
+ expected_timestamp):
+ set_http_connect(*statuses, timestamps=timestamps)
+ self.app.memcache.store = {}
+ req = Request.blank('/v1/a/c/o', {'REQUEST_METHOD': 'GET'},
+ headers={'x-newest': 'true'})
+ self.app.update_request(req)
+ res = req.get_response(self.app)
+ self.assertEqual(res.status[:len(str(expected))],
+ str(expected))
+ self.assertEqual(res.headers.get('last-modified'),
+ expected_timestamp)
+
+ test_status_map((200, 200, 200, 200, 200), 200, ('0', '0', '1',
+ '2', '3'), '3')
+ test_status_map((200, 200, 200, 200, 200), 200, ('0', '0', '1',
+ '3', '2'), '3')
+ test_status_map((200, 200, 200, 200, 200), 200, ('0', '0', '1',
+ '3', '1'), '3')
+ test_status_map((200, 200, 200, 200, 200), 200, ('0', '0', '3',
+ '3', '1'), '3')
+ test_status_map((200, 200, 200, 200, 200), 200, ('0', '0', None,
+ None, None), None)
+ test_status_map((200, 200, 200, 200, 200), 200, ('0', '0', None,
+ None, '1'), '1')
+
+ with save_globals():
+ def test_status_map(statuses, expected, timestamps,
+ expected_timestamp):
+ set_http_connect(*statuses, timestamps=timestamps)
+ self.app.memcache.store = {}
+ req = Request.blank('/v1/a/c/o', {'REQUEST_METHOD': 'HEAD'})
+ self.app.update_request(req)
+ res = req.get_response(self.app)
+ self.assertEqual(res.status[:len(str(expected))],
+ str(expected))
+ self.assertEqual(res.headers.get('last-modified'),
+ expected_timestamp)
+
+ test_status_map((200, 200, 200, 200, 200), 200, ('0', '0', '1',
+ '2', '3'), '1')
+ test_status_map((200, 200, 200, 200, 200), 200, ('0', '0', '1',
+ '3', '2'), '1')
+ test_status_map((200, 200, 200, 200, 200), 200, ('0', '0', '1',
+ '3', '1'), '1')
+ test_status_map((200, 200, 200, 200, 200), 200, ('0', '0', '3',
+ '3', '1'), '3')
+ test_status_map((200, 200, 200, 200, 200), 200, ('0', '0', None,
+ '1', '2'), None)
+
+ def test_POST_meta_val_len(self):
+ with save_globals():
+ limit = constraints.MAX_META_VALUE_LENGTH
+ self.app.object_post_as_copy = False
+ ReplicatedObjectController(
+ self.app, 'account', 'container', 'object')
+ set_http_connect(200, 200, 202, 202, 202)
+ # acct cont obj obj obj
+ req = Request.blank('/v1/a/c/o', {'REQUEST_METHOD': 'POST'},
+ headers={'Content-Type': 'foo/bar',
+ 'X-Object-Meta-Foo': 'x' * limit})
+ self.app.update_request(req)
+ res = req.get_response(self.app)
+ self.assertEqual(res.status_int, 202)
+ set_http_connect(202, 202, 202)
+ req = Request.blank(
+ '/v1/a/c/o', {'REQUEST_METHOD': 'POST'},
+ headers={'Content-Type': 'foo/bar',
+ 'X-Object-Meta-Foo': 'x' * (limit + 1)})
+ self.app.update_request(req)
+ res = req.get_response(self.app)
+ self.assertEqual(res.status_int, 400)
+
+ def test_POST_meta_authorize(self):
+ def authorize(req):
+ req.headers['X-Object-Meta-Foo'] = 'x' * (limit + 1)
+ return
+ with save_globals():
+ limit = constraints.MAX_META_VALUE_LENGTH
+ self.app.object_post_as_copy = False
+ controller = ReplicatedObjectController(
+ self.app, 'account', 'container', 'object')
+ set_http_connect(200, 200, 202, 202, 202)
+ # acct cont obj obj obj
+ req = Request.blank('/v1/a/c/o', {'REQUEST_METHOD': 'POST'},
+ headers={'Content-Type': 'foo/bar',
+ 'X-Object-Meta-Foo': 'x'})
+ req.environ['swift.authorize'] = authorize
+ self.app.update_request(req)
+ res = controller.POST(req)
+ self.assertEqual(res.status_int, 400)
+
+ def test_POST_meta_key_len(self):
+ with save_globals():
+ limit = constraints.MAX_META_NAME_LENGTH
+ self.app.object_post_as_copy = False
+ set_http_connect(200, 200, 202, 202, 202)
+ # acct cont obj obj obj
+ req = Request.blank(
+ '/v1/a/c/o', {'REQUEST_METHOD': 'POST'},
+ headers={'Content-Type': 'foo/bar',
+ ('X-Object-Meta-' + 'x' * limit): 'x'})
+ self.app.update_request(req)
+ res = req.get_response(self.app)
+ self.assertEqual(res.status_int, 202)
+ set_http_connect(202, 202, 202)
+ req = Request.blank(
+ '/v1/a/c/o', {'REQUEST_METHOD': 'POST'},
+ headers={'Content-Type': 'foo/bar',
+ ('X-Object-Meta-' + 'x' * (limit + 1)): 'x'})
+ self.app.update_request(req)
+ res = req.get_response(self.app)
+ self.assertEqual(res.status_int, 400)
+
+ def test_POST_meta_count(self):
+ with save_globals():
+ limit = constraints.MAX_META_COUNT
+ headers = dict(
+ (('X-Object-Meta-' + str(i), 'a') for i in range(limit + 1)))
+ headers.update({'Content-Type': 'foo/bar'})
+ set_http_connect(202, 202, 202)
+ req = Request.blank('/v1/a/c/o', {'REQUEST_METHOD': 'POST'},
+ headers=headers)
+ self.app.update_request(req)
+ res = req.get_response(self.app)
+ self.assertEqual(res.status_int, 400)
+
+ def test_POST_meta_size(self):
+ with save_globals():
+ limit = constraints.MAX_META_OVERALL_SIZE
+ count = limit / 256 # enough to cause the limit to be reached
+ headers = dict(
+ (('X-Object-Meta-' + str(i), 'a' * 256)
+ for i in range(count + 1)))
+ headers.update({'Content-Type': 'foo/bar'})
+ set_http_connect(202, 202, 202)
+ req = Request.blank('/v1/a/c/o', {'REQUEST_METHOD': 'POST'},
+ headers=headers)
+ self.app.update_request(req)
+ res = req.get_response(self.app)
+ self.assertEqual(res.status_int, 400)
+
+ def test_PUT_not_autodetect_content_type(self):
+ with save_globals():
+ headers = {'Content-Type': 'something/right', 'Content-Length': 0}
+ it_worked = []
+
+ def verify_content_type(ipaddr, port, device, partition,
+ method, path, headers=None,
+ query_string=None):
+ if path == '/a/c/o.html':
+ it_worked.append(
+ headers['Content-Type'].startswith('something/right'))
+
+ set_http_connect(204, 204, 201, 201, 201,
+ give_connect=verify_content_type)
+ req = Request.blank('/v1/a/c/o.html', {'REQUEST_METHOD': 'PUT'},
+ headers=headers)
+ self.app.update_request(req)
+ req.get_response(self.app)
+ self.assertNotEqual(it_worked, [])
+ self.assertTrue(all(it_worked))
+
+ def test_PUT_autodetect_content_type(self):
+ with save_globals():
+ headers = {'Content-Type': 'something/wrong', 'Content-Length': 0,
+ 'X-Detect-Content-Type': 'True'}
+ it_worked = []
+
+ def verify_content_type(ipaddr, port, device, partition,
+ method, path, headers=None,
+ query_string=None):
+ if path == '/a/c/o.html':
+ it_worked.append(
+ headers['Content-Type'].startswith('text/html'))
+
+ set_http_connect(204, 204, 201, 201, 201,
+ give_connect=verify_content_type)
+ req = Request.blank('/v1/a/c/o.html', {'REQUEST_METHOD': 'PUT'},
+ headers=headers)
+ self.app.update_request(req)
+ req.get_response(self.app)
+ self.assertNotEqual(it_worked, [])
+ self.assertTrue(all(it_worked))
+
+ def test_client_timeout(self):
+ with save_globals():
+ self.app.account_ring.get_nodes('account')
+ for dev in self.app.account_ring.devs:
+ dev['ip'] = '127.0.0.1'
+ dev['port'] = 1
+ self.app.container_ring.get_nodes('account')
+ for dev in self.app.container_ring.devs:
+ dev['ip'] = '127.0.0.1'
+ dev['port'] = 1
+ object_ring = self.app.get_object_ring(None)
+ object_ring.get_nodes('account')
+ for dev in object_ring.devs:
+ dev['ip'] = '127.0.0.1'
+ dev['port'] = 1
+
+ class SlowBody(object):
+
+ def __init__(self):
+ self.sent = 0
+
+ def read(self, size=-1):
+ if self.sent < 4:
+ sleep(0.1)
+ self.sent += 1
+ return ' '
+ return ''
+
+ req = Request.blank('/v1/a/c/o',
+ environ={'REQUEST_METHOD': 'PUT',
+ 'wsgi.input': SlowBody()},
+ headers={'Content-Length': '4',
+ 'Content-Type': 'text/plain'})
+ self.app.update_request(req)
+ set_http_connect(200, 200, 201, 201, 201)
+ # acct cont obj obj obj
+ resp = req.get_response(self.app)
+ self.assertEqual(resp.status_int, 201)
+ self.app.client_timeout = 0.05
+ req = Request.blank('/v1/a/c/o',
+ environ={'REQUEST_METHOD': 'PUT',
+ 'wsgi.input': SlowBody()},
+ headers={'Content-Length': '4',
+ 'Content-Type': 'text/plain'})
+ self.app.update_request(req)
+ set_http_connect(201, 201, 201)
+ # obj obj obj
+ resp = req.get_response(self.app)
+ self.assertEqual(resp.status_int, 408)
+
+ def test_client_disconnect(self):
+ with save_globals():
+ self.app.account_ring.get_nodes('account')
+ for dev in self.app.account_ring.devs:
+ dev['ip'] = '127.0.0.1'
+ dev['port'] = 1
+ self.app.container_ring.get_nodes('account')
+ for dev in self.app.container_ring.devs:
+ dev['ip'] = '127.0.0.1'
+ dev['port'] = 1
+ object_ring = self.app.get_object_ring(None)
+ object_ring.get_nodes('account')
+ for dev in object_ring.devs:
+ dev['ip'] = '127.0.0.1'
+ dev['port'] = 1
+
+ class DisconnectedBody(object):
+
+ def __init__(self):
+ self.sent = 0
+
+ def read(self, size=-1):
+ return ''
+
+ req = Request.blank('/v1/a/c/o',
+ environ={'REQUEST_METHOD': 'PUT',
+ 'wsgi.input': DisconnectedBody()},
+ headers={'Content-Length': '4',
+ 'Content-Type': 'text/plain'})
+ self.app.update_request(req)
+ set_http_connect(200, 200, 201, 201, 201)
+ # acct cont obj obj obj
+ resp = req.get_response(self.app)
+ self.assertEqual(resp.status_int, 499)
+
+ def test_node_read_timeout(self):
+ with save_globals():
+ self.app.account_ring.get_nodes('account')
+ for dev in self.app.account_ring.devs:
+ dev['ip'] = '127.0.0.1'
+ dev['port'] = 1
+ self.app.container_ring.get_nodes('account')
+ for dev in self.app.container_ring.devs:
+ dev['ip'] = '127.0.0.1'
+ dev['port'] = 1
+ object_ring = self.app.get_object_ring(None)
+ object_ring.get_nodes('account')
+ for dev in object_ring.devs:
+ dev['ip'] = '127.0.0.1'
+ 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=0.1)
+ req.sent_size = 0
+ resp = req.get_response(self.app)
+ got_exc = False
+ try:
+ resp.body
+ except ChunkReadTimeout:
+ got_exc = True
+ self.assertFalse(got_exc)
+ self.app.recoverable_node_timeout = 0.1
+ set_http_connect(200, 200, 200, slow=1.0)
+ resp = req.get_response(self.app)
+ got_exc = False
+ try:
+ resp.body
+ except ChunkReadTimeout:
+ got_exc = True
+ self.assertTrue(got_exc)
+
+ def test_node_read_timeout_retry(self):
+ with save_globals():
+ self.app.account_ring.get_nodes('account')
+ for dev in self.app.account_ring.devs:
+ dev['ip'] = '127.0.0.1'
+ dev['port'] = 1
+ self.app.container_ring.get_nodes('account')
+ for dev in self.app.container_ring.devs:
+ dev['ip'] = '127.0.0.1'
+ dev['port'] = 1
+ object_ring = self.app.get_object_ring(None)
+ object_ring.get_nodes('account')
+ for dev in object_ring.devs:
+ dev['ip'] = '127.0.0.1'
+ dev['port'] = 1
+ req = Request.blank('/v1/a/c/o', environ={'REQUEST_METHOD': 'GET'})
+ self.app.update_request(req)
+
+ self.app.recoverable_node_timeout = 0.1
+ set_http_connect(200, 200, 200, slow=[1.0, 1.0, 1.0])
+ resp = req.get_response(self.app)
+ got_exc = False
+ try:
+ self.assertEqual('', resp.body)
+ except ChunkReadTimeout:
+ got_exc = True
+ self.assertTrue(got_exc)
+
+ set_http_connect(200, 200, 200, body='lalala',
+ slow=[1.0, 1.0])
+ resp = req.get_response(self.app)
+ got_exc = False
+ try:
+ self.assertEqual(resp.body, 'lalala')
+ except ChunkReadTimeout:
+ got_exc = True
+ self.assertFalse(got_exc)
+
+ 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:
+ self.assertEqual(resp.body, 'lalala')
+ except ChunkReadTimeout:
+ got_exc = True
+ self.assertFalse(got_exc)
+
+ 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:
+ self.assertEqual(resp.body, 'lalala')
+ except ChunkReadTimeout:
+ got_exc = True
+ self.assertFalse(got_exc)
+
+ req = Request.blank('/v1/a/c/o', environ={'REQUEST_METHOD': 'GET'})
+ 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:
+ resp.body
+ except ChunkReadTimeout:
+ got_exc = True
+ self.assertTrue(got_exc)
+
+ def test_node_write_timeout(self):
+ with save_globals():
+ self.app.account_ring.get_nodes('account')
+ for dev in self.app.account_ring.devs:
+ dev['ip'] = '127.0.0.1'
+ dev['port'] = 1
+ self.app.container_ring.get_nodes('account')
+ for dev in self.app.container_ring.devs:
+ dev['ip'] = '127.0.0.1'
+ dev['port'] = 1
+ object_ring = self.app.get_object_ring(None)
+ object_ring.get_nodes('account')
+ for dev in object_ring.devs:
+ dev['ip'] = '127.0.0.1'
+ dev['port'] = 1
+ 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(200, 200, 201, 201, 201, slow=0.1)
+ resp = req.get_response(self.app)
+ self.assertEqual(resp.status_int, 201)
+ self.app.node_timeout = 0.1
+ 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.assertEqual(resp.status_int, 503)
+
+ def test_node_request_setting(self):
+ baseapp = proxy_server.Application({'request_node_count': '3'},
+ FakeMemcache(),
+ container_ring=FakeRing(),
+ account_ring=FakeRing())
+ self.assertEqual(baseapp.request_node_count(3), 3)
+
+ def test_iter_nodes(self):
+ with save_globals():
+ try:
+ object_ring = self.app.get_object_ring(None)
+ object_ring.max_more_nodes = 2
+ partition, nodes = object_ring.get_nodes('account',
+ 'container',
+ 'object')
+ collected_nodes = []
+ for node in self.app.iter_nodes(object_ring,
+ partition):
+ collected_nodes.append(node)
+ self.assertEqual(len(collected_nodes), 5)
+
+ object_ring.max_more_nodes = 6
+ self.app.request_node_count = lambda r: 20
+ partition, nodes = object_ring.get_nodes('account',
+ 'container',
+ 'object')
+ collected_nodes = []
+ for node in self.app.iter_nodes(object_ring,
+ partition):
+ collected_nodes.append(node)
+ self.assertEqual(len(collected_nodes), 9)
+
+ # zero error-limited primary nodes -> no handoff warnings
+ self.app.log_handoffs = True
+ self.app.logger = FakeLogger()
+ self.app.request_node_count = lambda r: 7
+ object_ring.max_more_nodes = 20
+ partition, nodes = object_ring.get_nodes('account',
+ 'container',
+ 'object')
+ collected_nodes = []
+ for node in self.app.iter_nodes(object_ring, partition):
+ collected_nodes.append(node)
+ self.assertEqual(len(collected_nodes), 7)
+ self.assertEqual(self.app.logger.log_dict['warning'], [])
+ self.assertEqual(self.app.logger.get_increments(), [])
+
+ # one error-limited primary node -> one handoff warning
+ self.app.log_handoffs = True
+ self.app.logger = FakeLogger()
+ self.app.request_node_count = lambda r: 7
+ self.app._error_limiting = {} # clear out errors
+ set_node_errors(self.app, object_ring._devs[0], 999,
+ last_error=(2 ** 63 - 1))
+
+ collected_nodes = []
+ for node in self.app.iter_nodes(object_ring, partition):
+ collected_nodes.append(node)
+ self.assertEqual(len(collected_nodes), 7)
+ self.assertEqual(self.app.logger.log_dict['warning'], [
+ (('Handoff requested (5)',), {})])
+ self.assertEqual(self.app.logger.get_increments(),
+ ['handoff_count'])
+
+ # two error-limited primary nodes -> two handoff warnings
+ self.app.log_handoffs = True
+ self.app.logger = FakeLogger()
+ self.app.request_node_count = lambda r: 7
+ self.app._error_limiting = {} # clear out errors
+ for i in range(2):
+ set_node_errors(self.app, object_ring._devs[i], 999,
+ last_error=(2 ** 63 - 1))
+
+ collected_nodes = []
+ for node in self.app.iter_nodes(object_ring, partition):
+ collected_nodes.append(node)
+ self.assertEqual(len(collected_nodes), 7)
+ self.assertEqual(self.app.logger.log_dict['warning'], [
+ (('Handoff requested (5)',), {}),
+ (('Handoff requested (6)',), {})])
+ self.assertEqual(self.app.logger.get_increments(),
+ ['handoff_count',
+ 'handoff_count'])
+
+ # all error-limited primary nodes -> four handoff warnings,
+ # plus a handoff-all metric
+ self.app.log_handoffs = True
+ self.app.logger = FakeLogger()
+ self.app.request_node_count = lambda r: 10
+ object_ring.set_replicas(4) # otherwise we run out of handoffs
+ self.app._error_limiting = {} # clear out errors
+ for i in range(4):
+ set_node_errors(self.app, object_ring._devs[i], 999,
+ last_error=(2 ** 63 - 1))
+
+ collected_nodes = []
+ for node in self.app.iter_nodes(object_ring, partition):
+ collected_nodes.append(node)
+ self.assertEqual(len(collected_nodes), 10)
+ self.assertEqual(self.app.logger.log_dict['warning'], [
+ (('Handoff requested (7)',), {}),
+ (('Handoff requested (8)',), {}),
+ (('Handoff requested (9)',), {}),
+ (('Handoff requested (10)',), {})])
+ self.assertEqual(self.app.logger.get_increments(),
+ ['handoff_count',
+ 'handoff_count',
+ 'handoff_count',
+ 'handoff_count',
+ 'handoff_all_count'])
+
+ finally:
+ object_ring.max_more_nodes = 0
+
+ def test_iter_nodes_calls_sort_nodes(self):
+ with mock.patch.object(self.app, 'sort_nodes') as sort_nodes:
+ object_ring = self.app.get_object_ring(None)
+ for node in self.app.iter_nodes(object_ring, 0):
+ pass
+ sort_nodes.assert_called_once_with(
+ object_ring.get_part_nodes(0))
+
+ def test_iter_nodes_skips_error_limited(self):
+ with mock.patch.object(self.app, 'sort_nodes', lambda n: n):
+ object_ring = self.app.get_object_ring(None)
+ first_nodes = list(self.app.iter_nodes(object_ring, 0))
+ second_nodes = list(self.app.iter_nodes(object_ring, 0))
+ self.assertIn(first_nodes[0], second_nodes)
+
+ self.app.error_limit(first_nodes[0], 'test')
+ second_nodes = list(self.app.iter_nodes(object_ring, 0))
+ self.assertNotIn(first_nodes[0], second_nodes)
+
+ def test_iter_nodes_gives_extra_if_error_limited_inline(self):
+ object_ring = self.app.get_object_ring(None)
+ with mock.patch.object(self.app, 'sort_nodes', lambda n: n), \
+ mock.patch.object(self.app, 'request_node_count',
+ lambda r: 6), \
+ mock.patch.object(object_ring, 'max_more_nodes', 99):
+ first_nodes = list(self.app.iter_nodes(object_ring, 0))
+ second_nodes = []
+ for node in self.app.iter_nodes(object_ring, 0):
+ if not second_nodes:
+ self.app.error_limit(node, 'test')
+ second_nodes.append(node)
+ self.assertEqual(len(first_nodes), 6)
+ self.assertEqual(len(second_nodes), 7)
+
+ def test_iter_nodes_with_custom_node_iter(self):
+ object_ring = self.app.get_object_ring(None)
+ node_list = [dict(id=n, ip='1.2.3.4', port=n, device='D')
+ for n in range(10)]
+ with mock.patch.object(self.app, 'sort_nodes', lambda n: n), \
+ mock.patch.object(self.app, 'request_node_count',
+ lambda r: 3):
+ got_nodes = list(self.app.iter_nodes(object_ring, 0,
+ node_iter=iter(node_list)))
+ self.assertEqual(node_list[:3], got_nodes)
+
+ with mock.patch.object(self.app, 'sort_nodes', lambda n: n), \
+ mock.patch.object(self.app, 'request_node_count',
+ lambda r: 1000000):
+ got_nodes = list(self.app.iter_nodes(object_ring, 0,
+ node_iter=iter(node_list)))
+ self.assertEqual(node_list, got_nodes)
+
+ def test_best_response_sets_headers(self):
+ controller = ReplicatedObjectController(
+ self.app, 'account', 'container', 'object')
+ req = Request.blank('/v1/a/c/o', environ={'REQUEST_METHOD': 'GET'})
+ resp = controller.best_response(req, [200] * 3, ['OK'] * 3, [''] * 3,
+ 'Object', headers=[{'X-Test': '1'},
+ {'X-Test': '2'},
+ {'X-Test': '3'}])
+ self.assertEqual(resp.headers['X-Test'], '1')
+
+ def test_best_response_sets_etag(self):
+ controller = ReplicatedObjectController(
+ self.app, 'account', 'container', 'object')
+ req = Request.blank('/v1/a/c/o', environ={'REQUEST_METHOD': 'GET'})
+ resp = controller.best_response(req, [200] * 3, ['OK'] * 3, [''] * 3,
+ 'Object')
+ self.assertIsNone(resp.etag)
+ resp = controller.best_response(req, [200] * 3, ['OK'] * 3, [''] * 3,
+ 'Object',
+ etag='68b329da9893e34099c7d8ad5cb9c940'
+ )
+ self.assertEqual(resp.etag, '68b329da9893e34099c7d8ad5cb9c940')
+
+ def test_proxy_passes_content_type(self):
+ with save_globals():
+ req = Request.blank('/v1/a/c/o', environ={'REQUEST_METHOD': 'GET'})
+ self.app.update_request(req)
+ set_http_connect(200, 200, 200)
+ resp = req.get_response(self.app)
+ self.assertEqual(resp.status_int, 200)
+ self.assertEqual(resp.content_type, 'x-application/test')
+ set_http_connect(200, 200, 200)
+ resp = req.get_response(self.app)
+ self.assertEqual(resp.status_int, 200)
+ self.assertEqual(resp.content_length, 0)
+ set_http_connect(200, 200, 200, slow=True)
+ resp = req.get_response(self.app)
+ self.assertEqual(resp.status_int, 200)
+ self.assertEqual(resp.content_length, 4)
+
+ def test_proxy_passes_content_length_on_head(self):
+ with save_globals():
+ req = Request.blank('/v1/a/c/o',
+ environ={'REQUEST_METHOD': 'HEAD'})
+ self.app.update_request(req)
+ controller = ReplicatedObjectController(
+ self.app, 'account', 'container', 'object')
+ set_http_connect(200, 200, 200)
+ resp = controller.HEAD(req)
+ self.assertEqual(resp.status_int, 200)
+ self.assertEqual(resp.content_length, 0)
+ set_http_connect(200, 200, 200, slow=True)
+ resp = controller.HEAD(req)
+ self.assertEqual(resp.status_int, 200)
+ self.assertEqual(resp.content_length, 4)
+
+ def test_error_limiting(self):
+ with save_globals():
+ controller = ReplicatedObjectController(
+ self.app, 'account', 'container', 'object')
+ controller.app.sort_nodes = lambda l: l
+ object_ring = controller.app.get_object_ring(None)
+ self.assert_status_map(controller.HEAD, (200, 200, 503, 200, 200),
+ 200)
+ self.assertEqual(
+ node_error_count(controller.app, object_ring.devs[0]), 2)
+ self.assertTrue(
+ node_last_error(controller.app, object_ring.devs[0])
+ is not None)
+ for _junk in range(self.app.error_suppression_limit):
+ self.assert_status_map(controller.HEAD, (200, 200, 503, 503,
+ 503), 503)
+ self.assertEqual(
+ node_error_count(controller.app, object_ring.devs[0]),
+ self.app.error_suppression_limit + 1)
+ self.assert_status_map(controller.HEAD, (200, 200, 200, 200, 200),
+ 503)
+ self.assertTrue(
+ node_last_error(controller.app, object_ring.devs[0])
+ is not None)
+ self.assert_status_map(controller.PUT, (200, 200, 200, 201, 201,
+ 201), 503)
+ self.assert_status_map(controller.POST,
+ (200, 200, 200, 200, 200, 200, 202, 202,
+ 202), 503)
+ self.assert_status_map(controller.DELETE,
+ (200, 200, 200, 204, 204, 204), 503)
+ self.app.error_suppression_interval = -300
+ self.assert_status_map(controller.HEAD, (200, 200, 200, 200, 200),
+ 200)
+ self.assertRaises(BaseException,
+ self.assert_status_map, controller.DELETE,
+ (200, 200, 200, 204, 204, 204), 503,
+ raise_exc=True)
+
+ def test_error_limiting_survives_ring_reload(self):
+ with save_globals():
+ controller = ReplicatedObjectController(
+ self.app, 'account', 'container', 'object')
+ controller.app.sort_nodes = lambda l: l
+ object_ring = controller.app.get_object_ring(None)
+ self.assert_status_map(controller.HEAD, (200, 200, 503, 200, 200),
+ 200)
+ self.assertEqual(
+ node_error_count(controller.app, object_ring.devs[0]), 2)
+ self.assertTrue(
+ node_last_error(controller.app, object_ring.devs[0])
+ is not None)
+ for _junk in range(self.app.error_suppression_limit):
+ self.assert_status_map(controller.HEAD, (200, 200, 503, 503,
+ 503), 503)
+ self.assertEqual(
+ node_error_count(controller.app, object_ring.devs[0]),
+ self.app.error_suppression_limit + 1)
+
+ # wipe out any state in the ring
+ for policy in POLICIES:
+ policy.object_ring = FakeRing(base_port=3000)
+
+ # and we still get an error, which proves that the
+ # error-limiting info survived a ring reload
+ self.assert_status_map(controller.HEAD, (200, 200, 200, 200, 200),
+ 503)
+
+ def test_PUT_error_limiting(self):
+ with save_globals():
+ controller = ReplicatedObjectController(
+ self.app, 'account', 'container', 'object')
+ controller.app.sort_nodes = lambda l: l
+ object_ring = controller.app.get_object_ring(None)
+ # acc con obj obj obj
+ self.assert_status_map(controller.PUT, (200, 200, 503, 200, 200),
+ 200)
+
+ # 2, not 1, because assert_status_map() calls the method twice
+ odevs = object_ring.devs
+ self.assertEqual(node_error_count(controller.app, odevs[0]), 2)
+ self.assertEqual(node_error_count(controller.app, odevs[1]), 0)
+ self.assertEqual(node_error_count(controller.app, odevs[2]), 0)
+ self.assertTrue(
+ node_last_error(controller.app, odevs[0]) is not None)
+ self.assertTrue(node_last_error(controller.app, odevs[1]) is None)
+ self.assertTrue(node_last_error(controller.app, odevs[2]) is None)
+
+ def test_PUT_error_limiting_last_node(self):
+ with save_globals():
+ controller = ReplicatedObjectController(
+ self.app, 'account', 'container', 'object')
+ controller.app.sort_nodes = lambda l: l
+ object_ring = controller.app.get_object_ring(None)
+ # acc con obj obj obj
+ self.assert_status_map(controller.PUT, (200, 200, 200, 200, 503),
+ 200)
+
+ # 2, not 1, because assert_status_map() calls the method twice
+ odevs = object_ring.devs
+ self.assertEqual(node_error_count(controller.app, odevs[0]), 0)
+ self.assertEqual(node_error_count(controller.app, odevs[1]), 0)
+ self.assertEqual(node_error_count(controller.app, odevs[2]), 2)
+ self.assertTrue(node_last_error(controller.app, odevs[0]) is None)
+ self.assertTrue(node_last_error(controller.app, odevs[1]) is None)
+ self.assertTrue(
+ node_last_error(controller.app, odevs[2]) is not None)
+
+ def test_acc_or_con_missing_returns_404(self):
+ with save_globals():
+ self.app.memcache = FakeMemcacheReturnsNone()
+ self.app._error_limiting = {}
+ controller = ReplicatedObjectController(
+ self.app, 'account', 'container', 'object')
+ set_http_connect(200, 200, 200, 200, 200, 200)
+ req = Request.blank('/v1/a/c/o',
+ environ={'REQUEST_METHOD': 'DELETE'})
+ self.app.update_request(req)
+ resp = getattr(controller, 'DELETE')(req)
+ self.assertEqual(resp.status_int, 200)
+
+ set_http_connect(404, 404, 404)
+ # acct acct acct
+ # make sure to use a fresh request without cached env
+ req = Request.blank('/v1/a/c/o',
+ environ={'REQUEST_METHOD': 'DELETE'})
+ resp = getattr(controller, 'DELETE')(req)
+ self.assertEqual(resp.status_int, 404)
+
+ set_http_connect(503, 404, 404)
+ # acct acct acct
+ # make sure to use a fresh request without cached env
+ req = Request.blank('/v1/a/c/o',
+ environ={'REQUEST_METHOD': 'DELETE'})
+ resp = getattr(controller, 'DELETE')(req)
+ self.assertEqual(resp.status_int, 404)
+
+ set_http_connect(503, 503, 404)
+ # acct acct acct
+ # make sure to use a fresh request without cached env
+ req = Request.blank('/v1/a/c/o',
+ environ={'REQUEST_METHOD': 'DELETE'})
+ resp = getattr(controller, 'DELETE')(req)
+ self.assertEqual(resp.status_int, 404)
+
+ set_http_connect(503, 503, 503)
+ # acct acct acct
+ # make sure to use a fresh request without cached env
+ req = Request.blank('/v1/a/c/o',
+ environ={'REQUEST_METHOD': 'DELETE'})
+ resp = getattr(controller, 'DELETE')(req)
+ self.assertEqual(resp.status_int, 404)
+
+ set_http_connect(200, 200, 204, 204, 204)
+ # acct cont obj obj obj
+ # make sure to use a fresh request without cached env
+ req = Request.blank('/v1/a/c/o',
+ environ={'REQUEST_METHOD': 'DELETE'})
+ resp = getattr(controller, 'DELETE')(req)
+ self.assertEqual(resp.status_int, 204)
+
+ set_http_connect(200, 404, 404, 404)
+ # acct cont cont cont
+ # make sure to use a fresh request without cached env
+ req = Request.blank('/v1/a/c/o',
+ environ={'REQUEST_METHOD': 'DELETE'})
+ resp = getattr(controller, 'DELETE')(req)
+ self.assertEqual(resp.status_int, 404)
+
+ set_http_connect(200, 503, 503, 503)
+ # acct cont cont cont
+ # make sure to use a fresh request without cached env
+ req = Request.blank('/v1/a/c/o',
+ environ={'REQUEST_METHOD': 'DELETE'})
+ resp = getattr(controller, 'DELETE')(req)
+ self.assertEqual(resp.status_int, 404)
+
+ for dev in self.app.account_ring.devs:
+ set_node_errors(
+ self.app, dev, self.app.error_suppression_limit + 1,
+ time.time())
+ set_http_connect(200)
+ # acct [isn't actually called since everything
+ # is error limited]
+ # make sure to use a fresh request without cached env
+ req = Request.blank('/v1/a/c/o',
+ environ={'REQUEST_METHOD': 'DELETE'})
+ resp = getattr(controller, 'DELETE')(req)
+ self.assertEqual(resp.status_int, 404)
+
+ for dev in self.app.account_ring.devs:
+ set_node_errors(self.app, dev, 0, last_error=None)
+ for dev in self.app.container_ring.devs:
+ set_node_errors(self.app, dev,
+ self.app.error_suppression_limit + 1,
+ time.time())
+ set_http_connect(200, 200)
+ # acct cont [isn't actually called since
+ # everything is error limited]
+ # make sure to use a fresh request without cached env
+ req = Request.blank('/v1/a/c/o',
+ environ={'REQUEST_METHOD': 'DELETE'})
+ resp = getattr(controller, 'DELETE')(req)
+ self.assertEqual(resp.status_int, 404)
+
+ def test_PUT_POST_requires_container_exist(self):
+ with save_globals():
+ self.app.object_post_as_copy = False
+ self.app.memcache = FakeMemcacheReturnsNone()
+ controller = ReplicatedObjectController(
+ self.app, 'account', 'container', 'object')
+
+ set_http_connect(200, 404, 404, 404, 200, 200, 200)
+ req = Request.blank('/v1/a/c/o',
+ environ={'REQUEST_METHOD': 'PUT'})
+ self.app.update_request(req)
+ resp = controller.PUT(req)
+ self.assertEqual(resp.status_int, 404)
+
+ set_http_connect(200, 404, 404, 404, 200, 200)
+ req = Request.blank('/v1/a/c/o',
+ environ={'REQUEST_METHOD': 'POST'},
+ headers={'Content-Type': 'text/plain'})
+ self.app.update_request(req)
+ resp = controller.POST(req)
+ self.assertEqual(resp.status_int, 404)
+
+ def test_bad_metadata(self):
+ with save_globals():
+ controller = ReplicatedObjectController(
+ self.app, 'account', 'container', 'object')
+ set_http_connect(200, 200, 201, 201, 201)
+ # acct cont obj obj obj
+ req = Request.blank('/v1/a/c/o', environ={'REQUEST_METHOD': 'PUT'},
+ headers={'Content-Length': '0'})
+ self.app.update_request(req)
+ resp = controller.PUT(req)
+ self.assertEqual(resp.status_int, 201)
+
+ set_http_connect(201, 201, 201)
+ req = Request.blank(
+ '/v1/a/c/o', environ={'REQUEST_METHOD': 'PUT'},
+ headers={'Content-Length': '0',
+ 'X-Object-Meta-' + (
+ 'a' * constraints.MAX_META_NAME_LENGTH): 'v'})
+ self.app.update_request(req)
+ resp = controller.PUT(req)
+ self.assertEqual(resp.status_int, 201)
+ set_http_connect(201, 201, 201)
+ req = Request.blank(
+ '/v1/a/c/o', environ={'REQUEST_METHOD': 'PUT'},
+ headers={
+ 'Content-Length': '0',
+ 'X-Object-Meta-' + (
+ 'a' * (constraints.MAX_META_NAME_LENGTH + 1)): 'v'})
+ self.app.update_request(req)
+ resp = controller.PUT(req)
+ self.assertEqual(resp.status_int, 400)
+
+ set_http_connect(201, 201, 201)
+ req = Request.blank('/v1/a/c/o', environ={'REQUEST_METHOD': 'PUT'},
+ headers={'Content-Length': '0',
+ 'X-Object-Meta-Too-Long': 'a' *
+ constraints.MAX_META_VALUE_LENGTH})
+ self.app.update_request(req)
+ resp = controller.PUT(req)
+ self.assertEqual(resp.status_int, 201)
+ set_http_connect(201, 201, 201)
+ req = Request.blank(
+ '/v1/a/c/o', environ={'REQUEST_METHOD': 'PUT'},
+ headers={'Content-Length': '0',
+ 'X-Object-Meta-Too-Long': 'a' *
+ (constraints.MAX_META_VALUE_LENGTH + 1)})
+ self.app.update_request(req)
+ resp = controller.PUT(req)
+ self.assertEqual(resp.status_int, 400)
+
+ set_http_connect(201, 201, 201)
+ headers = {'Content-Length': '0'}
+ for x in range(constraints.MAX_META_COUNT):
+ headers['X-Object-Meta-%d' % x] = 'v'
+ req = Request.blank('/v1/a/c/o', environ={'REQUEST_METHOD': 'PUT'},
+ headers=headers)
+ self.app.update_request(req)
+ resp = controller.PUT(req)
+ self.assertEqual(resp.status_int, 201)
+ set_http_connect(201, 201, 201)
+ headers = {'Content-Length': '0'}
+ for x in range(constraints.MAX_META_COUNT + 1):
+ headers['X-Object-Meta-%d' % x] = 'v'
+ req = Request.blank('/v1/a/c/o', environ={'REQUEST_METHOD': 'PUT'},
+ headers=headers)
+ self.app.update_request(req)
+ resp = controller.PUT(req)
+ self.assertEqual(resp.status_int, 400)
+
+ set_http_connect(201, 201, 201)
+ headers = {'Content-Length': '0'}
+ header_value = 'a' * constraints.MAX_META_VALUE_LENGTH
+ size = 0
+ x = 0
+ while size < constraints.MAX_META_OVERALL_SIZE - 4 - \
+ constraints.MAX_META_VALUE_LENGTH:
+ size += 4 + constraints.MAX_META_VALUE_LENGTH
+ headers['X-Object-Meta-%04d' % x] = header_value
+ x += 1
+ if constraints.MAX_META_OVERALL_SIZE - size > 1:
+ headers['X-Object-Meta-a'] = \
+ 'a' * (constraints.MAX_META_OVERALL_SIZE - size - 1)
+ req = Request.blank('/v1/a/c/o', environ={'REQUEST_METHOD': 'PUT'},
+ headers=headers)
+ self.app.update_request(req)
+ resp = controller.PUT(req)
+ self.assertEqual(resp.status_int, 201)
+ set_http_connect(201, 201, 201)
+ headers['X-Object-Meta-a'] = \
+ 'a' * (constraints.MAX_META_OVERALL_SIZE - size)
+ req = Request.blank('/v1/a/c/o', environ={'REQUEST_METHOD': 'PUT'},
+ headers=headers)
+ self.app.update_request(req)
+ resp = controller.PUT(req)
+ self.assertEqual(resp.status_int, 400)
+
+ @contextmanager
+ def controller_context(self, req, *args, **kwargs):
+ _v, account, container, obj = utils.split_path(req.path, 4, 4, True)
+ controller = ReplicatedObjectController(
+ self.app, account, container, obj)
+ self.app.update_request(req)
+ self.app.memcache.store = {}
+ with save_globals():
+ new_connect = set_http_connect(*args, **kwargs)
+ yield controller
+ unused_status_list = []
+ while True:
+ try:
+ unused_status_list.append(next(new_connect.code_iter))
+ except StopIteration:
+ break
+ if unused_status_list:
+ raise self.fail('UN-USED STATUS CODES: %r' %
+ unused_status_list)
+
+ def test_mismatched_etags(self):
+ with save_globals():
+ # no etag supplied, object servers return success w/ diff values
+ controller = ReplicatedObjectController(
+ self.app, 'account', 'container', 'object')
+ req = Request.blank('/v1/a/c/o', environ={'REQUEST_METHOD': 'PUT'},
+ headers={'Content-Length': '0'})
+ self.app.update_request(req)
+ set_http_connect(200, 201, 201, 201,
+ etags=[None,
+ '68b329da9893e34099c7d8ad5cb9c940',
+ '68b329da9893e34099c7d8ad5cb9c940',
+ '68b329da9893e34099c7d8ad5cb9c941'])
+ resp = controller.PUT(req)
+ self.assertEqual(resp.status_int // 100, 5) # server error
+
+ # req supplies etag, object servers return 422 - mismatch
+ headers = {'Content-Length': '0',
+ 'ETag': '68b329da9893e34099c7d8ad5cb9c940'}
+ req = Request.blank('/v1/a/c/o', environ={'REQUEST_METHOD': 'PUT'},
+ headers=headers)
+ self.app.update_request(req)
+ set_http_connect(200, 422, 422, 503,
+ etags=['68b329da9893e34099c7d8ad5cb9c940',
+ '68b329da9893e34099c7d8ad5cb9c941',
+ None,
+ None])
+ resp = controller.PUT(req)
+ self.assertEqual(resp.status_int // 100, 4) # client error
+
+ def test_response_get_accept_ranges_header(self):
+ with save_globals():
+ req = Request.blank('/v1/a/c/o', environ={'REQUEST_METHOD': 'GET'})
+ self.app.update_request(req)
+ controller = ReplicatedObjectController(
+ self.app, 'account', 'container', 'object')
+ set_http_connect(200, 200, 200)
+ resp = controller.GET(req)
+ self.assertIn('accept-ranges', resp.headers)
+ self.assertEqual(resp.headers['accept-ranges'], 'bytes')
+
+ def test_response_head_accept_ranges_header(self):
+ with save_globals():
+ req = Request.blank('/v1/a/c/o',
+ environ={'REQUEST_METHOD': 'HEAD'})
+ self.app.update_request(req)
+ controller = ReplicatedObjectController(
+ self.app, 'account', 'container', 'object')
+ set_http_connect(200, 200, 200)
+ resp = controller.HEAD(req)
+ self.assertIn('accept-ranges', resp.headers)
+ self.assertEqual(resp.headers['accept-ranges'], 'bytes')
+
+ def test_GET_calls_authorize(self):
+ called = [False]
+
+ def authorize(req):
+ called[0] = True
+ return HTTPUnauthorized(request=req)
+ with save_globals():
+ set_http_connect(200, 200, 201, 201, 201)
+ controller = ReplicatedObjectController(
+ self.app, 'account', 'container', 'object')
+ req = Request.blank('/v1/a/c/o')
+ req.environ['swift.authorize'] = authorize
+ self.app.update_request(req)
+ controller.GET(req)
+ self.assertTrue(called[0])
+
+ def test_HEAD_calls_authorize(self):
+ called = [False]
+
+ def authorize(req):
+ called[0] = True
+ return HTTPUnauthorized(request=req)
+ with save_globals():
+ set_http_connect(200, 200, 201, 201, 201)
+ controller = ReplicatedObjectController(
+ self.app, 'account', 'container', 'object')
+ req = Request.blank('/v1/a/c/o', {'REQUEST_METHOD': 'HEAD'})
+ req.environ['swift.authorize'] = authorize
+ self.app.update_request(req)
+ controller.HEAD(req)
+ self.assertTrue(called[0])
+
+ def test_POST_calls_authorize(self):
+ called = [False]
+
+ def authorize(req):
+ called[0] = True
+ return HTTPUnauthorized(request=req)
+ with save_globals():
+ self.app.object_post_as_copy = False
+ set_http_connect(200, 200, 201, 201, 201)
+ controller = ReplicatedObjectController(
+ self.app, 'account', 'container', 'object')
+ req = Request.blank('/v1/a/c/o',
+ environ={'REQUEST_METHOD': 'POST'},
+ headers={'Content-Length': '5'}, body='12345')
+ req.environ['swift.authorize'] = authorize
+ self.app.update_request(req)
+ controller.POST(req)
+ self.assertTrue(called[0])
+
+ def test_PUT_calls_authorize(self):
+ called = [False]
+
+ def authorize(req):
+ called[0] = True
+ return HTTPUnauthorized(request=req)
+ with save_globals():
+ set_http_connect(200, 200, 201, 201, 201)
+ controller = ReplicatedObjectController(
+ self.app, 'account', 'container', 'object')
+ req = Request.blank('/v1/a/c/o', environ={'REQUEST_METHOD': 'PUT'},
+ headers={'Content-Length': '5'}, body='12345')
+ req.environ['swift.authorize'] = authorize
+ self.app.update_request(req)
+ controller.PUT(req)
+ self.assertTrue(called[0])
+
+ def test_POST_converts_delete_after_to_delete_at(self):
+ with save_globals():
+ self.app.object_post_as_copy = False
+ controller = ReplicatedObjectController(
+ self.app, 'account', 'container', 'object')
+ set_http_connect(200, 200, 202, 202, 202)
+ self.app.memcache.store = {}
+ orig_time = time.time
+ try:
+ t = time.time()
+ time.time = lambda: t
+ req = Request.blank('/v1/a/c/o', {},
+ headers={'Content-Type': 'foo/bar',
+ 'X-Delete-After': '60'})
+ self.app.update_request(req)
+ res = controller.POST(req)
+ self.assertEqual(res.status, '202 Fake')
+ self.assertEqual(req.headers.get('x-delete-at'),
+ str(int(t + 60)))
+ finally:
+ time.time = orig_time
+
+ def test_OPTIONS(self):
+ with save_globals():
+ controller = ReplicatedObjectController(
+ self.app, 'a', 'c', 'o.jpg')
+
+ def my_empty_container_info(*args):
+ return {}
+ controller.container_info = my_empty_container_info
+ req = Request.blank(
+ '/v1/a/c/o.jpg',
+ {'REQUEST_METHOD': 'OPTIONS'},
+ headers={'Origin': 'http://foo.com',
+ 'Access-Control-Request-Method': 'GET'})
+ resp = controller.OPTIONS(req)
+ self.assertEqual(401, resp.status_int)
+
+ def my_empty_origin_container_info(*args):
+ return {'cors': {'allow_origin': None}}
+ controller.container_info = my_empty_origin_container_info
+ req = Request.blank(
+ '/v1/a/c/o.jpg',
+ {'REQUEST_METHOD': 'OPTIONS'},
+ headers={'Origin': 'http://foo.com',
+ 'Access-Control-Request-Method': 'GET'})
+ resp = controller.OPTIONS(req)
+ self.assertEqual(401, resp.status_int)
+
+ def my_container_info(*args):
+ return {
+ 'cors': {
+ 'allow_origin': 'http://foo.bar:8080 https://foo.bar',
+ 'max_age': '999',
+ }
+ }
+ controller.container_info = my_container_info
+ req = Request.blank(
+ '/v1/a/c/o.jpg',
+ {'REQUEST_METHOD': 'OPTIONS'},
+ headers={'Origin': 'https://foo.bar',
+ 'Access-Control-Request-Method': 'GET'})
+ req.content_length = 0
+ resp = controller.OPTIONS(req)
+ self.assertEqual(200, resp.status_int)
+ self.assertEqual(
+ 'https://foo.bar',
+ resp.headers['access-control-allow-origin'])
+ for verb in 'OPTIONS GET POST PUT DELETE HEAD'.split():
+ self.assertIn(verb,
+ resp.headers['access-control-allow-methods'])
+ self.assertEqual(
+ len(resp.headers['access-control-allow-methods'].split(', ')),
+ 6)
+ self.assertEqual('999', resp.headers['access-control-max-age'])
+ req = Request.blank(
+ '/v1/a/c/o.jpg',
+ {'REQUEST_METHOD': 'OPTIONS'},
+ headers={'Origin': 'https://foo.bar'})
+ req.content_length = 0
+ resp = controller.OPTIONS(req)
+ self.assertEqual(401, resp.status_int)
+ req = Request.blank('/v1/a/c/o.jpg', {'REQUEST_METHOD': 'OPTIONS'})
+ req.content_length = 0
+ resp = controller.OPTIONS(req)
+ self.assertEqual(200, resp.status_int)
+ for verb in 'OPTIONS GET POST PUT DELETE HEAD'.split():
+ self.assertIn(verb, resp.headers['Allow'])
+ self.assertEqual(len(resp.headers['Allow'].split(', ')), 6)
+ req = Request.blank(
+ '/v1/a/c/o.jpg',
+ {'REQUEST_METHOD': 'OPTIONS'},
+ headers={'Origin': 'http://foo.com'})
+ resp = controller.OPTIONS(req)
+ self.assertEqual(401, resp.status_int)
+ req = Request.blank(
+ '/v1/a/c/o.jpg',
+ {'REQUEST_METHOD': 'OPTIONS'},
+ headers={'Origin': 'http://foo.bar',
+ 'Access-Control-Request-Method': 'GET'})
+ controller.app.cors_allow_origin = ['http://foo.bar', ]
+ resp = controller.OPTIONS(req)
+ self.assertEqual(200, resp.status_int)
+
+ def my_container_info_wildcard(*args):
+ return {
+ 'cors': {
+ 'allow_origin': '*',
+ 'max_age': '999',
+ }
+ }
+ controller.container_info = my_container_info_wildcard
+ req = Request.blank(
+ '/v1/a/c/o.jpg',
+ {'REQUEST_METHOD': 'OPTIONS'},
+ headers={'Origin': 'https://bar.baz',
+ 'Access-Control-Request-Method': 'GET'})
+ req.content_length = 0
+ resp = controller.OPTIONS(req)
+ self.assertEqual(200, resp.status_int)
+ self.assertEqual('*', resp.headers['access-control-allow-origin'])
+ for verb in 'OPTIONS GET POST PUT DELETE HEAD'.split():
+ self.assertIn(verb,
+ resp.headers['access-control-allow-methods'])
+ self.assertEqual(
+ len(resp.headers['access-control-allow-methods'].split(', ')),
+ 6)
+ self.assertEqual('999', resp.headers['access-control-max-age'])
+
+ def _get_CORS_response(self, container_cors, strict_mode, object_get=None):
+ with save_globals():
+ controller = ReplicatedObjectController(
+ self.app, 'a', 'c', 'o')
+
+ def stubContainerInfo(*args):
+ return {
+ 'cors': container_cors
+ }
+
+ controller.container_info = stubContainerInfo
+ controller.app.strict_cors_mode = strict_mode
+
+ def objectGET(controller, req):
+ return Response(headers={
+ 'X-Object-Meta-Color': 'red',
+ 'X-Super-Secret': 'hush',
+ })
+
+ mock_object_get = object_get or objectGET
+
+ req = Request.blank(
+ '/v1/a/c/o.jpg',
+ {'REQUEST_METHOD': 'GET'},
+ headers={'Origin': 'http://foo.bar'})
+
+ resp = cors_validation(mock_object_get)(controller, req)
+
+ return resp
+
+ def test_CORS_valid_non_strict(self):
+ # test expose_headers to non-allowed origins
+ container_cors = {'allow_origin': 'http://not.foo.bar',
+ 'expose_headers': 'X-Object-Meta-Color '
+ 'X-Object-Meta-Color-Ex'}
+ resp = self._get_CORS_response(
+ container_cors=container_cors, strict_mode=False)
+
+ self.assertEqual(200, resp.status_int)
+ self.assertEqual('http://foo.bar',
+ resp.headers['access-control-allow-origin'])
+ self.assertEqual('red', resp.headers['x-object-meta-color'])
+ # X-Super-Secret is in the response, but not "exposed"
+ self.assertEqual('hush', resp.headers['x-super-secret'])
+ self.assertIn('access-control-expose-headers', resp.headers)
+ exposed = set(
+ h.strip() for h in
+ resp.headers['access-control-expose-headers'].split(','))
+ expected_exposed = set([
+ 'cache-control', 'content-language', 'content-type', 'expires',
+ 'last-modified', 'pragma', 'etag', 'x-timestamp', 'x-trans-id',
+ 'x-openstack-request-id', 'x-object-meta-color',
+ 'x-object-meta-color-ex'])
+ self.assertEqual(expected_exposed, exposed)
+
+ # test allow_origin *
+ container_cors = {'allow_origin': '*'}
+
+ resp = self._get_CORS_response(
+ container_cors=container_cors, strict_mode=False)
+ self.assertEqual(200, resp.status_int)
+ self.assertEqual('*',
+ resp.headers['access-control-allow-origin'])
+
+ # test allow_origin empty
+ container_cors = {'allow_origin': ''}
+ resp = self._get_CORS_response(
+ container_cors=container_cors, strict_mode=False)
+ self.assertEqual(200, resp.status_int)
+ self.assertEqual('http://foo.bar',
+ resp.headers['access-control-allow-origin'])
+
+ def test_CORS_valid_strict(self):
+ # test expose_headers to non-allowed origins
+ container_cors = {'allow_origin': 'http://not.foo.bar',
+ 'expose_headers': 'X-Object-Meta-Color '
+ 'X-Object-Meta-Color-Ex'}
+ resp = self._get_CORS_response(
+ container_cors=container_cors, strict_mode=True)
+
+ self.assertEqual(200, resp.status_int)
+ self.assertNotIn('access-control-expose-headers', resp.headers)
+ self.assertNotIn('access-control-allow-origin', resp.headers)
+
+ # test allow_origin *
+ container_cors = {'allow_origin': '*'}
+
+ resp = self._get_CORS_response(
+ container_cors=container_cors, strict_mode=True)
+ self.assertEqual(200, resp.status_int)
+ self.assertEqual('*',
+ resp.headers['access-control-allow-origin'])
+ self.assertEqual('red', resp.headers['x-object-meta-color'])
+ # X-Super-Secret is in the response, but not "exposed"
+ self.assertEqual('hush', resp.headers['x-super-secret'])
+ self.assertIn('access-control-expose-headers', resp.headers)
+ exposed = set(
+ h.strip() for h in
+ resp.headers['access-control-expose-headers'].split(','))
+ expected_exposed = set([
+ 'cache-control', 'content-language', 'content-type', 'expires',
+ 'last-modified', 'pragma', 'etag', 'x-timestamp', 'x-trans-id',
+ 'x-openstack-request-id', 'x-object-meta-color'])
+ self.assertEqual(expected_exposed, exposed)
+
+ # test allow_origin empty
+ container_cors = {'allow_origin': ''}
+ resp = self._get_CORS_response(
+ container_cors=container_cors, strict_mode=True)
+ self.assertNotIn('access-control-expose-headers', resp.headers)
+ self.assertNotIn('access-control-allow-origin', resp.headers)
+
+ def test_CORS_valid_with_obj_headers(self):
+ container_cors = {'allow_origin': 'http://foo.bar'}
+
+ 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'
+ })
+
+ resp = self._get_CORS_response(
+ container_cors=container_cors, strict_mode=True,
+ object_get=objectGET)
+
+ self.assertEqual(200, resp.status_int)
+ self.assertEqual('http://obj.origin',
+ resp.headers['access-control-allow-origin'])
+ self.assertEqual('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',
+ 'X-Container-Host',
+ 'X-Container-Partition'])
+ seen_headers = []
+
+ def capture_headers(ipaddr, port, device, partition, method,
+ path, headers=None, query_string=None):
+ captured = {}
+ for header in header_list:
+ captured[header] = headers.get(header)
+ seen_headers.append(captured)
+
+ with save_globals():
+ self.app.allow_account_management = True
+
+ set_http_connect(*connect_args, give_connect=capture_headers,
+ **kwargs)
+ resp = controller_call(req)
+ self.assertEqual(2, resp.status_int // 100) # sanity check
+
+ # don't care about the account/container HEADs, so chuck
+ # the first two requests
+ return sorted(seen_headers[2:],
+ key=lambda d: d.get(header_list[0]) or 'z')
+
+ def test_PUT_x_container_headers_with_equal_replicas(self):
+ req = Request.blank('/v1/a/c/o', environ={'REQUEST_METHOD': 'PUT'},
+ headers={'Content-Length': '5'}, body='12345')
+ controller = ReplicatedObjectController(
+ self.app, 'a', 'c', 'o')
+ seen_headers = self._gather_x_container_headers(
+ controller.PUT, req,
+ 200, 200, 201, 201, 201) # HEAD HEAD PUT PUT PUT
+ self.assertEqual(
+ seen_headers, [
+ {'X-Container-Host': '10.0.0.0:1000',
+ 'X-Container-Partition': '0',
+ 'X-Container-Device': 'sda'},
+ {'X-Container-Host': '10.0.0.1:1001',
+ 'X-Container-Partition': '0',
+ 'X-Container-Device': 'sdb'},
+ {'X-Container-Host': '10.0.0.2:1002',
+ 'X-Container-Partition': '0',
+ 'X-Container-Device': 'sdc'}])
+
+ def test_PUT_x_container_headers_with_fewer_container_replicas(self):
+ self.app.container_ring.set_replicas(2)
+
+ req = Request.blank('/v1/a/c/o', environ={'REQUEST_METHOD': 'PUT'},
+ headers={'Content-Length': '5'}, body='12345')
+ controller = ReplicatedObjectController(
+ self.app, 'a', 'c', 'o')
+ seen_headers = self._gather_x_container_headers(
+ controller.PUT, req,
+ 200, 200, 201, 201, 201) # HEAD HEAD PUT PUT PUT
+
+ self.assertEqual(
+ seen_headers, [
+ {'X-Container-Host': '10.0.0.0:1000',
+ 'X-Container-Partition': '0',
+ 'X-Container-Device': 'sda'},
+ {'X-Container-Host': '10.0.0.0:1000',
+ 'X-Container-Partition': '0',
+ 'X-Container-Device': 'sda'},
+ {'X-Container-Host': '10.0.0.1:1001',
+ 'X-Container-Partition': '0',
+ 'X-Container-Device': 'sdb'}])
+
+ def test_PUT_x_container_headers_with_more_container_replicas(self):
+ self.app.container_ring.set_replicas(4)
+
+ req = Request.blank('/v1/a/c/o', environ={'REQUEST_METHOD': 'PUT'},
+ headers={'Content-Length': '5'}, body='12345')
+ controller = ReplicatedObjectController(
+ self.app, 'a', 'c', 'o')
+ seen_headers = self._gather_x_container_headers(
+ controller.PUT, req,
+ 200, 200, 201, 201, 201) # HEAD HEAD PUT PUT PUT
+
+ self.assertEqual(
+ seen_headers, [
+ {'X-Container-Host': '10.0.0.0:1000,10.0.0.3:1003',
+ 'X-Container-Partition': '0',
+ 'X-Container-Device': 'sda,sdd'},
+ {'X-Container-Host': '10.0.0.1:1001',
+ 'X-Container-Partition': '0',
+ 'X-Container-Device': 'sdb'},
+ {'X-Container-Host': '10.0.0.2:1002',
+ 'X-Container-Partition': '0',
+ 'X-Container-Device': 'sdc'}])
+
+ def test_POST_x_container_headers_with_more_container_replicas(self):
+ self.app.container_ring.set_replicas(4)
+ self.app.object_post_as_copy = False
+
+ req = Request.blank('/v1/a/c/o',
+ environ={'REQUEST_METHOD': 'POST'},
+ headers={'Content-Type': 'application/stuff'})
+ controller = ReplicatedObjectController(
+ self.app, 'a', 'c', 'o')
+ seen_headers = self._gather_x_container_headers(
+ controller.POST, req,
+ 200, 200, 200, 200, 200) # HEAD HEAD POST POST POST
+
+ self.assertEqual(
+ seen_headers, [
+ {'X-Container-Host': '10.0.0.0:1000,10.0.0.3:1003',
+ 'X-Container-Partition': '0',
+ 'X-Container-Device': 'sda,sdd'},
+ {'X-Container-Host': '10.0.0.1:1001',
+ 'X-Container-Partition': '0',
+ 'X-Container-Device': 'sdb'},
+ {'X-Container-Host': '10.0.0.2:1002',
+ 'X-Container-Partition': '0',
+ 'X-Container-Device': 'sdc'}])
+
+ def test_DELETE_x_container_headers_with_more_container_replicas(self):
+ self.app.container_ring.set_replicas(4)
+
+ req = Request.blank('/v1/a/c/o',
+ environ={'REQUEST_METHOD': 'DELETE'},
+ headers={'Content-Type': 'application/stuff'})
+ controller = ReplicatedObjectController(
+ self.app, 'a', 'c', 'o')
+ seen_headers = self._gather_x_container_headers(
+ controller.DELETE, req,
+ 200, 200, 200, 200, 200) # HEAD HEAD DELETE DELETE DELETE
+
+ self.assertEqual(seen_headers, [
+ {'X-Container-Host': '10.0.0.0:1000,10.0.0.3:1003',
+ 'X-Container-Partition': '0',
+ 'X-Container-Device': 'sda,sdd'},
+ {'X-Container-Host': '10.0.0.1:1001',
+ 'X-Container-Partition': '0',
+ 'X-Container-Device': 'sdb'},
+ {'X-Container-Host': '10.0.0.2:1002',
+ 'X-Container-Partition': '0',
+ 'X-Container-Device': 'sdc'}
+ ])
+
+ @mock.patch('time.time', new=lambda: STATIC_TIME)
+ def test_PUT_x_delete_at_with_fewer_container_replicas(self):
+ self.app.container_ring.set_replicas(2)
+
+ delete_at_timestamp = int(time.time()) + 100000
+ delete_at_container = utils.get_expirer_container(
+ delete_at_timestamp, self.app.expiring_objects_container_divisor,
+ 'a', 'c', 'o')
+ req = Request.blank('/v1/a/c/o', environ={'REQUEST_METHOD': 'PUT'},
+ headers={'Content-Type': 'application/stuff',
+ 'Content-Length': '0',
+ 'X-Delete-At': str(delete_at_timestamp)})
+ controller = ReplicatedObjectController(
+ self.app, 'a', 'c', 'o')
+ seen_headers = self._gather_x_container_headers(
+ controller.PUT, req,
+ 200, 200, 201, 201, 201, # HEAD HEAD PUT PUT PUT
+ header_list=('X-Delete-At-Host', 'X-Delete-At-Device',
+ 'X-Delete-At-Partition', 'X-Delete-At-Container'))
+
+ self.assertEqual(seen_headers, [
+ {'X-Delete-At-Host': '10.0.0.0:1000',
+ 'X-Delete-At-Container': delete_at_container,
+ 'X-Delete-At-Partition': '0',
+ 'X-Delete-At-Device': 'sda'},
+ {'X-Delete-At-Host': '10.0.0.1:1001',
+ 'X-Delete-At-Container': delete_at_container,
+ 'X-Delete-At-Partition': '0',
+ 'X-Delete-At-Device': 'sdb'},
+ {'X-Delete-At-Host': None,
+ 'X-Delete-At-Container': None,
+ 'X-Delete-At-Partition': None,
+ 'X-Delete-At-Device': None}
+ ])
+
+ @mock.patch('time.time', new=lambda: STATIC_TIME)
+ def test_PUT_x_delete_at_with_more_container_replicas(self):
+ self.app.container_ring.set_replicas(4)
+ self.app.expiring_objects_account = 'expires'
+ self.app.expiring_objects_container_divisor = 60
+
+ delete_at_timestamp = int(time.time()) + 100000
+ delete_at_container = utils.get_expirer_container(
+ delete_at_timestamp, self.app.expiring_objects_container_divisor,
+ 'a', 'c', 'o')
+ req = Request.blank('/v1/a/c/o', environ={'REQUEST_METHOD': 'PUT'},
+ headers={'Content-Type': 'application/stuff',
+ 'Content-Length': 0,
+ 'X-Delete-At': str(delete_at_timestamp)})
+ controller = ReplicatedObjectController(
+ self.app, 'a', 'c', 'o')
+ seen_headers = self._gather_x_container_headers(
+ controller.PUT, req,
+ 200, 200, 201, 201, 201, # HEAD HEAD PUT PUT PUT
+ header_list=('X-Delete-At-Host', 'X-Delete-At-Device',
+ 'X-Delete-At-Partition', 'X-Delete-At-Container'))
+ self.assertEqual(seen_headers, [
+ {'X-Delete-At-Host': '10.0.0.0:1000,10.0.0.3:1003',
+ 'X-Delete-At-Container': delete_at_container,
+ 'X-Delete-At-Partition': '0',
+ 'X-Delete-At-Device': 'sda,sdd'},
+ {'X-Delete-At-Host': '10.0.0.1:1001',
+ 'X-Delete-At-Container': delete_at_container,
+ 'X-Delete-At-Partition': '0',
+ 'X-Delete-At-Device': 'sdb'},
+ {'X-Delete-At-Host': '10.0.0.2:1002',
+ 'X-Delete-At-Container': delete_at_container,
+ 'X-Delete-At-Partition': '0',
+ 'X-Delete-At-Device': 'sdc'}
+ ])
+
+
if __name__ == '__main__':
+ setup()
unittest.main()
diff --git a/test/unit/proxy/test_mem_server.py b/test/unit/proxy/test_mem_server.py
index 2221ee926eaa..b0dff95ddbce 100644
--- a/test/unit/proxy/test_mem_server.py
+++ b/test/unit/proxy/test_mem_server.py
@@ -17,6 +17,8 @@ import unittest
from test.unit.proxy import test_server
from test.unit.proxy.test_server import teardown
+from test.unit.proxy.controllers import test_account
+from test.unit.proxy.controllers import test_container
from swift.obj import mem_server
@@ -55,16 +57,16 @@ class TestObjectController(test_server.TestObjectController):
pass
-class TestContainerController(test_server.TestContainerController):
+class TestContainerController(test_container.TestContainerController):
pass
-class TestAccountController(test_server.TestAccountController):
+class TestAccountController(test_account.TestAccountController):
pass
class TestAccountControllerFakeGetResponse(
- test_server.TestAccountControllerFakeGetResponse):
+ test_account.TestAccountControllerFakeGetResponse):
pass
diff --git a/test/unit/proxy/test_server.py b/test/unit/proxy/test_server.py
index 08e9e2abd3e5..89560416bf0a 100644
--- a/test/unit/proxy/test_server.py
+++ b/test/unit/proxy/test_server.py
@@ -37,24 +37,23 @@ import operator
import functools
from swift.obj import diskfile
import re
-import random
from collections import defaultdict
import uuid
import mock
from eventlet import sleep, spawn, wsgi, listen, Timeout, debug
from eventlet.green import httplib
-from six import BytesIO
from six import StringIO
from six.moves import range
from six.moves.urllib.parse import quote
-from swift.common.utils import hash_path, storage_directory, \
- parse_content_type, parse_mime_headers, \
- iter_multipart_mime_documents, public
+from swift.common.utils import (
+ hash_path, storage_directory,
+ parse_content_type, parse_mime_headers, iter_multipart_mime_documents,
+ public)
from test.unit import (
- connect_tcp, readuntil2crlfs, FakeLogger, fake_http_connect, FakeRing,
+ connect_tcp, readuntil2crlfs, FakeLogger, FakeRing,
FakeMemcache, debug_logger, patch_policies, write_fake_ring,
mocked_http_conn, DEFAULT_TEST_EC_TYPE, make_timestamp_iter)
from swift.proxy import server as proxy_server
@@ -62,7 +61,6 @@ from swift.proxy.controllers.obj import ReplicatedObjectController
from swift.obj import server as object_server
from swift.common.middleware import proxy_logging, versioned_writes, \
copy
-from swift.common.middleware.acl import parse_acl, format_acl
from swift.common.exceptions import ChunkReadTimeout, DiskFileNotExist, \
APIVersionError, ChunkWriteTimeout
from swift.common import utils, constraints
@@ -78,9 +76,11 @@ from swift.common.swob import Request, Response, HTTPUnauthorized, \
HTTPException, HTTPBadRequest
from swift.common.storage_policy import StoragePolicy, POLICIES
import swift.common.request_helpers
-from swift.common.request_helpers import get_sys_meta_prefix
from test.unit.helpers import setup_servers, teardown_servers
+from test.unit.proxy import (
+ save_globals, set_http_connect, FakeMemcacheReturnsNone)
+
# mocks
logging.getLogger().addHandler(logging.StreamHandler(sys.stdout))
@@ -168,54 +168,6 @@ def set_node_errors(proxy_app, ring_node, value, last_error):
stats['last_error'] = last_error
-class FakeMemcacheReturnsNone(FakeMemcache):
-
- def get(self, key):
- # Returns None as the timestamp of the container; assumes we're only
- # using the FakeMemcache for container existence checks.
- return None
-
-
-@contextmanager
-def save_globals():
- orig_http_connect = getattr(swift.proxy.controllers.base, 'http_connect',
- None)
- orig_account_info = getattr(swift.proxy.controllers.Controller,
- 'account_info', None)
- orig_container_info = getattr(swift.proxy.controllers.Controller,
- 'container_info', None)
-
- try:
- yield True
- finally:
- swift.proxy.controllers.Controller.account_info = orig_account_info
- swift.proxy.controllers.base.http_connect = orig_http_connect
- swift.proxy.controllers.obj.http_connect = orig_http_connect
- swift.proxy.controllers.account.http_connect = orig_http_connect
- swift.proxy.controllers.container.http_connect = orig_http_connect
- swift.proxy.controllers.Controller.container_info = orig_container_info
-
-
-def set_http_connect(*args, **kwargs):
- new_connect = fake_http_connect(*args, **kwargs)
- swift.proxy.controllers.base.http_connect = new_connect
- 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
-
-
-def _make_callback_func(calls):
- def callback(ipaddr, port, device, partition, method, path,
- headers=None, query_string=None, ssl=False):
- context = {}
- context['method'] = method
- context['path'] = path
- context['headers'] = headers or {}
- calls.append(context)
- return callback
-
-
def _limit_max_file_size(f):
"""
This will limit constraints.MAX_FILE_SIZE for the duration of the
@@ -1217,174 +1169,6 @@ class TestObjectController(unittest.TestCase):
break
@unpatch_policies
- def test_policy_IO(self):
- def check_file(policy, cont, devs, check_val):
- partition, nodes = policy.object_ring.get_nodes('a', cont, 'o')
- conf = {'devices': _testdir, 'mount_check': 'false'}
- df_mgr = diskfile.DiskFileManager(conf, FakeLogger())
- for dev in devs:
- file = df_mgr.get_diskfile(dev, partition, 'a',
- cont, 'o',
- policy=policy)
- if check_val is True:
- file.open()
-
- prolis = _test_sockets[0]
- prosrv = _test_servers[0]
-
- # check policy 0: put file on c, read it back, check loc on disk
- sock = connect_tcp(('localhost', prolis.getsockname()[1]))
- fd = sock.makefile()
- obj = 'test_object0'
- path = '/v1/a/c/o'
- fd.write('PUT %s HTTP/1.1\r\n'
- 'Host: localhost\r\n'
- 'Connection: close\r\n'
- 'X-Storage-Token: t\r\n'
- 'Content-Length: %s\r\n'
- 'Content-Type: text/plain\r\n'
- '\r\n%s' % (path, str(len(obj)), obj))
- fd.flush()
- headers = readuntil2crlfs(fd)
- exp = 'HTTP/1.1 201'
- self.assertEqual(headers[:len(exp)], exp)
- req = Request.blank(path,
- environ={'REQUEST_METHOD': 'GET'},
- headers={'Content-Type':
- 'text/plain'})
- res = req.get_response(prosrv)
- self.assertEqual(res.status_int, 200)
- self.assertEqual(res.body, obj)
-
- check_file(POLICIES[0], 'c', ['sda1', 'sdb1'], True)
- check_file(POLICIES[0], 'c', ['sdc1', 'sdd1', 'sde1', 'sdf1'], False)
-
- # check policy 1: put file on c1, read it back, check loc on disk
- sock = connect_tcp(('localhost', prolis.getsockname()[1]))
- fd = sock.makefile()
- path = '/v1/a/c1/o'
- obj = 'test_object1'
- fd.write('PUT %s HTTP/1.1\r\n'
- 'Host: localhost\r\n'
- 'Connection: close\r\n'
- 'X-Storage-Token: t\r\n'
- 'Content-Length: %s\r\n'
- 'Content-Type: text/plain\r\n'
- '\r\n%s' % (path, str(len(obj)), obj))
- fd.flush()
- headers = readuntil2crlfs(fd)
- self.assertEqual(headers[:len(exp)], exp)
- req = Request.blank(path,
- environ={'REQUEST_METHOD': 'GET'},
- headers={'Content-Type':
- 'text/plain'})
- res = req.get_response(prosrv)
- self.assertEqual(res.status_int, 200)
- self.assertEqual(res.body, obj)
-
- check_file(POLICIES[1], 'c1', ['sdc1', 'sdd1'], True)
- check_file(POLICIES[1], 'c1', ['sda1', 'sdb1', 'sde1', 'sdf1'], False)
-
- # check policy 2: put file on c2, read it back, check loc on disk
- sock = connect_tcp(('localhost', prolis.getsockname()[1]))
- fd = sock.makefile()
- path = '/v1/a/c2/o'
- obj = 'test_object2'
- fd.write('PUT %s HTTP/1.1\r\n'
- 'Host: localhost\r\n'
- 'Connection: close\r\n'
- 'X-Storage-Token: t\r\n'
- 'Content-Length: %s\r\n'
- 'Content-Type: text/plain\r\n'
- '\r\n%s' % (path, str(len(obj)), obj))
- fd.flush()
- headers = readuntil2crlfs(fd)
- self.assertEqual(headers[:len(exp)], exp)
- req = Request.blank(path,
- environ={'REQUEST_METHOD': 'GET'},
- headers={'Content-Type':
- 'text/plain'})
- res = req.get_response(prosrv)
- self.assertEqual(res.status_int, 200)
- self.assertEqual(res.body, obj)
-
- check_file(POLICIES[2], 'c2', ['sde1', 'sdf1'], True)
- check_file(POLICIES[2], 'c2', ['sda1', 'sdb1', 'sdc1', 'sdd1'], False)
-
- @unpatch_policies
- def test_policy_IO_override(self):
- if hasattr(_test_servers[-1], '_filesystem'):
- # ironically, the _filesystem attribute on the object server means
- # the in-memory diskfile is in use, so this test does not apply
- return
-
- prosrv = _test_servers[0]
-
- # validate container policy is 1
- req = Request.blank('/v1/a/c1', method='HEAD')
- res = req.get_response(prosrv)
- self.assertEqual(res.status_int, 204) # sanity check
- self.assertEqual(POLICIES[1].name, res.headers['x-storage-policy'])
-
- # check overrides: put it in policy 2 (not where the container says)
- req = Request.blank(
- '/v1/a/c1/wrong-o',
- environ={'REQUEST_METHOD': 'PUT',
- 'wsgi.input': BytesIO(b"hello")},
- headers={'Content-Type': 'text/plain',
- 'Content-Length': '5',
- 'X-Backend-Storage-Policy-Index': '2'})
- res = req.get_response(prosrv)
- self.assertEqual(res.status_int, 201) # sanity check
-
- # go to disk to make sure it's there
- partition, nodes = prosrv.get_object_ring(2).get_nodes(
- 'a', 'c1', 'wrong-o')
- node = nodes[0]
- conf = {'devices': _testdir, 'mount_check': 'false'}
- df_mgr = diskfile.DiskFileManager(conf, FakeLogger())
- df = df_mgr.get_diskfile(node['device'], partition, 'a',
- 'c1', 'wrong-o', policy=POLICIES[2])
- with df.open():
- contents = ''.join(df.reader())
- self.assertEqual(contents, "hello")
-
- # can't get it from the normal place
- req = Request.blank('/v1/a/c1/wrong-o',
- environ={'REQUEST_METHOD': 'GET'},
- headers={'Content-Type': 'text/plain'})
- res = req.get_response(prosrv)
- self.assertEqual(res.status_int, 404) # sanity check
-
- # but we can get it from policy 2
- req = Request.blank('/v1/a/c1/wrong-o',
- environ={'REQUEST_METHOD': 'GET'},
- headers={'Content-Type': 'text/plain',
- 'X-Backend-Storage-Policy-Index': '2'})
-
- res = req.get_response(prosrv)
- self.assertEqual(res.status_int, 200)
- self.assertEqual(res.body, 'hello')
-
- # and we can delete it the same way
- req = Request.blank('/v1/a/c1/wrong-o',
- environ={'REQUEST_METHOD': 'DELETE'},
- headers={'Content-Type': 'text/plain',
- 'X-Backend-Storage-Policy-Index': '2'})
-
- res = req.get_response(prosrv)
- self.assertEqual(res.status_int, 204)
-
- df = df_mgr.get_diskfile(node['device'], partition, 'a',
- 'c1', 'wrong-o', policy=POLICIES[2])
- try:
- df.open()
- except DiskFileNotExist as e:
- self.assertGreater(float(e.timestamp), 0)
- else:
- self.fail('did not raise DiskFileNotExist')
-
- @unpatch_policies
def test_GET_newest_large_file(self):
prolis = _test_sockets[0]
prosrv = _test_servers[0]
@@ -6460,1960 +6244,6 @@ class TestObjectECRangedGET(unittest.TestCase):
self.assertEqual(second_byterange.get_payload(), self.obj[4090:5011])
-@patch_policies([
- StoragePolicy(0, 'zero', True, object_ring=FakeRing(base_port=3000)),
- StoragePolicy(1, 'one', False, object_ring=FakeRing(base_port=3000)),
- StoragePolicy(2, 'two', False, True, object_ring=FakeRing(base_port=3000))
-])
-class TestContainerController(unittest.TestCase):
- "Test swift.proxy_server.ContainerController"
-
- def setUp(self):
- self.app = proxy_server.Application(
- None, FakeMemcache(),
- account_ring=FakeRing(),
- container_ring=FakeRing(base_port=2000),
- logger=debug_logger())
-
- def test_convert_policy_to_index(self):
- controller = swift.proxy.controllers.ContainerController(self.app,
- 'a', 'c')
- expected = {
- 'zero': 0,
- 'ZeRo': 0,
- 'one': 1,
- 'OnE': 1,
- }
- for name, index in expected.items():
- req = Request.blank('/a/c', headers={'Content-Length': '0',
- 'Content-Type': 'text/plain',
- 'X-Storage-Policy': name})
- self.assertEqual(controller._convert_policy_to_index(req), index)
- # default test
- req = Request.blank('/a/c', headers={'Content-Length': '0',
- 'Content-Type': 'text/plain'})
- self.assertIsNone(controller._convert_policy_to_index(req))
- # negative test
- req = Request.blank('/a/c',
- headers={'Content-Length': '0',
- 'Content-Type': 'text/plain',
- 'X-Storage-Policy': 'nada'})
- self.assertRaises(HTTPException, controller._convert_policy_to_index,
- req)
- # storage policy two is deprecated
- req = Request.blank('/a/c', headers={'Content-Length': '0',
- 'Content-Type': 'text/plain',
- 'X-Storage-Policy': 'two'})
- self.assertRaises(HTTPException, controller._convert_policy_to_index,
- req)
-
- def test_convert_index_to_name(self):
- policy = random.choice(list(POLICIES))
- req = Request.blank('/v1/a/c')
- with mocked_http_conn(
- 200, 200,
- headers={'X-Backend-Storage-Policy-Index': int(policy)},
- ) as fake_conn:
- resp = req.get_response(self.app)
- self.assertRaises(StopIteration, fake_conn.code_iter.next)
- self.assertEqual(resp.status_int, 200)
- self.assertEqual(resp.headers['X-Storage-Policy'], policy.name)
-
- def test_no_convert_index_to_name_when_container_not_found(self):
- policy = random.choice(list(POLICIES))
- req = Request.blank('/v1/a/c')
- with mocked_http_conn(
- 200, 404, 404, 404,
- headers={'X-Backend-Storage-Policy-Index':
- int(policy)}) as fake_conn:
- resp = req.get_response(self.app)
- self.assertRaises(StopIteration, fake_conn.code_iter.next)
- self.assertEqual(resp.status_int, 404)
- self.assertIsNone(resp.headers['X-Storage-Policy'])
-
- def test_error_convert_index_to_name(self):
- req = Request.blank('/v1/a/c')
- with mocked_http_conn(
- 200, 200,
- headers={'X-Backend-Storage-Policy-Index': '-1'}) as fake_conn:
- resp = req.get_response(self.app)
- self.assertRaises(StopIteration, fake_conn.code_iter.next)
- self.assertEqual(resp.status_int, 200)
- self.assertIsNone(resp.headers['X-Storage-Policy'])
- error_lines = self.app.logger.get_lines_for_level('error')
- self.assertEqual(2, len(error_lines))
- for msg in error_lines:
- expected = "Could not translate " \
- "X-Backend-Storage-Policy-Index ('-1')"
- self.assertIn(expected, msg)
-
- def test_transfer_headers(self):
- src_headers = {'x-remove-versions-location': 'x',
- 'x-container-read': '*:user',
- 'x-remove-container-sync-key': 'x'}
- dst_headers = {'x-versions-location': 'backup'}
- controller = swift.proxy.controllers.ContainerController(self.app,
- 'a', 'c')
- controller.transfer_headers(src_headers, dst_headers)
- expected_headers = {'x-versions-location': '',
- 'x-container-read': '*:user',
- 'x-container-sync-key': ''}
- self.assertEqual(dst_headers, expected_headers)
-
- def assert_status_map(self, method, statuses, expected,
- raise_exc=False, missing_container=False):
- with save_globals():
- kwargs = {}
- if raise_exc:
- kwargs['raise_exc'] = raise_exc
- kwargs['missing_container'] = missing_container
- set_http_connect(*statuses, **kwargs)
- self.app.memcache.store = {}
- req = Request.blank('/v1/a/c', headers={'Content-Length': '0',
- 'Content-Type': 'text/plain'})
- self.app.update_request(req)
- res = method(req)
- self.assertEqual(res.status_int, expected)
- set_http_connect(*statuses, **kwargs)
- self.app.memcache.store = {}
- req = Request.blank('/v1/a/c/', headers={'Content-Length': '0',
- 'Content-Type': 'text/plain'})
- self.app.update_request(req)
- res = method(req)
- self.assertEqual(res.status_int, expected)
-
- def test_HEAD_GET(self):
- with save_globals():
- controller = proxy_server.ContainerController(self.app, 'a', 'c')
-
- def test_status_map(statuses, expected,
- c_expected=None, a_expected=None, **kwargs):
- set_http_connect(*statuses, **kwargs)
- self.app.memcache.store = {}
- req = Request.blank('/v1/a/c', {})
- self.app.update_request(req)
- res = controller.HEAD(req)
- self.assertEqual(res.status[:len(str(expected))],
- str(expected))
- infocache = res.environ.get('swift.infocache', {})
- if expected < 400:
- self.assertIn('x-works', res.headers)
- self.assertEqual(res.headers['x-works'], 'yes')
- if expected < 300:
- self.assertIn('last-modified', res.headers)
- self.assertEqual(res.headers['last-modified'], '1')
- if c_expected:
- self.assertIn('container/a/c', infocache)
- self.assertEqual(
- infocache['container/a/c']['status'],
- c_expected)
- else:
- self.assertNotIn('container/a/c', infocache)
- if a_expected:
- self.assertIn('account/a', infocache)
- self.assertEqual(infocache['account/a']['status'],
- a_expected)
- else:
- self.assertNotIn('account/a', res.environ)
-
- set_http_connect(*statuses, **kwargs)
- self.app.memcache.store = {}
- req = Request.blank('/v1/a/c', {})
- self.app.update_request(req)
- res = controller.GET(req)
- self.assertEqual(res.status[:len(str(expected))],
- str(expected))
- infocache = res.environ.get('swift.infocache', {})
- if expected < 400:
- self.assertIn('x-works', res.headers)
- self.assertEqual(res.headers['x-works'], 'yes')
- if expected < 300:
- self.assertIn('last-modified', res.headers)
- self.assertEqual(res.headers['last-modified'], '1')
- if c_expected:
- self.assertIn('container/a/c', infocache)
- self.assertEqual(
- infocache['container/a/c']['status'],
- c_expected)
- else:
- self.assertNotIn('container/a/c', infocache)
- if a_expected:
- self.assertIn('account/a', infocache)
- self.assertEqual(infocache['account/a']['status'],
- a_expected)
- else:
- self.assertNotIn('account/a', infocache)
- # In all the following tests cache 200 for account
- # return and cache vary for container
- # return 200 and cache 200 for account and container
- test_status_map((200, 200, 404, 404), 200, 200, 200)
- test_status_map((200, 200, 500, 404), 200, 200, 200)
- # return 304 don't cache container
- test_status_map((200, 304, 500, 404), 304, None, 200)
- # return 404 and cache 404 for container
- test_status_map((200, 404, 404, 404), 404, 404, 200)
- test_status_map((200, 404, 404, 500), 404, 404, 200)
- # return 503, don't cache container
- test_status_map((200, 500, 500, 500), 503, None, 200)
- self.assertFalse(self.app.account_autocreate)
-
- # return 404 (as account is not found) and don't cache container
- test_status_map((404, 404, 404), 404, None, 404)
-
- # cache a 204 for the account because it's sort of like it
- # exists
- self.app.account_autocreate = True
- test_status_map((404, 404, 404), 404, None, 204)
-
- def test_PUT_policy_headers(self):
- backend_requests = []
-
- def capture_requests(ipaddr, port, device, partition, method,
- path, headers=None, query_string=None):
- if method == 'PUT':
- backend_requests.append(headers)
-
- def test_policy(requested_policy):
- with save_globals():
- mock_conn = set_http_connect(200, 201, 201, 201,
- give_connect=capture_requests)
- self.app.memcache.store = {}
- req = Request.blank('/v1/a/test', method='PUT',
- headers={'Content-Length': 0})
- if requested_policy:
- expected_policy = requested_policy
- req.headers['X-Storage-Policy'] = policy.name
- else:
- expected_policy = POLICIES.default
- res = req.get_response(self.app)
- if expected_policy.is_deprecated:
- self.assertEqual(res.status_int, 400)
- self.assertEqual(0, len(backend_requests))
- expected = 'is deprecated'
- self.assertIn(expected, res.body,
- '%r did not include %r' % (
- res.body, expected))
- return
- self.assertEqual(res.status_int, 201)
- self.assertEqual(
- expected_policy.object_ring.replicas,
- len(backend_requests))
- for headers in backend_requests:
- if not requested_policy:
- self.assertNotIn('X-Backend-Storage-Policy-Index',
- headers)
- self.assertIn('X-Backend-Storage-Policy-Default',
- headers)
- self.assertEqual(
- int(expected_policy),
- int(headers['X-Backend-Storage-Policy-Default']))
- else:
- self.assertIn('X-Backend-Storage-Policy-Index',
- headers)
- self.assertEqual(int(headers
- ['X-Backend-Storage-Policy-Index']),
- int(policy))
- # make sure all mocked responses are consumed
- self.assertRaises(StopIteration, mock_conn.code_iter.next)
-
- test_policy(None) # no policy header
- for policy in POLICIES:
- backend_requests = [] # reset backend requests
- test_policy(policy)
-
- def test_PUT(self):
- with save_globals():
- controller = proxy_server.ContainerController(self.app, 'account',
- 'container')
-
- def test_status_map(statuses, expected, **kwargs):
- set_http_connect(*statuses, **kwargs)
- self.app.memcache.store = {}
- req = Request.blank('/v1/a/c', {})
- req.content_length = 0
- self.app.update_request(req)
- res = controller.PUT(req)
- expected = str(expected)
- self.assertEqual(res.status[:len(expected)], expected)
-
- test_status_map((200, 201, 201, 201), 201, missing_container=True)
- test_status_map((200, 201, 201, 500), 201, missing_container=True)
- test_status_map((200, 204, 404, 404), 404, missing_container=True)
- test_status_map((200, 204, 500, 404), 503, missing_container=True)
- self.assertFalse(self.app.account_autocreate)
- test_status_map((404, 404, 404), 404, missing_container=True)
- self.app.account_autocreate = True
- # fail to retrieve account info
- test_status_map(
- (503, 503, 503), # account_info fails on 503
- 404, missing_container=True)
- # account fail after creation
- test_status_map(
- (404, 404, 404, # account_info fails on 404
- 201, 201, 201, # PUT account
- 404, 404, 404), # account_info fail
- 404, missing_container=True)
- test_status_map(
- (503, 503, 404, # account_info fails on 404
- 503, 503, 503, # PUT account
- 503, 503, 404), # account_info fail
- 404, missing_container=True)
- # put fails
- test_status_map(
- (404, 404, 404, # account_info fails on 404
- 201, 201, 201, # PUT account
- 200, # account_info success
- 503, 503, 201), # put container fail
- 503, missing_container=True)
- # all goes according to plan
- test_status_map(
- (404, 404, 404, # account_info fails on 404
- 201, 201, 201, # PUT account
- 200, # account_info success
- 201, 201, 201), # put container success
- 201, missing_container=True)
- test_status_map(
- (503, 404, 404, # account_info fails on 404
- 503, 201, 201, # PUT account
- 503, 200, # account_info success
- 503, 201, 201), # put container success
- 201, missing_container=True)
-
- def test_PUT_autocreate_account_with_sysmeta(self):
- # x-account-sysmeta headers in a container PUT request should be
- # transferred to the account autocreate PUT request
- with save_globals():
- controller = proxy_server.ContainerController(self.app, 'account',
- 'container')
-
- def test_status_map(statuses, expected, headers=None, **kwargs):
- set_http_connect(*statuses, **kwargs)
- self.app.memcache.store = {}
- req = Request.blank('/v1/a/c', {}, headers=headers)
- req.content_length = 0
- self.app.update_request(req)
- res = controller.PUT(req)
- expected = str(expected)
- self.assertEqual(res.status[:len(expected)], expected)
-
- self.app.account_autocreate = True
- calls = []
- callback = _make_callback_func(calls)
- key, value = 'X-Account-Sysmeta-Blah', 'something'
- headers = {key: value}
-
- # all goes according to plan
- test_status_map(
- (404, 404, 404, # account_info fails on 404
- 201, 201, 201, # PUT account
- 200, # account_info success
- 201, 201, 201), # put container success
- 201, missing_container=True,
- headers=headers,
- give_connect=callback)
-
- self.assertEqual(10, len(calls))
- for call in calls[3:6]:
- self.assertEqual('/account', call['path'])
- self.assertIn(key, call['headers'],
- '%s call, key %s missing in headers %s' % (
- call['method'], key, call['headers']))
- self.assertEqual(value, call['headers'][key])
-
- def test_POST(self):
- with save_globals():
- controller = proxy_server.ContainerController(self.app, 'account',
- 'container')
-
- def test_status_map(statuses, expected, **kwargs):
- set_http_connect(*statuses, **kwargs)
- self.app.memcache.store = {}
- req = Request.blank('/v1/a/c', {})
- req.content_length = 0
- self.app.update_request(req)
- res = controller.POST(req)
- expected = str(expected)
- self.assertEqual(res.status[:len(expected)], expected)
-
- test_status_map((200, 201, 201, 201), 201, missing_container=True)
- test_status_map((200, 201, 201, 500), 201, missing_container=True)
- test_status_map((200, 204, 404, 404), 404, missing_container=True)
- test_status_map((200, 204, 500, 404), 503, missing_container=True)
- self.assertFalse(self.app.account_autocreate)
- test_status_map((404, 404, 404), 404, missing_container=True)
- self.app.account_autocreate = True
- test_status_map((404, 404, 404), 404, missing_container=True)
-
- def test_PUT_max_containers_per_account(self):
- with save_globals():
- self.app.max_containers_per_account = 12346
- controller = proxy_server.ContainerController(self.app, 'account',
- 'container')
- self.assert_status_map(controller.PUT,
- (200, 201, 201, 201), 201,
- missing_container=True)
-
- self.app.max_containers_per_account = 12345
- controller = proxy_server.ContainerController(self.app, 'account',
- 'container')
- self.assert_status_map(controller.PUT,
- (200, 200, 201, 201, 201), 201,
- missing_container=True)
-
- controller = proxy_server.ContainerController(self.app, 'account',
- 'container_new')
-
- self.assert_status_map(controller.PUT, (200, 404, 404, 404), 403,
- missing_container=True)
-
- self.app.max_containers_per_account = 12345
- self.app.max_containers_whitelist = ['account']
- controller = proxy_server.ContainerController(self.app, 'account',
- 'container')
- self.assert_status_map(controller.PUT,
- (200, 201, 201, 201), 201,
- missing_container=True)
-
- def test_PUT_max_container_name_length(self):
- with save_globals():
- limit = constraints.MAX_CONTAINER_NAME_LENGTH
- controller = proxy_server.ContainerController(self.app, 'account',
- '1' * limit)
- self.assert_status_map(controller.PUT,
- (200, 201, 201, 201), 201,
- missing_container=True)
- controller = proxy_server.ContainerController(self.app, 'account',
- '2' * (limit + 1))
- self.assert_status_map(controller.PUT, (201, 201, 201), 400,
- missing_container=True)
-
- def test_PUT_connect_exceptions(self):
- with save_globals():
- controller = proxy_server.ContainerController(self.app, 'account',
- 'container')
- self.assert_status_map(controller.PUT, (200, 201, 201, -1), 201,
- missing_container=True)
- self.assert_status_map(controller.PUT, (200, 201, -1, -1), 503,
- missing_container=True)
- self.assert_status_map(controller.PUT, (200, 503, 503, -1), 503,
- missing_container=True)
-
- def test_acc_missing_returns_404(self):
- for meth in ('DELETE', 'PUT'):
- with save_globals():
- self.app.memcache = FakeMemcacheReturnsNone()
- self.app._error_limiting = {}
- controller = proxy_server.ContainerController(self.app,
- 'account',
- 'container')
- if meth == 'PUT':
- set_http_connect(200, 200, 200, 200, 200, 200,
- missing_container=True)
- else:
- set_http_connect(200, 200, 200, 200)
- self.app.memcache.store = {}
- req = Request.blank('/v1/a/c',
- environ={'REQUEST_METHOD': meth})
- self.app.update_request(req)
- resp = getattr(controller, meth)(req)
- self.assertEqual(resp.status_int, 200)
-
- set_http_connect(404, 404, 404, 200, 200, 200)
- # Make sure it is a blank request wthout env caching
- req = Request.blank('/v1/a/c',
- environ={'REQUEST_METHOD': meth})
- resp = getattr(controller, meth)(req)
- self.assertEqual(resp.status_int, 404)
-
- set_http_connect(503, 404, 404)
- # Make sure it is a blank request wthout env caching
- req = Request.blank('/v1/a/c',
- environ={'REQUEST_METHOD': meth})
- resp = getattr(controller, meth)(req)
- self.assertEqual(resp.status_int, 404)
-
- set_http_connect(503, 404, raise_exc=True)
- # Make sure it is a blank request wthout env caching
- req = Request.blank('/v1/a/c',
- environ={'REQUEST_METHOD': meth})
- resp = getattr(controller, meth)(req)
- self.assertEqual(resp.status_int, 404)
-
- for dev in self.app.account_ring.devs:
- set_node_errors(self.app, dev,
- self.app.error_suppression_limit + 1,
- time.time())
- set_http_connect(200, 200, 200, 200, 200, 200)
- # Make sure it is a blank request wthout env caching
- req = Request.blank('/v1/a/c',
- environ={'REQUEST_METHOD': meth})
- resp = getattr(controller, meth)(req)
- self.assertEqual(resp.status_int, 404)
-
- def test_put_locking(self):
-
- class MockMemcache(FakeMemcache):
-
- def __init__(self, allow_lock=None):
- self.allow_lock = allow_lock
- super(MockMemcache, self).__init__()
-
- @contextmanager
- def soft_lock(self, key, timeout=0, retries=5):
- if self.allow_lock:
- yield True
- else:
- raise NotImplementedError
-
- with save_globals():
- controller = proxy_server.ContainerController(self.app, 'account',
- 'container')
- self.app.memcache = MockMemcache(allow_lock=True)
- set_http_connect(200, 201, 201, 201,
- missing_container=True)
- req = Request.blank('/v1/a/c', environ={'REQUEST_METHOD': 'PUT'})
- self.app.update_request(req)
- res = controller.PUT(req)
- self.assertEqual(res.status_int, 201)
-
- def test_error_limiting(self):
- with save_globals():
- controller = proxy_server.ContainerController(self.app, 'account',
- 'container')
- container_ring = controller.app.container_ring
- controller.app.sort_nodes = lambda l: l
- self.assert_status_map(controller.HEAD, (200, 503, 200, 200), 200,
- missing_container=False)
-
- self.assertEqual(
- node_error_count(controller.app, container_ring.devs[0]), 2)
- self.assertTrue(
- node_last_error(controller.app, container_ring.devs[0])
- is not None)
- for _junk in range(self.app.error_suppression_limit):
- self.assert_status_map(controller.HEAD,
- (200, 503, 503, 503), 503)
- self.assertEqual(
- node_error_count(controller.app, container_ring.devs[0]),
- self.app.error_suppression_limit + 1)
- self.assert_status_map(controller.HEAD, (200, 200, 200, 200), 503)
- self.assertTrue(
- node_last_error(controller.app, container_ring.devs[0])
- is not None)
- self.assert_status_map(controller.PUT, (200, 201, 201, 201), 503,
- missing_container=True)
- self.assert_status_map(controller.DELETE,
- (200, 204, 204, 204), 503)
- self.app.error_suppression_interval = -300
- self.assert_status_map(controller.HEAD, (200, 200, 200, 200), 200)
- self.assert_status_map(controller.DELETE, (200, 204, 204, 204),
- 404, raise_exc=True)
-
- def test_DELETE(self):
- with save_globals():
- controller = proxy_server.ContainerController(self.app, 'account',
- 'container')
- self.assert_status_map(controller.DELETE,
- (200, 204, 204, 204), 204)
- self.assert_status_map(controller.DELETE,
- (200, 204, 204, 503), 204)
- self.assert_status_map(controller.DELETE,
- (200, 204, 503, 503), 503)
- self.assert_status_map(controller.DELETE,
- (200, 204, 404, 404), 404)
- self.assert_status_map(controller.DELETE,
- (200, 404, 404, 404), 404)
- self.assert_status_map(controller.DELETE,
- (200, 204, 503, 404), 503)
-
- self.app.memcache = FakeMemcacheReturnsNone()
- # 200: Account check, 404x3: Container check
- self.assert_status_map(controller.DELETE,
- (200, 404, 404, 404), 404)
-
- def test_response_get_accept_ranges_header(self):
- with save_globals():
- set_http_connect(200, 200, body='{}')
- controller = proxy_server.ContainerController(self.app, 'account',
- 'container')
- req = Request.blank('/v1/a/c?format=json')
- self.app.update_request(req)
- res = controller.GET(req)
- self.assertIn('accept-ranges', res.headers)
- self.assertEqual(res.headers['accept-ranges'], 'bytes')
-
- def test_response_head_accept_ranges_header(self):
- with save_globals():
- set_http_connect(200, 200, body='{}')
- controller = proxy_server.ContainerController(self.app, 'account',
- 'container')
- req = Request.blank('/v1/a/c?format=json')
- self.app.update_request(req)
- res = controller.HEAD(req)
- self.assertIn('accept-ranges', res.headers)
- self.assertEqual(res.headers['accept-ranges'], 'bytes')
-
- def test_PUT_metadata(self):
- self.metadata_helper('PUT')
-
- def test_POST_metadata(self):
- self.metadata_helper('POST')
-
- def metadata_helper(self, method):
- for test_header, test_value in (
- ('X-Container-Meta-TestHeader', 'TestValue'),
- ('X-Container-Meta-TestHeader', ''),
- ('X-Remove-Container-Meta-TestHeader', 'anything'),
- ('X-Container-Read', '.r:*'),
- ('X-Remove-Container-Read', 'anything'),
- ('X-Container-Write', 'anyone'),
- ('X-Remove-Container-Write', 'anything')):
- test_errors = []
-
- def test_connect(ipaddr, port, device, partition, method, path,
- headers=None, query_string=None):
- if path == '/a/c':
- find_header = test_header
- find_value = test_value
- if find_header.lower().startswith('x-remove-'):
- find_header = \
- find_header.lower().replace('-remove', '', 1)
- find_value = ''
- for k, v in headers.items():
- if k.lower() == find_header.lower() and \
- v == find_value:
- break
- else:
- test_errors.append('%s: %s not in %s' %
- (find_header, find_value, headers))
- with save_globals():
- controller = \
- proxy_server.ContainerController(self.app, 'a', 'c')
- set_http_connect(200, 201, 201, 201, give_connect=test_connect)
- req = Request.blank(
- '/v1/a/c',
- environ={'REQUEST_METHOD': method, 'swift_owner': True},
- headers={test_header: test_value})
- self.app.update_request(req)
- getattr(controller, method)(req)
- self.assertEqual(test_errors, [])
-
- def test_PUT_bad_metadata(self):
- self.bad_metadata_helper('PUT')
-
- def test_POST_bad_metadata(self):
- self.bad_metadata_helper('POST')
-
- def bad_metadata_helper(self, method):
- with save_globals():
- controller = proxy_server.ContainerController(self.app, 'a', 'c')
- set_http_connect(200, 201, 201, 201)
- req = Request.blank('/v1/a/c', environ={'REQUEST_METHOD': method})
- self.app.update_request(req)
- resp = getattr(controller, method)(req)
- self.assertEqual(resp.status_int, 201)
-
- set_http_connect(201, 201, 201)
- req = Request.blank('/v1/a/c', environ={'REQUEST_METHOD': method},
- headers={'X-Container-Meta-' +
- ('a' * constraints.MAX_META_NAME_LENGTH): 'v'})
- self.app.update_request(req)
- resp = getattr(controller, method)(req)
- self.assertEqual(resp.status_int, 201)
- set_http_connect(201, 201, 201)
- req = Request.blank(
- '/v1/a/c', environ={'REQUEST_METHOD': method},
- headers={'X-Container-Meta-' +
- ('a' * (constraints.MAX_META_NAME_LENGTH + 1)): 'v'})
- self.app.update_request(req)
- resp = getattr(controller, method)(req)
- self.assertEqual(resp.status_int, 400)
-
- set_http_connect(201, 201, 201)
- req = Request.blank('/v1/a/c', environ={'REQUEST_METHOD': method},
- headers={'X-Container-Meta-Too-Long':
- 'a' * constraints.MAX_META_VALUE_LENGTH})
- self.app.update_request(req)
- resp = getattr(controller, method)(req)
- self.assertEqual(resp.status_int, 201)
- set_http_connect(201, 201, 201)
- req = Request.blank('/v1/a/c', environ={'REQUEST_METHOD': method},
- headers={'X-Container-Meta-Too-Long':
- 'a' * (constraints.MAX_META_VALUE_LENGTH + 1)})
- self.app.update_request(req)
- resp = getattr(controller, method)(req)
- self.assertEqual(resp.status_int, 400)
-
- set_http_connect(201, 201, 201)
- headers = {}
- for x in range(constraints.MAX_META_COUNT):
- headers['X-Container-Meta-%d' % x] = 'v'
- req = Request.blank('/v1/a/c', environ={'REQUEST_METHOD': method},
- headers=headers)
- self.app.update_request(req)
- resp = getattr(controller, method)(req)
- self.assertEqual(resp.status_int, 201)
- set_http_connect(201, 201, 201)
- headers = {}
- for x in range(constraints.MAX_META_COUNT + 1):
- headers['X-Container-Meta-%d' % x] = 'v'
- req = Request.blank('/v1/a/c', environ={'REQUEST_METHOD': method},
- headers=headers)
- self.app.update_request(req)
- resp = getattr(controller, method)(req)
- self.assertEqual(resp.status_int, 400)
-
- set_http_connect(201, 201, 201)
- headers = {}
- header_value = 'a' * constraints.MAX_META_VALUE_LENGTH
- size = 0
- x = 0
- while size < (constraints.MAX_META_OVERALL_SIZE - 4
- - constraints.MAX_META_VALUE_LENGTH):
- size += 4 + constraints.MAX_META_VALUE_LENGTH
- headers['X-Container-Meta-%04d' % x] = header_value
- x += 1
- if constraints.MAX_META_OVERALL_SIZE - size > 1:
- headers['X-Container-Meta-a'] = \
- 'a' * (constraints.MAX_META_OVERALL_SIZE - size - 1)
- req = Request.blank('/v1/a/c', environ={'REQUEST_METHOD': method},
- headers=headers)
- self.app.update_request(req)
- resp = getattr(controller, method)(req)
- self.assertEqual(resp.status_int, 201)
- set_http_connect(201, 201, 201)
- headers['X-Container-Meta-a'] = \
- 'a' * (constraints.MAX_META_OVERALL_SIZE - size)
- req = Request.blank('/v1/a/c', environ={'REQUEST_METHOD': method},
- headers=headers)
- self.app.update_request(req)
- resp = getattr(controller, method)(req)
- self.assertEqual(resp.status_int, 400)
-
- def test_POST_calls_clean_acl(self):
- called = [False]
-
- def clean_acl(header, value):
- called[0] = True
- raise ValueError('fake error')
- with save_globals():
- set_http_connect(200, 201, 201, 201)
- controller = proxy_server.ContainerController(self.app, 'account',
- 'container')
- req = Request.blank('/v1/a/c', environ={'REQUEST_METHOD': 'POST'},
- headers={'X-Container-Read': '.r:*'})
- req.environ['swift.clean_acl'] = clean_acl
- self.app.update_request(req)
- controller.POST(req)
- self.assertTrue(called[0])
- called[0] = False
- with save_globals():
- set_http_connect(200, 201, 201, 201)
- controller = proxy_server.ContainerController(self.app, 'account',
- 'container')
- req = Request.blank('/v1/a/c', environ={'REQUEST_METHOD': 'POST'},
- headers={'X-Container-Write': '.r:*'})
- req.environ['swift.clean_acl'] = clean_acl
- self.app.update_request(req)
- controller.POST(req)
- self.assertTrue(called[0])
-
- def test_PUT_calls_clean_acl(self):
- called = [False]
-
- def clean_acl(header, value):
- called[0] = True
- raise ValueError('fake error')
- with save_globals():
- set_http_connect(200, 201, 201, 201)
- controller = proxy_server.ContainerController(self.app, 'account',
- 'container')
- req = Request.blank('/v1/a/c', environ={'REQUEST_METHOD': 'PUT'},
- headers={'X-Container-Read': '.r:*'})
- req.environ['swift.clean_acl'] = clean_acl
- self.app.update_request(req)
- controller.PUT(req)
- self.assertTrue(called[0])
- called[0] = False
- with save_globals():
- set_http_connect(200, 201, 201, 201)
- controller = proxy_server.ContainerController(self.app, 'account',
- 'container')
- req = Request.blank('/v1/a/c', environ={'REQUEST_METHOD': 'PUT'},
- headers={'X-Container-Write': '.r:*'})
- req.environ['swift.clean_acl'] = clean_acl
- self.app.update_request(req)
- controller.PUT(req)
- self.assertTrue(called[0])
-
- def test_GET_no_content(self):
- with save_globals():
- set_http_connect(200, 204, 204, 204)
- controller = proxy_server.ContainerController(self.app, 'a', 'c')
- req = Request.blank('/v1/a/c')
- self.app.update_request(req)
- res = controller.GET(req)
- self.assertEqual(res.status_int, 204)
- ic = res.environ['swift.infocache']
- self.assertEqual(ic['container/a/c']['status'], 204)
- self.assertEqual(res.content_length, 0)
- self.assertNotIn('transfer-encoding', res.headers)
-
- def test_GET_calls_authorize(self):
- called = [False]
-
- def authorize(req):
- called[0] = True
- return HTTPUnauthorized(request=req)
- with save_globals():
- set_http_connect(200, 201, 201, 201)
- controller = proxy_server.ContainerController(self.app, 'a', 'c')
- req = Request.blank('/v1/a/c')
- req.environ['swift.authorize'] = authorize
- self.app.update_request(req)
- res = controller.GET(req)
- self.assertEqual(
- res.environ['swift.infocache']['container/a/c']['status'],
- 201)
- self.assertTrue(called[0])
-
- def test_HEAD_calls_authorize(self):
- called = [False]
-
- def authorize(req):
- called[0] = True
- return HTTPUnauthorized(request=req)
- with save_globals():
- set_http_connect(200, 201, 201, 201)
- controller = proxy_server.ContainerController(self.app, 'a', 'c')
- req = Request.blank('/v1/a/c', {'REQUEST_METHOD': 'HEAD'})
- req.environ['swift.authorize'] = authorize
- self.app.update_request(req)
- controller.HEAD(req)
- self.assertTrue(called[0])
-
- def test_unauthorized_requests_when_account_not_found(self):
- # verify unauthorized container requests always return response
- # from swift.authorize
- called = [0, 0]
-
- def authorize(req):
- called[0] += 1
- return HTTPUnauthorized(request=req)
-
- def account_info(*args):
- called[1] += 1
- return None, None, None
-
- def _do_test(method):
- with save_globals():
- swift.proxy.controllers.Controller.account_info = account_info
- app = proxy_server.Application(None, FakeMemcache(),
- account_ring=FakeRing(),
- container_ring=FakeRing())
- set_http_connect(201, 201, 201)
- req = Request.blank('/v1/a/c', {'REQUEST_METHOD': method})
- req.environ['swift.authorize'] = authorize
- self.app.update_request(req)
- res = app.handle_request(req)
- return res
-
- for method in ('PUT', 'POST', 'DELETE'):
- # no delay_denial on method, expect one call to authorize
- called = [0, 0]
- res = _do_test(method)
- self.assertEqual(401, res.status_int)
- self.assertEqual([1, 0], called)
-
- for method in ('HEAD', 'GET'):
- # delay_denial on method, expect two calls to authorize
- called = [0, 0]
- res = _do_test(method)
- self.assertEqual(401, res.status_int)
- self.assertEqual([2, 1], called)
-
- def test_authorized_requests_when_account_not_found(self):
- # verify authorized container requests always return 404 when
- # account not found
- called = [0, 0]
-
- def authorize(req):
- called[0] += 1
-
- def account_info(*args):
- called[1] += 1
- return None, None, None
-
- def _do_test(method):
- with save_globals():
- swift.proxy.controllers.Controller.account_info = account_info
- app = proxy_server.Application(None, FakeMemcache(),
- account_ring=FakeRing(),
- container_ring=FakeRing())
- set_http_connect(201, 201, 201)
- req = Request.blank('/v1/a/c', {'REQUEST_METHOD': method})
- req.environ['swift.authorize'] = authorize
- self.app.update_request(req)
- res = app.handle_request(req)
- return res
-
- for method in ('PUT', 'POST', 'DELETE', 'HEAD', 'GET'):
- # expect one call to authorize
- called = [0, 0]
- res = _do_test(method)
- self.assertEqual(404, res.status_int)
- self.assertEqual([1, 1], called)
-
- def test_OPTIONS_get_info_drops_origin(self):
- with save_globals():
- controller = proxy_server.ContainerController(self.app, 'a', 'c')
-
- count = [0]
-
- def my_get_info(app, env, account, container=None,
- ret_not_found=False, swift_source=None):
- if count[0] > 11:
- return {}
- count[0] += 1
- if not container:
- return {'some': 'stuff'}
- return proxy_base.was_get_info(
- app, env, account, container, ret_not_found, swift_source)
-
- proxy_base.was_get_info = proxy_base.get_info
- with mock.patch.object(proxy_base, 'get_info', my_get_info):
- proxy_base.get_info = my_get_info
- req = Request.blank(
- '/v1/a/c',
- {'REQUEST_METHOD': 'OPTIONS'},
- headers={'Origin': 'http://foo.com',
- 'Access-Control-Request-Method': 'GET'})
- controller.OPTIONS(req)
- self.assertLess(count[0], 11)
-
- def test_OPTIONS(self):
- with save_globals():
- controller = proxy_server.ContainerController(self.app, 'a', 'c')
-
- def my_empty_container_info(*args):
- return {}
- controller.container_info = my_empty_container_info
- req = Request.blank(
- '/v1/a/c',
- {'REQUEST_METHOD': 'OPTIONS'},
- headers={'Origin': 'http://foo.com',
- 'Access-Control-Request-Method': 'GET'})
- resp = controller.OPTIONS(req)
- self.assertEqual(401, resp.status_int)
-
- def my_empty_origin_container_info(*args):
- return {'cors': {'allow_origin': None}}
- controller.container_info = my_empty_origin_container_info
- req = Request.blank(
- '/v1/a/c',
- {'REQUEST_METHOD': 'OPTIONS'},
- headers={'Origin': 'http://foo.com',
- 'Access-Control-Request-Method': 'GET'})
- resp = controller.OPTIONS(req)
- self.assertEqual(401, resp.status_int)
-
- def my_container_info(*args):
- return {
- 'cors': {
- 'allow_origin': 'http://foo.bar:8080 https://foo.bar',
- 'max_age': '999',
- }
- }
- controller.container_info = my_container_info
- req = Request.blank(
- '/v1/a/c',
- {'REQUEST_METHOD': 'OPTIONS'},
- headers={'Origin': 'https://foo.bar',
- 'Access-Control-Request-Method': 'GET'})
- req.content_length = 0
- resp = controller.OPTIONS(req)
- self.assertEqual(200, resp.status_int)
- self.assertEqual(
- 'https://foo.bar',
- resp.headers['access-control-allow-origin'])
- for verb in 'OPTIONS GET POST PUT DELETE HEAD'.split():
- self.assertIn(verb,
- resp.headers['access-control-allow-methods'])
- self.assertEqual(
- len(resp.headers['access-control-allow-methods'].split(', ')),
- 6)
- self.assertEqual('999', resp.headers['access-control-max-age'])
- req = Request.blank(
- '/v1/a/c',
- {'REQUEST_METHOD': 'OPTIONS'},
- headers={'Origin': 'https://foo.bar'})
- req.content_length = 0
- resp = controller.OPTIONS(req)
- self.assertEqual(401, resp.status_int)
- req = Request.blank('/v1/a/c', {'REQUEST_METHOD': 'OPTIONS'})
- req.content_length = 0
- resp = controller.OPTIONS(req)
- self.assertEqual(200, resp.status_int)
- for verb in 'OPTIONS GET POST PUT DELETE HEAD'.split():
- self.assertIn(verb, resp.headers['Allow'])
- self.assertEqual(len(resp.headers['Allow'].split(', ')), 6)
- req = Request.blank(
- '/v1/a/c',
- {'REQUEST_METHOD': 'OPTIONS'},
- headers={'Origin': 'http://foo.bar',
- 'Access-Control-Request-Method': 'GET'})
- resp = controller.OPTIONS(req)
- self.assertEqual(401, resp.status_int)
- req = Request.blank(
- '/v1/a/c',
- {'REQUEST_METHOD': 'OPTIONS'},
- headers={'Origin': 'http://foo.bar',
- 'Access-Control-Request-Method': 'GET'})
- controller.app.cors_allow_origin = ['http://foo.bar', ]
- resp = controller.OPTIONS(req)
- self.assertEqual(200, resp.status_int)
-
- def my_container_info_wildcard(*args):
- return {
- 'cors': {
- 'allow_origin': '*',
- 'max_age': '999',
- }
- }
- controller.container_info = my_container_info_wildcard
- req = Request.blank(
- '/v1/a/c/o.jpg',
- {'REQUEST_METHOD': 'OPTIONS'},
- headers={'Origin': 'https://bar.baz',
- 'Access-Control-Request-Method': 'GET'})
- req.content_length = 0
- resp = controller.OPTIONS(req)
- self.assertEqual(200, resp.status_int)
- self.assertEqual('*', resp.headers['access-control-allow-origin'])
- for verb in 'OPTIONS GET POST PUT DELETE HEAD'.split():
- self.assertIn(verb,
- resp.headers['access-control-allow-methods'])
- self.assertEqual(
- len(resp.headers['access-control-allow-methods'].split(', ')),
- 6)
- self.assertEqual('999', resp.headers['access-control-max-age'])
-
- req = Request.blank(
- '/v1/a/c/o.jpg',
- {'REQUEST_METHOD': 'OPTIONS'},
- headers={'Origin': 'https://bar.baz',
- 'Access-Control-Request-Headers':
- 'x-foo, x-bar, x-auth-token',
- 'Access-Control-Request-Method': 'GET'}
- )
- req.content_length = 0
- resp = controller.OPTIONS(req)
- self.assertEqual(200, resp.status_int)
- self.assertEqual(
- sortHeaderNames('x-foo, x-bar, x-auth-token'),
- sortHeaderNames(resp.headers['access-control-allow-headers']))
-
- def test_CORS_valid(self):
- with save_globals():
- controller = proxy_server.ContainerController(self.app, 'a', 'c')
-
- def stubContainerInfo(*args):
- return {
- 'cors': {
- 'allow_origin': 'http://foo.bar'
- }
- }
- controller.container_info = stubContainerInfo
-
- def containerGET(controller, req):
- return Response(headers={
- 'X-Container-Meta-Color': 'red',
- 'X-Super-Secret': 'hush',
- })
-
- req = Request.blank(
- '/v1/a/c',
- {'REQUEST_METHOD': 'GET'},
- headers={'Origin': 'http://foo.bar'})
-
- resp = cors_validation(containerGET)(controller, req)
-
- self.assertEqual(200, resp.status_int)
- self.assertEqual('http://foo.bar',
- resp.headers['access-control-allow-origin'])
- self.assertEqual('red', resp.headers['x-container-meta-color'])
- # X-Super-Secret is in the response, but not "exposed"
- self.assertEqual('hush', resp.headers['x-super-secret'])
- self.assertIn('access-control-expose-headers', resp.headers)
- exposed = set(
- h.strip() for h in
- resp.headers['access-control-expose-headers'].split(','))
- expected_exposed = set([
- 'cache-control', 'content-language', 'content-type', 'expires',
- 'last-modified', 'pragma', 'etag', 'x-timestamp', 'x-trans-id',
- 'x-openstack-request-id', 'x-container-meta-color'])
- self.assertEqual(expected_exposed, exposed)
-
- def _gather_x_account_headers(self, controller_call, req, *connect_args,
- **kwargs):
- seen_headers = []
- to_capture = ('X-Account-Partition', 'X-Account-Host',
- 'X-Account-Device')
-
- def capture_headers(ipaddr, port, device, partition, method,
- path, headers=None, query_string=None):
- captured = {}
- for header in to_capture:
- captured[header] = headers.get(header)
- seen_headers.append(captured)
-
- with save_globals():
- self.app.allow_account_management = True
-
- set_http_connect(*connect_args, give_connect=capture_headers,
- **kwargs)
- resp = controller_call(req)
- self.assertEqual(2, resp.status_int // 100) # sanity check
-
- # don't care about the account HEAD, so throw away the
- # first element
- return sorted(seen_headers[1:],
- key=lambda d: d['X-Account-Host'] or 'Z')
-
- def test_PUT_x_account_headers_with_fewer_account_replicas(self):
- self.app.account_ring.set_replicas(2)
- req = Request.blank('/v1/a/c', headers={'': ''})
- controller = proxy_server.ContainerController(self.app, 'a', 'c')
-
- seen_headers = self._gather_x_account_headers(
- controller.PUT, req,
- 200, 201, 201, 201) # HEAD PUT PUT PUT
- self.assertEqual(seen_headers, [
- {'X-Account-Host': '10.0.0.0:1000',
- 'X-Account-Partition': '0',
- 'X-Account-Device': 'sda'},
- {'X-Account-Host': '10.0.0.1:1001',
- 'X-Account-Partition': '0',
- 'X-Account-Device': 'sdb'},
- {'X-Account-Host': None,
- 'X-Account-Partition': None,
- 'X-Account-Device': None}
- ])
-
- def test_PUT_x_account_headers_with_more_account_replicas(self):
- self.app.account_ring.set_replicas(4)
- req = Request.blank('/v1/a/c', headers={'': ''})
- controller = proxy_server.ContainerController(self.app, 'a', 'c')
-
- seen_headers = self._gather_x_account_headers(
- controller.PUT, req,
- 200, 201, 201, 201) # HEAD PUT PUT PUT
- self.assertEqual(seen_headers, [
- {'X-Account-Host': '10.0.0.0:1000,10.0.0.3:1003',
- 'X-Account-Partition': '0',
- 'X-Account-Device': 'sda,sdd'},
- {'X-Account-Host': '10.0.0.1:1001',
- 'X-Account-Partition': '0',
- 'X-Account-Device': 'sdb'},
- {'X-Account-Host': '10.0.0.2:1002',
- 'X-Account-Partition': '0',
- 'X-Account-Device': 'sdc'}
- ])
-
- def test_DELETE_x_account_headers_with_fewer_account_replicas(self):
- self.app.account_ring.set_replicas(2)
- req = Request.blank('/v1/a/c', headers={'': ''})
- controller = proxy_server.ContainerController(self.app, 'a', 'c')
-
- seen_headers = self._gather_x_account_headers(
- controller.DELETE, req,
- 200, 204, 204, 204) # HEAD DELETE DELETE DELETE
- self.assertEqual(seen_headers, [
- {'X-Account-Host': '10.0.0.0:1000',
- 'X-Account-Partition': '0',
- 'X-Account-Device': 'sda'},
- {'X-Account-Host': '10.0.0.1:1001',
- 'X-Account-Partition': '0',
- 'X-Account-Device': 'sdb'},
- {'X-Account-Host': None,
- 'X-Account-Partition': None,
- 'X-Account-Device': None}
- ])
-
- def test_DELETE_x_account_headers_with_more_account_replicas(self):
- self.app.account_ring.set_replicas(4)
- req = Request.blank('/v1/a/c', headers={'': ''})
- controller = proxy_server.ContainerController(self.app, 'a', 'c')
-
- seen_headers = self._gather_x_account_headers(
- controller.DELETE, req,
- 200, 204, 204, 204) # HEAD DELETE DELETE DELETE
- self.assertEqual(seen_headers, [
- {'X-Account-Host': '10.0.0.0:1000,10.0.0.3:1003',
- 'X-Account-Partition': '0',
- 'X-Account-Device': 'sda,sdd'},
- {'X-Account-Host': '10.0.0.1:1001',
- 'X-Account-Partition': '0',
- 'X-Account-Device': 'sdb'},
- {'X-Account-Host': '10.0.0.2:1002',
- 'X-Account-Partition': '0',
- '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 existence 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 existence check
- self.assertEqual(3, len(timestamps))
- for timestamp in timestamps:
- self.assertEqual(timestamp, timestamps[0])
- self.assertTrue(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 existence 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 existence check
- self.assertEqual(3, len(timestamps))
- for timestamp in timestamps:
- self.assertEqual(timestamp, timestamps[0])
- self.assertTrue(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=[1.0, 1.0])
- resp = req.get_response(self.app)
- got_exc = False
- try:
- resp.body
- except ChunkReadTimeout:
- got_exc = True
- self.assertTrue(got_exc)
-
-
-@patch_policies([StoragePolicy(0, 'zero', True, object_ring=FakeRing())])
-class TestAccountController(unittest.TestCase):
-
- def setUp(self):
- self.app = proxy_server.Application(None, FakeMemcache(),
- account_ring=FakeRing(),
- container_ring=FakeRing())
-
- def assert_status_map(self, method, statuses, expected, env_expected=None,
- headers=None, **kwargs):
- headers = headers or {}
- with save_globals():
- set_http_connect(*statuses, **kwargs)
- req = Request.blank('/v1/a', {}, headers=headers)
- self.app.update_request(req)
- res = method(req)
- self.assertEqual(res.status_int, expected)
- infocache = res.environ.get('swift.infocache', {})
- if env_expected:
- self.assertEqual(infocache['account/a']['status'],
- env_expected)
- set_http_connect(*statuses)
- req = Request.blank('/v1/a/', {})
- self.app.update_request(req)
- res = method(req)
- infocache = res.environ.get('swift.infocache', {})
- self.assertEqual(res.status_int, expected)
- if env_expected:
- self.assertEqual(infocache['account/a']['status'],
- env_expected)
-
- def test_OPTIONS(self):
- with save_globals():
- self.app.allow_account_management = False
- controller = proxy_server.AccountController(self.app, 'account')
- req = Request.blank('/v1/account', {'REQUEST_METHOD': 'OPTIONS'})
- req.content_length = 0
- resp = controller.OPTIONS(req)
- self.assertEqual(200, resp.status_int)
- for verb in 'OPTIONS GET POST HEAD'.split():
- self.assertIn(verb, resp.headers['Allow'])
- self.assertEqual(len(resp.headers['Allow'].split(', ')), 4)
-
- # Test a CORS OPTIONS request (i.e. including Origin and
- # Access-Control-Request-Method headers)
- self.app.allow_account_management = False
- controller = proxy_server.AccountController(self.app, 'account')
- req = Request.blank(
- '/v1/account', {'REQUEST_METHOD': 'OPTIONS'},
- headers={'Origin': 'http://foo.com',
- 'Access-Control-Request-Method': 'GET'})
- req.content_length = 0
- resp = controller.OPTIONS(req)
- self.assertEqual(200, resp.status_int)
- for verb in 'OPTIONS GET POST HEAD'.split():
- self.assertIn(verb, resp.headers['Allow'])
- self.assertEqual(len(resp.headers['Allow'].split(', ')), 4)
-
- self.app.allow_account_management = True
- controller = proxy_server.AccountController(self.app, 'account')
- req = Request.blank('/v1/account', {'REQUEST_METHOD': 'OPTIONS'})
- req.content_length = 0
- resp = controller.OPTIONS(req)
- self.assertEqual(200, resp.status_int)
- for verb in 'OPTIONS GET POST PUT DELETE HEAD'.split():
- self.assertIn(verb, resp.headers['Allow'])
- self.assertEqual(len(resp.headers['Allow'].split(', ')), 6)
-
- def test_GET(self):
- with save_globals():
- controller = proxy_server.AccountController(self.app, 'a')
- # GET returns after the first successful call to an Account Server
- self.assert_status_map(controller.GET, (200,), 200, 200)
- self.assert_status_map(controller.GET, (503, 200), 200, 200)
- self.assert_status_map(controller.GET, (503, 503, 200), 200, 200)
- self.assert_status_map(controller.GET, (204,), 204, 204)
- self.assert_status_map(controller.GET, (503, 204), 204, 204)
- self.assert_status_map(controller.GET, (503, 503, 204), 204, 204)
- self.assert_status_map(controller.GET, (404, 200), 200, 200)
- self.assert_status_map(controller.GET, (404, 404, 200), 200, 200)
- self.assert_status_map(controller.GET, (404, 503, 204), 204, 204)
- # If Account servers fail, if autocreate = False, return majority
- # response
- self.assert_status_map(controller.GET, (404, 404, 404), 404, 404)
- self.assert_status_map(controller.GET, (404, 404, 503), 404, 404)
- self.assert_status_map(controller.GET, (404, 503, 503), 503)
-
- self.app.memcache = FakeMemcacheReturnsNone()
- self.assert_status_map(controller.GET, (404, 404, 404), 404, 404)
-
- def test_GET_autocreate(self):
- with save_globals():
- controller = proxy_server.AccountController(self.app, 'a')
- self.app.memcache = FakeMemcacheReturnsNone()
- self.assertFalse(self.app.account_autocreate)
- # Repeat the test for autocreate = False and 404 by all
- self.assert_status_map(controller.GET,
- (404, 404, 404), 404)
- self.assert_status_map(controller.GET,
- (404, 503, 404), 404)
- # When autocreate is True, if none of the nodes respond 2xx
- # And quorum of the nodes responded 404,
- # ALL nodes are asked to create the account
- # If successful, the GET request is repeated.
- controller.app.account_autocreate = True
- self.assert_status_map(controller.GET,
- (404, 404, 404), 204)
- self.assert_status_map(controller.GET,
- (404, 503, 404), 204)
-
- # We always return 503 if no majority between 4xx, 3xx or 2xx found
- self.assert_status_map(controller.GET,
- (500, 500, 400), 503)
-
- def test_HEAD(self):
- # Same behaviour as GET
- with save_globals():
- controller = proxy_server.AccountController(self.app, 'a')
- self.assert_status_map(controller.HEAD, (200,), 200, 200)
- self.assert_status_map(controller.HEAD, (503, 200), 200, 200)
- self.assert_status_map(controller.HEAD, (503, 503, 200), 200, 200)
- self.assert_status_map(controller.HEAD, (204,), 204, 204)
- self.assert_status_map(controller.HEAD, (503, 204), 204, 204)
- self.assert_status_map(controller.HEAD, (204, 503, 503), 204, 204)
- self.assert_status_map(controller.HEAD, (204,), 204, 204)
- self.assert_status_map(controller.HEAD, (404, 404, 404), 404, 404)
- self.assert_status_map(controller.HEAD, (404, 404, 200), 200, 200)
- self.assert_status_map(controller.HEAD, (404, 200), 200, 200)
- self.assert_status_map(controller.HEAD, (404, 404, 503), 404, 404)
- self.assert_status_map(controller.HEAD, (404, 503, 503), 503)
- self.assert_status_map(controller.HEAD, (404, 503, 204), 204, 204)
-
- def test_HEAD_autocreate(self):
- # Same behaviour as GET
- with save_globals():
- controller = proxy_server.AccountController(self.app, 'a')
- self.app.memcache = FakeMemcacheReturnsNone()
- self.assertFalse(self.app.account_autocreate)
- self.assert_status_map(controller.HEAD,
- (404, 404, 404), 404)
- controller.app.account_autocreate = True
- self.assert_status_map(controller.HEAD,
- (404, 404, 404), 204)
- self.assert_status_map(controller.HEAD,
- (500, 404, 404), 204)
- # We always return 503 if no majority between 4xx, 3xx or 2xx found
- self.assert_status_map(controller.HEAD,
- (500, 500, 400), 503)
-
- def test_POST_autocreate(self):
- with save_globals():
- controller = proxy_server.AccountController(self.app, 'a')
- self.app.memcache = FakeMemcacheReturnsNone()
- # first test with autocreate being False
- self.assertFalse(self.app.account_autocreate)
- self.assert_status_map(controller.POST,
- (404, 404, 404), 404)
- # next turn it on and test account being created than updated
- controller.app.account_autocreate = True
- self.assert_status_map(
- controller.POST,
- (404, 404, 404, 202, 202, 202, 201, 201, 201), 201)
- # account_info PUT account POST account
- self.assert_status_map(
- controller.POST,
- (404, 404, 503, 201, 201, 503, 204, 204, 504), 204)
- # what if create fails
- self.assert_status_map(
- controller.POST,
- (404, 404, 404, 403, 403, 403, 400, 400, 400), 400)
-
- def test_POST_autocreate_with_sysmeta(self):
- with save_globals():
- controller = proxy_server.AccountController(self.app, 'a')
- self.app.memcache = FakeMemcacheReturnsNone()
- # first test with autocreate being False
- self.assertFalse(self.app.account_autocreate)
- self.assert_status_map(controller.POST,
- (404, 404, 404), 404)
- # next turn it on and test account being created than updated
- controller.app.account_autocreate = True
- calls = []
- callback = _make_callback_func(calls)
- key, value = 'X-Account-Sysmeta-Blah', 'something'
- headers = {key: value}
- self.assert_status_map(
- controller.POST,
- (404, 404, 404, 202, 202, 202, 201, 201, 201), 201,
- # POST , autocreate PUT, POST again
- headers=headers,
- give_connect=callback)
- self.assertEqual(9, len(calls))
- for call in calls:
- self.assertIn(key, call['headers'],
- '%s call, key %s missing in headers %s' %
- (call['method'], key, call['headers']))
- self.assertEqual(value, call['headers'][key])
-
- def test_connection_refused(self):
- self.app.account_ring.get_nodes('account')
- for dev in self.app.account_ring.devs:
- dev['ip'] = '127.0.0.1'
- dev['port'] = 1 # can't connect on this port
- controller = proxy_server.AccountController(self.app, 'account')
- req = Request.blank('/v1/account', environ={'REQUEST_METHOD': 'HEAD'})
- self.app.update_request(req)
- resp = controller.HEAD(req)
- self.assertEqual(resp.status_int, 503)
-
- def test_other_socket_error(self):
- self.app.account_ring.get_nodes('account')
- for dev in self.app.account_ring.devs:
- dev['ip'] = '127.0.0.1'
- dev['port'] = -1 # invalid port number
- controller = proxy_server.AccountController(self.app, 'account')
- req = Request.blank('/v1/account', environ={'REQUEST_METHOD': 'HEAD'})
- self.app.update_request(req)
- resp = controller.HEAD(req)
- self.assertEqual(resp.status_int, 503)
-
- def test_response_get_accept_ranges_header(self):
- with save_globals():
- set_http_connect(200, 200, body='{}')
- controller = proxy_server.AccountController(self.app, 'account')
- req = Request.blank('/v1/a?format=json')
- self.app.update_request(req)
- res = controller.GET(req)
- self.assertIn('accept-ranges', res.headers)
- self.assertEqual(res.headers['accept-ranges'], 'bytes')
-
- def test_response_head_accept_ranges_header(self):
- with save_globals():
- set_http_connect(200, 200, body='{}')
- controller = proxy_server.AccountController(self.app, 'account')
- req = Request.blank('/v1/a?format=json')
- self.app.update_request(req)
- res = controller.HEAD(req)
- res.body
- self.assertIn('accept-ranges', res.headers)
- self.assertEqual(res.headers['accept-ranges'], 'bytes')
-
- def test_PUT(self):
- with save_globals():
- controller = proxy_server.AccountController(self.app, 'account')
-
- def test_status_map(statuses, expected, **kwargs):
- set_http_connect(*statuses, **kwargs)
- self.app.memcache.store = {}
- req = Request.blank('/v1/a', {})
- req.content_length = 0
- self.app.update_request(req)
- res = controller.PUT(req)
- expected = str(expected)
- self.assertEqual(res.status[:len(expected)], expected)
- test_status_map((201, 201, 201), 405)
- self.app.allow_account_management = True
- test_status_map((201, 201, 201), 201)
- test_status_map((201, 201, 500), 201)
- test_status_map((201, 500, 500), 503)
- test_status_map((204, 500, 404), 503)
-
- def test_PUT_max_account_name_length(self):
- with save_globals():
- self.app.allow_account_management = True
- limit = constraints.MAX_ACCOUNT_NAME_LENGTH
- controller = proxy_server.AccountController(self.app, '1' * limit)
- self.assert_status_map(controller.PUT, (201, 201, 201), 201)
- controller = proxy_server.AccountController(
- self.app, '2' * (limit + 1))
- self.assert_status_map(controller.PUT, (201, 201, 201), 400)
-
- def test_PUT_connect_exceptions(self):
- with save_globals():
- self.app.allow_account_management = True
- controller = proxy_server.AccountController(self.app, 'account')
- self.assert_status_map(controller.PUT, (201, 201, -1), 201)
- self.assert_status_map(controller.PUT, (201, -1, -1), 503)
- self.assert_status_map(controller.PUT, (503, 503, -1), 503)
-
- def test_PUT_status(self):
- with save_globals():
- self.app.allow_account_management = True
- controller = proxy_server.AccountController(self.app, 'account')
- self.assert_status_map(controller.PUT, (201, 201, 202), 202)
-
- def test_PUT_metadata(self):
- self.metadata_helper('PUT')
-
- def test_POST_metadata(self):
- self.metadata_helper('POST')
-
- def metadata_helper(self, method):
- for test_header, test_value in (
- ('X-Account-Meta-TestHeader', 'TestValue'),
- ('X-Account-Meta-TestHeader', ''),
- ('X-Remove-Account-Meta-TestHeader', 'anything')):
- test_errors = []
-
- def test_connect(ipaddr, port, device, partition, method, path,
- headers=None, query_string=None):
- if path == '/a':
- find_header = test_header
- find_value = test_value
- if find_header.lower().startswith('x-remove-'):
- find_header = \
- find_header.lower().replace('-remove', '', 1)
- find_value = ''
- for k, v in headers.items():
- if k.lower() == find_header.lower() and \
- v == find_value:
- break
- else:
- test_errors.append('%s: %s not in %s' %
- (find_header, find_value, headers))
- with save_globals():
- self.app.allow_account_management = True
- controller = \
- proxy_server.AccountController(self.app, 'a')
- set_http_connect(201, 201, 201, give_connect=test_connect)
- req = Request.blank('/v1/a/c',
- environ={'REQUEST_METHOD': method},
- headers={test_header: test_value})
- self.app.update_request(req)
- getattr(controller, method)(req)
- self.assertEqual(test_errors, [])
-
- def test_PUT_bad_metadata(self):
- self.bad_metadata_helper('PUT')
-
- def test_POST_bad_metadata(self):
- self.bad_metadata_helper('POST')
-
- def bad_metadata_helper(self, method):
- with save_globals():
- self.app.allow_account_management = True
- controller = proxy_server.AccountController(self.app, 'a')
- set_http_connect(200, 201, 201, 201)
- req = Request.blank('/v1/a/c', environ={'REQUEST_METHOD': method})
- self.app.update_request(req)
- resp = getattr(controller, method)(req)
- self.assertEqual(resp.status_int, 201)
-
- set_http_connect(201, 201, 201)
- req = Request.blank('/v1/a/c', environ={'REQUEST_METHOD': method},
- headers={'X-Account-Meta-' +
- ('a' * constraints.MAX_META_NAME_LENGTH): 'v'})
- self.app.update_request(req)
- resp = getattr(controller, method)(req)
- self.assertEqual(resp.status_int, 201)
- set_http_connect(201, 201, 201)
- req = Request.blank(
- '/v1/a/c', environ={'REQUEST_METHOD': method},
- headers={'X-Account-Meta-' +
- ('a' * (constraints.MAX_META_NAME_LENGTH + 1)): 'v'})
- self.app.update_request(req)
- resp = getattr(controller, method)(req)
- self.assertEqual(resp.status_int, 400)
-
- set_http_connect(201, 201, 201)
- req = Request.blank('/v1/a/c', environ={'REQUEST_METHOD': method},
- headers={'X-Account-Meta-Too-Long':
- 'a' * constraints.MAX_META_VALUE_LENGTH})
- self.app.update_request(req)
- resp = getattr(controller, method)(req)
- self.assertEqual(resp.status_int, 201)
- set_http_connect(201, 201, 201)
- req = Request.blank('/v1/a/c', environ={'REQUEST_METHOD': method},
- headers={'X-Account-Meta-Too-Long':
- 'a' * (constraints.MAX_META_VALUE_LENGTH + 1)})
- self.app.update_request(req)
- resp = getattr(controller, method)(req)
- self.assertEqual(resp.status_int, 400)
-
- set_http_connect(201, 201, 201)
- headers = {}
- for x in range(constraints.MAX_META_COUNT):
- headers['X-Account-Meta-%d' % x] = 'v'
- req = Request.blank('/v1/a/c', environ={'REQUEST_METHOD': method},
- headers=headers)
- self.app.update_request(req)
- resp = getattr(controller, method)(req)
- self.assertEqual(resp.status_int, 201)
- set_http_connect(201, 201, 201)
- headers = {}
- for x in range(constraints.MAX_META_COUNT + 1):
- headers['X-Account-Meta-%d' % x] = 'v'
- req = Request.blank('/v1/a/c', environ={'REQUEST_METHOD': method},
- headers=headers)
- self.app.update_request(req)
- resp = getattr(controller, method)(req)
- self.assertEqual(resp.status_int, 400)
-
- set_http_connect(201, 201, 201)
- headers = {}
- header_value = 'a' * constraints.MAX_META_VALUE_LENGTH
- size = 0
- x = 0
- while size < (constraints.MAX_META_OVERALL_SIZE - 4
- - constraints.MAX_META_VALUE_LENGTH):
- size += 4 + constraints.MAX_META_VALUE_LENGTH
- headers['X-Account-Meta-%04d' % x] = header_value
- x += 1
- if constraints.MAX_META_OVERALL_SIZE - size > 1:
- headers['X-Account-Meta-a'] = \
- 'a' * (constraints.MAX_META_OVERALL_SIZE - size - 1)
- req = Request.blank('/v1/a/c', environ={'REQUEST_METHOD': method},
- headers=headers)
- self.app.update_request(req)
- resp = getattr(controller, method)(req)
- self.assertEqual(resp.status_int, 201)
- set_http_connect(201, 201, 201)
- headers['X-Account-Meta-a'] = \
- 'a' * (constraints.MAX_META_OVERALL_SIZE - size)
- req = Request.blank('/v1/a/c', environ={'REQUEST_METHOD': method},
- headers=headers)
- self.app.update_request(req)
- resp = getattr(controller, method)(req)
- self.assertEqual(resp.status_int, 400)
-
- def test_DELETE(self):
- with save_globals():
- controller = proxy_server.AccountController(self.app, 'account')
-
- def test_status_map(statuses, expected, **kwargs):
- set_http_connect(*statuses, **kwargs)
- self.app.memcache.store = {}
- req = Request.blank('/v1/a', {'REQUEST_METHOD': 'DELETE'})
- req.content_length = 0
- self.app.update_request(req)
- res = controller.DELETE(req)
- expected = str(expected)
- self.assertEqual(res.status[:len(expected)], expected)
- test_status_map((201, 201, 201), 405)
- self.app.allow_account_management = True
- test_status_map((201, 201, 201), 201)
- test_status_map((201, 201, 500), 201)
- test_status_map((201, 500, 500), 503)
- test_status_map((204, 500, 404), 503)
-
- def test_DELETE_with_query_string(self):
- # Extra safety in case someone typos a query string for an
- # account-level DELETE request that was really meant to be caught by
- # some middleware.
- with save_globals():
- controller = proxy_server.AccountController(self.app, 'account')
-
- def test_status_map(statuses, expected, **kwargs):
- set_http_connect(*statuses, **kwargs)
- self.app.memcache.store = {}
- req = Request.blank('/v1/a?whoops',
- environ={'REQUEST_METHOD': 'DELETE'})
- req.content_length = 0
- self.app.update_request(req)
- res = controller.DELETE(req)
- expected = str(expected)
- self.assertEqual(res.status[:len(expected)], expected)
- test_status_map((201, 201, 201), 400)
- self.app.allow_account_management = True
- test_status_map((201, 201, 201), 400)
- test_status_map((201, 201, 500), 400)
- test_status_map((201, 500, 500), 400)
- test_status_map((204, 500, 404), 400)
-
-
-@patch_policies([StoragePolicy(0, 'zero', True, object_ring=FakeRing())])
-class TestAccountControllerFakeGetResponse(unittest.TestCase):
- """
- Test all the faked-out GET responses for accounts that don't exist. They
- have to match the responses for empty accounts that really exist.
- """
- def setUp(self):
- conf = {'account_autocreate': 'yes'}
- self.app = proxy_server.Application(conf, FakeMemcache(),
- account_ring=FakeRing(),
- container_ring=FakeRing())
- self.app.memcache = FakeMemcacheReturnsNone()
-
- def test_GET_autocreate_accept_json(self):
- with save_globals():
- set_http_connect(*([404] * 100)) # nonexistent: all backends 404
- req = Request.blank(
- '/v1/a', headers={'Accept': 'application/json'},
- environ={'REQUEST_METHOD': 'GET',
- 'PATH_INFO': '/v1/a'})
- resp = req.get_response(self.app)
- self.assertEqual(200, resp.status_int)
- self.assertEqual('application/json; charset=utf-8',
- resp.headers['Content-Type'])
- self.assertEqual("[]", resp.body)
-
- def test_GET_autocreate_format_json(self):
- with save_globals():
- set_http_connect(*([404] * 100)) # nonexistent: all backends 404
- req = Request.blank('/v1/a?format=json',
- environ={'REQUEST_METHOD': 'GET',
- 'PATH_INFO': '/v1/a',
- 'QUERY_STRING': 'format=json'})
- resp = req.get_response(self.app)
- self.assertEqual(200, resp.status_int)
- self.assertEqual('application/json; charset=utf-8',
- resp.headers['Content-Type'])
- self.assertEqual("[]", resp.body)
-
- def test_GET_autocreate_accept_xml(self):
- with save_globals():
- set_http_connect(*([404] * 100)) # nonexistent: all backends 404
- req = Request.blank('/v1/a', headers={"Accept": "text/xml"},
- environ={'REQUEST_METHOD': 'GET',
- 'PATH_INFO': '/v1/a'})
-
- resp = req.get_response(self.app)
- self.assertEqual(200, resp.status_int)
-
- self.assertEqual('text/xml; charset=utf-8',
- resp.headers['Content-Type'])
- empty_xml_listing = ('<?xml version="1.0" encoding="UTF-8"?>\n'
- '<account name="a">\n</account>')
- self.assertEqual(empty_xml_listing, resp.body)
-
- def test_GET_autocreate_format_xml(self):
- with save_globals():
- set_http_connect(*([404] * 100)) # nonexistent: all backends 404
- req = Request.blank('/v1/a?format=xml',
- environ={'REQUEST_METHOD': 'GET',
- 'PATH_INFO': '/v1/a',
- 'QUERY_STRING': 'format=xml'})
- resp = req.get_response(self.app)
- self.assertEqual(200, resp.status_int)
- self.assertEqual('application/xml; charset=utf-8',
- resp.headers['Content-Type'])
- empty_xml_listing = ('<?xml version="1.0" encoding="UTF-8"?>\n'
- '<account name="a">\n</account>')
- self.assertEqual(empty_xml_listing, resp.body)
-
- def test_GET_autocreate_accept_unknown(self):
- with save_globals():
- set_http_connect(*([404] * 100)) # nonexistent: all backends 404
- req = Request.blank('/v1/a', headers={"Accept": "mystery/meat"},
- environ={'REQUEST_METHOD': 'GET',
- 'PATH_INFO': '/v1/a'})
- resp = req.get_response(self.app)
- self.assertEqual(406, resp.status_int)
-
- def test_GET_autocreate_format_invalid_utf8(self):
- with save_globals():
- set_http_connect(*([404] * 100)) # nonexistent: all backends 404
- req = Request.blank('/v1/a?format=\xff\xfe',
- environ={'REQUEST_METHOD': 'GET',
- 'PATH_INFO': '/v1/a',
- 'QUERY_STRING': 'format=\xff\xfe'})
- 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())
-
- 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.assertNotIn(header, 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.assertIn(header, 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())
- 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.assertIsNone(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.assertIsNone(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):
def __init__(self):
--
2.7.4
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment