Merge remote-tracking branch 'gerrit/master' into feature/deep

Mainly because I want the new probetests-during-check patch.

Change-Id: Iee0e6ed3bf1a8d8fe0b1c4851b92ffa130eab5cd
This commit is contained in:
Tim Burke 2018-03-06 19:37:37 +00:00
commit b2a6b7bcdc
20 changed files with 480 additions and 39 deletions

View File

@ -93,6 +93,19 @@
parent: swift-tox-func-ec parent: swift-tox-func-ec
nodeset: centos-7 nodeset: centos-7
- job:
name: swift-probetests-centos-7
parent: unittests
nodeset: centos-7
voting: false
description: |
Setup a SAIO dev environment and run Swift's probe tests
pre-run:
- playbooks/saio_single_node_setup/install_dependencies.yaml
- playbooks/saio_single_node_setup/setup_saio.yaml
- playbooks/saio_single_node_setup/make_rings.yaml
run: playbooks/probetests/run.yaml
- project: - project:
check: check:
jobs: jobs:
@ -101,6 +114,7 @@
- swift-tox-func - swift-tox-func
- swift-tox-func-encryption - swift-tox-func-encryption
- swift-tox-func-ec - swift-tox-func-ec
- swift-probetests-centos-7
gate: gate:
jobs: jobs:
- swift-tox-py27 - swift-tox-py27

View File

@ -0,0 +1,26 @@
# Copyright (c) 2018 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.
- hosts: all
tasks:
- name: run probe tests
shell:
cmd: |
source ~/.bashrc
nosetests test/probe/
executable: /bin/bash
chdir: '{{ zuul.project.src_dir }}'

View File

@ -0,0 +1,24 @@
# Copyright (c) 2018 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.
- hosts: all
become: true
tasks:
- name: installing dependencies
yum: name={{ item }} state=present
with_items:
- python-eventlet
- python-pyeclib
- python-nose
- python-swiftclient

View File

@ -0,0 +1,29 @@
# Copyright (c) 2018 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.
- hosts: all
tasks:
- name: install swift
become: true
shell:
cmd: python setup.py develop
executable: /bin/bash
chdir: '{{ zuul.project.src_dir }}'
- name: make rings
shell:
cmd: remakerings
executable: /bin/bash
chdir: '/etc/swift'

View File

@ -0,0 +1,174 @@
# Copyright (c) 2018 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.
- hosts: all
become: true
tasks:
- name: assure /srv directory exists
file: path=/srv state=directory
- name: create loopback device
command: truncate -s 1GB /srv/swift-disk creates=/srv/swift-disk
- name: create filesystem /srv/swift-disk
become: true
filesystem: fstype=xfs dev=/srv/swift-disk
- name: create mount path /mnt/sdb1
file: path=/mnt/sdb1 state=directory
- name: mount /mnt/sdb1
mount: name=/mnt/sdb1 src=/srv/swift-disk fstype=xfs opts="loop,noatime,nodiratime,nobarrier,logbufs=8" dump=0 passno=0 state=mounted
- name: create sub-partitions
file: >
path=/mnt/sdb1/{{ item }}
state=directory
owner={{ ansible_user_id }}
group={{ ansible_user_gid }}
with_items:
- 1
- 2
- 3
- 4
- name: create symlinks
become: true
file: >
src=/mnt/sdb1/{{ item }}
dest=/srv/{{ item }}
owner={{ ansible_user_id }}
group={{ ansible_user_gid }}
state=link
with_items:
- 1
- 2
- 3
- 4
- name: create node partition directories
file: >
path=/srv/{{ item[1] }}/node/sdb{{ item[0] + item[1] }}
owner={{ ansible_user_id }}
group={{ ansible_user_gid }}
state=directory
with_nested:
- [0, 4]
- [1, 2, 3, 4]
- name: create /var/run/swift
file: >
path=/var/run/swift
owner={{ ansible_user_id }}
group={{ ansible_user_gid }}
state=directory
- name: create /var/cache/swift
file: >
path=/var/cache/swift
owner={{ ansible_user_id }}
group={{ ansible_user_gid }}
state=directory
- name: create /var/cache/swift[n]
file: >
path=/var/cache/swift{{ item }}
owner={{ ansible_user_id }}
group={{ ansible_user_gid }}
state=directory
with_items:
- 2
- 3
- 4
- name: create rc.local from template
template: src=rc.local.j2 dest=/etc/rc.d/rc.local owner=root group=root mode=0755
- name: create /etc/rsyncd.conf
command: cp {{ zuul.project.src_dir }}/doc/saio/rsyncd.conf /etc/
- name: update rsyncd.conf with correct username
replace: dest=/etc/rsyncd.conf regexp=<your-user-name> replace={{ ansible_user_id }}
- name: enable rsync
lineinfile: dest=/etc/xinetd.d/rsync line="disable = no" create=yes
- name: set selinux to permissive
selinux: policy=targeted state=disabled
- name: restart rsync
service: name=rsyncd state=restarted enabled=yes
- name: start memcache
service: name=memcached state=started enabled=yes
- name: configure rsyslog
command: cp {{ zuul.project.src_dir }}/doc/saio/rsyslog.d/10-swift.conf /etc/rsyslog.d/
- name: modify /etc/rsyslog.conf
lineinfile: dest=/etc/rsyslog.conf
line="$PrivDropToGroup adm"
create=yes
insertafter="^#### GLOBAL DIRECTIVES"
- name: assure /var/log/swift directory exists
file: path=/var/log/swift
state=directory
owner=root
group=adm
mode="g+w"
- name: restart rsyslog
service: name=rsyslog state=restarted enabled=yes
- name: clean up /etc/swift directory
file: path=/etc/swift state=absent
- name: create clean /etc/swift
command: cp -r {{ zuul.project.src_dir }}/doc/saio/swift /etc/swift
- name: copy the sample configuration files for running tests
command: cp -r {{ zuul.project.src_dir }}/test/sample.conf /etc/swift/test.conf
- name: set correct ownership of /etc/swift
file: path=/etc/swift owner={{ ansible_user_id }} group={{ ansible_user_gid }} recurse=yes
- name: find config files to modify user option
find: paths="/etc/swift" patterns="*.conf" recurse=yes
register: find_result
- name: replace user name
replace: dest={{ item.path }} regexp=<your-user-name> replace={{ ansible_user_id }}
with_items: "{{ find_result.files }}"
- name: copy the SAIO scripts for resetting the environment
command: cp -r {{ zuul.project.src_dir }}/doc/saio/bin /home/{{ ansible_ssh_user }}/bin creates=/home/{{ ansible_ssh_user }}/bin
- name: set the correct file mode for SAIO scripts
file: dest=/home/{{ ansible_ssh_user }}/bin mode=0777 recurse=yes
- name: add new env. variable for loopback device
lineinfile: dest=/home/{{ ansible_ssh_user }}/.bashrc line="export SAIO_BLOCK_DEVICE=/srv/swift-disk"
- name: remove line from resetswift
lineinfile: dest=/home/{{ ansible_ssh_user }}/bin/resetswift line="sudo find /var/log/swift -type f -exec rm -f {} \;" state=absent
- name: add new env. variable for running tests
lineinfile: dest=/home/{{ ansible_ssh_user }}/.bashrc line="export SWIFT_TEST_CONFIG_FILE=/etc/swift/test.conf"
- name: make sure PATH includes the bin directory
lineinfile: dest=/home/{{ ansible_ssh_user }}/.bashrc line="export PATH=${PATH}:/home/{{ ansible_ssh_user }}/bin"
- name: increase open files limit to run probe tests
lineinfile: dest=/home/{{ ansible_ssh_user }}/.bashrc line="ulimit -n 4096"

View File

@ -0,0 +1,8 @@
#!/bin/bash
mkdir -p /var/cache/swift /var/cache/swift2 /var/cache/swift3 /var/cache/swift4
chown {{ ansible_user_id }}:{{ ansible_user_gid }} /var/cache/swift*
mkdir -p /var/run/swift
chown {{ ansible_user_id }}:{{ ansible_user_gid }} /var/run/swift
exit 0

View File

@ -69,6 +69,17 @@ def quarantine_db(object_file, server_type):
renamer(object_dir, quarantine_dir, fsync=False) renamer(object_dir, quarantine_dir, fsync=False)
def looks_like_partition(dir_name):
"""
True if the directory name is a valid partition number, False otherwise.
"""
try:
part = int(dir_name)
return part >= 0
except ValueError:
return False
def roundrobin_datadirs(datadirs): def roundrobin_datadirs(datadirs):
""" """
Generator to walk the data dirs in a round robin manner, evenly Generator to walk the data dirs in a round robin manner, evenly
@ -81,7 +92,8 @@ def roundrobin_datadirs(datadirs):
""" """
def walk_datadir(datadir, node_id): def walk_datadir(datadir, node_id):
partitions = os.listdir(datadir) partitions = [pd for pd in os.listdir(datadir)
if looks_like_partition(pd)]
random.shuffle(partitions) random.shuffle(partitions)
for partition in partitions: for partition in partitions:
part_dir = os.path.join(datadir, partition) part_dir = os.path.join(datadir, partition)

View File

@ -119,6 +119,8 @@ class ConfigString(NamedConfigLoader):
} }
self.parser = loadwsgi.NicerConfigParser("string", defaults=defaults) self.parser = loadwsgi.NicerConfigParser("string", defaults=defaults)
self.parser.optionxform = str # Don't lower-case keys self.parser.optionxform = str # Don't lower-case keys
# Defaults don't need interpolation (crazy PasteDeploy...)
self.parser.defaults = lambda: dict(self.parser._defaults, **defaults)
self.parser.readfp(self.contents) self.parser.readfp(self.contents)

View File

@ -608,10 +608,10 @@ class DiskFileRouter(object):
self.policy_to_manager = {} self.policy_to_manager = {}
for policy in POLICIES: for policy in POLICIES:
manager_cls = self.policy_type_to_manager_cls[policy.policy_type] manager_cls = self.policy_type_to_manager_cls[policy.policy_type]
self.policy_to_manager[policy] = manager_cls(*args, **kwargs) self.policy_to_manager[int(policy)] = manager_cls(*args, **kwargs)
def __getitem__(self, policy): def __getitem__(self, policy):
return self.policy_to_manager[policy] return self.policy_to_manager[int(policy)]
class BaseDiskFileManager(object): class BaseDiskFileManager(object):

View File

@ -169,7 +169,8 @@ class ObjectReplicator(Daemon):
:returns: return code of rsync process. 0 is successful :returns: return code of rsync process. 0 is successful
""" """
start_time = time.time() start_time = time.time()
ret_val = None proc = ret_val = None
try: try:
with Timeout(self.rsync_timeout): with Timeout(self.rsync_timeout):
proc = subprocess.Popen(args, proc = subprocess.Popen(args,
@ -177,9 +178,24 @@ class ObjectReplicator(Daemon):
stderr=subprocess.STDOUT) stderr=subprocess.STDOUT)
results = proc.stdout.read() results = proc.stdout.read()
ret_val = proc.wait() ret_val = proc.wait()
except GreenletExit:
self.logger.error(_("Killing by lockup detector"))
if proc:
# Assume rsync is still responsive and give it a chance
# to shut down gracefully
proc.terminate()
# Final good-faith effort to clean up the process table.
# Note that this blocks, but worst-case we wait for the
# lockup detector to come around and kill us. This can
# happen if the process is stuck down in kernel-space
# waiting on I/O or something.
proc.wait()
raise
except Timeout: except Timeout:
self.logger.error(_("Killing long-running rsync: %s"), str(args)) self.logger.error(_("Killing long-running rsync: %s"), str(args))
proc.kill() if proc:
proc.kill()
proc.wait()
return 1 # failure response code return 1 # failure response code
total_time = time.time() - start_time total_time = time.time() - start_time
for result in results.split('\n'): for result in results.split('\n'):

View File

@ -15,6 +15,7 @@
""" Object Server for Swift """ """ Object Server for Swift """
import six
import six.moves.cPickle as pickle import six.moves.cPickle as pickle
import json import json
import os import os
@ -170,7 +171,9 @@ class ObjectController(BaseStorageServer):
# disk_chunk_size parameter. However, it affects all created sockets # disk_chunk_size parameter. However, it affects all created sockets
# using this class so we have chosen to tie it to the # using this class so we have chosen to tie it to the
# network_chunk_size parameter value instead. # network_chunk_size parameter value instead.
socket._fileobject.default_bufsize = self.network_chunk_size if six.PY2:
socket._fileobject.default_bufsize = self.network_chunk_size
# TODO: find a way to enable similar functionality in py3
# Provide further setup specific to an object server implementation. # Provide further setup specific to an object server implementation.
self.setup(conf) self.setup(conf)

View File

@ -151,11 +151,11 @@ class ObjectControllerRouter(object):
def __init__(self): def __init__(self):
self.policy_to_controller_cls = {} self.policy_to_controller_cls = {}
for policy in POLICIES: for policy in POLICIES:
self.policy_to_controller_cls[policy] = \ self.policy_to_controller_cls[int(policy)] = \
self.policy_type_to_controller_map[policy.policy_type] self.policy_type_to_controller_map[policy.policy_type]
def __getitem__(self, policy): def __getitem__(self, policy):
return self.policy_to_controller_cls[policy] return self.policy_to_controller_cls[int(policy)]
class BaseObjectController(Controller): class BaseObjectController(Controller):

View File

@ -276,7 +276,10 @@ class Application(object):
# #
# ** Because it affects the client as well, currently, we use the # ** Because it affects the client as well, currently, we use the
# client chunk size as the govenor and not the object chunk size. # client chunk size as the govenor and not the object chunk size.
socket._fileobject.default_bufsize = self.client_chunk_size if sys.version_info < (3,):
socket._fileobject.default_bufsize = self.client_chunk_size
# TODO: find a way to enable similar functionality in py3
self.expose_info = config_true_value( self.expose_info = config_true_value(
conf.get('expose_info', 'yes')) conf.get('expose_info', 'yes'))
self.disallowed_sections = list_from_csv( self.disallowed_sections = list_from_csv(

View File

@ -543,7 +543,7 @@ class Container(Base):
def delete_files(self): def delete_files(self):
for f in listing_items(self.files): for f in listing_items(self.files):
file_item = self.file(f) file_item = self.file(f)
if not file_item.delete(): if not file_item.delete(tolerate_missing=True):
return False return False
return listing_empty(self.files) return listing_empty(self.files)
@ -764,14 +764,19 @@ class File(Base):
self.conn.make_path(self.path)) self.conn.make_path(self.path))
return True return True
def delete(self, hdrs=None, parms=None, cfg=None): def delete(self, hdrs=None, parms=None, cfg=None, tolerate_missing=False):
if hdrs is None: if hdrs is None:
hdrs = {} hdrs = {}
if parms is None: if parms is None:
parms = {} parms = {}
if self.conn.make_request('DELETE', self.path, hdrs=hdrs, if tolerate_missing:
cfg=cfg, parms=parms) != 204: allowed_statuses = (204, 404)
else:
allowed_statuses = (204,)
if self.conn.make_request(
'DELETE', self.path, hdrs=hdrs, cfg=cfg,
parms=parms) not in allowed_statuses:
raise ResponseError(self.conn.response, 'DELETE', raise ResponseError(self.conn.response, 'DELETE',
self.conn.make_path(self.path)) self.conn.make_path(self.path))

View File

@ -1323,9 +1323,11 @@ class TestDBReplicator(unittest.TestCase):
return [] return []
path = path[len('/srv/node/sdx/containers'):] path = path[len('/srv/node/sdx/containers'):]
if path == '': if path == '':
return ['123', '456', '789', '9999'] return ['123', '456', '789', '9999', "-5", "not-a-partition"]
# 456 will pretend to be a file # 456 will pretend to be a file
# 9999 will be an empty partition with no contents # 9999 will be an empty partition with no contents
# -5 and not-a-partition were created by something outside
# Swift
elif path == '/123': elif path == '/123':
return ['abc', 'def.db'] # def.db will pretend to be a file return ['abc', 'def.db'] # def.db will pretend to be a file
elif path == '/123/abc': elif path == '/123/abc':
@ -1349,6 +1351,10 @@ class TestDBReplicator(unittest.TestCase):
'weird2'] # weird2 will pretend to be a dir, if asked 'weird2'] # weird2 will pretend to be a dir, if asked
elif path == '9999': elif path == '9999':
return [] return []
elif path == 'not-a-partition':
raise Exception("shouldn't look in not-a-partition")
elif path == '-5':
raise Exception("shouldn't look in -5")
return [] return []
def _isdir(path): def _isdir(path):

View File

@ -624,16 +624,7 @@ class TestTimestamp(unittest.TestCase):
'%r is not greater than %r given %r' % ( '%r is not greater than %r given %r' % (
timestamp, int(other), value)) timestamp, int(other), value))
def test_greater_with_offset(self): def _test_greater_with_offset(self, now, test_values):
now = time.time()
older = now - 1
test_values = (
0, '0', 0.0, '0.0', '0000.0000', '000.000_000',
1, '1', 1.1, '1.1', '1111.1111', '111.111_111',
1402443346.935174, '1402443346.93517', '1402443346.935169_ffff',
older, '%f' % older, '%f_0000ffff' % older,
now, '%f' % now, '%f_00000000' % now,
)
for offset in range(1, 1000, 100): for offset in range(1, 1000, 100):
timestamp = utils.Timestamp(now, offset=offset) timestamp = utils.Timestamp(now, offset=offset)
for value in test_values: for value in test_values:
@ -658,6 +649,43 @@ class TestTimestamp(unittest.TestCase):
'%r is not greater than %r given %r' % ( '%r is not greater than %r given %r' % (
timestamp, int(other), value)) timestamp, int(other), value))
def test_greater_with_offset(self):
# Part 1: use the natural time of the Python. This is deliciously
# unpredictable, but completely legitimate and realistic. Finds bugs!
now = time.time()
older = now - 1
test_values = (
0, '0', 0.0, '0.0', '0000.0000', '000.000_000',
1, '1', 1.1, '1.1', '1111.1111', '111.111_111',
1402443346.935174, '1402443346.93517', '1402443346.935169_ffff',
older, now,
)
self._test_greater_with_offset(now, test_values)
# Part 2: Same as above, but with fixed time values that reproduce
# specific corner cases.
now = 1519830570.6949348
older = now - 1
test_values = (
0, '0', 0.0, '0.0', '0000.0000', '000.000_000',
1, '1', 1.1, '1.1', '1111.1111', '111.111_111',
1402443346.935174, '1402443346.93517', '1402443346.935169_ffff',
older, now,
)
self._test_greater_with_offset(now, test_values)
# Part 3: The '%f' problem. Timestamps cannot be converted to %f
# strings, then back to timestamps, then compared with originals.
# You can only "import" a floating point representation once.
now = 1519830570.6949348
now = float('%f' % now)
older = now - 1
test_values = (
0, '0', 0.0, '0.0', '0000.0000', '000.000_000',
1, '1', 1.1, '1.1', '1111.1111', '111.111_111',
older, '%f' % older, '%f_0000ffff' % older,
now, '%f' % now, '%s_00000000' % now,
)
self._test_greater_with_offset(now, test_values)
def test_smaller_no_offset(self): def test_smaller_no_offset(self):
now = time.time() now = time.time()
newer = now + 1 newer = now + 1

View File

@ -203,7 +203,7 @@ class TestWSGI(unittest.TestCase):
conf_file = os.path.join(tempdir, 'file.conf') conf_file = os.path.join(tempdir, 'file.conf')
def _write_and_load_conf_file(conf): def _write_and_load_conf_file(conf):
with open(conf_file, 'wb') as fd: with open(conf_file, 'wt') as fd:
fd.write(dedent(conf)) fd.write(dedent(conf))
return wsgi.load_app_config(conf_file) return wsgi.load_app_config(conf_file)
@ -659,12 +659,12 @@ class TestWSGI(unittest.TestCase):
oldenv = {} oldenv = {}
newenv = wsgi.make_pre_authed_env(oldenv) newenv = wsgi.make_pre_authed_env(oldenv)
self.assertTrue('wsgi.input' in newenv) self.assertTrue('wsgi.input' in newenv)
self.assertEqual(newenv['wsgi.input'].read(), '') self.assertEqual(newenv['wsgi.input'].read(), b'')
oldenv = {'wsgi.input': BytesIO(b'original wsgi.input')} oldenv = {'wsgi.input': BytesIO(b'original wsgi.input')}
newenv = wsgi.make_pre_authed_env(oldenv) newenv = wsgi.make_pre_authed_env(oldenv)
self.assertTrue('wsgi.input' in newenv) self.assertTrue('wsgi.input' in newenv)
self.assertEqual(newenv['wsgi.input'].read(), '') self.assertEqual(newenv['wsgi.input'].read(), b'')
oldenv = {'swift.source': 'UT'} oldenv = {'swift.source': 'UT'}
newenv = wsgi.make_pre_authed_env(oldenv) newenv = wsgi.make_pre_authed_env(oldenv)
@ -677,7 +677,7 @@ class TestWSGI(unittest.TestCase):
def test_pre_auth_req(self): def test_pre_auth_req(self):
class FakeReq(object): class FakeReq(object):
@classmethod @classmethod
def fake_blank(cls, path, environ=None, body='', headers=None): def fake_blank(cls, path, environ=None, body=b'', headers=None):
if environ is None: if environ is None:
environ = {} environ = {}
if headers is None: if headers is None:
@ -687,7 +687,7 @@ class TestWSGI(unittest.TestCase):
was_blank = Request.blank was_blank = Request.blank
Request.blank = FakeReq.fake_blank Request.blank = FakeReq.fake_blank
wsgi.make_pre_authed_request({'HTTP_X_TRANS_ID': '1234'}, wsgi.make_pre_authed_request({'HTTP_X_TRANS_ID': '1234'},
'PUT', '/', body='tester', headers={}) 'PUT', '/', body=b'tester', headers={})
wsgi.make_pre_authed_request({'HTTP_X_TRANS_ID': '1234'}, wsgi.make_pre_authed_request({'HTTP_X_TRANS_ID': '1234'},
'PUT', '/', headers={}) 'PUT', '/', headers={})
Request.blank = was_blank Request.blank = was_blank
@ -695,7 +695,7 @@ class TestWSGI(unittest.TestCase):
def test_pre_auth_req_with_quoted_path(self): def test_pre_auth_req_with_quoted_path(self):
r = wsgi.make_pre_authed_request( r = wsgi.make_pre_authed_request(
{'HTTP_X_TRANS_ID': '1234'}, 'PUT', path=quote('/a space'), {'HTTP_X_TRANS_ID': '1234'}, 'PUT', path=quote('/a space'),
body='tester', headers={}) body=b'tester', headers={})
self.assertEqual(r.path, quote('/a space')) self.assertEqual(r.path, quote('/a space'))
def test_pre_auth_req_drops_query(self): def test_pre_auth_req_drops_query(self):
@ -711,8 +711,8 @@ class TestWSGI(unittest.TestCase):
def test_pre_auth_req_with_body(self): def test_pre_auth_req_with_body(self):
r = wsgi.make_pre_authed_request( r = wsgi.make_pre_authed_request(
{'QUERY_STRING': 'original'}, 'GET', 'path', 'the body') {'QUERY_STRING': 'original'}, 'GET', 'path', b'the body')
self.assertEqual(r.body, 'the body') self.assertEqual(r.body, b'the body')
def test_pre_auth_creates_script_name(self): def test_pre_auth_creates_script_name(self):
e = wsgi.make_pre_authed_env({}) e = wsgi.make_pre_authed_env({})
@ -730,9 +730,9 @@ class TestWSGI(unittest.TestCase):
def test_pre_auth_req_swift_source(self): def test_pre_auth_req_swift_source(self):
r = wsgi.make_pre_authed_request( r = wsgi.make_pre_authed_request(
{'QUERY_STRING': 'original'}, 'GET', 'path', 'the body', {'QUERY_STRING': 'original'}, 'GET', 'path', b'the body',
swift_source='UT') swift_source='UT')
self.assertEqual(r.body, 'the body') self.assertEqual(r.body, b'the body')
self.assertEqual(r.environ['swift.source'], 'UT') self.assertEqual(r.environ['swift.source'], 'UT')
def test_run_server_global_conf_callback(self): def test_run_server_global_conf_callback(self):
@ -1363,7 +1363,8 @@ class TestWSGIContext(unittest.TestCase):
self.assertEqual('aaaaa', next(iterator)) self.assertEqual('aaaaa', next(iterator))
self.assertEqual('bbbbb', next(iterator)) self.assertEqual('bbbbb', next(iterator))
iterable.close() iterable.close()
self.assertRaises(StopIteration, iterator.next) with self.assertRaises(StopIteration):
next(iterator)
def test_update_content_length(self): def test_update_content_length(self):
statuses = ['200 Ok'] statuses = ['200 Ok']

View File

@ -27,10 +27,11 @@ from collections import defaultdict
from errno import ENOENT, ENOTEMPTY, ENOTDIR from errno import ENOENT, ENOTEMPTY, ENOTDIR
from eventlet.green import subprocess from eventlet.green import subprocess
from eventlet import Timeout from eventlet import Timeout, sleep
from test.unit import (debug_logger, patch_policies, make_timestamp_iter, from test.unit import (debug_logger, patch_policies, make_timestamp_iter,
mocked_http_conn, mock_check_drive, skip_if_no_xattrs) mocked_http_conn, mock_check_drive, skip_if_no_xattrs,
SkipTest)
from swift.common import utils from swift.common import utils
from swift.common.utils import (hash_path, mkdirs, normalize_timestamp, from swift.common.utils import (hash_path, mkdirs, normalize_timestamp,
storage_directory) storage_directory)
@ -132,6 +133,32 @@ def _mock_process(ret):
object_replicator.subprocess.Popen = orig_process object_replicator.subprocess.Popen = orig_process
class MockHungProcess(object):
def __init__(self, *args, **kwargs):
class MockStdout(object):
def read(self):
pass
self.stdout = MockStdout()
self._state = 'running'
self._calls = []
def wait(self):
self._calls.append(('wait', self._state))
if self._state == 'running':
# Sleep so we trip either the lockup detector or the rsync timeout
sleep(1)
raise BaseException('You need to mock out some timeouts')
def terminate(self):
self._calls.append(('terminate', self._state))
if self._state == 'running':
self._state = 'terminating'
def kill(self):
self._calls.append(('kill', self._state))
self._state = 'killed'
def _create_test_rings(path, devs=None, next_part_power=None): def _create_test_rings(path, devs=None, next_part_power=None):
testgz = os.path.join(path, 'object.ring.gz') testgz = os.path.join(path, 'object.ring.gz')
intended_replica2part2dev_id = [ intended_replica2part2dev_id = [
@ -2009,6 +2036,68 @@ class TestObjectReplicator(unittest.TestCase):
self.assertIn( self.assertIn(
"next_part_power set in policy 'one'. Skipping", warnings) "next_part_power set in policy 'one'. Skipping", warnings)
def test_replicate_lockup_detector(self):
raise SkipTest("this is not a reliable test and must be fixed")
cur_part = '0'
df = self.df_mgr.get_diskfile('sda', cur_part, 'a', 'c', 'o',
policy=POLICIES[0])
mkdirs(df._datadir)
f = open(os.path.join(df._datadir,
normalize_timestamp(time.time()) + '.data'),
'wb')
f.write('1234567890')
f.close()
mock_procs = []
def new_mock(*a, **kw):
proc = MockHungProcess()
mock_procs.append(proc)
return proc
with mock.patch('swift.obj.replicator.http_connect',
mock_http_connect(200)), \
mock.patch.object(self.replicator, 'lockup_timeout', 0.01), \
mock.patch('eventlet.green.subprocess.Popen', new_mock):
self.replicator.replicate()
for proc in mock_procs:
self.assertEqual(proc._calls, [
('wait', 'running'),
('terminate', 'running'),
('wait', 'terminating'),
])
self.assertEqual(len(mock_procs), 1)
def test_replicate_rsync_timeout(self):
cur_part = '0'
df = self.df_mgr.get_diskfile('sda', cur_part, 'a', 'c', 'o',
policy=POLICIES[0])
mkdirs(df._datadir)
f = open(os.path.join(df._datadir,
normalize_timestamp(time.time()) + '.data'),
'wb')
f.write('1234567890')
f.close()
mock_procs = []
def new_mock(*a, **kw):
proc = MockHungProcess()
mock_procs.append(proc)
return proc
with mock.patch('swift.obj.replicator.http_connect',
mock_http_connect(200)), \
mock.patch.object(self.replicator, 'rsync_timeout', 0.01), \
mock.patch('eventlet.green.subprocess.Popen', new_mock):
self.replicator.replicate()
for proc in mock_procs:
self.assertEqual(proc._calls, [
('wait', 'running'),
('kill', 'running'),
('wait', 'killed'),
])
self.assertEqual(len(mock_procs), 2)
if __name__ == '__main__': if __name__ == '__main__':
unittest.main() unittest.main()

View File

@ -25,6 +25,6 @@ function is_rhel7 {
if is_rhel7; then if is_rhel7; then
# Install CentOS OpenStack repos so that we have access to some extra # Install CentOS OpenStack repos so that we have access to some extra
# packages. # packages.
sudo yum install -y centos-release-openstack-pike sudo yum install -y centos-release-openstack-queens
sudo yum install -y liberasurecode-devel sudo yum install -y liberasurecode-devel
fi fi

View File

@ -40,7 +40,8 @@ commands =
test/unit/common/test_manager.py \ test/unit/common/test_manager.py \
test/unit/common/test_splice.py \ test/unit/common/test_splice.py \
test/unit/common/test_storage_policy.py \ test/unit/common/test_storage_policy.py \
test/unit/common/test_utils.py test/unit/common/test_utils.py \
test/unit/common/test_wsgi.py
[testenv:py35] [testenv:py35]
commands = {[testenv:py34]commands} commands = {[testenv:py34]commands}