more runroot tests
This commit is contained in:
parent
f2390dad1c
commit
b133f9c983
4 changed files with 329 additions and 7 deletions
2
Makefile
2
Makefile
|
|
@ -66,7 +66,7 @@ git-clean:
|
||||||
|
|
||||||
test:
|
test:
|
||||||
coverage erase
|
coverage erase
|
||||||
PYTHONPATH=hub/.:plugins/hub/. nosetests --with-coverage --cover-package .
|
PYTHONPATH=hub/.:plugins/hub/.:plugins/builder/. nosetests --with-coverage --cover-package .
|
||||||
coverage html
|
coverage html
|
||||||
@echo Coverage report in htmlcov/index.html
|
@echo Coverage report in htmlcov/index.html
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -246,7 +246,7 @@ class RunRootTask(tasks.BaseTaskHandler):
|
||||||
self.logger.info('New runroot')
|
self.logger.info('New runroot')
|
||||||
self.logger.info("Runroot mounts: %s" % mounts)
|
self.logger.info("Runroot mounts: %s" % mounts)
|
||||||
fn = '%s/tmp/runroot_mounts' % rootdir
|
fn = '%s/tmp/runroot_mounts' % rootdir
|
||||||
fslog = file(fn, 'a')
|
fslog = open(fn, 'a')
|
||||||
logfile = "%s/do_mounts.log" % self.workdir
|
logfile = "%s/do_mounts.log" % self.workdir
|
||||||
uploadpath = self.getUploadDir()
|
uploadpath = self.getUploadDir()
|
||||||
error = None
|
error = None
|
||||||
|
|
@ -264,9 +264,6 @@ class RunRootTask(tasks.BaseTaskHandler):
|
||||||
error = koji.GenericError("No such directory or mount: %s" % dev)
|
error = koji.GenericError("No such directory or mount: %s" % dev)
|
||||||
break
|
break
|
||||||
type = 'none'
|
type = 'none'
|
||||||
if path is None:
|
|
||||||
#shorthand for "same path"
|
|
||||||
path = dev
|
|
||||||
if 'bg' in opts:
|
if 'bg' in opts:
|
||||||
error = koji.GenericError("bad config: background mount not allowed")
|
error = koji.GenericError("bad config: background mount not allowed")
|
||||||
break
|
break
|
||||||
|
|
@ -294,8 +291,8 @@ class RunRootTask(tasks.BaseTaskHandler):
|
||||||
mounts = {}
|
mounts = {}
|
||||||
fn = '%s/tmp/runroot_mounts' % rootdir
|
fn = '%s/tmp/runroot_mounts' % rootdir
|
||||||
if os.path.exists(fn):
|
if os.path.exists(fn):
|
||||||
fslog = file(fn, 'r')
|
fslog = open(fn, 'r')
|
||||||
for line in fslog:
|
for line in fslog.readlines():
|
||||||
mounts.setdefault(line.strip(), 1)
|
mounts.setdefault(line.strip(), 1)
|
||||||
fslog.close()
|
fslog.close()
|
||||||
#also, check /proc/mounts just in case
|
#also, check /proc/mounts just in case
|
||||||
|
|
|
||||||
202
tests/test_plugins/test_runroot_builder.py
Normal file
202
tests/test_plugins/test_runroot_builder.py
Normal file
|
|
@ -0,0 +1,202 @@
|
||||||
|
import unittest
|
||||||
|
import mock
|
||||||
|
import ConfigParser
|
||||||
|
|
||||||
|
# inject builder data
|
||||||
|
from tests.test_builder.loadkojid import kojid
|
||||||
|
import __main__
|
||||||
|
__main__.BuildRoot = kojid.BuildRoot
|
||||||
|
|
||||||
|
import koji
|
||||||
|
from runroot import RunRootTask
|
||||||
|
|
||||||
|
class FakeConfigParser(object):
|
||||||
|
def __init__(self):
|
||||||
|
self.CONFIG = {
|
||||||
|
'paths': {
|
||||||
|
'default_mounts': '/mnt/archive,/mnt/workdir',
|
||||||
|
'safe_roots': '/mnt/workdir/tmp',
|
||||||
|
'path_subs':
|
||||||
|
'/mnt/archive/prehistory/,/mnt/prehistoric_disk/archive/prehistory',
|
||||||
|
},
|
||||||
|
'path0': {
|
||||||
|
'mountpoint': '/mnt/archive',
|
||||||
|
'path': 'archive.org:/vol/archive',
|
||||||
|
'fstype': 'nfs',
|
||||||
|
'options': 'ro,hard,intr,nosuid,nodev,noatime,tcp',
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
def read(self, path):
|
||||||
|
return
|
||||||
|
|
||||||
|
def has_option(self, section, key):
|
||||||
|
return section in self.CONFIG and key in self.CONFIG[section]
|
||||||
|
|
||||||
|
def has_section(self, section):
|
||||||
|
return section in self.CONFIG
|
||||||
|
|
||||||
|
def get(self, section, key):
|
||||||
|
try:
|
||||||
|
return self.CONFIG[section][key]
|
||||||
|
except KeyError:
|
||||||
|
raise ConfigParser.NoOptionError(section, key)
|
||||||
|
|
||||||
|
|
||||||
|
class TestRunrootConfig(unittest.TestCase):
|
||||||
|
@mock.patch('ConfigParser.SafeConfigParser')
|
||||||
|
def test_bad_config_paths0(self, safe_config_parser):
|
||||||
|
cp = FakeConfigParser()
|
||||||
|
del cp.CONFIG['path0']['mountpoint']
|
||||||
|
safe_config_parser.return_value = cp
|
||||||
|
session = mock.MagicMock()
|
||||||
|
options = mock.MagicMock()
|
||||||
|
options.workdir = '/tmp/nonexistentdirectory'
|
||||||
|
with self.assertRaises(koji.GenericError) as cm:
|
||||||
|
RunRootTask(123, 'runroot', {}, session, options)
|
||||||
|
self.assertEqual(cm.exception.message,
|
||||||
|
"bad config: missing options in path0 section")
|
||||||
|
|
||||||
|
@mock.patch('ConfigParser.SafeConfigParser')
|
||||||
|
def test_bad_config_absolute_path(self, safe_config_parser):
|
||||||
|
cp = FakeConfigParser()
|
||||||
|
cp.CONFIG['paths']['default_mounts'] = ''
|
||||||
|
safe_config_parser.return_value = cp
|
||||||
|
session = mock.MagicMock()
|
||||||
|
options = mock.MagicMock()
|
||||||
|
options.workdir = '/tmp/nonexistentdirectory'
|
||||||
|
with self.assertRaises(koji.GenericError) as cm:
|
||||||
|
RunRootTask(123, 'runroot', {}, session, options)
|
||||||
|
self.assertEqual(cm.exception.message,
|
||||||
|
"bad config: all paths (default_mounts, safe_roots, path_subs) needs to be absolute: ")
|
||||||
|
|
||||||
|
@mock.patch('ConfigParser.SafeConfigParser')
|
||||||
|
def test_valid_config(self, safe_config_parser):
|
||||||
|
safe_config_parser.return_value = FakeConfigParser()
|
||||||
|
session = mock.MagicMock()
|
||||||
|
options = mock.MagicMock()
|
||||||
|
options.workdir = '/tmp/nonexistentdirectory'
|
||||||
|
RunRootTask(123, 'runroot', {}, session, options)
|
||||||
|
|
||||||
|
class TestMounts(unittest.TestCase):
|
||||||
|
@mock.patch('ConfigParser.SafeConfigParser')
|
||||||
|
def setUp(self, safe_config_parser):
|
||||||
|
safe_config_parser.return_value = FakeConfigParser()
|
||||||
|
self.session = mock.MagicMock()
|
||||||
|
options = mock.MagicMock()
|
||||||
|
options.workdir = '/tmp/nonexistentdirectory'
|
||||||
|
self.t = RunRootTask(123, 'runroot', {}, self.session, options)
|
||||||
|
|
||||||
|
def test_get_path_params(self):
|
||||||
|
# non-existent item
|
||||||
|
with self.assertRaises(koji.GenericError):
|
||||||
|
self.t._get_path_params('nonexistent_dir')
|
||||||
|
|
||||||
|
# valid item
|
||||||
|
self.assertEqual(self.t._get_path_params('/mnt/archive', 'rw'),
|
||||||
|
('archive.org:/vol/archive/', '/mnt/archive', 'nfs', 'rw,hard,intr,nosuid,nodev,noatime,tcp'))
|
||||||
|
|
||||||
|
@mock.patch('os.path.isdir')
|
||||||
|
@mock.patch('runroot.open')
|
||||||
|
@mock.patch('runroot.log_output')
|
||||||
|
def test_do_mounts(self, log_output, file_mock, is_dir):
|
||||||
|
log_output.return_value = 0 # successful mount
|
||||||
|
|
||||||
|
# no mounts, don't do anything
|
||||||
|
self.t.logger = mock.MagicMock()
|
||||||
|
self.t.do_mounts('rootdir', [])
|
||||||
|
self.t.logger.assert_not_called()
|
||||||
|
|
||||||
|
# mountpoint has no absolute_path
|
||||||
|
with self.assertRaises(koji.GenericError) as cm:
|
||||||
|
self.t.do_mounts('rootdir', [('nfs:nfs', 'relative_path', 'nfs', '')])
|
||||||
|
self.assertEqual(cm.exception.message,
|
||||||
|
"invalid mount point: relative_path")
|
||||||
|
|
||||||
|
# cover missing opts
|
||||||
|
self.t.do_mounts('rootdir', [('nfs:nfs', '/mnt/archive', 'nfs', None)])
|
||||||
|
|
||||||
|
# standard
|
||||||
|
log_output.reset_mock()
|
||||||
|
mounts = [self.t._get_path_params('/mnt/archive')]
|
||||||
|
self.t.do_mounts('rootdir', mounts)
|
||||||
|
log_output.assert_called_once_with(self.session, 'mount',
|
||||||
|
['mount', '-t', 'nfs', '-o', 'ro,hard,intr,nosuid,nodev,noatime,tcp',
|
||||||
|
'archive.org:/vol/archive/', 'rootdir/mnt/archive'],
|
||||||
|
'/tmp/nonexistentdirectory/tasks/123/123/do_mounts.log',
|
||||||
|
'tasks/123/123', append=True, logerror=True)
|
||||||
|
|
||||||
|
# mount command failed
|
||||||
|
log_output.reset_mock()
|
||||||
|
log_output.return_value = 1
|
||||||
|
#self.t.undo_mounts = mock.MagicMock()
|
||||||
|
mounts = [self.t._get_path_params('/mnt/archive')]
|
||||||
|
with self.assertRaises(koji.GenericError) as cm:
|
||||||
|
self.t.do_mounts('rootdir', mounts)
|
||||||
|
self.assertEqual(cm.exception.message,
|
||||||
|
'Unable to mount rootdir/mnt/archive: mount -t nfs -o'
|
||||||
|
' ro,hard,intr,nosuid,nodev,noatime,tcp archive.org:/vol/archive/'
|
||||||
|
' rootdir/mnt/archive was killed by signal 1')
|
||||||
|
|
||||||
|
# bind ok
|
||||||
|
log_output.return_value = 0
|
||||||
|
log_output.reset_mock()
|
||||||
|
mount = list(self.t._get_path_params('/mnt/archive'))
|
||||||
|
mount[3] += ',bind'
|
||||||
|
is_dir.return_value = True
|
||||||
|
self.t.do_mounts('rootdir', [mount])
|
||||||
|
log_output.assert_called_once_with(self.session, 'mount',
|
||||||
|
['mount', '-t', 'none', '-o', 'ro,hard,intr,nosuid,nodev,noatime,tcp,bind',
|
||||||
|
'archive.org:/vol/archive/', 'rootdir/mnt/archive'],
|
||||||
|
'/tmp/nonexistentdirectory/tasks/123/123/do_mounts.log',
|
||||||
|
'tasks/123/123', append=True, logerror=True)
|
||||||
|
|
||||||
|
# bind - target doesn't exist
|
||||||
|
mount = list(self.t._get_path_params('/mnt/archive'))
|
||||||
|
mount[3] += ',bind'
|
||||||
|
is_dir.return_value = False
|
||||||
|
with self.assertRaises(koji.GenericError) as cm:
|
||||||
|
self.t.do_mounts('rootdir', [mount])
|
||||||
|
self.assertEqual(cm.exception.message,
|
||||||
|
"No such directory or mount: archive.org:/vol/archive/")
|
||||||
|
|
||||||
|
# bg option forbidden
|
||||||
|
log_output.reset_mock()
|
||||||
|
mount = list(self.t._get_path_params('/mnt/archive'))
|
||||||
|
mount[3] += ',bg'
|
||||||
|
is_dir.return_value = False
|
||||||
|
with self.assertRaises(koji.GenericError) as cm:
|
||||||
|
self.t.do_mounts('rootdir', [mount])
|
||||||
|
self.assertEqual(cm.exception.message,
|
||||||
|
"bad config: background mount not allowed")
|
||||||
|
|
||||||
|
@mock.patch('os.unlink')
|
||||||
|
@mock.patch('commands.getstatusoutput')
|
||||||
|
@mock.patch('os.path.exists')
|
||||||
|
def test_undo_mounts(self, path_exists, getstatusoutput, os_unlink):
|
||||||
|
self.t.logger = mock.MagicMock()
|
||||||
|
|
||||||
|
# correct
|
||||||
|
getstatusoutput.return_value = (0, 'ok')
|
||||||
|
path_exists.return_value = True
|
||||||
|
with mock.patch('runroot.open', mock.mock_open(read_data = 'mountpoint')):
|
||||||
|
self.t.undo_mounts('rootdir')
|
||||||
|
self.t.logger.assert_has_calls([
|
||||||
|
mock.call.debug('Unmounting runroot mounts'),
|
||||||
|
mock.call.info("Unmounting (runroot): ['mountpoint']"),
|
||||||
|
])
|
||||||
|
os_unlink.assert_called_once_with('rootdir/tmp/runroot_mounts')
|
||||||
|
|
||||||
|
# fail
|
||||||
|
os_unlink.reset_mock()
|
||||||
|
getstatusoutput.return_value = (1, 'error')
|
||||||
|
path_exists.return_value = True
|
||||||
|
with mock.patch('runroot.open', mock.mock_open(read_data = 'mountpoint')):
|
||||||
|
with self.assertRaises(koji.GenericError) as cm:
|
||||||
|
self.t.undo_mounts('rootdir')
|
||||||
|
self.assertEqual(cm.exception.message, 'Unable to unmount: mountpoint: error')
|
||||||
|
os_unlink.assert_not_called()
|
||||||
|
|
||||||
|
class TestHandler(unittest.TestCase):
|
||||||
|
# TODO
|
||||||
|
pass
|
||||||
|
|
@ -1,6 +1,7 @@
|
||||||
import unittest
|
import unittest
|
||||||
import mock
|
import mock
|
||||||
|
|
||||||
|
import koji
|
||||||
import runroot_hub
|
import runroot_hub
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -21,3 +22,125 @@ class TestRunrootHub(unittest.TestCase):
|
||||||
arch='x86_64',
|
arch='x86_64',
|
||||||
channel='runroot',
|
channel='runroot',
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@mock.patch('kojihub.get_tag')
|
||||||
|
@mock.patch('runroot_hub.context')
|
||||||
|
def test_noarch_wrong_tag(self, context, get_tag):
|
||||||
|
context.session.assertPerm = mock.MagicMock()
|
||||||
|
get_tag.return_value = {'name': 'some_tag', 'arches': ''}
|
||||||
|
with self.assertRaises(koji.GenericError):
|
||||||
|
runroot_hub.runroot(
|
||||||
|
tagInfo='some_tag',
|
||||||
|
arch='noarch',
|
||||||
|
command='ls',
|
||||||
|
)
|
||||||
|
get_tag.assert_called_once_with('some_tag')
|
||||||
|
|
||||||
|
@mock.patch('kojihub.make_task')
|
||||||
|
@mock.patch('kojihub.get_all_arches')
|
||||||
|
@mock.patch('kojihub.get_tag')
|
||||||
|
@mock.patch('runroot_hub.context')
|
||||||
|
def test_noarch_good_tag(self, context, get_tag, get_all_arches, make_task):
|
||||||
|
context.session.assertPerm = mock.MagicMock()
|
||||||
|
context.handlers = mock.MagicMock()
|
||||||
|
context.handlers.call = mock.MagicMock()
|
||||||
|
context.handlers.call.side_effect = [
|
||||||
|
{'id': 2, 'name': 'runroot'}, # getChannel
|
||||||
|
[ # listHosts
|
||||||
|
{
|
||||||
|
'arches': 'i386 x86_64',
|
||||||
|
'capacity': 20.0,
|
||||||
|
'comment': '',
|
||||||
|
'description': '',
|
||||||
|
'enabled': True,
|
||||||
|
'id': 1,
|
||||||
|
'name': 'builder.example.com',
|
||||||
|
'ready': True,
|
||||||
|
'task_load': 0.0,
|
||||||
|
'user_id': 1
|
||||||
|
}
|
||||||
|
]
|
||||||
|
]
|
||||||
|
get_tag.return_value = {
|
||||||
|
'arches': 's390 x86_64',
|
||||||
|
'extra': {},
|
||||||
|
'id': 123456,
|
||||||
|
'locked': False,
|
||||||
|
'maven_include_all': False,
|
||||||
|
'maven_support': False,
|
||||||
|
'name': 'some_tag',
|
||||||
|
'perm': None,
|
||||||
|
'perm_id': None
|
||||||
|
}
|
||||||
|
get_all_arches.return_value = ['s390', 's390x', 'x86_64']
|
||||||
|
runroot_hub.runroot(
|
||||||
|
tagInfo='some_tag',
|
||||||
|
arch='noarch',
|
||||||
|
command='ls',
|
||||||
|
)
|
||||||
|
|
||||||
|
# check results
|
||||||
|
get_tag.assert_called_once_with('some_tag')
|
||||||
|
context.handlers.call.assert_has_calls([
|
||||||
|
mock.call('getChannel', 'runroot', strict=True),
|
||||||
|
mock.call('listHosts', channelID=2, enabled=True),
|
||||||
|
])
|
||||||
|
make_task.assert_called_once_with(
|
||||||
|
'runroot',
|
||||||
|
('some_tag', 'noarch', 'ls'),
|
||||||
|
priority=15,
|
||||||
|
arch='x86_64',
|
||||||
|
channel='runroot',
|
||||||
|
)
|
||||||
|
|
||||||
|
@mock.patch('kojihub.make_task')
|
||||||
|
@mock.patch('kojihub.get_all_arches')
|
||||||
|
@mock.patch('kojihub.get_tag')
|
||||||
|
@mock.patch('runroot_hub.context')
|
||||||
|
def test_noarch_good_tag_missing_arch(self, context, get_tag, get_all_arches, make_task):
|
||||||
|
context.session.assertPerm = mock.MagicMock()
|
||||||
|
context.handlers = mock.MagicMock()
|
||||||
|
context.handlers.call = mock.MagicMock()
|
||||||
|
context.handlers.call.side_effect = [
|
||||||
|
{'id': 2, 'name': 'runroot'}, # getChannel
|
||||||
|
[ # listHosts
|
||||||
|
{
|
||||||
|
'arches': 'i386 x86_64',
|
||||||
|
'capacity': 20.0,
|
||||||
|
'comment': '',
|
||||||
|
'description': '',
|
||||||
|
'enabled': True,
|
||||||
|
'id': 1,
|
||||||
|
'name': 'builder.example.com',
|
||||||
|
'ready': True,
|
||||||
|
'task_load': 0.0,
|
||||||
|
'user_id': 1
|
||||||
|
}
|
||||||
|
]
|
||||||
|
]
|
||||||
|
get_tag.return_value = {
|
||||||
|
'arches': 's390',
|
||||||
|
'extra': {},
|
||||||
|
'id': 123456,
|
||||||
|
'locked': False,
|
||||||
|
'maven_include_all': False,
|
||||||
|
'maven_support': False,
|
||||||
|
'name': 'some_tag',
|
||||||
|
'perm': None,
|
||||||
|
'perm_id': None
|
||||||
|
}
|
||||||
|
get_all_arches.return_value = ['s390x']
|
||||||
|
with self.assertRaises(koji.GenericError):
|
||||||
|
runroot_hub.runroot(
|
||||||
|
tagInfo='some_tag',
|
||||||
|
arch='noarch',
|
||||||
|
command='ls',
|
||||||
|
)
|
||||||
|
|
||||||
|
# check results
|
||||||
|
get_tag.assert_called_once_with('some_tag')
|
||||||
|
context.handlers.call.assert_has_calls([
|
||||||
|
mock.call('getChannel', 'runroot', strict=True),
|
||||||
|
mock.call('listHosts', channelID=2, enabled=True),
|
||||||
|
])
|
||||||
|
make_task.assert_not_called()
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue