2a312d1cd5
Change-Id: I19ca860deaa6dbf388bdcd1f0b0f77f72ff19689
338 lines
13 KiB
Python
338 lines
13 KiB
Python
# Copyright (c) 2013 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.
|
|
|
|
import json
|
|
import os
|
|
import shutil
|
|
import tempfile
|
|
import unittest
|
|
import uuid
|
|
import mock
|
|
|
|
from swift.common import swob
|
|
from swift.common.middleware import container_sync
|
|
from swift.proxy.controllers.base import get_cache_key
|
|
from swift.proxy.controllers.info import InfoController
|
|
|
|
from test.debug_logger import debug_logger
|
|
|
|
|
|
class FakeApp(object):
|
|
|
|
def __call__(self, env, start_response):
|
|
if env.get('PATH_INFO') == '/info':
|
|
controller = InfoController(
|
|
app=None, version=None, expose_info=True,
|
|
disallowed_sections=[], admin_key=None)
|
|
handler = getattr(controller, env.get('REQUEST_METHOD'))
|
|
return handler(swob.Request(env))(env, start_response)
|
|
if env.get('swift.authorize_override'):
|
|
body = b'Response to Authorized Request'
|
|
else:
|
|
body = b'Pass-Through Response'
|
|
headers = [('Content-Length', str(len(body)))]
|
|
if 'HTTP_X_TIMESTAMP' in env:
|
|
headers.append(('X-Timestamp', env['HTTP_X_TIMESTAMP']))
|
|
start_response('200 OK', headers)
|
|
return [body]
|
|
|
|
|
|
class TestContainerSync(unittest.TestCase):
|
|
|
|
def setUp(self):
|
|
self.tempdir = tempfile.mkdtemp()
|
|
with open(
|
|
os.path.join(self.tempdir, 'container-sync-realms.conf'),
|
|
'w') as fp:
|
|
fp.write('''
|
|
[US]
|
|
key = 9ff3b71c849749dbaec4ccdd3cbab62b
|
|
key2 = 1a0a5a0cbd66448084089304442d6776
|
|
cluster_dfw1 = http://dfw1.host/v1/
|
|
''')
|
|
self.app = FakeApp()
|
|
self.conf = {'swift_dir': self.tempdir}
|
|
self.sync = container_sync.ContainerSync(self.app, self.conf)
|
|
|
|
def tearDown(self):
|
|
shutil.rmtree(self.tempdir, ignore_errors=1)
|
|
|
|
def test_current_not_set(self):
|
|
# no 'current' option set by default
|
|
self.assertIsNone(self.sync.realm)
|
|
self.assertIsNone(self.sync.cluster)
|
|
info = {}
|
|
|
|
def capture_swift_info(key, **options):
|
|
info[key] = options
|
|
|
|
with mock.patch(
|
|
'swift.common.middleware.container_sync.register_swift_info',
|
|
new=capture_swift_info):
|
|
self.sync.register_info()
|
|
|
|
for realm, realm_info in info['container_sync']['realms'].items():
|
|
for cluster, options in realm_info['clusters'].items():
|
|
self.assertEqual(options.get('current', False), False)
|
|
|
|
def test_current_invalid(self):
|
|
self.conf = {'swift_dir': self.tempdir, 'current': 'foo'}
|
|
self.sync = container_sync.ContainerSync(self.app, self.conf,
|
|
logger=debug_logger())
|
|
self.assertIsNone(self.sync.realm)
|
|
self.assertIsNone(self.sync.cluster)
|
|
info = {}
|
|
|
|
def capture_swift_info(key, **options):
|
|
info[key] = options
|
|
|
|
with mock.patch(
|
|
'swift.common.middleware.container_sync.register_swift_info',
|
|
new=capture_swift_info):
|
|
self.sync.register_info()
|
|
|
|
for realm, realm_info in info['container_sync']['realms'].items():
|
|
for cluster, options in realm_info['clusters'].items():
|
|
self.assertEqual(options.get('current', False), False)
|
|
|
|
error_lines = self.sync.logger.get_lines_for_level('error')
|
|
self.assertEqual(error_lines, ['Invalid current '
|
|
'//REALM/CLUSTER (foo)'])
|
|
|
|
def test_current_in_realms_conf(self):
|
|
self.conf = {'swift_dir': self.tempdir, 'current': '//us/dfw1'}
|
|
self.sync = container_sync.ContainerSync(self.app, self.conf)
|
|
self.assertEqual('US', self.sync.realm)
|
|
self.assertEqual('DFW1', self.sync.cluster)
|
|
info = {}
|
|
|
|
def capture_swift_info(key, **options):
|
|
info[key] = options
|
|
|
|
with mock.patch(
|
|
'swift.common.middleware.container_sync.register_swift_info',
|
|
new=capture_swift_info):
|
|
self.sync.register_info()
|
|
|
|
for realm, realm_info in info['container_sync']['realms'].items():
|
|
for cluster, options in realm_info['clusters'].items():
|
|
if options.get('current'):
|
|
break
|
|
self.assertEqual(realm, self.sync.realm)
|
|
self.assertEqual(cluster, self.sync.cluster)
|
|
|
|
def test_missing_from_realms_conf(self):
|
|
self.conf = {'swift_dir': self.tempdir, 'current': 'foo/bar'}
|
|
self.sync = container_sync.ContainerSync(self.app, self.conf,
|
|
logger=debug_logger())
|
|
self.assertEqual('FOO', self.sync.realm)
|
|
self.assertEqual('BAR', self.sync.cluster)
|
|
info = {}
|
|
|
|
def capture_swift_info(key, **options):
|
|
info[key] = options
|
|
|
|
with mock.patch(
|
|
'swift.common.middleware.container_sync.register_swift_info',
|
|
new=capture_swift_info):
|
|
self.sync.register_info()
|
|
|
|
for realm, realm_info in info['container_sync']['realms'].items():
|
|
for cluster, options in realm_info['clusters'].items():
|
|
self.assertEqual(options.get('current', False), False)
|
|
|
|
for line in self.sync.logger.get_lines_for_level('error'):
|
|
self.assertEqual(line, 'Unknown current '
|
|
'//REALM/CLUSTER (//FOO/BAR)')
|
|
|
|
def test_pass_through(self):
|
|
req = swob.Request.blank('/v1/a/c')
|
|
resp = req.get_response(self.sync)
|
|
self.assertEqual(resp.status, '200 OK')
|
|
self.assertEqual(resp.body, b'Pass-Through Response')
|
|
|
|
def test_not_enough_args(self):
|
|
req = swob.Request.blank(
|
|
'/v1/a/c', headers={'x-container-sync-auth': 'a'})
|
|
resp = req.get_response(self.sync)
|
|
self.assertEqual(resp.status, '401 Unauthorized')
|
|
self.assertEqual(
|
|
resp.body,
|
|
b'X-Container-Sync-Auth header not valid; '
|
|
b'contact cluster operator for support.')
|
|
self.assertTrue(
|
|
'cs:not-3-args' in req.environ.get('swift.log_info'),
|
|
req.environ.get('swift.log_info'))
|
|
|
|
def test_realm_miss(self):
|
|
req = swob.Request.blank(
|
|
'/v1/a/c', headers={'x-container-sync-auth': 'invalid nonce sig'})
|
|
resp = req.get_response(self.sync)
|
|
self.assertEqual(resp.status, '401 Unauthorized')
|
|
self.assertEqual(
|
|
resp.body,
|
|
b'X-Container-Sync-Auth header not valid; '
|
|
b'contact cluster operator for support.')
|
|
self.assertTrue(
|
|
'cs:no-local-realm-key' in req.environ.get('swift.log_info'),
|
|
req.environ.get('swift.log_info'))
|
|
|
|
def test_user_key_miss(self):
|
|
req = swob.Request.blank(
|
|
'/v1/a/c', headers={'x-container-sync-auth': 'US nonce sig'})
|
|
resp = req.get_response(self.sync)
|
|
self.assertEqual(resp.status, '401 Unauthorized')
|
|
self.assertEqual(
|
|
resp.body,
|
|
b'X-Container-Sync-Auth header not valid; '
|
|
b'contact cluster operator for support.')
|
|
self.assertTrue(
|
|
'cs:no-local-user-key' in req.environ.get('swift.log_info'),
|
|
req.environ.get('swift.log_info'))
|
|
|
|
def test_invalid_sig(self):
|
|
req = swob.Request.blank(
|
|
'/v1/a/c', headers={'x-container-sync-auth': 'US nonce sig'})
|
|
infocache = req.environ.setdefault('swift.infocache', {})
|
|
infocache[get_cache_key('a', 'c')] = {'sync_key': 'abc'}
|
|
resp = req.get_response(self.sync)
|
|
self.assertEqual(resp.status, '401 Unauthorized')
|
|
self.assertEqual(
|
|
resp.body,
|
|
b'X-Container-Sync-Auth header not valid; '
|
|
b'contact cluster operator for support.')
|
|
self.assertIn('cs:invalid-sig', req.environ.get('swift.log_info'))
|
|
self.assertNotIn('swift.authorize_override', req.environ)
|
|
self.assertNotIn('swift.slo_override', req.environ)
|
|
self.assertNotIn('swift.symlink_override', req.environ)
|
|
|
|
def test_valid_sig(self):
|
|
ts = '1455221706.726999_0123456789abcdef'
|
|
sig = self.sync.realms_conf.get_sig(
|
|
'GET', '/v1/a/c', ts, 'nonce',
|
|
self.sync.realms_conf.key('US'), 'abc')
|
|
req = swob.Request.blank('/v1/a/c', headers={
|
|
'x-container-sync-auth': 'US nonce ' + sig,
|
|
'x-backend-inbound-x-timestamp': ts})
|
|
infocache = req.environ.setdefault('swift.infocache', {})
|
|
infocache[get_cache_key('a', 'c')] = {'sync_key': 'abc'}
|
|
resp = req.get_response(self.sync)
|
|
self.assertEqual(resp.status, '200 OK')
|
|
self.assertEqual(resp.body, b'Response to Authorized Request')
|
|
self.assertIn('cs:valid', req.environ.get('swift.log_info'))
|
|
self.assertIn('X-Timestamp', resp.headers)
|
|
self.assertEqual(ts, resp.headers['X-Timestamp'])
|
|
self.assertIn('swift.authorize_override', req.environ)
|
|
self.assertIn('swift.slo_override', req.environ)
|
|
self.assertIn('swift.symlink_override', req.environ)
|
|
|
|
def test_valid_sig2(self):
|
|
sig = self.sync.realms_conf.get_sig(
|
|
'GET', '/v1/a/c', '0', 'nonce',
|
|
self.sync.realms_conf.key2('US'), 'abc')
|
|
req = swob.Request.blank(
|
|
'/v1/a/c', headers={'x-container-sync-auth': 'US nonce ' + sig})
|
|
infocache = req.environ.setdefault('swift.infocache', {})
|
|
infocache[get_cache_key('a', 'c')] = {'sync_key': 'abc'}
|
|
resp = req.get_response(self.sync)
|
|
self.assertEqual(resp.status, '200 OK')
|
|
self.assertEqual(resp.body, b'Response to Authorized Request')
|
|
self.assertIn('cs:valid', req.environ.get('swift.log_info'))
|
|
self.assertIn('swift.authorize_override', req.environ)
|
|
self.assertIn('swift.slo_override', req.environ)
|
|
self.assertIn('swift.symlink_override', req.environ)
|
|
|
|
def test_info(self):
|
|
req = swob.Request.blank('/info')
|
|
resp = req.get_response(self.sync)
|
|
self.assertEqual(resp.status, '200 OK')
|
|
result = json.loads(resp.body)
|
|
self.assertEqual(
|
|
result.get('container_sync'),
|
|
{'realms': {'US': {'clusters': {'DFW1': {}}}}})
|
|
|
|
def test_info_always_fresh(self):
|
|
req = swob.Request.blank('/info')
|
|
resp = req.get_response(self.sync)
|
|
self.assertEqual(resp.status, '200 OK')
|
|
result = json.loads(resp.body)
|
|
self.assertEqual(
|
|
result.get('container_sync'),
|
|
{'realms': {'US': {'clusters': {'DFW1': {}}}}})
|
|
with open(
|
|
os.path.join(self.tempdir, 'container-sync-realms.conf'),
|
|
'w') as fp:
|
|
fp.write('''
|
|
[US]
|
|
key = 9ff3b71c849749dbaec4ccdd3cbab62b
|
|
key2 = 1a0a5a0cbd66448084089304442d6776
|
|
cluster_dfw1 = http://dfw1.host/v1/
|
|
|
|
[UK]
|
|
key = 400b3b357a80413f9d956badff1d9dfe
|
|
cluster_lon3 = http://lon3.host/v1/
|
|
''')
|
|
self.sync.realms_conf.reload()
|
|
req = swob.Request.blank('/info')
|
|
resp = req.get_response(self.sync)
|
|
self.assertEqual(resp.status, '200 OK')
|
|
result = json.loads(resp.body)
|
|
self.assertEqual(
|
|
result.get('container_sync'),
|
|
{'realms': {
|
|
'US': {'clusters': {'DFW1': {}}},
|
|
'UK': {'clusters': {'LON3': {}}}}})
|
|
|
|
def test_allow_full_urls_setting(self):
|
|
req = swob.Request.blank(
|
|
'/v1/a/c',
|
|
environ={'REQUEST_METHOD': 'PUT'},
|
|
headers={'x-container-sync-to': 'http://host/v1/a/c'})
|
|
resp = req.get_response(self.sync)
|
|
self.assertEqual(resp.status, '200 OK')
|
|
self.conf = {'swift_dir': self.tempdir, 'allow_full_urls': 'false'}
|
|
self.sync = container_sync.ContainerSync(self.app, self.conf)
|
|
req = swob.Request.blank(
|
|
'/v1/a/c',
|
|
environ={'REQUEST_METHOD': 'PUT'},
|
|
headers={'x-container-sync-to': 'http://host/v1/a/c'})
|
|
resp = req.get_response(self.sync)
|
|
self.assertEqual(resp.status, '400 Bad Request')
|
|
self.assertEqual(
|
|
resp.body,
|
|
b'Full URLs are not allowed for X-Container-Sync-To values. Only '
|
|
b'realm values of the format //realm/cluster/account/container '
|
|
b'are allowed.\n')
|
|
|
|
def test_filter(self):
|
|
app = FakeApp()
|
|
unique = uuid.uuid4().hex
|
|
sync = container_sync.filter_factory(
|
|
{'global': 'global_value', 'swift_dir': unique},
|
|
**{'local': 'local_value'})(app)
|
|
self.assertEqual(sync.app, app)
|
|
self.assertEqual(sync.conf, {
|
|
'global': 'global_value', 'swift_dir': unique,
|
|
'local': 'local_value'})
|
|
req = swob.Request.blank('/info')
|
|
resp = req.get_response(sync)
|
|
self.assertEqual(resp.status, '200 OK')
|
|
result = json.loads(resp.body)
|
|
self.assertEqual(result.get('container_sync'), {'realms': {}})
|
|
|
|
|
|
if __name__ == '__main__':
|
|
unittest.main()
|