more runroot tests

This commit is contained in:
Tomas Kopecek 2017-03-21 17:36:43 +01:00 committed by Mike McLean
parent f2390dad1c
commit b133f9c983
4 changed files with 329 additions and 7 deletions

View file

@ -66,7 +66,7 @@ git-clean:
test:
coverage erase
PYTHONPATH=hub/.:plugins/hub/. nosetests --with-coverage --cover-package .
PYTHONPATH=hub/.:plugins/hub/.:plugins/builder/. nosetests --with-coverage --cover-package .
coverage html
@echo Coverage report in htmlcov/index.html

View file

@ -246,7 +246,7 @@ class RunRootTask(tasks.BaseTaskHandler):
self.logger.info('New runroot')
self.logger.info("Runroot mounts: %s" % mounts)
fn = '%s/tmp/runroot_mounts' % rootdir
fslog = file(fn, 'a')
fslog = open(fn, 'a')
logfile = "%s/do_mounts.log" % self.workdir
uploadpath = self.getUploadDir()
error = None
@ -264,9 +264,6 @@ class RunRootTask(tasks.BaseTaskHandler):
error = koji.GenericError("No such directory or mount: %s" % dev)
break
type = 'none'
if path is None:
#shorthand for "same path"
path = dev
if 'bg' in opts:
error = koji.GenericError("bad config: background mount not allowed")
break
@ -294,8 +291,8 @@ class RunRootTask(tasks.BaseTaskHandler):
mounts = {}
fn = '%s/tmp/runroot_mounts' % rootdir
if os.path.exists(fn):
fslog = file(fn, 'r')
for line in fslog:
fslog = open(fn, 'r')
for line in fslog.readlines():
mounts.setdefault(line.strip(), 1)
fslog.close()
#also, check /proc/mounts just in case

View 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

View file

@ -1,6 +1,7 @@
import unittest
import mock
import koji
import runroot_hub
@ -21,3 +22,125 @@ class TestRunrootHub(unittest.TestCase):
arch='x86_64',
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()