swift/test/unit/common/test_manager.py
Samuel Merritt 728b4ba140 Add checksum to object extended attributes
Currently, our integrity checking for objects is pretty weak when it
comes to object metadata. If the extended attributes on a .data or
.meta file get corrupted in such a way that we can still unpickle it,
we don't have anything that detects that.

This could be especially bad with encrypted etags; if the encrypted
etag (X-Object-Sysmeta-Crypto-Etag or whatever it is) gets some bits
flipped, then we'll cheerfully decrypt the cipherjunk into plainjunk,
then send it to the client. Net effect is that the client sees a GET
response with an ETag that doesn't match the MD5 of the object *and*
Swift has no way of detecting and quarantining this object.

Note that, with an unencrypted object, if the ETag metadatum gets
mangled, then the object will be quarantined by the object server or
auditor, whichever notices first.

As part of this commit, I also ripped out some mocking of
getxattr/setxattr in tests. It appears to be there to allow unit tests
to run on systems where /tmp doesn't support xattrs. However, since
the mock is keyed off of inode number and inode numbers get re-used,
there's lots of leakage between different test runs. On a real FS,
unlinking a file and then creating a new one of the same name will
also reset the xattrs; this isn't the case with the mock.

The mock was pretty old; Ubuntu 12.04 and up all support xattrs in
/tmp, and recent Red Hat / CentOS releases do too. The xattr mock was
added in 2011; maybe it was to support Ubuntu Lucid Lynx?

Bonus: now you can pause a test with the debugger, inspect its files
in /tmp, and actually see the xattrs along with the data.

Since this patch now uses a real filesystem for testing filesystem
operations, tests are skipped if the underlying filesystem does not
support setting xattrs (eg tmpfs or more than 4k of xattrs on ext4).

References to "/tmp" have been replaced with calls to
tempfile.gettempdir(). This will allow setting the TMPDIR envvar in
test setup and getting an XFS filesystem instead of ext4 or tmpfs.

THIS PATCH SIGNIFICANTLY CHANGES TESTING ENVIRONMENTS

With this patch, every test environment will require TMPDIR to be
using a filesystem that supports at least 4k of extended attributes.
Neither ext4 nor tempfs support this. XFS is recommended.

So why all the SkipTests? Why not simply raise an error? We still need
the tests to run on the base image for OpenStack's CI system. Since
we were previously mocking out xattr, there wasn't a problem, but we
also weren't actually testing anything. This patch adds functionality
to validate xattr data, so we need to drop the mock.

`test.unit.skip_if_no_xattrs()` is also imported into `test.functional`
so that functional tests can import it from the functional test
namespace.

The related OpenStack CI infrastructure changes are made in
https://review.openstack.org/#/c/394600/.

Co-Authored-By: John Dickinson <me@not.mn>

Change-Id: I98a37c0d451f4960b7a12f648e4405c6c6716808
2017-11-03 13:30:05 -04:00

2167 lines
89 KiB
Python

# Copyright (c) 2010-2012 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 unittest
from test.unit import temptree
import mock
import os
import sys
import resource
import signal
import errno
from collections import defaultdict
from time import sleep, time
import tempfile
from six.moves import reload_module
from swift.common import manager
from swift.common.exceptions import InvalidPidFileException
import eventlet
threading = eventlet.patcher.original('threading')
DUMMY_SIG = 1
class MockOs(object):
RAISE_EPERM_SIG = 99
def __init__(self, pids):
self.running_pids = pids
self.pid_sigs = defaultdict(list)
self.closed_fds = []
self.child_pid = 9999 # fork defaults to test parent process path
self.execlp_called = False
def kill(self, pid, sig):
if sig == self.RAISE_EPERM_SIG:
raise OSError(errno.EPERM, 'Operation not permitted')
if pid not in self.running_pids:
raise OSError(3, 'No such process')
self.pid_sigs[pid].append(sig)
def __getattr__(self, name):
# I only over-ride portions of the os module
try:
return object.__getattr__(self, name)
except AttributeError:
return getattr(os, name)
def pop_stream(f):
"""read everything out of file from the top and clear it out
"""
f.flush()
f.seek(0)
output = f.read()
f.seek(0)
f.truncate()
return output
class TestManagerModule(unittest.TestCase):
def test_servers(self):
main_plus_rest = set(manager.MAIN_SERVERS + manager.REST_SERVERS)
self.assertEqual(set(manager.ALL_SERVERS), main_plus_rest)
# make sure there's no server listed in both
self.assertEqual(len(main_plus_rest), len(manager.MAIN_SERVERS) +
len(manager.REST_SERVERS))
def test_setup_env(self):
class MockResource(object):
def __init__(self, error=None):
self.error = error
self.called_with_args = []
def setrlimit(self, resource, limits):
if self.error:
raise self.error
self.called_with_args.append((resource, limits))
def __getattr__(self, name):
# I only over-ride portions of the resource module
try:
return object.__getattr__(self, name)
except AttributeError:
return getattr(resource, name)
_orig_resource = manager.resource
_orig_environ = os.environ
try:
manager.resource = MockResource()
manager.os.environ = {}
manager.setup_env()
expected = [
(resource.RLIMIT_NOFILE, (manager.MAX_DESCRIPTORS,
manager.MAX_DESCRIPTORS)),
(resource.RLIMIT_DATA, (manager.MAX_MEMORY,
manager.MAX_MEMORY)),
(resource.RLIMIT_NPROC, (manager.MAX_PROCS,
manager.MAX_PROCS)),
]
self.assertEqual(manager.resource.called_with_args, expected)
self.assertTrue(
manager.os.environ['PYTHON_EGG_CACHE'].startswith(
tempfile.gettempdir()))
# test error condition
manager.resource = MockResource(error=ValueError())
manager.os.environ = {}
manager.setup_env()
self.assertEqual(manager.resource.called_with_args, [])
self.assertTrue(
manager.os.environ['PYTHON_EGG_CACHE'].startswith(
tempfile.gettempdir()))
manager.resource = MockResource(error=OSError())
manager.os.environ = {}
self.assertRaises(OSError, manager.setup_env)
self.assertIsNone(manager.os.environ.get('PYTHON_EGG_CACHE'))
finally:
manager.resource = _orig_resource
os.environ = _orig_environ
def test_command_wrapper(self):
@manager.command
def myfunc(arg1):
"""test doc
"""
return arg1
self.assertEqual(myfunc.__doc__.strip(), 'test doc')
self.assertEqual(myfunc(1), 1)
self.assertEqual(myfunc(0), 0)
self.assertEqual(myfunc(True), 1)
self.assertEqual(myfunc(False), 0)
self.assertTrue(hasattr(myfunc, 'publicly_accessible'))
self.assertTrue(myfunc.publicly_accessible)
def test_watch_server_pids(self):
class MockOs(object):
WNOHANG = os.WNOHANG
def __init__(self, pid_map=None):
if pid_map is None:
pid_map = {}
self.pid_map = {}
for pid, v in pid_map.items():
self.pid_map[pid] = (x for x in v)
def waitpid(self, pid, options):
try:
rv = next(self.pid_map[pid])
except StopIteration:
raise OSError(errno.ECHILD, os.strerror(errno.ECHILD))
except KeyError:
raise OSError(errno.ESRCH, os.strerror(errno.ESRCH))
if isinstance(rv, Exception):
raise rv
else:
return rv
class MockTime(object):
def __init__(self, ticks=None):
self.tock = time()
if not ticks:
ticks = []
self.ticks = (t for t in ticks)
def time(self):
try:
self.tock += next(self.ticks)
except StopIteration:
self.tock += 1
return self.tock
def sleep(*args):
return
class MockServer(object):
def __init__(self, pids, run_dir=manager.RUN_DIR, zombie=0):
self.heartbeat = (pids for _ in range(zombie))
def get_running_pids(self):
try:
rv = next(self.heartbeat)
return rv
except StopIteration:
return {}
_orig_os = manager.os
_orig_time = manager.time
_orig_server = manager.Server
try:
manager.time = MockTime()
manager.os = MockOs()
# this server always says it's dead when you ask for running pids
server = MockServer([1])
# list of pids keyed on servers to watch
server_pids = {
server: [1],
}
# basic test, server dies
gen = manager.watch_server_pids(server_pids)
expected = [(server, 1)]
self.assertEqual([x for x in gen], expected)
# start long running server and short interval
server = MockServer([1], zombie=15)
server_pids = {
server: [1],
}
gen = manager.watch_server_pids(server_pids)
self.assertEqual([x for x in gen], [])
# wait a little longer
gen = manager.watch_server_pids(server_pids, interval=15)
self.assertEqual([x for x in gen], [(server, 1)])
# zombie process
server = MockServer([1], zombie=200)
server_pids = {
server: [1],
}
# test weird os error
manager.os = MockOs({1: [OSError()]})
gen = manager.watch_server_pids(server_pids)
self.assertRaises(OSError, lambda: [x for x in gen])
# test multi-server
server1 = MockServer([1, 10], zombie=200)
server2 = MockServer([2, 20], zombie=8)
server_pids = {
server1: [1, 10],
server2: [2, 20],
}
pid_map = {
1: [None for _ in range(10)],
2: [None for _ in range(8)],
20: [None for _ in range(4)],
}
manager.os = MockOs(pid_map)
gen = manager.watch_server_pids(server_pids,
interval=manager.KILL_WAIT)
expected = [
(server2, 2),
(server2, 20),
]
self.assertEqual([x for x in gen], expected)
finally:
manager.os = _orig_os
manager.time = _orig_time
manager.Server = _orig_server
def test_safe_kill(self):
manager.os = MockOs([1, 2, 3, 4])
proc_files = (
('1/cmdline', 'same-procname'),
('2/cmdline', 'another-procname'),
('4/cmdline', 'another-procname'),
)
files, contents = zip(*proc_files)
with temptree(files, contents) as t:
manager.PROC_DIR = t
manager.safe_kill(1, signal.SIG_DFL, 'same-procname')
self.assertRaises(InvalidPidFileException, manager.safe_kill,
2, signal.SIG_DFL, 'same-procname')
manager.safe_kill(3, signal.SIG_DFL, 'same-procname')
manager.safe_kill(4, signal.SIGHUP, 'same-procname')
def test_exc(self):
self.assertTrue(issubclass(manager.UnknownCommandError, Exception))
class TestServer(unittest.TestCase):
def tearDown(self):
reload_module(manager)
def join_swift_dir(self, path):
return os.path.join(manager.SWIFT_DIR, path)
def join_run_dir(self, path):
return os.path.join(manager.RUN_DIR, path)
def test_create_server(self):
server = manager.Server('proxy')
self.assertEqual(server.server, 'proxy-server')
self.assertEqual(server.type, 'proxy')
self.assertEqual(server.cmd, 'swift-proxy-server')
server = manager.Server('object-replicator')
self.assertEqual(server.server, 'object-replicator')
self.assertEqual(server.type, 'object')
self.assertEqual(server.cmd, 'swift-object-replicator')
def test_server_to_string(self):
server = manager.Server('Proxy')
self.assertEqual(str(server), 'proxy-server')
server = manager.Server('object-replicator')
self.assertEqual(str(server), 'object-replicator')
def test_server_repr(self):
server = manager.Server('proxy')
self.assertTrue(server.__class__.__name__ in repr(server))
self.assertTrue(str(server) in repr(server))
def test_server_equality(self):
server1 = manager.Server('Proxy')
server2 = manager.Server('proxy-server')
self.assertEqual(server1, server2)
# it is NOT a string
self.assertNotEqual(server1, 'proxy-server')
def test_get_pid_file_name(self):
server = manager.Server('proxy')
conf_file = self.join_swift_dir('proxy-server.conf')
pid_file = self.join_run_dir('proxy-server.pid')
self.assertEqual(pid_file, server.get_pid_file_name(conf_file))
server = manager.Server('object-replicator')
conf_file = self.join_swift_dir('object-server/1.conf')
pid_file = self.join_run_dir('object-replicator/1.pid')
self.assertEqual(pid_file, server.get_pid_file_name(conf_file))
server = manager.Server('container-auditor')
conf_file = self.join_swift_dir(
'container-server/1/container-auditor.conf')
pid_file = self.join_run_dir(
'container-auditor/1/container-auditor.pid')
self.assertEqual(pid_file, server.get_pid_file_name(conf_file))
def test_get_custom_pid_file_name(self):
random_run_dir = "/random/dir"
get_random_run_dir = lambda x: os.path.join(random_run_dir, x)
server = manager.Server('proxy', run_dir=random_run_dir)
conf_file = self.join_swift_dir('proxy-server.conf')
pid_file = get_random_run_dir('proxy-server.pid')
self.assertEqual(pid_file, server.get_pid_file_name(conf_file))
server = manager.Server('object-replicator', run_dir=random_run_dir)
conf_file = self.join_swift_dir('object-server/1.conf')
pid_file = get_random_run_dir('object-replicator/1.pid')
self.assertEqual(pid_file, server.get_pid_file_name(conf_file))
server = manager.Server('container-auditor', run_dir=random_run_dir)
conf_file = self.join_swift_dir(
'container-server/1/container-auditor.conf')
pid_file = get_random_run_dir(
'container-auditor/1/container-auditor.pid')
self.assertEqual(pid_file, server.get_pid_file_name(conf_file))
def test_get_conf_file_name(self):
server = manager.Server('proxy')
conf_file = self.join_swift_dir('proxy-server.conf')
pid_file = self.join_run_dir('proxy-server.pid')
self.assertEqual(conf_file, server.get_conf_file_name(pid_file))
server = manager.Server('object-replicator')
conf_file = self.join_swift_dir('object-server/1.conf')
pid_file = self.join_run_dir('object-replicator/1.pid')
self.assertEqual(conf_file, server.get_conf_file_name(pid_file))
server = manager.Server('container-auditor')
conf_file = self.join_swift_dir(
'container-server/1/container-auditor.conf')
pid_file = self.join_run_dir(
'container-auditor/1/container-auditor.pid')
self.assertEqual(conf_file, server.get_conf_file_name(pid_file))
server_name = manager.STANDALONE_SERVERS[0]
server = manager.Server(server_name)
conf_file = self.join_swift_dir(server_name + '.conf')
pid_file = self.join_run_dir(server_name + '.pid')
self.assertEqual(conf_file, server.get_conf_file_name(pid_file))
def test_conf_files(self):
# test get single conf file
conf_files = (
'proxy-server.conf',
'proxy-server.ini',
'auth-server.conf',
)
with temptree(conf_files) as t:
manager.SWIFT_DIR = t
server = manager.Server('proxy')
conf_files = server.conf_files()
self.assertEqual(len(conf_files), 1)
conf_file = conf_files[0]
proxy_conf = self.join_swift_dir('proxy-server.conf')
self.assertEqual(conf_file, proxy_conf)
# test multi server conf files & grouping of server-type config
conf_files = (
'object-server1.conf',
'object-server/2.conf',
'object-server/object3.conf',
'object-server/conf/server4.conf',
'object-server.txt',
'proxy-server.conf',
)
with temptree(conf_files) as t:
manager.SWIFT_DIR = t
server = manager.Server('object-replicator')
conf_files = server.conf_files()
self.assertEqual(len(conf_files), 4)
c1 = self.join_swift_dir('object-server1.conf')
c2 = self.join_swift_dir('object-server/2.conf')
c3 = self.join_swift_dir('object-server/object3.conf')
c4 = self.join_swift_dir('object-server/conf/server4.conf')
for c in [c1, c2, c3, c4]:
self.assertTrue(c in conf_files)
# test configs returned sorted
sorted_confs = sorted([c1, c2, c3, c4])
self.assertEqual(conf_files, sorted_confs)
# test get single numbered conf
conf_files = (
'account-server/1.conf',
'account-server/2.conf',
'account-server/3.conf',
'account-server/4.conf',
)
with temptree(conf_files) as t:
manager.SWIFT_DIR = t
server = manager.Server('account')
conf_files = server.conf_files(number=2)
self.assertEqual(len(conf_files), 1)
conf_file = conf_files[0]
self.assertEqual(conf_file,
self.join_swift_dir('account-server/2.conf'))
# test missing config number
conf_files = server.conf_files(number=5)
self.assertFalse(conf_files)
# test getting specific conf
conf_files = (
'account-server/1.conf',
'account-server/2.conf',
'account-server/3.conf',
'account-server/4.conf',
)
with temptree(conf_files) as t:
manager.SWIFT_DIR = t
server = manager.Server('account.2')
conf_files = server.conf_files()
self.assertEqual(len(conf_files), 1)
conf_file = conf_files[0]
self.assertEqual(conf_file,
self.join_swift_dir('account-server/2.conf'))
# test verbose & quiet
conf_files = (
'auth-server.ini',
'container-server/1.conf',
)
with temptree(conf_files) as t:
manager.SWIFT_DIR = t
old_stdout = sys.stdout
try:
with open(os.path.join(t, 'output'), 'w+') as f:
sys.stdout = f
server = manager.Server('auth')
# check warn "unable to locate"
conf_files = server.conf_files()
self.assertFalse(conf_files)
self.assertTrue('unable to locate config for auth'
in pop_stream(f).lower())
# check quiet will silence warning
conf_files = server.conf_files(verbose=True, quiet=True)
self.assertEqual(pop_stream(f), '')
# check found config no warning
server = manager.Server('container-auditor')
conf_files = server.conf_files()
self.assertEqual(pop_stream(f), '')
# check missing config number warn "unable to locate"
conf_files = server.conf_files(number=2)
self.assertTrue(
'unable to locate config number 2 for ' +
'container-auditor' in pop_stream(f).lower())
# check verbose lists configs
conf_files = server.conf_files(number=2, verbose=True)
c1 = self.join_swift_dir('container-server/1.conf')
self.assertIn(c1, pop_stream(f))
finally:
sys.stdout = old_stdout
# test standalone conf file
server_name = manager.STANDALONE_SERVERS[0]
conf_files = (server_name + '.conf',)
with temptree(conf_files) as t:
manager.SWIFT_DIR = t
server = manager.Server(server_name)
conf_files = server.conf_files()
self.assertEqual(len(conf_files), 1)
conf_file = conf_files[0]
conf = self.join_swift_dir(server_name + '.conf')
self.assertEqual(conf_file, conf)
def test_proxy_conf_dir(self):
conf_files = (
'proxy-server.conf.d/00.conf',
'proxy-server.conf.d/01.conf',
)
with temptree(conf_files) as t:
manager.SWIFT_DIR = t
server = manager.Server('proxy')
conf_dirs = server.conf_files()
self.assertEqual(len(conf_dirs), 1)
conf_dir = conf_dirs[0]
proxy_conf_dir = self.join_swift_dir('proxy-server.conf.d')
self.assertEqual(proxy_conf_dir, conf_dir)
def test_named_conf_dir(self):
conf_files = (
'object-server/base.conf-template',
'object-server/object-server.conf.d/00_base.conf',
'object-server/object-server.conf.d/10_server.conf',
'object-server/object-replication.conf.d/00_base.conf',
'object-server/object-replication.conf.d/10_server.conf',
)
with temptree(conf_files) as t:
manager.SWIFT_DIR = t
server = manager.Server('object.replication')
conf_dirs = server.conf_files()
self.assertEqual(len(conf_dirs), 1)
conf_dir = conf_dirs[0]
replication_server_conf_dir = self.join_swift_dir(
'object-server/object-replication.conf.d')
self.assertEqual(replication_server_conf_dir, conf_dir)
# and again with no named filter
server = manager.Server('object')
conf_dirs = server.conf_files()
self.assertEqual(len(conf_dirs), 2)
for named_conf in ('server', 'replication'):
conf_dir = self.join_swift_dir(
'object-server/object-%s.conf.d' % named_conf)
self.assertTrue(conf_dir in conf_dirs)
def test_conf_dir(self):
conf_files = (
'object-server/object-server.conf-base',
'object-server/1.conf.d/base.conf',
'object-server/1.conf.d/1.conf',
'object-server/2.conf.d/base.conf',
'object-server/2.conf.d/2.conf',
'object-server/3.conf.d/base.conf',
'object-server/3.conf.d/3.conf',
'object-server/4.conf.d/base.conf',
'object-server/4.conf.d/4.conf',
)
with temptree(conf_files) as t:
manager.SWIFT_DIR = t
server = manager.Server('object-replicator')
conf_dirs = server.conf_files()
self.assertEqual(len(conf_dirs), 4)
c1 = self.join_swift_dir('object-server/1.conf.d')
c2 = self.join_swift_dir('object-server/2.conf.d')
c3 = self.join_swift_dir('object-server/3.conf.d')
c4 = self.join_swift_dir('object-server/4.conf.d')
for c in [c1, c2, c3, c4]:
self.assertTrue(c in conf_dirs)
# test configs returned sorted
sorted_confs = sorted([c1, c2, c3, c4])
self.assertEqual(conf_dirs, sorted_confs)
def test_named_conf_dir_pid_files(self):
conf_files = (
'object-server/object-server.pid.d',
'object-server/object-replication.pid.d',
)
with temptree(conf_files) as t:
manager.RUN_DIR = t
server = manager.Server('object.replication', run_dir=t)
pid_files = server.pid_files()
self.assertEqual(len(pid_files), 1)
pid_file = pid_files[0]
replication_server_pid = self.join_run_dir(
'object-server/object-replication.pid.d')
self.assertEqual(replication_server_pid, pid_file)
# and again with no named filter
server = manager.Server('object', run_dir=t)
pid_files = server.pid_files()
self.assertEqual(len(pid_files), 2)
for named_pid in ('server', 'replication'):
pid_file = self.join_run_dir(
'object-server/object-%s.pid.d' % named_pid)
self.assertTrue(pid_file in pid_files)
def test_iter_pid_files(self):
"""
Server.iter_pid_files is kinda boring, test the
Server.pid_files stuff here as well
"""
pid_files = (
('proxy-server.pid', 1),
('auth-server.pid', 'blah'),
('object-replicator/1.pid', 11),
('object-replicator/2.pid', 12),
)
files, contents = zip(*pid_files)
with temptree(files, contents) as t:
manager.RUN_DIR = t
server = manager.Server('proxy', run_dir=t)
# test get one file
iterator = server.iter_pid_files()
pid_file, pid = next(iterator)
self.assertEqual(pid_file, self.join_run_dir('proxy-server.pid'))
self.assertEqual(pid, 1)
# ... and only one file
self.assertRaises(StopIteration, next, iterator)
# test invalid value in pid file
server = manager.Server('auth', run_dir=t)
pid_file, pid = next(server.iter_pid_files())
self.assertIsNone(pid)
# test object-server doesn't steal pids from object-replicator
server = manager.Server('object', run_dir=t)
self.assertRaises(StopIteration, next, server.iter_pid_files())
# test multi-pid iter
server = manager.Server('object-replicator', run_dir=t)
real_map = {
11: self.join_run_dir('object-replicator/1.pid'),
12: self.join_run_dir('object-replicator/2.pid'),
}
pid_map = {}
for pid_file, pid in server.iter_pid_files():
pid_map[pid] = pid_file
self.assertEqual(pid_map, real_map)
# test get pid_files by number
conf_files = (
'object-server/1.conf',
'object-server/2.conf',
'object-server/3.conf',
'object-server/4.conf',
)
pid_files = (
('object-server/1.pid', 1),
('object-server/2.pid', 2),
('object-server/5.pid', 5),
)
with temptree(conf_files) as swift_dir:
manager.SWIFT_DIR = swift_dir
files, pids = zip(*pid_files)
with temptree(files, pids) as t:
manager.RUN_DIR = t
server = manager.Server('object', run_dir=t)
# test get all pid files
real_map = {
1: self.join_run_dir('object-server/1.pid'),
2: self.join_run_dir('object-server/2.pid'),
5: self.join_run_dir('object-server/5.pid'),
}
pid_map = {}
for pid_file, pid in server.iter_pid_files():
pid_map[pid] = pid_file
self.assertEqual(pid_map, real_map)
# test get pid with matching conf
pids = list(server.iter_pid_files(number=2))
self.assertEqual(len(pids), 1)
pid_file, pid = pids[0]
self.assertEqual(pid, 2)
pid_two = self.join_run_dir('object-server/2.pid')
self.assertEqual(pid_file, pid_two)
# try to iter on a pid number with a matching conf but no pid
pids = list(server.iter_pid_files(number=3))
self.assertFalse(pids)
# test get pids w/o matching conf
pids = list(server.iter_pid_files(number=5))
self.assertFalse(pids)
# test get pid_files by conf name
conf_files = (
'object-server/1.conf',
'object-server/2.conf',
'object-server/3.conf',
'object-server/4.conf',
)
pid_files = (
('object-server/1.pid', 1),
('object-server/2.pid', 2),
('object-server/5.pid', 5),
)
with temptree(conf_files) as swift_dir:
manager.SWIFT_DIR = swift_dir
files, pids = zip(*pid_files)
with temptree(files, pids) as t:
manager.RUN_DIR = t
server = manager.Server('object.2', run_dir=t)
# test get pid with matching conf
pids = list(server.iter_pid_files())
self.assertEqual(len(pids), 1)
pid_file, pid = pids[0]
self.assertEqual(pid, 2)
pid_two = self.join_run_dir('object-server/2.pid')
self.assertEqual(pid_file, pid_two)
def test_signal_pids(self):
temp_files = (
('var/run/zero-server.pid', 0),
('var/run/proxy-server.pid', 1),
('var/run/auth-server.pid', 2),
('var/run/one-server.pid', 3),
('var/run/object-server.pid', 4),
('var/run/invalid-server.pid', 'Forty-Two'),
('proc/3/cmdline', 'swift-another-server')
)
with temptree(*zip(*temp_files)) as t:
manager.RUN_DIR = os.path.join(t, 'var/run')
manager.PROC_DIR = os.path.join(t, 'proc')
# mock os with so both the first and second are running
manager.os = MockOs([1, 2])
server = manager.Server('proxy', run_dir=manager.RUN_DIR)
pids = server.signal_pids(DUMMY_SIG)
self.assertEqual(len(pids), 1)
self.assertIn(1, pids)
self.assertEqual(manager.os.pid_sigs[1], [DUMMY_SIG])
# make sure other process not signaled
self.assertNotIn(2, pids)
self.assertNotIn(2, manager.os.pid_sigs)
# capture stdio
old_stdout = sys.stdout
try:
with open(os.path.join(t, 'output'), 'w+') as f:
sys.stdout = f
# test print details
pids = server.signal_pids(DUMMY_SIG)
output = pop_stream(f)
self.assertIn('pid: %s' % 1, output)
self.assertIn('signal: %s' % DUMMY_SIG, output)
# test no details on signal.SIG_DFL
pids = server.signal_pids(signal.SIG_DFL)
self.assertEqual(pop_stream(f), '')
# reset mock os so only the second server is running
manager.os = MockOs([2])
# test pid not running
pids = server.signal_pids(signal.SIG_DFL)
self.assertNotIn(1, pids)
self.assertNotIn(1, manager.os.pid_sigs)
# test remove stale pid file
self.assertFalse(os.path.exists(
self.join_run_dir('proxy-server.pid')))
# reset mock os with no running pids
manager.os = MockOs([])
server = manager.Server('auth', run_dir=manager.RUN_DIR)
# test verbose warns on removing stale pid file
pids = server.signal_pids(signal.SIG_DFL, verbose=True)
output = pop_stream(f)
self.assertTrue('stale pid' in output.lower())
auth_pid = self.join_run_dir('auth-server.pid')
self.assertTrue(auth_pid in output)
# reset mock os so only the third server is running
manager.os = MockOs([3])
server = manager.Server('one', run_dir=manager.RUN_DIR)
# test verbose warns on removing invalid pid file
pids = server.signal_pids(signal.SIG_DFL, verbose=True)
output = pop_stream(f)
old_stdout.write('output %s' % output)
self.assertTrue('removing pid file' in output.lower())
one_pid = self.join_run_dir('one-server.pid')
self.assertTrue(one_pid in output)
server = manager.Server('zero', run_dir=manager.RUN_DIR)
self.assertTrue(os.path.exists(
self.join_run_dir('zero-server.pid'))) # sanity
# test verbose warns on removing pid file with invalid pid
pids = server.signal_pids(signal.SIG_DFL, verbose=True)
output = pop_stream(f)
old_stdout.write('output %s' % output)
self.assertTrue('with invalid pid' in output.lower())
self.assertFalse(os.path.exists(
self.join_run_dir('zero-server.pid')))
server = manager.Server('invalid-server',
run_dir=manager.RUN_DIR)
self.assertTrue(os.path.exists(
self.join_run_dir('invalid-server.pid'))) # sanity
# test verbose warns on removing pid file with invalid pid
pids = server.signal_pids(signal.SIG_DFL, verbose=True)
output = pop_stream(f)
old_stdout.write('output %s' % output)
self.assertTrue('with invalid pid' in output.lower())
self.assertFalse(os.path.exists(
self.join_run_dir('invalid-server.pid')))
# reset mock os with no running pids
manager.os = MockOs([])
# test warning with insufficient permissions
server = manager.Server('object', run_dir=manager.RUN_DIR)
pids = server.signal_pids(manager.os.RAISE_EPERM_SIG)
output = pop_stream(f)
self.assertTrue('no permission to signal pid 4' in
output.lower(), output)
finally:
sys.stdout = old_stdout
def test_get_running_pids(self):
# test only gets running pids
temp_files = (
('var/run/test-server1.pid', 1),
('var/run/test-server2.pid', 2),
('var/run/test-server3.pid', 3),
('proc/1/cmdline', 'swift-test-server'),
('proc/3/cmdline', 'swift-another-server')
)
with temptree(*zip(*temp_files)) as t:
manager.RUN_DIR = os.path.join(t, 'var/run')
manager.PROC_DIR = os.path.join(t, 'proc')
server = manager.Server(
'test-server', run_dir=manager.RUN_DIR)
# mock os, only pid '1' is running
manager.os = MockOs([1, 3])
running_pids = server.get_running_pids()
self.assertEqual(len(running_pids), 1)
self.assertIn(1, running_pids)
self.assertNotIn(2, running_pids)
self.assertNotIn(3, running_pids)
# test persistent running pid files
self.assertTrue(os.path.exists(
os.path.join(manager.RUN_DIR, 'test-server1.pid')))
# test clean up stale pids
pid_two = self.join_swift_dir('test-server2.pid')
self.assertFalse(os.path.exists(pid_two))
pid_three = self.join_swift_dir('test-server3.pid')
self.assertFalse(os.path.exists(pid_three))
# reset mock os, no pids running
manager.os = MockOs([])
running_pids = server.get_running_pids()
self.assertFalse(running_pids)
# and now all pid files are cleaned out
pid_one = self.join_run_dir('test-server1.pid')
self.assertFalse(os.path.exists(pid_one))
all_pids = os.listdir(manager.RUN_DIR)
self.assertEqual(len(all_pids), 0)
# test only get pids for right server
pid_files = (
('thing-doer.pid', 1),
('thing-sayer.pid', 2),
('other-doer.pid', 3),
('other-sayer.pid', 4),
)
files, pids = zip(*pid_files)
with temptree(files, pids) as t:
manager.RUN_DIR = t
# all pids are running
manager.os = MockOs(pids)
server = manager.Server('thing-doer', run_dir=t)
running_pids = server.get_running_pids()
# only thing-doer.pid, 1
self.assertEqual(len(running_pids), 1)
self.assertIn(1, running_pids)
# no other pids returned
for n in (2, 3, 4):
self.assertNotIn(n, running_pids)
# assert stale pids for other servers ignored
manager.os = MockOs([1]) # only thing-doer is running
running_pids = server.get_running_pids()
for f in ('thing-sayer.pid', 'other-doer.pid', 'other-sayer.pid'):
# other server pid files persist
self.assertTrue(os.path.exists, os.path.join(t, f))
# verify that servers are in fact not running
for server_name in ('thing-sayer', 'other-doer', 'other-sayer'):
server = manager.Server(server_name, run_dir=t)
running_pids = server.get_running_pids()
self.assertFalse(running_pids)
# and now all OTHER pid files are cleaned out
all_pids = os.listdir(t)
self.assertEqual(len(all_pids), 1)
self.assertTrue(os.path.exists(os.path.join(t, 'thing-doer.pid')))
def test_kill_running_pids(self):
pid_files = (
('object-server.pid', 1),
('object-replicator1.pid', 11),
('object-replicator2.pid', 12),
)
files, running_pids = zip(*pid_files)
with temptree(files, running_pids) as t:
manager.RUN_DIR = t
server = manager.Server('object', run_dir=t)
# test no servers running
manager.os = MockOs([])
pids = server.kill_running_pids()
self.assertFalse(pids, pids)
files, running_pids = zip(*pid_files)
with temptree(files, running_pids) as t:
manager.RUN_DIR = t
server.run_dir = t
# start up pid
manager.os = MockOs([1])
server = manager.Server('object', run_dir=t)
# test kill one pid
pids = server.kill_running_pids()
self.assertEqual(len(pids), 1)
self.assertIn(1, pids)
self.assertEqual(manager.os.pid_sigs[1], [signal.SIGTERM])
# reset os mock
manager.os = MockOs([1])
# test shutdown
self.assertTrue('object-server' in
manager.GRACEFUL_SHUTDOWN_SERVERS)
pids = server.kill_running_pids(graceful=True)
self.assertEqual(len(pids), 1)
self.assertIn(1, pids)
self.assertEqual(manager.os.pid_sigs[1], [signal.SIGHUP])
# start up other servers
manager.os = MockOs([11, 12])
# test multi server kill & ignore graceful on unsupported server
self.assertFalse('object-replicator' in
manager.GRACEFUL_SHUTDOWN_SERVERS)
server = manager.Server('object-replicator', run_dir=t)
pids = server.kill_running_pids(graceful=True)
self.assertEqual(len(pids), 2)
for pid in (11, 12):
self.assertTrue(pid in pids)
self.assertEqual(manager.os.pid_sigs[pid],
[signal.SIGTERM])
# and the other pid is of course not signaled
self.assertNotIn(1, manager.os.pid_sigs)
def test_status(self):
conf_files = (
'test-server/1.conf',
'test-server/2.conf',
'test-server/3.conf',
'test-server/4.conf',
)
pid_files = (
('test-server/1.pid', 1),
('test-server/2.pid', 2),
('test-server/3.pid', 3),
('test-server/4.pid', 4),
)
with temptree(conf_files) as swift_dir:
manager.SWIFT_DIR = swift_dir
files, pids = zip(*pid_files)
with temptree(files, pids) as t:
manager.RUN_DIR = t
# setup running servers
server = manager.Server('test', run_dir=t)
# capture stdio
old_stdout = sys.stdout
try:
with open(os.path.join(t, 'output'), 'w+') as f:
sys.stdout = f
# test status for all running
manager.os = MockOs(pids)
proc_files = (
('1/cmdline', 'swift-test-server'),
('2/cmdline', 'swift-test-server'),
('3/cmdline', 'swift-test-server'),
('4/cmdline', 'swift-test-server'),
)
files, contents = zip(*proc_files)
with temptree(files, contents) as t:
manager.PROC_DIR = t
self.assertEqual(server.status(), 0)
output = pop_stream(f).strip().splitlines()
self.assertEqual(len(output), 4)
for line in output:
self.assertTrue('test-server running' in line)
# test get single server by number
with temptree([], []) as t:
manager.PROC_DIR = t
self.assertEqual(server.status(number=4), 0)
output = pop_stream(f).strip().splitlines()
self.assertEqual(len(output), 1)
line = output[0]
self.assertTrue('test-server running' in line)
conf_four = self.join_swift_dir(conf_files[3])
self.assertTrue('4 - %s' % conf_four in line)
# test some servers not running
manager.os = MockOs([1, 2, 3])
proc_files = (
('1/cmdline', 'swift-test-server'),
('2/cmdline', 'swift-test-server'),
('3/cmdline', 'swift-test-server'),
)
files, contents = zip(*proc_files)
with temptree(files, contents) as t:
manager.PROC_DIR = t
self.assertEqual(server.status(), 0)
output = pop_stream(f).strip().splitlines()
self.assertEqual(len(output), 3)
for line in output:
self.assertTrue('test-server running' in line)
# test single server not running
manager.os = MockOs([1, 2])
proc_files = (
('1/cmdline', 'swift-test-server'),
('2/cmdline', 'swift-test-server'),
)
files, contents = zip(*proc_files)
with temptree(files, contents) as t:
manager.PROC_DIR = t
self.assertEqual(server.status(number=3), 1)
output = pop_stream(f).strip().splitlines()
self.assertEqual(len(output), 1)
line = output[0]
self.assertTrue('not running' in line)
conf_three = self.join_swift_dir(conf_files[2])
self.assertTrue(conf_three in line)
# test no running pids
manager.os = MockOs([])
with temptree([], []) as t:
manager.PROC_DIR = t
self.assertEqual(server.status(), 1)
output = pop_stream(f).lower()
self.assertTrue('no test-server running' in output)
# test use provided pids
pids = {
1: '1.pid',
2: '2.pid',
}
# shouldn't call get_running_pids
called = []
def mock(*args, **kwargs):
called.append(True)
server.get_running_pids = mock
status = server.status(pids=pids)
self.assertEqual(status, 0)
self.assertFalse(called)
output = pop_stream(f).strip().splitlines()
self.assertEqual(len(output), 2)
for line in output:
self.assertTrue('test-server running' in line)
finally:
sys.stdout = old_stdout
def test_spawn(self):
# mocks
class MockProcess(object):
NOTHING = 'default besides None'
STDOUT = 'stdout'
PIPE = 'pipe'
def __init__(self, pids=None):
if pids is None:
pids = []
self.pids = (p for p in pids)
def Popen(self, args, **kwargs):
return MockProc(next(self.pids), args, **kwargs)
class MockProc(object):
def __init__(self, pid, args, stdout=MockProcess.NOTHING,
stderr=MockProcess.NOTHING):
self.pid = pid
self.args = args
self.stdout = stdout
if stderr == MockProcess.STDOUT:
self.stderr = self.stdout
else:
self.stderr = stderr
# setup running servers
server = manager.Server('test')
with temptree(['test-server.conf']) as swift_dir:
manager.SWIFT_DIR = swift_dir
with temptree([]) as t:
manager.RUN_DIR = t
server.run_dir = t
old_subprocess = manager.subprocess
try:
# test single server process calls spawn once
manager.subprocess = MockProcess([1])
conf_file = self.join_swift_dir('test-server.conf')
# spawn server no kwargs
server.spawn(conf_file)
# test pid file
pid_file = self.join_run_dir('test-server.pid')
self.assertTrue(os.path.exists(pid_file))
pid_on_disk = int(open(pid_file).read().strip())
self.assertEqual(pid_on_disk, 1)
# assert procs args
self.assertTrue(server.procs)
self.assertEqual(len(server.procs), 1)
proc = server.procs[0]
expected_args = [
'swift-test-server',
conf_file,
]
self.assertEqual(proc.args, expected_args)
# assert stdout is piped
self.assertEqual(proc.stdout, MockProcess.PIPE)
self.assertEqual(proc.stderr, proc.stdout)
# test multi server process calls spawn multiple times
manager.subprocess = MockProcess([11, 12, 13, 14])
conf1 = self.join_swift_dir('test-server/1.conf')
conf2 = self.join_swift_dir('test-server/2.conf')
conf3 = self.join_swift_dir('test-server/3.conf')
conf4 = self.join_swift_dir('test-server/4.conf')
server = manager.Server('test', run_dir=t)
# test server run once
server.spawn(conf1, once=True)
self.assertTrue(server.procs)
self.assertEqual(len(server.procs), 1)
proc = server.procs[0]
expected_args = ['swift-test-server', conf1, 'once']
# assert stdout is piped
self.assertEqual(proc.stdout, MockProcess.PIPE)
self.assertEqual(proc.stderr, proc.stdout)
# test server not daemon
server.spawn(conf2, daemon=False)
self.assertTrue(server.procs)
self.assertEqual(len(server.procs), 2)
proc = server.procs[1]
expected_args = ['swift-test-server', conf2, 'verbose']
self.assertEqual(proc.args, expected_args)
# assert stdout is not changed
self.assertIsNone(proc.stdout)
self.assertIsNone(proc.stderr)
# test server wait
server.spawn(conf3, wait=False)
self.assertTrue(server.procs)
self.assertEqual(len(server.procs), 3)
proc = server.procs[2]
# assert stdout is /dev/null
with open('/dev/null', 'wb+') as fp:
self.assertTrue(isinstance(proc.stdout, type(fp)))
self.assertEqual(proc.stdout.name, os.devnull)
self.assertIn('b', proc.stdout.mode)
self.assertTrue(any(x in proc.stdout.mode for x in 'aw+'),
'mode must be writable, not %r' %
proc.stdout.mode)
self.assertEqual(proc.stderr, proc.stdout)
# test not daemon over-rides wait
server.spawn(conf4, wait=False, daemon=False, once=True)
self.assertTrue(server.procs)
self.assertEqual(len(server.procs), 4)
proc = server.procs[3]
expected_args = ['swift-test-server', conf4, 'once',
'verbose']
self.assertEqual(proc.args, expected_args)
# daemon behavior should trump wait, once shouldn't matter
self.assertIsNone(proc.stdout)
self.assertIsNone(proc.stderr)
# assert pids
for i, proc in enumerate(server.procs):
pid_file = self.join_run_dir('test-server/%d.pid' %
(i + 1))
pid_on_disk = int(open(pid_file).read().strip())
self.assertEqual(pid_on_disk, proc.pid)
finally:
manager.subprocess = old_subprocess
def test_wait(self):
server = manager.Server('test')
self.assertEqual(server.wait(), 0)
class MockProcess(threading.Thread):
def __init__(self, delay=0.1, fail_to_start=False):
threading.Thread.__init__(self)
# setup pipe
rfd, wfd = os.pipe()
# subprocess connection to read stdout
self.stdout = os.fdopen(rfd)
# real process connection to write stdout
self._stdout = os.fdopen(wfd, 'w')
self.delay = delay
self.finished = False
self.returncode = None
if fail_to_start:
self._returncode = 1
self.run = self.fail
else:
self._returncode = 0
def __enter__(self):
self.start()
return self
def __exit__(self, *args):
if self.isAlive():
self.join()
def close_stdout(self):
self._stdout.flush()
with open(os.devnull, 'wb') as nullfile:
try:
os.dup2(nullfile.fileno(), self._stdout.fileno())
except OSError:
pass
def fail(self):
print('mock process started', file=self._stdout)
sleep(self.delay) # perform setup processing
print('mock process failed to start', file=self._stdout)
self.close_stdout()
def poll(self):
self.returncode = self._returncode
return self.returncode or None
def run(self):
print('mock process started', file=self._stdout)
sleep(self.delay) # perform setup processing
print('setup complete!', file=self._stdout)
self.close_stdout()
sleep(self.delay) # do some more processing
print('mock process finished', file=self._stdout)
self.finished = True
class MockTime(object):
def time(self):
return time()
def sleep(self, *args, **kwargs):
pass
with temptree([]) as t:
old_stdout = sys.stdout
old_wait = manager.WARNING_WAIT
old_time = manager.time
try:
manager.WARNING_WAIT = 0.01
manager.time = MockTime()
with open(os.path.join(t, 'output'), 'w+') as f:
# actually capture the read stdout (for prints)
sys.stdout = f
# test closing pipe in subprocess unblocks read
with MockProcess() as proc:
server.procs = [proc]
status = server.wait()
self.assertEqual(status, 0)
# wait should return before process exits
self.assertTrue(proc.isAlive())
self.assertFalse(proc.finished)
self.assertTrue(proc.finished) # make sure it did finish
# test output kwarg prints subprocess output
with MockProcess() as proc:
server.procs = [proc]
status = server.wait(output=True)
output = pop_stream(f)
self.assertIn('mock process started', output)
self.assertIn('setup complete', output)
# make sure we don't get prints after stdout was closed
self.assertNotIn('mock process finished', output)
# test process which fails to start
with MockProcess(fail_to_start=True) as proc:
server.procs = [proc]
status = server.wait()
self.assertEqual(status, 1)
self.assertIn('failed', pop_stream(f))
# test multiple procs
procs = [MockProcess(delay=.5) for i in range(3)]
for proc in procs:
proc.start()
server.procs = procs
status = server.wait()
self.assertEqual(status, 0)
for proc in procs:
self.assertTrue(proc.isAlive())
for proc in procs:
proc.join()
finally:
sys.stdout = old_stdout
manager.WARNING_WAIT = old_wait
manager.time = old_time
def test_interact(self):
class MockProcess(object):
def __init__(self, fail=False):
self.returncode = None
if fail:
self._returncode = 1
else:
self._returncode = 0
def communicate(self):
self.returncode = self._returncode
return '', ''
server = manager.Server('test')
server.procs = [MockProcess()]
self.assertEqual(server.interact(), 0)
server.procs = [MockProcess(fail=True)]
self.assertEqual(server.interact(), 1)
procs = []
for fail in (False, True, True):
procs.append(MockProcess(fail=fail))
server.procs = procs
self.assertTrue(server.interact() > 0)
def test_launch(self):
# stubs
conf_files = (
'proxy-server.conf',
'auth-server.conf',
'object-server/1.conf',
'object-server/2.conf',
'object-server/3.conf',
'object-server/4.conf',
)
pid_files = (
('proxy-server.pid', 1),
('proxy-server/2.pid', 2),
)
# mocks
class MockSpawn(object):
def __init__(self, pids=None):
self.conf_files = []
self.kwargs = []
if not pids:
def one_forever():
while True:
yield 1
self.pids = one_forever()
else:
self.pids = (x for x in pids)
def __call__(self, conf_file, **kwargs):
self.conf_files.append(conf_file)
self.kwargs.append(kwargs)
rv = next(self.pids)
if isinstance(rv, Exception):
raise rv
else:
return rv
with temptree(conf_files) as swift_dir:
manager.SWIFT_DIR = swift_dir
files, pids = zip(*pid_files)
with temptree(files, pids) as t:
manager.RUN_DIR = t
old_stdout = sys.stdout
try:
with open(os.path.join(t, 'output'), 'w+') as f:
sys.stdout = f
# can't start server w/o an conf
server = manager.Server('test', run_dir=t)
self.assertFalse(server.launch())
# start mock os running all pids
manager.os = MockOs(pids)
proc_files = (
('1/cmdline', 'swift-proxy-server'),
('2/cmdline', 'swift-proxy-server'),
)
files, contents = zip(*proc_files)
with temptree(files, contents) as proc_dir:
manager.PROC_DIR = proc_dir
server = manager.Server('proxy', run_dir=t)
# can't start server if it's already running
self.assertFalse(server.launch())
output = pop_stream(f)
self.assertTrue('running' in output)
conf_file = self.join_swift_dir(
'proxy-server.conf')
self.assertTrue(conf_file in output)
pid_file = self.join_run_dir('proxy-server/2.pid')
self.assertTrue(pid_file in output)
self.assertTrue('already started' in output)
# no running pids
manager.os = MockOs([])
with temptree([], []) as proc_dir:
manager.PROC_DIR = proc_dir
# test ignore once for non-start-once server
mock_spawn = MockSpawn([1])
server.spawn = mock_spawn
conf_file = self.join_swift_dir(
'proxy-server.conf')
expected = {
1: conf_file,
}
self.assertEqual(server.launch(once=True),
expected)
self.assertEqual(mock_spawn.conf_files,
[conf_file])
expected = {
'once': False,
}
self.assertEqual(mock_spawn.kwargs, [expected])
output = pop_stream(f)
self.assertIn('Starting', output)
self.assertNotIn('once', output)
# test multi-server kwarg once
server = manager.Server('object-replicator')
with temptree([], []) as proc_dir:
manager.PROC_DIR = proc_dir
mock_spawn = MockSpawn([1, 2, 3, 4])
server.spawn = mock_spawn
conf1 = self.join_swift_dir('object-server/1.conf')
conf2 = self.join_swift_dir('object-server/2.conf')
conf3 = self.join_swift_dir('object-server/3.conf')
conf4 = self.join_swift_dir('object-server/4.conf')
expected = {
1: conf1,
2: conf2,
3: conf3,
4: conf4,
}
self.assertEqual(server.launch(once=True),
expected)
self.assertEqual(mock_spawn.conf_files, [
conf1, conf2, conf3, conf4])
expected = {
'once': True,
}
self.assertEqual(len(mock_spawn.kwargs), 4)
for kwargs in mock_spawn.kwargs:
self.assertEqual(kwargs, expected)
# test number kwarg
mock_spawn = MockSpawn([4])
manager.PROC_DIR = proc_dir
server.spawn = mock_spawn
expected = {
4: conf4,
}
self.assertEqual(server.launch(number=4),
expected)
self.assertEqual(mock_spawn.conf_files, [conf4])
expected = {
'number': 4
}
self.assertEqual(mock_spawn.kwargs, [expected])
# test cmd does not exist
server = manager.Server('auth')
with temptree([], []) as proc_dir:
manager.PROC_DIR = proc_dir
mock_spawn = MockSpawn([OSError(errno.ENOENT,
'blah')])
server.spawn = mock_spawn
self.assertEqual(server.launch(), {})
self.assertTrue(
'swift-auth-server does not exist' in
pop_stream(f))
finally:
sys.stdout = old_stdout
def test_stop(self):
conf_files = (
'account-server/1.conf',
'account-server/2.conf',
'account-server/3.conf',
'account-server/4.conf',
)
pid_files = (
('account-reaper/1.pid', 1),
('account-reaper/2.pid', 2),
('account-reaper/3.pid', 3),
('account-reaper/4.pid', 4),
)
with temptree(conf_files) as swift_dir:
manager.SWIFT_DIR = swift_dir
files, pids = zip(*pid_files)
with temptree(files, pids) as t:
manager.RUN_DIR = t
# start all pids in mock os
manager.os = MockOs(pids)
server = manager.Server('account-reaper', run_dir=t)
# test kill all running pids
pids = server.stop()
self.assertEqual(len(pids), 4)
for pid in (1, 2, 3, 4):
self.assertTrue(pid in pids)
self.assertEqual(manager.os.pid_sigs[pid],
[signal.SIGTERM])
conf1 = self.join_swift_dir('account-reaper/1.conf')
conf2 = self.join_swift_dir('account-reaper/2.conf')
conf3 = self.join_swift_dir('account-reaper/3.conf')
conf4 = self.join_swift_dir('account-reaper/4.conf')
# reset mock os with only 2 running pids
manager.os = MockOs([3, 4])
pids = server.stop()
self.assertEqual(len(pids), 2)
for pid in (3, 4):
self.assertTrue(pid in pids)
self.assertEqual(manager.os.pid_sigs[pid],
[signal.SIGTERM])
self.assertFalse(os.path.exists(conf1))
self.assertFalse(os.path.exists(conf2))
# test number kwarg
manager.os = MockOs([3, 4])
pids = server.stop(number=3)
self.assertEqual(len(pids), 1)
expected = {
3: conf3,
}
self.assertTrue(pids, expected)
self.assertEqual(manager.os.pid_sigs[3], [signal.SIGTERM])
self.assertFalse(os.path.exists(conf4))
self.assertFalse(os.path.exists(conf3))
class TestManager(unittest.TestCase):
def test_create(self):
m = manager.Manager(['test'])
self.assertEqual(len(m.servers), 1)
server = m.servers.pop()
self.assertTrue(isinstance(server, manager.Server))
self.assertEqual(server.server, 'test-server')
# test multi-server and simple dedupe
servers = ['object-replicator', 'object-auditor', 'object-replicator']
m = manager.Manager(servers)
self.assertEqual(len(m.servers), 2)
for server in m.servers:
self.assertTrue(server.server in servers)
# test all
m = manager.Manager(['all'])
self.assertEqual(len(m.servers), len(manager.ALL_SERVERS))
for server in m.servers:
self.assertTrue(server.server in manager.ALL_SERVERS)
# test main
m = manager.Manager(['main'])
self.assertEqual(len(m.servers), len(manager.MAIN_SERVERS))
for server in m.servers:
self.assertTrue(server.server in manager.MAIN_SERVERS)
# test rest
m = manager.Manager(['rest'])
self.assertEqual(len(m.servers), len(manager.REST_SERVERS))
for server in m.servers:
self.assertTrue(server.server in manager.REST_SERVERS)
# test main + rest == all
m = manager.Manager(['main', 'rest'])
self.assertEqual(len(m.servers), len(manager.ALL_SERVERS))
for server in m.servers:
self.assertTrue(server.server in manager.ALL_SERVERS)
# test dedupe
m = manager.Manager(['main', 'rest', 'proxy', 'object',
'container', 'account'])
self.assertEqual(len(m.servers), len(manager.ALL_SERVERS))
for server in m.servers:
self.assertTrue(server.server in manager.ALL_SERVERS)
# test glob
m = manager.Manager(['object-*'])
object_servers = [s for s in manager.ALL_SERVERS if
s.startswith('object')]
self.assertEqual(len(m.servers), len(object_servers))
for s in m.servers:
self.assertTrue(str(s) in object_servers)
m = manager.Manager(['*-replicator'])
replicators = [s for s in manager.ALL_SERVERS if
s.endswith('replicator')]
for s in m.servers:
self.assertTrue(str(s) in replicators)
def test_iter(self):
m = manager.Manager(['all'])
self.assertEqual(len(list(m)), len(manager.ALL_SERVERS))
for server in m:
self.assertTrue(server.server in manager.ALL_SERVERS)
def test_default_strict(self):
# test default strict
m = manager.Manager(['proxy'])
self.assertEqual(m._default_strict, True)
# aliases
m = manager.Manager(['main'])
self.assertEqual(m._default_strict, False)
m = manager.Manager(['proxy*'])
self.assertEqual(m._default_strict, False)
def test_status(self):
class MockServer(object):
def __init__(self, server, run_dir=manager.RUN_DIR):
self.server = server
self.called_kwargs = []
def status(self, **kwargs):
self.called_kwargs.append(kwargs)
if 'error' in self.server:
return 1
else:
return 0
old_server_class = manager.Server
try:
manager.Server = MockServer
m = manager.Manager(['test'])
status = m.status()
self.assertEqual(status, 0)
m = manager.Manager(['error'])
status = m.status()
self.assertEqual(status, 1)
# test multi-server
m = manager.Manager(['test', 'error'])
kwargs = {'key': 'value'}
status = m.status(**kwargs)
self.assertEqual(status, 1)
for server in m.servers:
self.assertEqual(server.called_kwargs, [kwargs])
finally:
manager.Server = old_server_class
def test_start(self):
def mock_setup_env():
getattr(mock_setup_env, 'called', []).append(True)
class MockServer(object):
def __init__(self, server, run_dir=manager.RUN_DIR):
self.server = server
self.called = defaultdict(list)
def launch(self, **kwargs):
self.called['launch'].append(kwargs)
if 'noconfig' in self.server:
return {}
elif 'somerunning' in self.server:
return {}
else:
return {1: self.server[0]}
def wait(self, **kwargs):
self.called['wait'].append(kwargs)
return int('error' in self.server)
def stop(self, **kwargs):
self.called['stop'].append(kwargs)
def interact(self, **kwargs):
self.called['interact'].append(kwargs)
if 'raise' in self.server:
raise KeyboardInterrupt
elif 'error' in self.server:
return 1
else:
return 0
old_setup_env = manager.setup_env
old_swift_server = manager.Server
try:
manager.setup_env = mock_setup_env
manager.Server = MockServer
# test no errors on launch
m = manager.Manager(['proxy'])
status = m.start()
self.assertEqual(status, 0)
for server in m.servers:
self.assertEqual(server.called['launch'], [{}])
# test error on launch
m = manager.Manager(['proxy', 'error'])
status = m.start()
self.assertEqual(status, 1)
for server in m.servers:
self.assertEqual(server.called['launch'], [{}])
self.assertEqual(server.called['wait'], [{}])
# test interact
m = manager.Manager(['proxy', 'error'])
kwargs = {'daemon': False}
status = m.start(**kwargs)
self.assertEqual(status, 1)
for server in m.servers:
self.assertEqual(server.called['launch'], [kwargs])
self.assertEqual(server.called['interact'], [kwargs])
m = manager.Manager(['raise'])
kwargs = {'daemon': False}
status = m.start(**kwargs)
# test no config
m = manager.Manager(['proxy', 'noconfig'])
status = m.start()
self.assertEqual(status, 1)
for server in m.servers:
self.assertEqual(server.called['launch'], [{}])
self.assertEqual(server.called['wait'], [{}])
# test no config with --non-strict
m = manager.Manager(['proxy', 'noconfig'])
status = m.start(strict=False)
self.assertEqual(status, 0)
for server in m.servers:
self.assertEqual(server.called['launch'], [{'strict': False}])
self.assertEqual(server.called['wait'], [{'strict': False}])
# test no config --strict
m = manager.Manager(['proxy', 'noconfig'])
status = m.start(strict=True)
self.assertEqual(status, 1)
for server in m.servers:
self.assertEqual(server.called['launch'], [{'strict': True}])
self.assertEqual(server.called['wait'], [{'strict': True}])
# test no config with alias
m = manager.Manager(['main', 'noconfig'])
status = m.start()
self.assertEqual(status, 0)
for server in m.servers:
self.assertEqual(server.called['launch'], [{}])
self.assertEqual(server.called['wait'], [{}])
# test no config with alias and --non-strict
m = manager.Manager(['main', 'noconfig'])
status = m.start(strict=False)
self.assertEqual(status, 0)
for server in m.servers:
self.assertEqual(server.called['launch'], [{'strict': False}])
self.assertEqual(server.called['wait'], [{'strict': False}])
# test no config with alias and --strict
m = manager.Manager(['main', 'noconfig'])
status = m.start(strict=True)
self.assertEqual(status, 1)
for server in m.servers:
self.assertEqual(server.called['launch'], [{'strict': True}])
self.assertEqual(server.called['wait'], [{'strict': True}])
# test already all running
m = manager.Manager(['proxy', 'somerunning'])
status = m.start()
self.assertEqual(status, 1)
for server in m.servers:
self.assertEqual(server.called['launch'], [{}])
self.assertEqual(server.called['wait'], [{}])
# test already all running --non-strict
m = manager.Manager(['proxy', 'somerunning'])
status = m.start(strict=False)
self.assertEqual(status, 0)
for server in m.servers:
self.assertEqual(server.called['launch'], [{'strict': False}])
self.assertEqual(server.called['wait'], [{'strict': False}])
# test already all running --strict
m = manager.Manager(['proxy', 'somerunning'])
status = m.start(strict=True)
self.assertEqual(status, 1)
for server in m.servers:
self.assertEqual(server.called['launch'], [{'strict': True}])
self.assertEqual(server.called['wait'], [{'strict': True}])
# test already all running with alias
m = manager.Manager(['main', 'somerunning'])
status = m.start()
self.assertEqual(status, 0)
for server in m.servers:
self.assertEqual(server.called['launch'], [{}])
self.assertEqual(server.called['wait'], [{}])
# test already all running with alias and --non-strict
m = manager.Manager(['main', 'somerunning'])
status = m.start(strict=False)
self.assertEqual(status, 0)
for server in m.servers:
self.assertEqual(server.called['launch'], [{'strict': False}])
self.assertEqual(server.called['wait'], [{'strict': False}])
# test already all running with alias and --strict
m = manager.Manager(['main', 'somerunning'])
status = m.start(strict=True)
self.assertEqual(status, 1)
for server in m.servers:
self.assertEqual(server.called['launch'], [{'strict': True}])
self.assertEqual(server.called['wait'], [{'strict': True}])
finally:
manager.setup_env = old_setup_env
manager.Server = old_swift_server
def test_no_wait(self):
class MockServer(object):
def __init__(self, server, run_dir=manager.RUN_DIR):
self.server = server
self.called = defaultdict(list)
def launch(self, **kwargs):
self.called['launch'].append(kwargs)
# must return non-empty dict if launch succeeded
return {1: self.server[0]}
def wait(self, **kwargs):
self.called['wait'].append(kwargs)
return int('error' in self.server)
orig_swift_server = manager.Server
try:
manager.Server = MockServer
# test success
init = manager.Manager(['proxy'])
status = init.no_wait()
self.assertEqual(status, 0)
for server in init.servers:
self.assertEqual(len(server.called['launch']), 1)
called_kwargs = server.called['launch'][0]
self.assertFalse(called_kwargs['wait'])
self.assertFalse(server.called['wait'])
# test no errocode status even on error
init = manager.Manager(['error'])
status = init.no_wait()
self.assertEqual(status, 0)
for server in init.servers:
self.assertEqual(len(server.called['launch']), 1)
called_kwargs = server.called['launch'][0]
self.assertTrue('wait' in called_kwargs)
self.assertFalse(called_kwargs['wait'])
self.assertFalse(server.called['wait'])
# test wait with once option
init = manager.Manager(['updater', 'replicator-error'])
status = init.no_wait(once=True)
self.assertEqual(status, 0)
for server in init.servers:
self.assertEqual(len(server.called['launch']), 1)
called_kwargs = server.called['launch'][0]
self.assertTrue('wait' in called_kwargs)
self.assertFalse(called_kwargs['wait'])
self.assertTrue('once' in called_kwargs)
self.assertTrue(called_kwargs['once'])
self.assertFalse(server.called['wait'])
finally:
manager.Server = orig_swift_server
def test_no_daemon(self):
class MockServer(object):
def __init__(self, server, run_dir=manager.RUN_DIR):
self.server = server
self.called = defaultdict(list)
def launch(self, **kwargs):
self.called['launch'].append(kwargs)
# must return non-empty dict if launch succeeded
return {1: self.server[0]}
def interact(self, **kwargs):
self.called['interact'].append(kwargs)
return int('error' in self.server)
orig_swift_server = manager.Server
try:
manager.Server = MockServer
# test success
init = manager.Manager(['proxy'])
stats = init.no_daemon()
self.assertEqual(stats, 0)
# test error
init = manager.Manager(['proxy', 'object-error'])
stats = init.no_daemon()
self.assertEqual(stats, 1)
# test once
init = manager.Manager(['proxy', 'object-error'])
stats = init.no_daemon()
for server in init.servers:
self.assertEqual(len(server.called['launch']), 1)
self.assertEqual(len(server.called['wait']), 0)
self.assertEqual(len(server.called['interact']), 1)
finally:
manager.Server = orig_swift_server
def test_once(self):
class MockServer(object):
def __init__(self, server, run_dir=manager.RUN_DIR):
self.server = server
self.called = defaultdict(list)
def wait(self, **kwargs):
self.called['wait'].append(kwargs)
if 'error' in self.server:
return 1
else:
return 0
def launch(self, **kwargs):
self.called['launch'].append(kwargs)
return {1: 'account-reaper'}
orig_swift_server = manager.Server
try:
manager.Server = MockServer
# test no errors
init = manager.Manager(['account-reaper'])
status = init.once()
self.assertEqual(status, 0)
# test error code on error
init = manager.Manager(['error-reaper'])
status = init.once()
self.assertEqual(status, 1)
for server in init.servers:
self.assertEqual(len(server.called['launch']), 1)
called_kwargs = server.called['launch'][0]
self.assertEqual(called_kwargs, {'once': True})
self.assertEqual(len(server.called['wait']), 1)
self.assertEqual(len(server.called['interact']), 0)
finally:
manager.Server = orig_swift_server
def test_stop(self):
class MockServerFactory(object):
class MockServer(object):
def __init__(self, pids, run_dir=manager.RUN_DIR):
self.pids = pids
def stop(self, **kwargs):
return self.pids
def status(self, **kwargs):
return not self.pids
def __init__(self, server_pids, run_dir=manager.RUN_DIR):
self.server_pids = server_pids
def __call__(self, server, run_dir=manager.RUN_DIR):
return MockServerFactory.MockServer(self.server_pids[server])
def mock_watch_server_pids(server_pids, **kwargs):
for server, pids in server_pids.items():
for pid in pids:
if pid is None:
continue
yield server, pid
def mock_kill_group(pid, sig):
self.fail('kill_group should not be called')
_orig_server = manager.Server
_orig_watch_server_pids = manager.watch_server_pids
_orig_kill_group = manager.kill_group
try:
manager.watch_server_pids = mock_watch_server_pids
manager.kill_group = mock_kill_group
# test stop one server
server_pids = {
'test': {1: "dummy.pid"}
}
manager.Server = MockServerFactory(server_pids)
m = manager.Manager(['test'])
status = m.stop()
self.assertEqual(status, 0)
# test not running
server_pids = {
'test': {}
}
manager.Server = MockServerFactory(server_pids)
m = manager.Manager(['test'])
status = m.stop()
self.assertEqual(status, 1)
# test kill not running
server_pids = {
'test': {}
}
manager.Server = MockServerFactory(server_pids)
m = manager.Manager(['test'])
status = m.kill()
self.assertEqual(status, 0)
# test won't die
server_pids = {
'test': {None: None}
}
manager.Server = MockServerFactory(server_pids)
m = manager.Manager(['test'])
status = m.stop()
self.assertEqual(status, 1)
finally:
manager.Server = _orig_server
manager.watch_server_pids = _orig_watch_server_pids
manager.kill_group = _orig_kill_group
def test_stop_kill_after_timeout(self):
class MockServerFactory(object):
class MockServer(object):
def __init__(self, pids, run_dir=manager.RUN_DIR):
self.pids = pids
def stop(self, **kwargs):
return self.pids
def status(self, **kwargs):
return not self.pids
def __init__(self, server_pids, run_dir=manager.RUN_DIR):
self.server_pids = server_pids
def __call__(self, server, run_dir=manager.RUN_DIR):
return MockServerFactory.MockServer(self.server_pids[server])
def mock_watch_server_pids(server_pids, **kwargs):
for server, pids in server_pids.items():
for pid in pids:
if pid is None:
continue
yield server, pid
mock_kill_group_called = []
def mock_kill_group(*args):
mock_kill_group_called.append(args)
def mock_kill_group_oserr(*args):
raise OSError()
def mock_kill_group_oserr_ESRCH(*args):
raise OSError(errno.ESRCH, 'No such process')
_orig_server = manager.Server
_orig_watch_server_pids = manager.watch_server_pids
_orig_kill_group = manager.kill_group
try:
manager.watch_server_pids = mock_watch_server_pids
manager.kill_group = mock_kill_group
# test stop one server
server_pids = {
'test': {None: None}
}
manager.Server = MockServerFactory(server_pids)
m = manager.Manager(['test'])
status = m.stop(kill_after_timeout=True)
self.assertEqual(status, 1)
self.assertEqual(mock_kill_group_called, [(None, 9)])
manager.kill_group = mock_kill_group_oserr
# test stop one server - OSError
server_pids = {
'test': {None: None}
}
manager.Server = MockServerFactory(server_pids)
m = manager.Manager(['test'])
with self.assertRaises(OSError):
status = m.stop(kill_after_timeout=True)
manager.kill_group = mock_kill_group_oserr_ESRCH
# test stop one server - OSError: No such process
server_pids = {
'test': {None: None}
}
manager.Server = MockServerFactory(server_pids)
m = manager.Manager(['test'])
status = m.stop(kill_after_timeout=True)
self.assertEqual(status, 1)
finally:
manager.Server = _orig_server
manager.watch_server_pids = _orig_watch_server_pids
manager.kill_group = _orig_kill_group
def test_shutdown(self):
m = manager.Manager(['test'])
m.stop_was_called = False
def mock_stop(*args, **kwargs):
m.stop_was_called = True
expected = {'graceful': True}
self.assertEqual(kwargs, expected)
return 0
m.stop = mock_stop
status = m.shutdown()
self.assertEqual(status, 0)
self.assertEqual(m.stop_was_called, True)
def test_restart(self):
m = manager.Manager(['test'])
m.stop_was_called = False
def mock_stop(*args, **kwargs):
m.stop_was_called = True
return 0
m.start_was_called = False
def mock_start(*args, **kwargs):
m.start_was_called = True
return 0
m.stop = mock_stop
m.start = mock_start
status = m.restart()
self.assertEqual(status, 0)
self.assertEqual(m.stop_was_called, True)
self.assertEqual(m.start_was_called, True)
def test_reload(self):
def do_test(graceful):
called = defaultdict(list)
def stop(self, **kwargs):
called[self].append(('stop', kwargs))
return 0
def start(self, **kwargs):
called[self].append(('start', kwargs))
return 0
m = manager.Manager(['*-server'])
self.assertEqual(len(m.servers), 4)
expected_servers = set([server.server for server in m.servers])
for server in expected_servers:
self.assertIn(server, manager.GRACEFUL_SHUTDOWN_SERVERS)
with mock.patch('swift.common.manager.Manager.start', start):
with mock.patch('swift.common.manager.Manager.stop', stop):
status = m.reload(graceful=graceful)
self.assertEqual(status, 0)
self.assertEqual(4, len(called))
actual_servers = set()
for m, calls in called.items():
self.assertEqual(calls, [('stop', {'graceful': True}),
('start', {'graceful': True})])
actual_servers.update([server.server for server in m.servers])
self.assertEqual(expected_servers, actual_servers)
do_test(graceful=True)
do_test(graceful=False) # graceful is forced regardless of the kwarg
def test_force_reload(self):
m = manager.Manager(['test'])
m.reload_was_called = False
def mock_reload(*args, **kwargs):
m.reload_was_called = True
return 0
m.reload = mock_reload
status = m.force_reload()
self.assertEqual(status, 0)
self.assertEqual(m.reload_was_called, True)
def test_get_command(self):
m = manager.Manager(['test'])
self.assertEqual(m.start, m.get_command('start'))
self.assertEqual(m.force_reload, m.get_command('force-reload'))
self.assertEqual(m.get_command('force-reload'),
m.get_command('force_reload'))
self.assertRaises(manager.UnknownCommandError, m.get_command,
'no_command')
self.assertRaises(manager.UnknownCommandError, m.get_command,
'__init__')
def test_list_commands(self):
for cmd, help in manager.Manager.list_commands():
method = getattr(manager.Manager, cmd.replace('-', '_'), None)
self.assertTrue(method, '%s is not a command' % cmd)
self.assertTrue(getattr(method, 'publicly_accessible', False))
self.assertEqual(method.__doc__.strip(), help)
def test_run_command(self):
m = manager.Manager(['test'])
m.cmd_was_called = False
def mock_cmd(*args, **kwargs):
m.cmd_was_called = True
expected = {'kw1': True, 'kw2': False}
self.assertEqual(kwargs, expected)
return 0
mock_cmd.publicly_accessible = True
m.mock_cmd = mock_cmd
kwargs = {'kw1': True, 'kw2': False}
status = m.run_command('mock_cmd', **kwargs)
self.assertEqual(status, 0)
self.assertEqual(m.cmd_was_called, True)
if __name__ == '__main__':
unittest.main()