OpenStack Storage (Swift)
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
swift/test/unit/proxy/test_server.py

10572 lines
455 KiB

# -*- coding: utf-8 -*-
# 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.
from __future__ import print_function
import email.parser
import logging
import json
import math
import os
import posix
import socket
import sys
import traceback
import unittest
from contextlib import contextmanager
from shutil import rmtree, copyfile, move
import gc
import time
from textwrap import dedent
from hashlib import md5
import collections
from pyeclib.ec_iface import ECDriverError
from tempfile import mkdtemp, NamedTemporaryFile
import weakref
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, 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, parse_qsl
from test import listen_zero
from test.unit import (
connect_tcp, readuntil2crlfs, FakeLogger, fake_http_connect, FakeRing,
FakeMemcache, debug_logger, patch_policies, write_fake_ring,
mocked_http_conn, DEFAULT_TEST_EC_TYPE, make_timestamp_iter,
skip_if_no_xattrs)
from test.unit.helpers import setup_servers, teardown_servers
from swift.proxy import server as proxy_server
from swift.proxy.controllers.obj import ReplicatedObjectController
from swift.obj import server as object_server
from swift.common.bufferedhttp import BufferedHTTPResponse
from swift.common.middleware import proxy_logging, versioned_writes, \
copy, listing_formats
from swift.common.middleware.acl import parse_acl, format_acl
from swift.common.exceptions import ChunkReadTimeout, DiskFileNotExist, \
APIVersionError, ChunkWriteTimeout, ChunkReadError
from swift.common import utils, constraints
from swift.common.utils import hash_path, storage_directory, \
parse_content_type, parse_mime_headers, \
iter_multipart_mime_documents, public, mkdirs, NullLogger
from swift.common.wsgi import monkey_patch_mimetools, loadapp, ConfigString
from swift.proxy.controllers import base as proxy_base
from swift.proxy.controllers.base import get_cache_key, cors_validation, \
get_account_info, get_container_info
import swift.proxy.controllers
import swift.proxy.controllers.obj
from swift.common.header_key_dict import HeaderKeyDict
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
# mocks
logging.getLogger().addHandler(logging.StreamHandler(sys.stdout))
STATIC_TIME = time.time()
_test_context = _test_servers = _test_sockets = _testdir = \
_test_POLICIES = None
def do_setup(object_server):
# setup test context and break out some globals for convenience
global _test_context, _testdir, _test_servers, _test_sockets, \
_test_POLICIES
monkey_patch_mimetools()
_test_context = setup_servers(object_server)
_testdir = _test_context["testdir"]
_test_servers = _test_context["test_servers"]
_test_sockets = _test_context["test_sockets"]
_test_POLICIES = _test_context["test_POLICIES"]
def unpatch_policies(f):
"""
This will unset a TestCase level patch_policies to use the module level
policies setup for the _test_servers instead.
N.B. You should NEVER modify the _test_server policies or rings during a
test because they persist for the life of the entire module!
"""
@functools.wraps(f)
def wrapper(*args, **kwargs):
with patch_policies(_test_POLICIES):
return f(*args, **kwargs)
return wrapper
def setUpModule():
do_setup(object_server)
def tearDownModule():
teardown_servers(_test_context)
def sortHeaderNames(headerNames):
"""
Return the given string of header names sorted.
headerName: a comma-delimited list of header names
"""
headers = [a.strip() for a in headerNames.split(',') if a.strip()]
headers.sort()
return ', '.join(headers)
def parse_headers_string(headers_str):
headers_dict = HeaderKeyDict()
for line in headers_str.split('\r\n'):
if ': ' in line:
header, value = line.split(': ', 1)
headers_dict[header] = value
return headers_dict
def node_error_count(proxy_app, ring_node):
# Reach into the proxy's internals to get the error count for a
# particular node
node_key = proxy_app._error_limit_node_key(ring_node)
return proxy_app._error_limiting.get(node_key, {}).get('errors', 0)
def node_last_error(proxy_app, ring_node):
# Reach into the proxy's internals to get the last error for a
# particular node
node_key = proxy_app._error_limit_node_key(ring_node)
return proxy_app._error_limiting.get(node_key, {}).get('last_error')
def set_node_errors(proxy_app, ring_node, value, last_error):
# Set the node's error count to value
node_key = proxy_app._error_limit_node_key(ring_node)
stats = proxy_app._error_limiting.setdefault(node_key, {})
stats['errors'] = value
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
wrapped function, based on whether MAX_FILE_SIZE exceeds the
sys.maxsize limit on the system running the tests.
This allows successful testing on 32 bit systems.
"""
@functools.wraps(f)
def wrapper(*args, **kwargs):
test_max_file_size = constraints.MAX_FILE_SIZE
if constraints.MAX_FILE_SIZE >= sys.maxsize:
test_max_file_size = (2 ** 30 + 2)
with mock.patch.object(constraints, 'MAX_FILE_SIZE',
test_max_file_size):
return f(*args, **kwargs)
return wrapper
# tests
class TestController(unittest.TestCase):
def setUp(self):
skip_if_no_xattrs()
self.account_ring = FakeRing()
self.container_ring = FakeRing()
self.memcache = FakeMemcache()
app = proxy_server.Application(None, self.memcache,
account_ring=self.account_ring,
container_ring=self.container_ring)
self.controller = swift.proxy.controllers.Controller(app)
class FakeReq(object):
def __init__(self):
self.url = "/foo/bar"
self.method = "METHOD"
def as_referer(self):
return self.method + ' ' + self.url
self.account = 'some_account'
self.container = 'some_container'
self.request = FakeReq()
self.read_acl = 'read_acl'
self.write_acl = 'write_acl'
def test_transfer_headers(self):
src_headers = {'x-remove-base-meta-owner': 'x',
'x-base-meta-size': '151M',
'new-owner': 'Kun'}
dst_headers = {'x-base-meta-owner': 'Gareth',
'x-base-meta-size': '150M'}
self.controller.transfer_headers(src_headers, dst_headers)
expected_headers = {'x-base-meta-owner': '',
'x-base-meta-size': '151M'}
self.assertEqual(dst_headers, expected_headers)
def check_account_info_return(self, partition, nodes, is_none=False):
if is_none:
p, n = None, None
else:
p, n = self.account_ring.get_nodes(self.account)
self.assertEqual(p, partition)
self.assertEqual(n, nodes)
def test_account_info_container_count(self):
with save_globals():
set_http_connect(200, count=123)
partition, nodes, count = \
self.controller.account_info(self.account)
self.assertEqual(count, 123)
with save_globals():
set_http_connect(200, count='123')
partition, nodes, count = \
self.controller.account_info(self.account)
self.assertEqual(count, 123)
with save_globals():
cache_key = get_cache_key(self.account)
account_info = {'status': 200, 'container_count': 1234}
self.memcache.set(cache_key, account_info)
partition, nodes, count = \
self.controller.account_info(self.account)
self.assertEqual(count, 1234)
with save_globals():
cache_key = get_cache_key(self.account)
account_info = {'status': 200, 'container_count': '1234'}
self.memcache.set(cache_key, account_info)
partition, nodes, count = \
self.controller.account_info(self.account)
self.assertEqual(count, 1234)
def test_make_requests(self):
with save_globals():
set_http_connect(200)
partition, nodes, count = \
self.controller.account_info(self.account, self.request)
set_http_connect(201, raise_timeout_exc=True)
self.controller._make_request(
nodes, partition, 'POST', '/', '', '',
self.controller.app.logger.thread_locals)
# tests if 200 is cached and used
def test_account_info_200(self):
with save_globals():
set_http_connect(200)
partition, nodes, count = \
self.controller.account_info(self.account, self.request)
self.check_account_info_return(partition, nodes)
self.assertEqual(count, 12345)
# Test the internal representation in memcache
# 'container_count' changed from int to str
cache_key = get_cache_key(self.account)
container_info = {'status': 200,
'account_really_exists': True,
'container_count': '12345',
'total_object_count': None,
'bytes': None,
'storage_policies': {p.idx: {
'container_count': 0,
'object_count': 0,
'bytes': 0} for p in POLICIES},
'meta': {},
'sysmeta': {}}
self.assertEqual(container_info,
self.memcache.get(cache_key))
set_http_connect()
partition, nodes, count = \
self.controller.account_info(self.account, self.request)
self.check_account_info_return(partition, nodes)
self.assertEqual(count, 12345)
# tests if 404 is cached and used
def test_account_info_404(self):
with save_globals():
set_http_connect(404, 404, 404)
partition, nodes, count = \
self.controller.account_info(self.account, self.request)
self.check_account_info_return(partition, nodes, True)
self.assertIsNone(count)
# Test the internal representation in memcache
# 'container_count' changed from 0 to None
cache_key = get_cache_key(self.account)
account_info = {'status': 404,
'container_count': None, # internally keep None
'total_object_count': None,
'bytes': None,
'storage_policies': {p.idx: {
'container_count': 0,
'object_count': 0,
'bytes': 0} for p in POLICIES},
'meta': {},
'sysmeta': {}}
self.assertEqual(account_info,
self.memcache.get(cache_key))
set_http_connect()
partition, nodes, count = \
self.controller.account_info(self.account, self.request)
self.check_account_info_return(partition, nodes, True)
self.assertIsNone(count)
# tests if some http status codes are not cached
def test_account_info_no_cache(self):
def test(*status_list):
set_http_connect(*status_list)
partition, nodes, count = \
self.controller.account_info(self.account, self.request)
self.assertEqual(len(self.memcache.keys()), 0)
self.check_account_info_return(partition, nodes, True)
self.assertIsNone(count)
with save_globals():
# We cache if we have two 404 responses - fail if only one
test(503, 503, 404)
test(504, 404, 503)
test(404, 507, 503)
test(503, 503, 503)
def test_account_info_no_account(self):
with save_globals():
self.memcache.store = {}
set_http_connect(404, 404, 404)
partition, nodes, count = \
self.controller.account_info(self.account, self.request)
self.check_account_info_return(partition, nodes, is_none=True)
self.assertIsNone(count)
def check_container_info_return(self, ret, is_none=False):
if is_none:
partition, nodes, read_acl, write_acl = None, None, None, None
else:
partition, nodes = self.container_ring.get_nodes(self.account,
self.container)
read_acl, write_acl = self.read_acl, self.write_acl
self.assertEqual(partition, ret['partition'])
self.assertEqual(nodes, ret['nodes'])
self.assertEqual(read_acl, ret['read_acl'])
self.assertEqual(write_acl, ret['write_acl'])
def test_container_info_invalid_account(self):
def account_info(self, account, request, autocreate=False):
return None, None
with save_globals():
swift.proxy.controllers.Controller.account_info = account_info
ret = self.controller.container_info(self.account,
self.container,
self.request)
self.check_container_info_return(ret, True)
# tests if 200 is cached and used
def test_container_info_200(self):
with save_globals():
headers = {'x-container-read': self.read_acl,
'x-container-write': self.write_acl}
set_http_connect(200, # account_info is found
200, headers=headers) # container_info is found
ret = self.controller.container_info(
self.account, self.container, self.request)
self.check_container_info_return(ret)
cache_key = get_cache_key(self.account, self.container)
cache_value = self.memcache.get(cache_key)
self.assertIsInstance(cache_value, dict)
self.assertEqual(200, cache_value.get('status'))
set_http_connect()
ret = self.controller.container_info(
self.account, self.container, self.request)
self.check_container_info_return(ret)
# tests if 404 is cached and used
def test_container_info_404(self):
def account_info(self, account, request):
return True, True, 0
with save_globals():
set_http_connect(503, 204, # account_info found
504, 404, 404) # container_info 'NotFound'
ret = self.controller.container_info(
self.account, self.container, self.request)
self.check_container_info_return(ret, True)
cache_key = get_cache_key(self.account, self.container)
cache_value = self.memcache.get(cache_key)
self.assertIsInstance(cache_value, dict)
self.assertEqual(404, cache_value.get('status'))
set_http_connect()
ret = self.controller.container_info(
self.account, self.container, self.request)
self.check_container_info_return(ret, True)
set_http_connect(503, 404, 404) # account_info 'NotFound'
ret = self.controller.container_info(
self.account, self.container, self.request)
self.check_container_info_return(ret, True)
cache_key = get_cache_key(self.account, self.container)
cache_value = self.memcache.get(cache_key)
self.assertIsInstance(cache_value, dict)
self.assertEqual(404, cache_value.get('status'))
set_http_connect()
ret = self.controller.container_info(
self.account, self.container, self.request)
self.check_container_info_return(ret, True)
# tests if some http status codes are not cached
def test_container_info_no_cache(self):
def test(*status_list):
set_http_connect(*status_list)
ret = self.controller.container_info(
self.account, self.container, self.request)
self.assertEqual(len(self.memcache.keys()), 0)
self.check_container_info_return(ret, True)
with save_globals():
# We cache if we have two 404 responses - fail if only one
test(503, 503, 404)
test(504, 404, 503)
test(404, 507, 503)
test(503, 503, 503)
def test_get_account_info_returns_values_as_strings(self):
app = mock.MagicMock()
app.memcache = mock.MagicMock()
app.memcache.get = mock.MagicMock()
app.memcache.get.return_value = {
u'foo': u'\u2603',
u'meta': {u'bar': u'\u2603'},
u'sysmeta': {u'baz': u'\u2603'}}
env = {'PATH_INFO': '/v1/a'}
ai = get_account_info(env, app)
# Test info is returned as strings
self.assertEqual(ai.get('foo'), '\xe2\x98\x83')
self.assertIsInstance(ai.get('foo'), str)
# Test info['meta'] is returned as strings
m = ai.get('meta', {})
self.assertEqual(m.get('bar'), '\xe2\x98\x83')
self.assertIsInstance(m.get('bar'), str)
# Test info['sysmeta'] is returned as strings
m = ai.get('sysmeta', {})
self.assertEqual(m.get('baz'), '\xe2\x98\x83')
self.assertIsInstance(m.get('baz'), str)
def test_get_container_info_returns_values_as_strings(self):
app = mock.MagicMock()
app.memcache = mock.MagicMock()
app.memcache.get = mock.MagicMock()
app.memcache.get.return_value = {
u'foo': u'\u2603',
u'meta': {u'bar': u'\u2603'},
u'sysmeta': {u'baz': u'\u2603'},
u'cors': {u'expose_headers': u'\u2603'}}
env = {'PATH_INFO': '/v1/a/c'}
ci = get_container_info(env, app)
# Test info is returned as strings
self.assertEqual(ci.get('foo'), '\xe2\x98\x83')
self.assertIsInstance(ci.get('foo'), str)
# Test info['meta'] is returned as strings
m = ci.get('meta', {})
self.assertEqual(m.get('bar'), '\xe2\x98\x83')
self.assertIsInstance(m.get('bar'), str)
# Test info['sysmeta'] is returned as strings
m = ci.get('sysmeta', {})
self.assertEqual(m.get('baz'), '\xe2\x98\x83')
self.assertIsInstance(m.get('baz'), str)
# Test info['cors'] is returned as strings
m = ci.get('cors', {})
self.assertEqual(m.get('expose_headers'), '\xe2\x98\x83')
self.assertIsInstance(m.get('expose_headers'), str)
@patch_policies([StoragePolicy(0, 'zero', True, object_ring=FakeRing())])
class TestProxyServerConfiguration(unittest.TestCase):
def _make_app(self, conf):
# helper function to instantiate a proxy server instance
return proxy_server.Application(conf, FakeMemcache(),
container_ring=FakeRing(),
account_ring=FakeRing())
def test_node_timeout(self):
# later config should be extended to assert more config options
app = self._make_app({'node_timeout': '3.5',
'recoverable_node_timeout': '1.5'})
self.assertEqual(app.node_timeout, 3.5)
self.assertEqual(app.recoverable_node_timeout, 1.5)
def test_cors_options(self):
# check defaults
app = self._make_app({})
self.assertFalse(app.cors_allow_origin)
self.assertFalse(app.cors_expose_headers)
self.assertTrue(app.strict_cors_mode)
# check custom configs
app = self._make_app({
'cors_allow_origin': '',
'cors_expose_headers': '',
'strict_cors_mode': 'True'})
self.assertTrue(app.strict_cors_mode)
app = self._make_app({
'cors_allow_origin': ' http://X.com,http://Y.com ,, http://Z.com',
'cors_expose_headers': ' custom1,,, custom2,custom3,,',
'strict_cors_mode': 'False'})
self.assertEqual({'http://X.com', 'http://Y.com', 'http://Z.com'},
set(app.cors_allow_origin))
self.assertEqual({'custom1', 'custom2', 'custom3'},
set(app.cors_expose_headers))
self.assertFalse(app.strict_cors_mode)
@patch_policies([StoragePolicy(0, 'zero', True, object_ring=FakeRing())])
class TestProxyServer(unittest.TestCase):
def test_get_object_ring(self):
baseapp = proxy_server.Application({},
FakeMemcache(),
container_ring=FakeRing(),
account_ring=FakeRing())
with patch_policies([
StoragePolicy(0, 'a', False, object_ring=123),
StoragePolicy(1, 'b', True, object_ring=456),
StoragePolicy(2, 'd', False, object_ring=789)
]):
# None means legacy so always use policy 0
ring = baseapp.get_object_ring(None)
self.assertEqual(ring, 123)
ring = baseapp.get_object_ring('')
self.assertEqual(ring, 123)
ring = baseapp.get_object_ring('0')
self.assertEqual(ring, 123)
ring = baseapp.get_object_ring('1')
self.assertEqual(ring, 456)
ring = baseapp.get_object_ring('2')
self.assertEqual(ring, 789)
# illegal values
self.assertRaises(ValueError, baseapp.get_object_ring, '99')
self.assertRaises(ValueError, baseapp.get_object_ring, 'asdf')
def test_unhandled_exception(self):
class MyApp(proxy_server.Application):
def get_controller(self, path):
raise Exception('this shouldn\'t be caught')
app = MyApp(None, FakeMemcache(), account_ring=FakeRing(),
container_ring=FakeRing())
req = Request.blank('/v1/account', environ={'REQUEST_METHOD': 'HEAD'})
app.update_request(req)
resp = app.handle_request(req)
self.assertEqual(resp.status_int, 500)
def test_internal_method_request(self):
baseapp = proxy_server.Application({},
FakeMemcache(),
container_ring=FakeRing(),
account_ring=FakeRing())
resp = baseapp.handle_request(
Request.blank('/v1/a', environ={'REQUEST_METHOD': '__init__'}))
self.assertEqual(resp.status, '405 Method Not Allowed')
def test_inexistent_method_request(self):
baseapp = proxy_server.Application({},
FakeMemcache(),
container_ring=FakeRing(),
account_ring=FakeRing())
resp = baseapp.handle_request(
Request.blank('/v1/a', environ={'REQUEST_METHOD': '!invalid'}))
self.assertEqual(resp.status, '405 Method Not Allowed')
def test_calls_authorize_allow(self):
called = [False]
def authorize(req):
called[0] = True
with save_globals():
set_http_connect(200)
app = proxy_server.Application(None, FakeMemcache(),
account_ring=FakeRing(),
container_ring=FakeRing())
req = Request.blank('/v1/a')
req.environ['swift.authorize'] = authorize
app.update_request(req)
app.handle_request(req)
self.assertTrue(called[0])
def test_calls_authorize_deny(self):
called = [False]
def authorize(req):
called[0] = True
return HTTPUnauthorized(request=req)
app = proxy_server.Application(None, FakeMemcache(),
account_ring=FakeRing(),
container_ring=FakeRing())
req = Request.blank('/v1/a')
req.environ['swift.authorize'] = authorize
app.update_request(req)
app.handle_request(req)
self.assertTrue(called[0])
def test_negative_content_length(self):
swift_dir = mkdtemp()
try:
baseapp = proxy_server.Application({'swift_dir': swift_dir},
FakeMemcache(), FakeLogger(),
FakeRing(), FakeRing())
resp = baseapp.handle_request(
Request.blank('/', environ={'CONTENT_LENGTH': '-1'}))
self.assertEqual(resp.status, '400 Bad Request')
self.assertEqual(resp.body, 'Invalid Content-Length')
resp = baseapp.handle_request(
Request.blank('/', environ={'CONTENT_LENGTH': '-123'}))
self.assertEqual(resp.status, '400 Bad Request')
self.assertEqual(resp.body, 'Invalid Content-Length')
finally:
rmtree(swift_dir, ignore_errors=True)
def test_adds_transaction_id(self):
swift_dir = mkdtemp()
try:
logger = FakeLogger()
baseapp = proxy_server.Application({'swift_dir': swift_dir},
FakeMemcache(), logger,
container_ring=FakeLogger(),
account_ring=FakeRing())
baseapp.handle_request(
Request.blank('/info',
environ={'HTTP_X_TRANS_ID_EXTRA': 'sardine',
'REQUEST_METHOD': 'GET'}))
# This is kind of a hokey way to get the transaction ID; it'd be
# better to examine response headers, but the catch_errors
# middleware is what sets the X-Trans-Id header, and we don't have
# that available here.
self.assertTrue(logger.txn_id.endswith('-sardine'))
finally:
rmtree(swift_dir, ignore_errors=True)
def test_adds_transaction_id_length_limit(self):
swift_dir = mkdtemp()
try:
logger = FakeLogger()
baseapp = proxy_server.Application({'swift_dir': swift_dir},
FakeMemcache(), logger,
container_ring=FakeLogger(),
account_ring=FakeRing())
baseapp.handle_request(
Request.blank('/info',
environ={'HTTP_X_TRANS_ID_EXTRA': 'a' * 1000,
'REQUEST_METHOD': 'GET'}))
self.assertTrue(logger.txn_id.endswith(
'-aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa'))
finally:
rmtree(swift_dir, ignore_errors=True)
def test_denied_host_header(self):
swift_dir = mkdtemp()
try:
baseapp = proxy_server.Application({'swift_dir': swift_dir,
'deny_host_headers':
'invalid_host.com'},
FakeMemcache(),
container_ring=FakeLogger(),
account_ring=FakeRing())
resp = baseapp.handle_request(
Request.blank('/v1/a/c/o',
environ={'HTTP_HOST': 'invalid_host.com'}))
self.assertEqual(resp.status, '403 Forbidden')
finally:
rmtree(swift_dir, ignore_errors=True)
def test_node_timing(self):
baseapp = proxy_server.Application({'sorting_method': 'timing'},
FakeMemcache(),
container_ring=FakeRing(),
account_ring=FakeRing())
self.assertEqual(baseapp.node_timings, {})
req = Request.blank('/v1/account', environ={'REQUEST_METHOD': 'HEAD'})
baseapp.update_request(req)
resp = baseapp.handle_request(req)
self.assertEqual(resp.status_int, 503) # couldn't connect to anything
exp_timings = {}
self.assertEqual(baseapp.node_timings, exp_timings)
times = [time.time()]
exp_timings = {'127.0.0.1': (0.1, times[0] + baseapp.timing_expiry)}
with mock.patch('swift.proxy.server.time', lambda: times.pop(0)):
baseapp.set_node_timing({'ip': '127.0.0.1'}, 0.1)
self.assertEqual(baseapp.node_timings, exp_timings)
nodes = [{'ip': '127.0.0.1'}, {'ip': '127.0.0.2'}, {'ip': '127.0.0.3'}]
with mock.patch('swift.proxy.server.shuffle', lambda l: l):
res = baseapp.sort_nodes(nodes)
exp_sorting = [{'ip': '127.0.0.2'}, {'ip': '127.0.0.3'},
{'ip': '127.0.0.1'}]
self.assertEqual(res, exp_sorting)
def _do_sort_nodes(self, conf, policy_conf, nodes, policy,
node_timings=None):
# Note with shuffling mocked out, sort_nodes will by default return
# nodes in the order they are given
nodes = list(nodes)
conf = dict(conf, policy_config=policy_conf)
baseapp = proxy_server.Application(conf,
FakeMemcache(),
logger=FakeLogger(),
container_ring=FakeRing(),
account_ring=FakeRing())
if node_timings:
for i, n in enumerate(nodes):
baseapp.set_node_timing(n, node_timings[i])
with mock.patch('swift.proxy.server.shuffle', lambda x: x):
app_sorted = baseapp.sort_nodes(nodes, policy)
self.assertFalse(baseapp.logger.get_lines_for_level('warning'))
return baseapp, app_sorted
def test_sort_nodes_default(self):
nodes = [{'region': 0, 'zone': 1, 'ip': '127.0.0.3'},
{'region': 1, 'zone': 1, 'ip': '127.0.0.1'},
{'region': 2, 'zone': 2, 'ip': '127.0.0.2'}]
# sanity check - no affinity conf results in node order unchanged
app, actual = self._do_sort_nodes({}, {}, nodes, None)
self.assertEqual(nodes, actual)
def test_sort_nodes_by_affinity_proxy_server_config(self):
nodes = [{'region': 0, 'zone': 1, 'ip': '127.0.0.3'},
{'region': 1, 'zone': 1, 'ip': '127.0.0.1'},
{'region': 2, 'zone': 2, 'ip': '127.0.0.2'}]
# proxy-server affinity conf is to prefer r2
conf = {'sorting_method': 'affinity', 'read_affinity': 'r2=1'}
app, actual = self._do_sort_nodes(conf, {}, nodes, None)
self.assertEqual([nodes[2], nodes[0], nodes[1]], actual)
app, actual = self._do_sort_nodes(conf, {}, nodes, POLICIES[0])
self.assertEqual([nodes[2], nodes[0], nodes[1]], actual)
# check that node timings are not collected if sorting_method != timing
self.assertFalse(app.sorts_by_timing) # sanity check
self.assertFalse(app.node_timings) # sanity check
# proxy-server affinity conf is to prefer region 1
conf = {'sorting_method': 'affinity', 'read_affinity': 'r1=1'}
app, actual = self._do_sort_nodes(conf, {}, nodes, None)
self.assertEqual([nodes[1], nodes[0], nodes[2]], actual)
app, actual = self._do_sort_nodes(conf, {}, nodes, POLICIES[0])
self.assertEqual([nodes[1], nodes[0], nodes[2]], actual)
@patch_policies([StoragePolicy(0, 'zero', True, object_ring=FakeRing()),
StoragePolicy(1, 'one', False, object_ring=FakeRing())])
def test_sort_nodes_by_affinity_per_policy(self):
nodes = [{'region': 0, 'zone': 1, 'ip': '127.0.0.4'},
{'region': 1, 'zone': 0, 'ip': '127.0.0.3'},
{'region': 2, 'zone': 1, 'ip': '127.0.0.1'},
{'region': 3, 'zone': 0, 'ip': '127.0.0.2'}]
conf = {'sorting_method': 'affinity', 'read_affinity': 'r3=1'}
per_policy = {'0': {'sorting_method': 'affinity',
'read_affinity': 'r1=1'},
'1': {'sorting_method': 'affinity',
'read_affinity': 'r2=1'}}
# policy 0 affinity prefers r1
app, actual = self._do_sort_nodes(conf, per_policy, nodes, POLICIES[0])
self.assertEqual([nodes[1], nodes[0], nodes[2], nodes[3]], actual)
# policy 1 affinity prefers r2
app, actual = self._do_sort_nodes(conf, per_policy, nodes, POLICIES[1])
self.assertEqual([nodes[2], nodes[0], nodes[1], nodes[3]], actual)
# default affinity prefers r3
app, actual = self._do_sort_nodes(conf, per_policy, nodes, None)
self.assertEqual([nodes[3], nodes[0], nodes[1], nodes[2]], actual)
def test_sort_nodes_by_affinity_per_policy_with_no_default(self):
# no proxy-server setting but policy 0 prefers r0
nodes = [{'region': 1, 'zone': 1, 'ip': '127.0.0.1'},
{'region': 0, 'zone': 2, 'ip': '127.0.0.2'}]
conf = {}
per_policy = {'0': {'sorting_method': 'affinity',
'read_affinity': 'r0=0'}}
# policy 0 uses affinity sorting
app, actual = self._do_sort_nodes(conf, per_policy, nodes, POLICIES[0])
self.assertEqual([nodes[1], nodes[0]], actual)
# any other policy will use default sorting
app, actual = self._do_sort_nodes(conf, per_policy, nodes, None)
self.assertEqual(nodes, actual)
def test_sort_nodes_by_affinity_per_policy_inherits(self):
# policy 0 has read_affinity but no sorting_method override,
nodes = [{'region': 1, 'zone': 1, 'ip': '127.0.0.1'},
{'region': 0, 'zone': 2, 'ip': '127.0.0.2'}]
conf = {}
per_policy = {'0': {'read_affinity': 'r0=0'}}
# policy 0 uses the default sorting method instead of affinity sorting
app, actual = self._do_sort_nodes(conf, per_policy, nodes, POLICIES[0])
self.assertEqual(nodes, actual)
# but if proxy-server sorting_method is affinity then policy 0 inherits
conf = {'sorting_method': 'affinity'}
app, actual = self._do_sort_nodes(conf, per_policy, nodes, POLICIES[0])
self.assertEqual([nodes[1], nodes[0]], actual)
def test_sort_nodes_by_affinity_per_policy_overrides(self):
# default setting is to sort by timing but policy 0 uses read affinity
nodes = [{'region': 0, 'zone': 1, 'ip': '127.0.0.3'},
{'region': 1, 'zone': 1, 'ip': '127.0.0.1'},
{'region': 2, 'zone': 2, 'ip': '127.0.0.2'}]
node_timings = [10, 1, 100]
conf = {'sorting_method': 'timing'}
per_policy = {'0': {'sorting_method': 'affinity',
'read_affinity': 'r1=1,r2=2'}}
app, actual = self._do_sort_nodes(conf, per_policy, nodes, POLICIES[0],
node_timings=node_timings)
self.assertEqual([nodes[1], nodes[2], nodes[0]], actual)
# check that timings are collected despite one policy using affinity
self.assertTrue(app.sorts_by_timing)
self.assertEqual(3, len(app.node_timings))
# check app defaults to sorting by timing when no policy specified
app, actual = self._do_sort_nodes(conf, per_policy, nodes, None,
node_timings=node_timings)
self.assertEqual([nodes[1], nodes[0], nodes[2]], actual)
@patch_policies([StoragePolicy(0, 'zero', True, object_ring=FakeRing()),
StoragePolicy(1, 'one', False, object_ring=FakeRing())])
def test_sort_nodes_by_timing_per_policy(self):
# default setting is to sort by affinity but policy 0 uses timing
nodes = [{'region': 0, 'zone': 1, 'ip': '127.0.0.3'},
{'region': 1, 'zone': 1, 'ip': '127.0.0.1'},
{'region': 2, 'zone': 2, 'ip': '127.0.0.2'}]
node_timings = [10, 1, 100]
conf = {'sorting_method': 'affinity', 'read_affinity': 'r1=1,r2=2'}
per_policy = {'0': {'sorting_method': 'timing',
'read_affinity': 'r1=1,r2=2'}, # should be ignored
'1': {'read_affinity': 'r2=1'}}
# policy 0 uses timing
app, actual = self._do_sort_nodes(conf, per_policy, nodes, POLICIES[0],
node_timings=node_timings)
self.assertEqual([nodes[1], nodes[0], nodes[2]], actual)
self.assertTrue(app.sorts_by_timing)
self.assertEqual(3, len(app.node_timings))
# policy 1 uses policy specific read affinity
app, actual = self._do_sort_nodes(conf, per_policy, nodes, POLICIES[1],
node_timings=node_timings)
self.assertEqual([nodes[2], nodes[0], nodes[1]], actual)
# check that with no policy specified the default read affinity is used
app, actual = self._do_sort_nodes(conf, per_policy, nodes, None,
node_timings=node_timings)
self.assertEqual([nodes[1], nodes[2], nodes[0]], actual)
def test_node_concurrency(self):
nodes = [{'region': 1, 'zone': 1, 'ip': '127.0.0.1', 'port': 6010,
'device': 'sda'},
{'region': 2, 'zone': 2, 'ip': '127.0.0.2', 'port': 6010,
'device': 'sda'},
{'region': 3, 'zone': 3, 'ip': '127.0.0.3', 'port': 6010,
'device': 'sda'}]
timings = {'127.0.0.1': 2, '127.0.0.2': 1, '127.0.0.3': 0}
statuses = {'127.0.0.1': 200, '127.0.0.2': 200, '127.0.0.3': 200}
req = Request.blank('/v1/account', environ={'REQUEST_METHOD': 'GET'})
def fake_iter_nodes(*arg, **karg):
return iter(nodes)
class FakeConn(object):
def __init__(self, ip, *args, **kargs):
self.ip = ip
self.args = args
self.kargs = kargs
def getresponse(self):
body = 'Response from %s' % self.ip
def mygetheader(header, *args, **kargs):
if header == "Content-Type":
return ""
elif header == "Content-Length":
return str(len(body))
else:
return 1
resp = mock.Mock()
resp.read.side_effect = [body, '']
resp.getheader = mygetheader
resp.getheaders.return_value = {}
resp.reason = ''
resp.status = statuses[self.ip]
sleep(timings[self.ip])
return resp
def myfake_http_connect_raw(ip, *args, **kargs):
conn = FakeConn(ip, *args, **kargs)
return conn
with mock.patch('swift.proxy.server.Application.iter_nodes',
fake_iter_nodes):
with mock.patch('swift.common.bufferedhttp.http_connect_raw',
myfake_http_connect_raw):
app_conf = {'concurrent_gets': 'on',
'concurrency_timeout': 0}
baseapp = proxy_server.Application(app_conf,
FakeMemcache(),
container_ring=FakeRing(),
account_ring=FakeRing())
self.assertTrue(baseapp.concurrent_gets)
self.assertEqual(baseapp.concurrency_timeout, 0)
baseapp.update_request(req)
resp = baseapp.handle_request(req)
# Should get 127.0.0.3 as this has a wait of 0 seconds.
self.assertEqual(resp.body, 'Response from 127.0.0.3')
# lets try again, with 127.0.0.1 with 0 timing but returns an
# error.
timings['127.0.0.1'] = 0
statuses['127.0.0.1'] = 500
# Should still get 127.0.0.3 as this has a wait of 0 seconds
# and a success
baseapp.update_request(req)
resp = baseapp.handle_request(req)
self.assertEqual(resp.body, 'Response from 127.0.0.3')
# Now lets set the concurrency_timeout
app_conf['concurrency_timeout'] = 2
baseapp = proxy_server.Application(app_conf,
FakeMemcache(),
container_ring=FakeRing(),
account_ring=FakeRing())
self.assertEqual(baseapp.concurrency_timeout, 2)
baseapp.update_request(req)
resp = baseapp.handle_request(req)
# Should get 127.0.0.2 as this has a wait of 1 seconds.
self.assertEqual(resp.body, 'Response from 127.0.0.2')
def test_info_defaults(self):
app = proxy_server.Application({}, FakeMemcache(),
account_ring=FakeRing(),
container_ring=FakeRing())
self.assertTrue(app.expose_info)
self.assertIsInstance(app.disallowed_sections, list)
self.assertEqual(1, len(app.disallowed_sections))
self.assertEqual(['swift.valid_api_versions'],
app.disallowed_sections)
self.assertIsNone(app.admin_key)
def test_get_info_controller(self):
req = Request.blank('/info')
app = proxy_server.Application({}, FakeMemcache(),
account_ring=FakeRing(),
container_ring=FakeRing())
controller, path_parts = app.get_controller(req)
self.assertIn('version', path_parts)
self.assertIsNone(path_parts['version'])
self.assertIn('disallowed_sections', path_parts)
self.assertIn('expose_info', path_parts)
self.assertIn('admin_key', path_parts)
self.assertEqual(controller.__name__, 'InfoController')
def test_exception_occurred(self):
def do_test(additional_info):
logger = debug_logger('test')
app = proxy_server.Application({}, FakeMemcache(),
account_ring=FakeRing(),
container_ring=FakeRing(),
logger=logger)
node = app.container_ring.get_part_nodes(0)[0]
node_key = app._error_limit_node_key(node)
self.assertNotIn(node_key, app._error_limiting) # sanity
try:
raise Exception('kaboom1!')
except Exception as err:
app.exception_occurred(node, 'server-type', additional_info)
self.assertEqual(1, app._error_limiting[node_key]['errors'])
line = logger.get_lines_for_level('error')[-1]
self.assertIn('server-type server', line)
self.assertIn(additional_info.decode('utf8'), line)
self.assertIn(node['ip'], line)
self.assertIn(str(node['port']), line)
self.assertIn(node['device'], line)
log_args, log_kwargs = logger.log_dict['error'][-1]
self.assertTrue(log_kwargs['exc_info'])
self.assertEqual(err, log_kwargs['exc_info'][1])
do_test('success')
do_test('succès')
do_test(u'success')
def test_error_occurred(self):
def do_test(msg):
logger = debug_logger('test')
app = proxy_server.Application({}, FakeMemcache(),
account_ring=FakeRing(),
container_ring=FakeRing(),
logger=logger)
node = app.container_ring.get_part_nodes(0)[0]
node_key = app._error_limit_node_key(node)
self.assertNotIn(node_key, app._error_limiting) # sanity
app.error_occurred(node, msg)
self.assertEqual(1, app._error_limiting[node_key]['errors'])
line = logger.get_lines_for_level('error')[-1]
self.assertIn(msg.decode('utf8'), line)
self.assertIn(node['ip'], line)
self.assertIn(str(node['port']), line)
self.assertIn(node['device'], line)
do_test('success')
do_test('succès')
do_test(u'success')
def test_error_limit_methods(self):
logger = debug_logger('test')
app = proxy_server.Application({}, FakeMemcache(),
account_ring=FakeRing(),
container_ring=FakeRing(),
logger=logger)
node = app.container_ring.get_part_nodes(0)[0]
# error occurred
app.error_occurred(node, 'test msg')
self.assertTrue('test msg' in
logger.get_lines_for_level('error')[-1])
self.assertEqual(1, node_error_count(app, node))
# exception occurred
try:
raise Exception('kaboom1!')
except Exception as e1:
app.exception_occurred(node, 'test1', 'test1 msg')
line = logger.get_lines_for_level('error')[-1]
self.assertIn('test1 server', line)
self.assertIn('test1 msg', line)
log_args, log_kwargs = logger.log_dict['error'][-1]
self.assertTrue(log_kwargs['exc_info'])
self.assertEqual(log_kwargs['exc_info'][1], e1)
self.assertEqual(2, node_error_count(app, node))
# warning exception occurred
try:
raise Exception('kaboom2!')
except Exception as e2:
app.exception_occurred(node, 'test2', 'test2 msg',
level=logging.WARNING)
line = logger.get_lines_for_level('warning')[-1]
self.assertIn('test2 server', line)
self.assertIn('test2 msg', line)
log_args, log_kwargs = logger.log_dict['warning'][-1]
self.assertTrue(log_kwargs['exc_info'])
self.assertEqual(log_kwargs['exc_info'][1], e2)
self.assertEqual(3, node_error_count(app, node))
# custom exception occurred
try:
raise Exception('kaboom3!')
except Exception as e3:
e3_info = sys.exc_info()
try:
raise Exception('kaboom4!')
except Exception:
pass
app.exception_occurred(node, 'test3', 'test3 msg',
level=logging.WARNING, exc_info=e3_info)
line = logger.get_lines_for_level('warning')[-1]
self.assertIn('test3 server', line)
self.assertIn('test3 msg', line)
log_args, log_kwargs = logger.log_dict['warning'][-1]
self.assertTrue(log_kwargs['exc_info'])
self.assertEqual(log_kwargs['exc_info'][1], e3)
self.assertEqual(4, node_error_count(app, node))
def test_valid_api_version(self):
app = proxy_server.Application({}, FakeMemcache(),
account_ring=FakeRing(),
container_ring=FakeRing())
# The version string is only checked for account, container and object
# requests; the raised APIVersionError returns a 404 to the client
for path in [
'/v2/a',
'/v2/a/c',
'/v2/a/c/o']:
req = Request.blank(path)
self.assertRaises(APIVersionError, app.get_controller, req)
# Default valid API versions are ok
for path in [
'/v1/a',
'/v1/a/c',
'/v1/a/c/o',
'/v1.0/a',
'/v1.0/a/c',
'/v1.0/a/c/o']:
req = Request.blank(path)
controller, path_parts = app.get_controller(req)
self.assertIsNotNone(controller)
# Ensure settings valid API version constraint works
for version in ["42", 42]:
try:
with NamedTemporaryFile() as f:
f.write('[swift-constraints]\n')
f.write('valid_api_versions = %s\n' % version)
f.flush()
with mock.patch.object(utils, 'SWIFT_CONF_FILE', f.name):
constraints.reload_constraints()
req = Request.blank('/%s/a' % version)
controller, _ = app.get_controller(req)
self.assertIsNotNone(controller)
# In this case v1 is invalid
req = Request.blank('/v1/a')
self.assertRaises(APIVersionError, app.get_controller, req)
finally:
constraints.reload_constraints()
# Check that the valid_api_versions is not exposed by default
req = Request.blank('/info')
controller, path_parts = app.get_controller(req)
self.assertTrue('swift.valid_api_versions' in
path_parts.get('disallowed_sections'))
@patch_policies([
StoragePolicy(0, 'zero', is_default=True),
StoragePolicy(1, 'one'),
])
class TestProxyServerLoading(unittest.TestCase):
def setUp(self):
self._orig_hash_suffix = utils.HASH_PATH_SUFFIX
utils.HASH_PATH_SUFFIX = b'endcap'
self.tempdir = mkdtemp()
def tearDown(self):
rmtree(self.tempdir)
utils.HASH_PATH_SUFFIX = self._orig_hash_suffix
for policy in POLICIES:
policy.object_ring = None
def test_load_policy_rings(self):
for policy in POLICIES:
self.assertFalse(policy.object_ring)
conf_path = os.path.join(self.tempdir, 'proxy-server.conf')
conf_body = """
[DEFAULT]
swift_dir = %s
[pipeline:main]
pipeline = catch_errors cache proxy-server
[app:proxy-server]
use = egg:swift#proxy
[filter:cache]
use = egg:swift#memcache
[filter:catch_errors]
use = egg:swift#catch_errors
""" % self.tempdir
with open(conf_path, 'w') as f:
f.write(dedent(conf_body))
account_ring_path = os.path.join(self.tempdir, 'account.ring.gz')
write_fake_ring(account_ring_path)
container_ring_path = os.path.join(self.tempdir, 'container.ring.gz')
write_fake_ring(container_ring_path)
for policy in POLICIES:
object_ring_path = os.path.join(self.tempdir,
policy.ring_name + '.ring.gz')
write_fake_ring(object_ring_path)
app = loadapp(conf_path)
# find the end of the pipeline
while hasattr(app, 'app'):
app = app.app
# validate loaded rings
self.assertEqual(app.account_ring.serialized_path,
account_ring_path)
self.assertEqual(app.container_ring.serialized_path,
container_ring_path)
for policy in POLICIES:
self.assertEqual(policy.object_ring,
app.get_object_ring(int(policy)))
def test_missing_rings(self):
conf_path = os.path.join(self.tempdir, 'proxy-server.conf')
conf_body = """
[DEFAULT]
swift_dir = %s
[pipeline:main]
pipeline = catch_errors cache proxy-server
[app:proxy-server]
use = egg:swift#proxy
[filter:cache]
use = egg:swift#memcache
[filter:catch_errors]
use = egg:swift#catch_errors
""" % self.tempdir
with open(conf_path, 'w') as f:
f.write(dedent(conf_body))
ring_paths = [
os.path.join(self.tempdir, 'account.ring.gz'),
os.path.join(self.tempdir, 'container.ring.gz'),
]
for policy in POLICIES:
self.assertFalse(policy.object_ring)
object_ring_path = os.path.join(self.tempdir,
policy.ring_name + '.ring.gz')
ring_paths.append(object_ring_path)
for policy in POLICIES:
self.assertFalse(policy.object_ring)
for ring_path in ring_paths:
self.assertFalse(os.path.exists(ring_path))
self.assertRaises(IOError, loadapp, conf_path)
write_fake_ring(ring_path)
# all rings exist, app should load
loadapp(conf_path)
for policy in POLICIES:
self.assertTrue(policy.object_ring)
@patch_policies()
class TestProxyServerConfigLoading(unittest.TestCase):
def setUp(self):
skip_if_no_xattrs()
self.tempdir = mkdtemp()
account_ring_path = os.path.join(self.tempdir, 'account.ring.gz')
write_fake_ring(account_ring_path)
container_ring_path = os.path.join(self.tempdir, 'container.ring.gz')
write_fake_ring(container_ring_path)
def tearDown(self):
rmtree(self.tempdir)
def _write_conf(self, conf_body):
# this is broken out to a method so that subclasses can override
conf_path = os.path.join(self.tempdir, 'proxy-server.conf')
with open(conf_path, 'w') as f:
f.write(dedent(conf_body))
return conf_path
def _write_conf_and_load_app(self, conf_sections, app_name='proxy-server'):
# write proxy-server.conf file, load app
conf_body = dedent("""
[DEFAULT]
swift_dir = %s
[pipeline:main]
pipeline = %s
%s
""") % (self.tempdir, app_name, dedent(conf_sections))
conf_path = self._write_conf(conf_body)
with mock.patch('swift.proxy.server.get_logger',
return_value=FakeLogger()):
app = loadapp(conf_path, allow_modify_pipeline=False)
return app
def _check_policy_options(self, app, exp_options, exp_is_local):
# verify expected config
for policy, options in exp_options.items():
for k, v in options.items():
actual = getattr(app.get_policy_options(policy), k)
if k == "write_affinity_node_count_fn":
if policy: # this check only applies when using a policy
actual = actual(policy.object_ring.replica_count)
self.assertEqual(v, actual)
continue
self.assertEqual(v, actual,
"Expected %s=%s but got %s=%s for policy %s" %
(k, v, k, actual, policy))
for policy, nodes in exp_is_local.items():
fn = app.get_policy_options(policy).write_affinity_is_local_fn
if nodes is None:
self.assertIsNone(fn)
continue
for node, expected_result in nodes:
actual = fn(node)
self.assertIs(expected_result, actual,
"Expected %s but got %s for %s, policy %s" %
(expected_result, actual, node, policy))
def test_per_policy_conf_none_configured(self):
conf_sections = """
[app:proxy-server]
use = egg:swift#proxy
"""
expected_default = {"read_affinity": "",
"sorting_method": "shuffle",
"write_affinity": "",
"write_affinity_node_count_fn": 6}
exp_options = {None: expected_default,
POLICIES[0]: expected_default,
POLICIES[1]: expected_default}
exp_is_local = {POLICIES[0]: None,
POLICIES[1]: None}
app = self._write_conf_and_load_app(conf_sections)
self._check_policy_options(app, exp_options, exp_is_local)
def test_per_policy_conf_one_configured(self):
conf_sections = """
[app:proxy-server]
use = egg:swift#proxy
[proxy-server:policy:0]
sorting_method = affinity
read_affinity = r1=100
write_affinity = r1
write_affinity_node_count = 1 * replicas
write_affinity_handoff_delete_count = 4
"""
expected_default = {"read_affinity": "",
"sorting_method": "shuffle",
"write_affinity": "",
"write_affinity_node_count_fn": 6,
"write_affinity_handoff_delete_count": None}
exp_options = {None: expected_default,
POLICIES[0]: {"read_affinity": "r1=100",
"sorting_method": "affinity",
"write_affinity": "r1",
"write_affinity_node_count_fn": 3,
"write_affinity_handoff_delete_count": 4},
POLICIES[1]: expected_default}
exp_is_local = {POLICIES[0]: [({'region': 1, 'zone': 2}, True),
({'region': 2, 'zone': 1}, False)],
POLICIES[1]: None}
app = self._write_conf_and_load_app(conf_sections)
self._check_policy_options(app, exp_options, exp_is_local)
default_options = app.get_policy_options(None)
self.assertEqual(
"ProxyOverrideOptions({}, {'sorting_method': 'shuffle', "
"'read_affinity': '', 'write_affinity': '', "
"'write_affinity_node_count': '2 * replicas', "
"'write_affinity_handoff_delete_count': None})",
repr(default_options))
self.assertEqual(default_options, eval(repr(default_options), {
'ProxyOverrideOptions': default_options.__class__}))
policy_0_options = app.get_policy_options(POLICIES[0])
self.assertEqual(
"ProxyOverrideOptions({}, {'sorting_method': 'affinity', "
"'read_affinity': 'r1=100', 'write_affinity': 'r1', "
"'write_affinity_node_count': '1 * replicas', "
"'write_affinity_handoff_delete_count': 4})",
repr(policy_0_options))
self.assertEqual(policy_0_options, eval(repr(policy_0_options), {
'ProxyOverrideOptions': policy_0_options.__class__}))
self.assertNotEqual(default_options, policy_0_options)
policy_1_options = app.get_policy_options(POLICIES[1])
self.assertIs(default_options, policy_1_options)
def test_per_policy_conf_inherits_defaults(self):
conf_sections = """
[app:proxy-server]
use = egg:swift#proxy
sorting_method = affinity
write_affinity_node_count = 1 * replicas
write_affinity_handoff_delete_count = 3
[proxy-server:policy:0]
read_affinity = r1=100
write_affinity = r1
"""
expected_default = {"read_affinity": "",
"sorting_method": "affinity",
"write_affinity": "",
"write_affinity_node_count_fn": 3,
"write_affinity_handoff_delete_count": 3}
exp_options = {None: expected_default,
POLICIES[0]: {"read_affinity": "r1=100",
"sorting_method": "affinity",
"write_affinity": "r1",
"write_affinity_node_count_fn": 3,
"write_affinity_handoff_delete_count": 3},
POLICIES[1]: expected_default}
exp_is_local = {POLICIES[0]: [({'region': 1, 'zone': 2}, True),
({'region': 2, 'zone': 1}, False)],
POLICIES[1]: None}
app = self._write_conf_and_load_app(conf_sections)
self._check_policy_options(app, exp_options, exp_is_local)
def test_per_policy_conf_overrides_default_affinity(self):
conf_sections = """
[app:proxy-server]
use = egg:swift#proxy
sorting_method = affinity
read_affinity = r2=10
write_affinity_node_count = 1 * replicas
write_affinity = r2
write_affinity_handoff_delete_count = 2
[proxy-server:policy:0]
read_affinity = r1=100
write_affinity = r1
write_affinity_node_count = 5
write_affinity_handoff_delete_count = 3
[proxy-server:policy:1]
read_affinity = r1=1
write_affinity = r3
write_affinity_node_count = 4
write_affinity_handoff_delete_count = 4
"""
exp_options = {None: {"read_affinity": "r2=10",
"sorting_method": "affinity",
"write_affinity": "r2",
"write_affinity_node_count_fn": 3,
"write_affinity_handoff_delete_count": 2},
POLICIES[0]: {"read_affinity": "r1=100",
"sorting_method": "affinity",
"write_affinity": "r1",
"write_affinity_node_count_fn": 5,
"write_affinity_handoff_delete_count": 3},
POLICIES[1]: {"read_affinity": "r1=1",
"sorting_method": "affinity",
"write_affinity": "r3",
"write_affinity_node_count_fn": 4,
"write_affinity_handoff_delete_count": 4}}
exp_is_local = {POLICIES[0]: [({'region': 1, 'zone': 2}, True),
({'region': 2, 'zone': 1}, False)],
POLICIES[1]: [({'region': 3, 'zone': 2}, True),
({'region': 1, 'zone': 1}, False),
({'region': 2, 'zone': 1}, False)]}
app = self._write_conf_and_load_app(conf_sections)
self._check_policy_options(app, exp_options, exp_is_local)
def test_per_policy_conf_overrides_default_sorting_method(self):
conf_sections = """
[app:proxy-server]
use = egg:swift#proxy
sorting_method = timing
[proxy-server:policy:0]
sorting_method = affinity
read_affinity = r1=100
[proxy-server:policy:1]
sorting_method = affinity
read_affinity = r1=1
"""
exp_options = {None: {"read_affinity": "",
"sorting_method": "timing"},
POLICIES[0]: {"read_affinity": "r1=100",
"sorting_method": "affinity"},
POLICIES[1]: {"read_affinity": "r1=1",
"sorting_method": "affinity"}}
app = self._write_conf_and_load_app(conf_sections)
self._check_policy_options(app, exp_options, {})
def test_per_policy_conf_with_DEFAULT_options(self):
conf_body = """
[DEFAULT]
write_affinity = r0
read_affinity = r0=100
swift_dir = %s
[pipeline:main]
pipeline = proxy-server
[app:proxy-server]
use = egg:swift#proxy
# in a paste-deploy section, DEFAULT section value overrides
write_affinity = r2
# ...but the use of 'set' overrides the DEFAULT section value
set read_affinity = r1=100
[proxy-server:policy:0]
# not a paste-deploy section so any value here overrides DEFAULT
sorting_method = affinity
write_affinity = r2
read_affinity = r2=100
[proxy-server:policy:1]
sorting_method = affinity
""" % self.tempdir
# Don't just use _write_conf_and_load_app, as we don't want to have
# duplicate DEFAULT sections
conf_path = self._write_conf(conf_body)
with mock.patch('swift.proxy.server.get_logger',
return_value=FakeLogger()):
app = loadapp(conf_path, allow_modify_pipeline=False)
exp_options = {
# default read_affinity is r1, set in proxy-server section
None: {"read_affinity": "r1=100",
"sorting_method": "shuffle",
"write_affinity": "r0",
"write_affinity_node_count_fn": 6,
"write_affinity_handoff_delete_count": None},
# policy 0 read affinity is r2, dictated by policy 0 section
POLICIES[0]: {"read_affinity": "r2=100",
"sorting_method": "affinity",
"write_affinity": "r2",
"write_affinity_node_count_fn": 6,
"write_affinity_handoff_delete_count": None},
# policy 1 read_affinity is r0, dictated by DEFAULT section,
# overrides proxy server section
POLICIES[1]: {"read_affinity": "r0=100",
"sorting_method": "affinity",
"write_affinity": "r0",
"write_affinity_node_count_fn": 6,
"write_affinity_handoff_delete_count": None}}
exp_is_local = {
# default write_affinity is r0, dictated by DEFAULT section
None: [({'region': 0, 'zone': 2}, True),
({'region': 1, 'zone': 1}, False)],
# policy 0 write_affinity is r2, dictated by policy 0 section
POLICIES[0]: [({'region': 0, 'zone': 2}, False),
({'region': 2, 'zone': 1}, True)],
# policy 1 write_affinity is r0, inherited from default
POLICIES[1]: [({'region': 0, 'zone': 2}, True),
({'region': 1, 'zone': 1}, False)]}
self._check_policy_options(app, exp_options, exp_is_local)
def test_per_policy_conf_warns_about_sorting_method_mismatch(self):
# verify that policy specific warnings are emitted when read_affinity
# is set but sorting_method is not affinity
conf_sections = """
[app:proxy-server]
use = egg:swift#proxy
read_affinity = r2=10
sorting_method = timing
[proxy-server:policy:0]
read_affinity = r1=100
[proxy-server:policy:1]
sorting_method = affinity
read_affinity = r1=1
"""
exp_options = {None: {"read_affinity": "r2=10",
"sorting_method": "timing"},
POLICIES[0]: {"read_affinity": "r1=100",
"sorting_method": "timing"},
POLICIES[1]: {"read_affinity": "r1=1",
"sorting_method": "affinity"}}
app = self._write_conf_and_load_app(conf_sections)
self._check_policy_options(app, exp_options, {})
lines = app.logger.get_lines_for_level('warning')
labels = {'default', 'policy 0 (nulo)'}
for line in lines[:2]:
self.assertIn(
"sorting_method is set to 'timing', not 'affinity'", line)
for label in labels:
if label in line:
labels.remove(label)
break
else:
self.fail("None of %s found in warning: %r" % (labels, line))
self.assertFalse(labels)
def test_per_policy_conf_warns_override_sorting_method_mismatch(self):
# verify that policy specific warnings are emitted when read_affinity
# is set but sorting_method is not affinity in a policy config
conf_sections = """
[app:proxy-server]
use = egg:swift#proxy
sorting_method = affinity
read_affinity = r2=10
[proxy-server:policy:0]
sorting_method = timing
"""
exp_options = {None: {"read_affinity": "r2=10",
"write_affinity": "",
"sorting_method": "affinity"},
POLICIES[0]: {"read_affinity": "r2=10",
"write_affinity": "",
"sorting_method": "timing"}}
app = self._write_conf_and_load_app(conf_sections)
self._check_policy_options(app, exp_options, {})
lines = app.logger.get_lines_for_level('warning')
for line in lines:
# proxy-server gets instantiated twice during loadapp so expect two
# warnings; check that both warnings refer to policy 0 and not the
# default config
self.assertIn(
"sorting_method is set to 'timing', not 'affinity'", line)
self.assertIn('policy 0 (nulo)', line)
self.assertFalse(lines[2:])
def test_per_policy_conf_section_name_inherits_from_app_section_name(self):
conf_sections = """
[app:proxy-srv]
use = egg:swift#proxy
sorting_method = affinity
[proxy-server:policy:0]
sorting_method = timing
# ignored!
[proxy-srv:policy:1]
sorting_method = shuffle
"""
exp_options = {None: {'sorting_method': 'affinity'},
POLICIES[0]: {'sorting_method': 'affinity'},
POLICIES[1]: {'sorting_method': 'shuffle'}}
app = self._write_conf_and_load_app(conf_sections, 'proxy-srv')
self._check_policy_options(app, exp_options, {})
def test_per_policy_conf_with_unknown_policy(self):
# verify that unknown policy section raises an error
conf_sections = """
[app:proxy-server]
use = egg:swift#proxy
read_affinity = r2=10
sorting_method = affinity
[proxy-server:policy:999]
read_affinity = r2z1=1
"""
with self.assertRaises(ValueError) as cm:
self._write_conf_and_load_app(conf_sections)
self.assertIn('No policy found for override config, index: 999',
cm.exception.message)
def test_per_policy_conf_sets_timing_sorting_method(self):
conf_sections = """
[app:proxy-server]
use = egg:swift#proxy
sorting_method = affinity
[proxy-server:policy:0]
sorting_method = timing
[proxy-server:policy:1]
read_affinity = r1=1
"""
exp_options = {None: {"read_affinity": "",
"sorting_method": "affinity"},
POLICIES[0]: {"read_affinity": "",
"sorting_method": "timing"},
POLICIES[1]: {"read_affinity": "r1=1",
"sorting_method": "affinity"}}
app = self._write_conf_and_load_app(conf_sections)
self._check_policy_options(app, exp_options, {})
def test_per_policy_conf_invalid_sorting_method_value(self):
def do_test(conf_sections, scope):
with self.assertRaises(ValueError) as cm:
self._write_conf_and_load_app(conf_sections)
self.assertEqual(
'Invalid sorting_method value; must be one of shuffle, '
"timing, affinity, not 'broken' for %s" % scope,
cm.exception.message)
conf_sections = """
[app:proxy-server]
use = egg:swift#proxy
sorting_method = shuffle
[proxy-server:policy:0]
sorting_method = broken
"""
do_test(conf_sections, 'policy 0 (nulo)')
conf_sections = """
[app:proxy-server]
use = egg:swift#proxy
sorting_method = broken
[proxy-server:policy:0]
sorting_method = shuffle
"""
do_test(conf_sections, '(default)')
def test_per_policy_conf_invalid_read_affinity_value(self):
def do_test(conf_sections, label):
with self.assertRaises(ValueError) as cm:
self._write_conf_and_load_app(conf_sections)
self.assertIn('broken', cm.exception.message)
self.assertIn(
'Invalid read_affinity value:', cm.exception.message)
self.assertIn(label, cm.exception.message)
conf_sections = """
[app:proxy-server]
use = egg:swift#proxy
sorting_method = affinity
read_affinity = r1=1
[proxy-server:policy:0]
sorting_method = affinity
read_affinity = broken
"""
do_test(conf_sections, 'policy 0 (nulo)')
conf_sections = """
[app:proxy-server]
use = egg:swift#proxy
sorting_method = affinity
read_affinity = broken
[proxy-server:policy:0]
sorting_method = affinity
read_affinity = r1=1
"""
do_test(conf_sections, '(default)')
def test_per_policy_conf_invalid_write_affinity_value(self):
def do_test(conf_sections, label):
with self.assertRaises(ValueError) as cm:
self._write_conf_and_load_app(conf_sections)
self.assertIn('broken', cm.exception.message)
self.assertIn(
'Invalid write_affinity value:', cm.exception.message)
self.assertIn(label, cm.exception.message)
conf_sections = """
[app:proxy-server]
use = egg:swift#proxy
write_affinity = r1
[proxy-server:policy:0]
sorting_method = affinity
write_affinity = broken
"""
do_test(conf_sections, 'policy 0 (nulo)')
conf_sections = """
[app:proxy-server]
use = egg:swift#proxy
write_affinity = broken
[proxy-server:policy:0]
write_affinity = r1
"""
do_test(conf_sections, '(default)')
def test_per_policy_conf_invalid_write_affinity_node_count_value(self):
def do_test(conf_sections, label):
with self.assertRaises(ValueError) as cm:
self._write_conf_and_load_app(conf_sections)
self.assertIn('2* replicas', cm.exception.message)
self.assertIn('Invalid write_affinity_node_count value:',
cm.exception.message)
self.assertIn(label, cm.exception.message)
conf_sections = """
[app:proxy-server]
use = egg:swift#proxy
write_affinity_node_count = 2 * replicas
[proxy-server:policy:0]
sorting_method = affinity
write_affinity_node_count = 2* replicas
"""
do_test(conf_sections, 'policy 0 (nulo)')
conf_sections = """
[app:proxy-server]
use = egg:swift#proxy
write_affinity_node_count = 2* replicas
[proxy-server:policy:0]
write_affinity_node_count = 2 * replicas
"""
do_test(conf_sections, '(default)')
def test_per_policy_conf_bad_section_name(self):
def do_test(policy):
conf_sections = """
[app:proxy-server]
use = egg:swift#proxy
[proxy-server:policy:%s]
""" % policy
with self.assertRaises(ValueError) as cm:
self._write_conf_and_load_app(conf_sections)
self.assertEqual(
"Override config must refer to policy index: %r" % policy,
cm.exception.message)
do_test('')
do_test('uno')
do_test('0.0')
class TestProxyServerConfigStringLoading(TestProxyServerConfigLoading):
# The proxy may be loaded from a conf string rather than a conf file, for
# example when ContainerSync creates an InternalClient from a default
# config string. So repeat super-class tests using a string loader.
def _write_conf(self, conf_body):
# this is broken out to a method so that subclasses can override
return ConfigString(conf_body)
class BaseTestObjectController(object):
"""
A root of TestObjController that implements helper methods for child
TestObjControllers.
"""
def setUp(self):
# clear proxy logger result for each test
_test_servers[0].logger._clear()
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 put_container(self, policy_name, container_name):
# Note: only works if called with unpatched policies
prolis = _test_sockets[0]
sock = connect_tcp(('localhost', prolis.getsockname()[1]))
fd = sock.makefile()
fd.write('PUT /v1/a/%s HTTP/1.1\r\n'
'Host: localhost\r\n'
'Connection: close\r\n'
'Content-Length: 0\r\n'
'X-Storage-Token: t\r\n'
'X-Storage-Policy: %s\r\n'
'\r\n' % (container_name, policy_name))
fd.flush()
headers = readuntil2crlfs(fd)
exp = 'HTTP/1.1 2'
self.assertEqual(headers[:len(exp)], exp)
def _test_conditional_GET(self, policy):
container_name = uuid.uuid4().hex
object_path = '/v1/a/%s/conditionals' % container_name
self.put_container(policy.name, container_name)
obj = 'this object has an etag and is otherwise unimportant'
etag = md5(obj).hexdigest()
not_etag = md5(obj + "blahblah").hexdigest()
prolis = _test_sockets[0]
prosrv = _test_servers[0]
sock = connect_tcp(('localhost', prolis.getsockname()[1]))
fd = sock.makefile()
fd.write('PUT %s HTTP/1.1\r\n'
'Host: localhost\r\n'
'Connection: close\r\n'
'Content-Length: %d\r\n'
'X-Storage-Token: t\r\n'
'Content-Type: application/octet-stream\r\n'
'\r\n%s' % (object_path, len(obj), obj))
fd.flush()
headers = readuntil2crlfs(fd)
exp = 'HTTP/1.1 201'
self.assertEqual(headers[:len(exp)], exp)
for verb, body in (('GET', obj), ('HEAD', '')):
# If-Match
req = Request.blank(
object_path,
environ={'REQUEST_METHOD': verb},
headers={'If-Match': etag})
resp = req.get_response(prosrv)
self.assertEqual(resp.status_int, 200)
self.assertEqual(resp.body, body)
self.assertEqual(etag, resp.headers.get('etag'))
self.assertEqual('bytes', resp.headers.get('accept-ranges'))
req = Request.blank(
object_path,
environ={'REQUEST_METHOD': verb},
headers={'If-Match': not_etag})
resp = req.get_response(prosrv)
self.assertEqual(resp.status_int, 412)
self.assertEqual(etag, resp.headers.get('etag'))
req = Request.blank(
object_path,
environ={'REQUEST_METHOD': verb},
headers={'If-Match': "*"})
resp = req.get_response(prosrv)
self.assertEqual(resp.status_int, 200)
self.assertEqual(resp.body, body)
self.assertEqual(etag, resp.headers.get('etag'))
self.assertEqual('bytes', resp.headers.get('accept-ranges'))
# If-None-Match
req = Request.blank(
object_path,
environ={'REQUEST_METHOD': verb},
headers={'If-None-Match': etag})
resp = req.get_response(prosrv)
self.assertEqual(resp.status_int, 304)
self.assertEqual(etag, resp.headers.get('etag'))
self.assertEqual('bytes', resp.headers.get('accept-ranges'))
req = Request.blank(
object_path,
environ={'REQUEST_METHOD': verb},
headers={'If-None-Match': not_etag})
resp = req.get_response(prosrv)
self.assertEqual(resp.status_int, 200)
self.assertEqual(resp.body, body)
self.assertEqual(etag, resp.headers.get('etag'))
self.assertEqual('bytes', resp.headers.get('accept-ranges'))
req = Request.blank(
object_path,
environ={'REQUEST_METHOD': verb},
headers={'If-None-Match': "*"})
resp = req.get_response(prosrv)
self.assertEqual(resp.status_int, 304)
self.assertEqual(etag, resp.headers.get('etag'))
self.assertEqual('bytes', resp.headers.get('accept-ranges'))
error_lines = prosrv.logger.get_lines_for_level('error')
warn_lines = prosrv.logger.get_lines_for_level('warning')
self.assertEqual(len(error_lines), 0) # sanity
self.assertEqual(len(warn_lines), 0) # sanity
@patch_policies([StoragePolicy(0, 'zero', True,
object_ring=FakeRing(base_port=3000))])
class TestReplicatedObjectController(
BaseTestObjectController, unittest.TestCase):
"""
Test suite for replication policy
"""
def setUp(self):
skip_if_no_xattrs()
self.app = proxy_server.Application(
None, FakeMemcache(),
logger=debug_logger('proxy-ut'),
account_ring=FakeRing(),
container_ring=FakeRing())
super(TestReplicatedObjectController, self).setUp()
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)
@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]
sock = connect_tcp(('localhost', prolis.getsockname()[1]))
fd = sock.makefile()
obj = 'a' * (1024 * 1024)
path = '/v1/a/c/o.large'
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: application/octet-stream\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':
'application/octet-stream',
'X-Newest': 'true'})
res = req.get_response(prosrv)
self.assertEqual(res.status_int, 200)
self.assertEqual(res.body, obj)
@unpatch_policies
def test_GET_ranges(self):
prolis = _test_sockets[0]
prosrv = _test_servers[0]
sock = connect_tcp(('localhost', prolis.getsockname()[1]))
fd = sock.makefile()
obj = (''.join(
('beans lots of beans lots of beans lots of beans yeah %04d ' % i)
for i in range(100)))
path = '/v1/a/c/o.beans'
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: application/octet-stream\r\n'
'\r\n%s' % (path, str(len(obj)), obj))
fd.flush()
headers = readuntil2crlfs(fd)