Move Python ramdisk code out of tree
New home will be IPA, patch is already being worked on: https://review.openstack.org/#/c/194116/ Anyway, we have to get rid of the ramdisk code before release. Change-Id: I1b71a466059e70fd249712eaaf325efd459addcf Closes-Bug: #1464708
This commit is contained in:
parent
9654a066de
commit
931685d008
@ -1,4 +0,0 @@
|
|||||||
#!/usr/bin/env python
|
|
||||||
|
|
||||||
from ironic_inspector_ramdisk import main
|
|
||||||
main.main()
|
|
@ -1,257 +0,0 @@
|
|||||||
# Licensed under the Apache License, Version 2.0 (the "License");
|
|
||||||
# you may not use this file except in compliance with the License.
|
|
||||||
# You may obtain a copy of the License at
|
|
||||||
#
|
|
||||||
# http://www.apache.org/licenses/LICENSE-2.0
|
|
||||||
#
|
|
||||||
# Unless required by applicable law or agreed to in writing, software
|
|
||||||
# distributed under the License is distributed on an "AS IS" BASIS,
|
|
||||||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
|
|
||||||
# implied.
|
|
||||||
# See the License for the specific language governing permissions and
|
|
||||||
# limitations under the License.
|
|
||||||
|
|
||||||
import base64
|
|
||||||
import json
|
|
||||||
import logging
|
|
||||||
import os
|
|
||||||
import subprocess
|
|
||||||
import tarfile
|
|
||||||
import tempfile
|
|
||||||
|
|
||||||
import netifaces
|
|
||||||
import requests
|
|
||||||
|
|
||||||
|
|
||||||
LOG = logging.getLogger('ironic-inspector-ramdisk')
|
|
||||||
|
|
||||||
|
|
||||||
def try_call(*cmd, **kwargs):
|
|
||||||
strip = kwargs.pop('strip', True)
|
|
||||||
kwargs['stdout'] = subprocess.PIPE
|
|
||||||
kwargs['stderr'] = subprocess.PIPE
|
|
||||||
try:
|
|
||||||
p = subprocess.Popen(cmd, **kwargs)
|
|
||||||
out, err = p.communicate()
|
|
||||||
except EnvironmentError as exc:
|
|
||||||
LOG.warn('command %s failed: %s', cmd, exc)
|
|
||||||
return
|
|
||||||
|
|
||||||
if p.returncode:
|
|
||||||
LOG.warn('command %s returned failure status %d:\n%s', cmd,
|
|
||||||
p.returncode, err.strip())
|
|
||||||
else:
|
|
||||||
return out.strip() if strip else out
|
|
||||||
|
|
||||||
|
|
||||||
def try_shell(sh, **kwargs):
|
|
||||||
strip = kwargs.pop('strip', True)
|
|
||||||
kwargs['stdout'] = subprocess.PIPE
|
|
||||||
kwargs['stderr'] = subprocess.PIPE
|
|
||||||
kwargs['shell'] = True
|
|
||||||
|
|
||||||
p = subprocess.Popen([sh], **kwargs)
|
|
||||||
out, err = p.communicate()
|
|
||||||
if p.returncode:
|
|
||||||
LOG.warn('shell script "%s" failed with code %d:\n%s', sh,
|
|
||||||
p.returncode, err.strip())
|
|
||||||
else:
|
|
||||||
return out.strip() if strip else out
|
|
||||||
|
|
||||||
|
|
||||||
class AccumulatedFailure(object):
|
|
||||||
"""Object accumulated failures without raising exception."""
|
|
||||||
def __init__(self):
|
|
||||||
self._failures = []
|
|
||||||
|
|
||||||
def add(self, fail, *fmt):
|
|
||||||
"""Add failure with optional formatting."""
|
|
||||||
if fmt:
|
|
||||||
fail = fail % fmt
|
|
||||||
LOG.error('%s', fail)
|
|
||||||
self._failures.append(fail)
|
|
||||||
|
|
||||||
def get_error(self):
|
|
||||||
"""Get error string or None."""
|
|
||||||
if not self._failures:
|
|
||||||
return
|
|
||||||
|
|
||||||
msg = ('The following errors were encountered during '
|
|
||||||
'hardware discovery:\n%s'
|
|
||||||
% '\n'.join('* %s' % item for item in self._failures))
|
|
||||||
return msg
|
|
||||||
|
|
||||||
def __nonzero__(self):
|
|
||||||
return bool(self._failures)
|
|
||||||
|
|
||||||
__bool__ = __nonzero__
|
|
||||||
|
|
||||||
def __repr__(self): # pragma: no cover
|
|
||||||
# This is for tests
|
|
||||||
if self:
|
|
||||||
return '<%s: %s>' % (self.__class__.__name__,
|
|
||||||
', '.join(self._failures))
|
|
||||||
else:
|
|
||||||
return '<%s: success>' % self.__class__.__name__
|
|
||||||
|
|
||||||
|
|
||||||
def discover_basic_properties(data, args):
|
|
||||||
# These properties might not be present, we don't count it as failure
|
|
||||||
data['boot_interface'] = args.bootif
|
|
||||||
data['ipmi_address'] = try_shell(
|
|
||||||
"ipmitool lan print | grep -e 'IP Address [^S]' | awk '{ print $4 }'")
|
|
||||||
LOG.info('BMC IP address: %s', data['ipmi_address'])
|
|
||||||
|
|
||||||
|
|
||||||
def discover_network_interfaces(data, failures):
|
|
||||||
data.setdefault('interfaces', {})
|
|
||||||
for iface in netifaces.interfaces():
|
|
||||||
if iface.startswith('lo'):
|
|
||||||
LOG.info('ignoring local network interface %s', iface)
|
|
||||||
continue
|
|
||||||
|
|
||||||
LOG.debug('found network interface %s', iface)
|
|
||||||
addrs = netifaces.ifaddresses(iface)
|
|
||||||
|
|
||||||
try:
|
|
||||||
mac = addrs[netifaces.AF_LINK][0]['addr']
|
|
||||||
except (KeyError, IndexError):
|
|
||||||
LOG.info('no link information for interface %s in %s',
|
|
||||||
iface, addrs)
|
|
||||||
continue
|
|
||||||
|
|
||||||
try:
|
|
||||||
ip = addrs[netifaces.AF_INET][0]['addr']
|
|
||||||
except (KeyError, IndexError):
|
|
||||||
LOG.info('no IP address for interface %s', iface)
|
|
||||||
ip = None
|
|
||||||
|
|
||||||
data['interfaces'][iface] = {'mac': mac, 'ip': ip}
|
|
||||||
|
|
||||||
if data['interfaces']:
|
|
||||||
LOG.info('network interfaces: %s', data['interfaces'])
|
|
||||||
else:
|
|
||||||
failures.add('no network interfaces found')
|
|
||||||
|
|
||||||
|
|
||||||
def discover_scheduling_properties(data, failures):
|
|
||||||
scripts = [
|
|
||||||
('cpus', "grep processor /proc/cpuinfo | wc -l"),
|
|
||||||
('cpu_arch', "lscpu | grep Architecture | awk '{ print $2 }'"),
|
|
||||||
('local_gb', "fdisk -l | grep Disk | awk '{print $5}' | head -n 1"),
|
|
||||||
]
|
|
||||||
for key, script in scripts:
|
|
||||||
data[key] = try_shell(script)
|
|
||||||
LOG.info('value for "%s" field is %s', key, data[key])
|
|
||||||
|
|
||||||
ram_info = try_shell(
|
|
||||||
"dmidecode --type memory | grep Size | awk '{ print $2; }'")
|
|
||||||
if ram_info:
|
|
||||||
total_ram = 0
|
|
||||||
for ram_record in ram_info.split('\n'):
|
|
||||||
try:
|
|
||||||
total_ram += int(ram_record)
|
|
||||||
except ValueError:
|
|
||||||
pass
|
|
||||||
data['memory_mb'] = total_ram
|
|
||||||
LOG.info('total RAM: %s MiB', total_ram)
|
|
||||||
else:
|
|
||||||
LOG.warn('failed to get RAM information')
|
|
||||||
|
|
||||||
for key in ('cpus', 'local_gb', 'memory_mb'):
|
|
||||||
try:
|
|
||||||
data[key] = int(data[key])
|
|
||||||
except (KeyError, ValueError, TypeError):
|
|
||||||
LOG.warn('value for %s is missing or malformed: %s',
|
|
||||||
key, data.get(key))
|
|
||||||
data[key] = None
|
|
||||||
|
|
||||||
# FIXME(dtantsur): -1 is required to give Ironic some spacing for
|
|
||||||
# partitioning and may be removed later
|
|
||||||
if data['local_gb']:
|
|
||||||
data['local_gb'] = data['local_gb'] / 1024 / 1024 / 1024 - 1
|
|
||||||
if data['local_gb'] < 1:
|
|
||||||
LOG.warn('local_gb is less than 1 GiB')
|
|
||||||
data['local_gb'] = None
|
|
||||||
|
|
||||||
|
|
||||||
def discover_additional_properties(args, data, failures):
|
|
||||||
hw_args = ('--benchmark', 'cpu', 'disk', 'mem') if args.benchmark else ()
|
|
||||||
hw_json = try_call('hardware-detect', *hw_args)
|
|
||||||
if hw_json:
|
|
||||||
try:
|
|
||||||
data['data'] = json.loads(hw_json)
|
|
||||||
except ValueError:
|
|
||||||
LOG.error('JSON value returned from hardware-detect cannot be '
|
|
||||||
'decoded:\n%s', hw_json)
|
|
||||||
failures.add('unable to get extended hardware properties')
|
|
||||||
else:
|
|
||||||
failures.add('unable to get extended hardware properties')
|
|
||||||
|
|
||||||
|
|
||||||
def discover_block_devices(data):
|
|
||||||
block_devices = try_shell(
|
|
||||||
"lsblk -no TYPE,SERIAL | grep disk | awk '{print $2}'")
|
|
||||||
if not block_devices:
|
|
||||||
LOG.warn('unable to get block devices')
|
|
||||||
return
|
|
||||||
|
|
||||||
serials = [item for item in block_devices.split('\n') if item.strip()]
|
|
||||||
data['block_devices'] = {'serials': serials}
|
|
||||||
|
|
||||||
|
|
||||||
def discover_hardware(args, data, failures):
|
|
||||||
try_call('modprobe', 'ipmi_msghandler')
|
|
||||||
try_call('modprobe', 'ipmi_devintf')
|
|
||||||
try_call('modprobe', 'ipmi_si')
|
|
||||||
|
|
||||||
discover_basic_properties(data, args)
|
|
||||||
discover_network_interfaces(data, failures)
|
|
||||||
discover_scheduling_properties(data, failures)
|
|
||||||
if args.use_hardware_detect:
|
|
||||||
discover_additional_properties(args, data, failures)
|
|
||||||
discover_block_devices(data)
|
|
||||||
|
|
||||||
|
|
||||||
def call_inspector(args, data, failures):
|
|
||||||
data['error'] = failures.get_error()
|
|
||||||
|
|
||||||
LOG.info('posting collected data to %s', args.callback_url)
|
|
||||||
resp = requests.post(args.callback_url, data=json.dumps(data))
|
|
||||||
if resp.status_code >= 400:
|
|
||||||
LOG.error('inspector error %d: %s',
|
|
||||||
resp.status_code,
|
|
||||||
resp.content.decode('utf-8'))
|
|
||||||
resp.raise_for_status()
|
|
||||||
return resp.json()
|
|
||||||
|
|
||||||
|
|
||||||
def collect_logs(args):
|
|
||||||
files = {args.log_file} | set(args.system_log_file or ())
|
|
||||||
with tempfile.TemporaryFile() as fp:
|
|
||||||
with tarfile.open(fileobj=fp, mode='w:gz') as tar:
|
|
||||||
with tempfile.NamedTemporaryFile() as jrnl_fp:
|
|
||||||
if try_shell("journalctl > '%s'" % jrnl_fp.name) is not None:
|
|
||||||
tar.add(jrnl_fp.name, arcname='journal')
|
|
||||||
else:
|
|
||||||
LOG.warn('failed to get system journal')
|
|
||||||
|
|
||||||
for fname in files:
|
|
||||||
if os.path.exists(fname):
|
|
||||||
tar.add(fname)
|
|
||||||
else:
|
|
||||||
LOG.warn('log file %s does not exist', fname)
|
|
||||||
|
|
||||||
fp.seek(0)
|
|
||||||
return base64.b64encode(fp.read())
|
|
||||||
|
|
||||||
|
|
||||||
def setup_ipmi_credentials(resp):
|
|
||||||
user, password = resp['ipmi_username'], resp['ipmi_password']
|
|
||||||
if try_call('ipmitool', 'user', 'set', 'name', '2', user) is None:
|
|
||||||
raise RuntimeError('failed to set IPMI user name to %s', user)
|
|
||||||
if try_call('ipmitool', 'user', 'set', 'password', '2', password) is None:
|
|
||||||
raise RuntimeError('failed to set IPMI password')
|
|
||||||
try_call('ipmitool', 'user', 'enable', '2')
|
|
||||||
try_call('ipmitool', 'channel', 'setaccess', '1', '2',
|
|
||||||
'link=on', 'ipmi=on', 'callin=on', 'privilege=4')
|
|
@ -1,93 +0,0 @@
|
|||||||
# Licensed under the Apache License, Version 2.0 (the "License");
|
|
||||||
# you may not use this file except in compliance with the License.
|
|
||||||
# You may obtain a copy of the License at
|
|
||||||
#
|
|
||||||
# http://www.apache.org/licenses/LICENSE-2.0
|
|
||||||
#
|
|
||||||
# Unless required by applicable law or agreed to in writing, software
|
|
||||||
# distributed under the License is distributed on an "AS IS" BASIS,
|
|
||||||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
|
|
||||||
# implied.
|
|
||||||
# See the License for the specific language governing permissions and
|
|
||||||
# limitations under the License.
|
|
||||||
|
|
||||||
import argparse
|
|
||||||
import logging
|
|
||||||
import sys
|
|
||||||
|
|
||||||
import requests
|
|
||||||
|
|
||||||
from ironic_inspector_ramdisk import discover
|
|
||||||
|
|
||||||
|
|
||||||
LOG = logging.getLogger('ironic-inspector-ramdisk')
|
|
||||||
|
|
||||||
|
|
||||||
def parse_args(args):
|
|
||||||
parser = argparse.ArgumentParser(description='Detect present hardware.')
|
|
||||||
parser.add_argument('-L', '--system-log-file', action='append',
|
|
||||||
help='System log file to be sent to inspector, may be '
|
|
||||||
'specified multiple times')
|
|
||||||
parser.add_argument('-l', '--log-file', default='discovery-logs',
|
|
||||||
help='Path to log file, defaults to ./discovery-logs')
|
|
||||||
parser.add_argument('--bootif', help='PXE boot interface')
|
|
||||||
# Support for edeploy plugin
|
|
||||||
parser.add_argument('--use-hardware-detect', action='store_true',
|
|
||||||
help='Use hardware-detect utility from '
|
|
||||||
'python-hardware package')
|
|
||||||
parser.add_argument('--benchmark', action='store_true',
|
|
||||||
help='Enables benchmarking for hardware-detect')
|
|
||||||
# ironic-inspector callback
|
|
||||||
parser.add_argument('callback_url',
|
|
||||||
help='Full ironic-inspector callback URL')
|
|
||||||
return parser.parse_args(args)
|
|
||||||
|
|
||||||
|
|
||||||
def setup_logging(args):
|
|
||||||
format = '%(asctime)s %(levelname)s: %(name)s: %(message)s'
|
|
||||||
logging.basicConfig(filename=args.log_file, filemode='w',
|
|
||||||
level=logging.DEBUG, format=format)
|
|
||||||
hnd = logging.StreamHandler()
|
|
||||||
hnd.setLevel(logging.WARNING)
|
|
||||||
formatter = logging.Formatter('%(levelname)s: %(message)s')
|
|
||||||
hnd.setFormatter(formatter)
|
|
||||||
logging.getLogger().addHandler(hnd)
|
|
||||||
|
|
||||||
|
|
||||||
def main():
|
|
||||||
args = parse_args(sys.argv[1:])
|
|
||||||
data = {}
|
|
||||||
setup_logging(args)
|
|
||||||
failures = discover.AccumulatedFailure()
|
|
||||||
|
|
||||||
try:
|
|
||||||
discover.discover_hardware(args, data, failures)
|
|
||||||
except Exception as exc:
|
|
||||||
LOG.exception('failed to discover data')
|
|
||||||
failures.add(exc)
|
|
||||||
|
|
||||||
try:
|
|
||||||
data['logs'] = discover.collect_logs(args)
|
|
||||||
except Exception:
|
|
||||||
LOG.exception('failed to collect logs')
|
|
||||||
|
|
||||||
call_error = True
|
|
||||||
resp = {}
|
|
||||||
try:
|
|
||||||
resp = discover.call_inspector(args, data, failures)
|
|
||||||
except requests.RequestException as exc:
|
|
||||||
LOG.error('%s when calling to inspector', exc)
|
|
||||||
except Exception:
|
|
||||||
LOG.exception('failed to call inspector')
|
|
||||||
else:
|
|
||||||
call_error = False
|
|
||||||
|
|
||||||
if resp.get('ipmi_setup_credentials'):
|
|
||||||
try:
|
|
||||||
discover.setup_ipmi_credentials(resp)
|
|
||||||
except Exception:
|
|
||||||
LOG.exception('failed to set IPMI credentials')
|
|
||||||
call_error = True
|
|
||||||
|
|
||||||
if failures or call_error:
|
|
||||||
sys.exit(1)
|
|
@ -1,412 +0,0 @@
|
|||||||
# Licensed under the Apache License, Version 2.0 (the "License");
|
|
||||||
# you may not use this file except in compliance with the License.
|
|
||||||
# You may obtain a copy of the License at
|
|
||||||
#
|
|
||||||
# http://www.apache.org/licenses/LICENSE-2.0
|
|
||||||
#
|
|
||||||
# Unless required by applicable law or agreed to in writing, software
|
|
||||||
# distributed under the License is distributed on an "AS IS" BASIS,
|
|
||||||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
|
|
||||||
# implied.
|
|
||||||
# See the License for the specific language governing permissions and
|
|
||||||
# limitations under the License.
|
|
||||||
|
|
||||||
import base64
|
|
||||||
import collections
|
|
||||||
import io
|
|
||||||
import os
|
|
||||||
import shutil
|
|
||||||
import subprocess
|
|
||||||
import tarfile
|
|
||||||
import tempfile
|
|
||||||
import unittest
|
|
||||||
|
|
||||||
try:
|
|
||||||
# mock library is buggy under Python 3.4, but we have a stdlib one
|
|
||||||
from unittest import mock
|
|
||||||
except ImportError:
|
|
||||||
import mock
|
|
||||||
import netifaces
|
|
||||||
import requests
|
|
||||||
|
|
||||||
from ironic_inspector_ramdisk import discover
|
|
||||||
|
|
||||||
|
|
||||||
def get_fake_args():
|
|
||||||
return mock.Mock(callback_url='url', daemonize_on_failure=True,
|
|
||||||
benchmark=None)
|
|
||||||
|
|
||||||
|
|
||||||
FAKE_ARGS = get_fake_args()
|
|
||||||
|
|
||||||
|
|
||||||
class TestCommands(unittest.TestCase):
|
|
||||||
@mock.patch.object(discover.LOG, 'warn', autospec=True)
|
|
||||||
@mock.patch.object(subprocess, 'Popen', autospec=True)
|
|
||||||
def test_try_call(self, mock_popen, mock_warn):
|
|
||||||
mock_popen.return_value.communicate.return_value = ('out', 'err')
|
|
||||||
mock_popen.return_value.returncode = 0
|
|
||||||
discover.try_call('ls', '-l')
|
|
||||||
mock_popen.assert_called_once_with(('ls', '-l'),
|
|
||||||
stderr=subprocess.PIPE,
|
|
||||||
stdout=subprocess.PIPE)
|
|
||||||
self.assertFalse(mock_warn.called)
|
|
||||||
|
|
||||||
@mock.patch.object(discover.LOG, 'warn', autospec=True)
|
|
||||||
@mock.patch.object(subprocess, 'Popen', autospec=True)
|
|
||||||
def test_try_call_fails(self, mock_popen, mock_warn):
|
|
||||||
mock_popen.return_value.communicate.return_value = ('out', 'err')
|
|
||||||
mock_popen.return_value.returncode = 42
|
|
||||||
discover.try_call('ls', '-l')
|
|
||||||
mock_popen.assert_called_once_with(('ls', '-l'),
|
|
||||||
stderr=subprocess.PIPE,
|
|
||||||
stdout=subprocess.PIPE)
|
|
||||||
mock_warn.assert_called_once_with(mock.ANY, ('ls', '-l'), 42, 'err')
|
|
||||||
|
|
||||||
@mock.patch.object(discover.LOG, 'warn', autospec=True)
|
|
||||||
def test_try_call_os_error(self, mock_warn):
|
|
||||||
discover.try_call('I don\'t exist!', '-l')
|
|
||||||
mock_warn.assert_called_once_with(mock.ANY, ('I don\'t exist!', '-l'),
|
|
||||||
mock.ANY)
|
|
||||||
|
|
||||||
@mock.patch.object(discover.LOG, 'warn', autospec=True)
|
|
||||||
def test_try_shell(self, mock_warn):
|
|
||||||
res = discover.try_shell('echo Hello; echo World')
|
|
||||||
self.assertEqual(b'Hello\nWorld', res)
|
|
||||||
self.assertFalse(mock_warn.called)
|
|
||||||
|
|
||||||
@mock.patch.object(discover.LOG, 'warn', autospec=True)
|
|
||||||
def test_try_shell_fails(self, mock_warn):
|
|
||||||
res = discover.try_shell('exit 1')
|
|
||||||
self.assertIsNone(res)
|
|
||||||
self.assertTrue(mock_warn.called)
|
|
||||||
|
|
||||||
@mock.patch.object(discover.LOG, 'warn', autospec=True)
|
|
||||||
def test_try_shell_no_strip(self, mock_warn):
|
|
||||||
res = discover.try_shell('echo Hello; echo World',
|
|
||||||
strip=False)
|
|
||||||
self.assertEqual(b'Hello\nWorld\n', res)
|
|
||||||
self.assertFalse(mock_warn.called)
|
|
||||||
|
|
||||||
|
|
||||||
class TestFailures(unittest.TestCase):
|
|
||||||
def test(self):
|
|
||||||
f = discover.AccumulatedFailure()
|
|
||||||
self.assertFalse(f)
|
|
||||||
self.assertIsNone(f.get_error())
|
|
||||||
f.add('foo')
|
|
||||||
f.add('%s', 'bar')
|
|
||||||
f.add(RuntimeError('baz'))
|
|
||||||
exp = ('The following errors were encountered during '
|
|
||||||
'hardware discovery:\n* foo\n* bar\n* baz')
|
|
||||||
self.assertEqual(exp, f.get_error())
|
|
||||||
self.assertTrue(f)
|
|
||||||
|
|
||||||
|
|
||||||
class BaseDiscoverTest(unittest.TestCase):
|
|
||||||
def setUp(self):
|
|
||||||
super(BaseDiscoverTest, self).setUp()
|
|
||||||
self.failures = discover.AccumulatedFailure()
|
|
||||||
self.data = {}
|
|
||||||
|
|
||||||
|
|
||||||
@mock.patch.object(discover, 'try_shell', autospec=True)
|
|
||||||
class TestDiscoverBasicProperties(BaseDiscoverTest):
|
|
||||||
def test(self, mock_shell):
|
|
||||||
mock_shell.return_value = '1.2.3.4'
|
|
||||||
|
|
||||||
discover.discover_basic_properties(
|
|
||||||
self.data, mock.Mock(bootif='boot:if'))
|
|
||||||
|
|
||||||
self.assertEqual({'ipmi_address': '1.2.3.4',
|
|
||||||
'boot_interface': 'boot:if'},
|
|
||||||
self.data)
|
|
||||||
|
|
||||||
|
|
||||||
@mock.patch.object(netifaces, 'ifaddresses', autospec=True)
|
|
||||||
@mock.patch.object(netifaces, 'interfaces', autospec=True)
|
|
||||||
class TestDiscoverNetworkInterfaces(BaseDiscoverTest):
|
|
||||||
def _call(self):
|
|
||||||
discover.discover_network_interfaces(self.data, self.failures)
|
|
||||||
|
|
||||||
def test_nothing(self, mock_ifaces, mock_ifaddr):
|
|
||||||
mock_ifaces.return_value = ['lo']
|
|
||||||
|
|
||||||
self._call()
|
|
||||||
|
|
||||||
mock_ifaces.assert_called_once_with()
|
|
||||||
self.assertFalse(mock_ifaddr.called)
|
|
||||||
self.assertIn('no network interfaces', self.failures.get_error())
|
|
||||||
self.assertEqual({'interfaces': {}}, self.data)
|
|
||||||
|
|
||||||
def test_ok(self, mock_ifaces, mock_ifaddr):
|
|
||||||
interfaces = [
|
|
||||||
{
|
|
||||||
netifaces.AF_LINK: [{'addr': '11:22:33:44:55:66'}],
|
|
||||||
netifaces.AF_INET: [{'addr': '1.2.3.4'}],
|
|
||||||
},
|
|
||||||
{
|
|
||||||
netifaces.AF_LINK: [{'addr': '11:22:33:44:55:44'}],
|
|
||||||
netifaces.AF_INET: [{'addr': '1.2.3.2'}],
|
|
||||||
},
|
|
||||||
]
|
|
||||||
mock_ifaces.return_value = ['lo', 'em1', 'em2']
|
|
||||||
mock_ifaddr.side_effect = iter(interfaces)
|
|
||||||
|
|
||||||
self._call()
|
|
||||||
|
|
||||||
mock_ifaddr.assert_any_call('em1')
|
|
||||||
mock_ifaddr.assert_any_call('em2')
|
|
||||||
self.assertEqual(2, mock_ifaddr.call_count)
|
|
||||||
self.assertEqual({'em1': {'mac': '11:22:33:44:55:66',
|
|
||||||
'ip': '1.2.3.4'},
|
|
||||||
'em2': {'mac': '11:22:33:44:55:44',
|
|
||||||
'ip': '1.2.3.2'}},
|
|
||||||
self.data['interfaces'])
|
|
||||||
self.assertFalse(self.failures)
|
|
||||||
|
|
||||||
def test_missing(self, mock_ifaces, mock_ifaddr):
|
|
||||||
interfaces = [
|
|
||||||
{
|
|
||||||
netifaces.AF_INET: [{'addr': '1.2.3.4'}],
|
|
||||||
},
|
|
||||||
{
|
|
||||||
netifaces.AF_LINK: [],
|
|
||||||
netifaces.AF_INET: [{'addr': '1.2.3.4'}],
|
|
||||||
},
|
|
||||||
{
|
|
||||||
netifaces.AF_LINK: [{'addr': '11:22:33:44:55:66'}],
|
|
||||||
netifaces.AF_INET: [],
|
|
||||||
},
|
|
||||||
{
|
|
||||||
netifaces.AF_LINK: [{'addr': '11:22:33:44:55:44'}],
|
|
||||||
},
|
|
||||||
]
|
|
||||||
mock_ifaces.return_value = ['lo', 'br0', 'br1', 'em1', 'em2']
|
|
||||||
mock_ifaddr.side_effect = iter(interfaces)
|
|
||||||
|
|
||||||
self._call()
|
|
||||||
|
|
||||||
self.assertEqual(4, mock_ifaddr.call_count)
|
|
||||||
self.assertEqual({'em1': {'mac': '11:22:33:44:55:66', 'ip': None},
|
|
||||||
'em2': {'mac': '11:22:33:44:55:44', 'ip': None}},
|
|
||||||
self.data['interfaces'])
|
|
||||||
self.assertFalse(self.failures)
|
|
||||||
|
|
||||||
|
|
||||||
@mock.patch.object(discover, 'try_shell', autospec=True)
|
|
||||||
class TestDiscoverSchedulingProperties(BaseDiscoverTest):
|
|
||||||
def test_ok(self, mock_shell):
|
|
||||||
mock_shell.side_effect = iter(('2', 'x86_64', '5368709120',
|
|
||||||
'1024\n1024\nno\n2048\n'))
|
|
||||||
|
|
||||||
discover.discover_scheduling_properties(self.data, self.failures)
|
|
||||||
|
|
||||||
self.assertFalse(self.failures)
|
|
||||||
self.assertEqual({'cpus': 2, 'cpu_arch': 'x86_64', 'local_gb': 4,
|
|
||||||
'memory_mb': 4096}, self.data)
|
|
||||||
|
|
||||||
def test_no_ram(self, mock_shell):
|
|
||||||
mock_shell.side_effect = iter(('2', 'x86_64', '5368709120', None))
|
|
||||||
|
|
||||||
discover.discover_scheduling_properties(self.data, self.failures)
|
|
||||||
|
|
||||||
self.assertFalse(self.failures)
|
|
||||||
self.assertEqual({'cpus': 2, 'cpu_arch': 'x86_64', 'local_gb': 4,
|
|
||||||
'memory_mb': None}, self.data)
|
|
||||||
|
|
||||||
def test_no_local_gb(self, mock_shell):
|
|
||||||
mock_shell.side_effect = iter(('2', 'x86_64', None,
|
|
||||||
'1024\n1024\nno\n2048\n'))
|
|
||||||
|
|
||||||
discover.discover_scheduling_properties(self.data, self.failures)
|
|
||||||
|
|
||||||
self.assertFalse(self.failures)
|
|
||||||
self.assertEqual({'cpus': 2, 'cpu_arch': 'x86_64', 'local_gb': None,
|
|
||||||
'memory_mb': 4096}, self.data)
|
|
||||||
|
|
||||||
def test_local_gb_too_small(self, mock_shell):
|
|
||||||
mock_shell.side_effect = iter(('2', 'x86_64', '42',
|
|
||||||
'1024\n1024\nno\n2048\n'))
|
|
||||||
|
|
||||||
discover.discover_scheduling_properties(self.data, self.failures)
|
|
||||||
|
|
||||||
self.assertFalse(self.failures)
|
|
||||||
self.assertEqual({'cpus': 2, 'cpu_arch': 'x86_64', 'local_gb': None,
|
|
||||||
'memory_mb': 4096}, self.data)
|
|
||||||
|
|
||||||
|
|
||||||
@mock.patch.object(discover, 'try_call')
|
|
||||||
class TestDiscoverAdditionalProperties(BaseDiscoverTest):
|
|
||||||
def test_ok(self, mock_call):
|
|
||||||
mock_call.return_value = '["prop1", "prop2"]'
|
|
||||||
|
|
||||||
discover.discover_additional_properties(
|
|
||||||
FAKE_ARGS, self.data, self.failures)
|
|
||||||
|
|
||||||
self.assertFalse(self.failures)
|
|
||||||
mock_call.assert_called_once_with('hardware-detect')
|
|
||||||
self.assertEqual(['prop1', 'prop2'], self.data['data'])
|
|
||||||
|
|
||||||
def test_failure(self, mock_call):
|
|
||||||
mock_call.return_value = None
|
|
||||||
|
|
||||||
discover.discover_additional_properties(
|
|
||||||
FAKE_ARGS, self.data, self.failures)
|
|
||||||
|
|
||||||
self.assertIn('unable to get extended hardware properties',
|
|
||||||
self.failures.get_error())
|
|
||||||
self.assertNotIn('data', self.data)
|
|
||||||
|
|
||||||
def test_not_json(self, mock_call):
|
|
||||||
mock_call.return_value = 'foo?'
|
|
||||||
|
|
||||||
discover.discover_additional_properties(
|
|
||||||
FAKE_ARGS, self.data, self.failures)
|
|
||||||
|
|
||||||
self.assertIn('unable to get extended hardware properties',
|
|
||||||
self.failures.get_error())
|
|
||||||
self.assertNotIn('data', self.data)
|
|
||||||
|
|
||||||
|
|
||||||
@mock.patch.object(discover, 'try_shell')
|
|
||||||
class TestDiscoverBlockDevices(BaseDiscoverTest):
|
|
||||||
def test_ok(self, mock_shell):
|
|
||||||
mock_shell.return_value = 'QM00005\nQM00006'
|
|
||||||
|
|
||||||
discover.discover_block_devices(self.data)
|
|
||||||
|
|
||||||
self.assertEqual({'serials': ['QM00005', 'QM00006']},
|
|
||||||
self.data['block_devices'])
|
|
||||||
|
|
||||||
def test_failure(self, mock_shell):
|
|
||||||
mock_shell.return_value = None
|
|
||||||
|
|
||||||
discover.discover_block_devices(self.data)
|
|
||||||
|
|
||||||
self.assertNotIn('block_devices', self.data)
|
|
||||||
|
|
||||||
|
|
||||||
@mock.patch.object(requests, 'post', autospec=True)
|
|
||||||
class TestCallDiscoverd(unittest.TestCase):
|
|
||||||
def test_ok(self, mock_post):
|
|
||||||
failures = discover.AccumulatedFailure()
|
|
||||||
data = collections.OrderedDict(data=42)
|
|
||||||
mock_post.return_value.status_code = 200
|
|
||||||
|
|
||||||
discover.call_inspector(FAKE_ARGS, data, failures)
|
|
||||||
|
|
||||||
mock_post.assert_called_once_with('url',
|
|
||||||
data='{"data": 42, "error": null}')
|
|
||||||
|
|
||||||
def test_send_failure(self, mock_post):
|
|
||||||
failures = mock.Mock(spec=discover.AccumulatedFailure)
|
|
||||||
failures.get_error.return_value = "boom"
|
|
||||||
data = collections.OrderedDict(data=42)
|
|
||||||
mock_post.return_value.status_code = 200
|
|
||||||
|
|
||||||
discover.call_inspector(FAKE_ARGS, data, failures)
|
|
||||||
|
|
||||||
mock_post.assert_called_once_with('url',
|
|
||||||
data='{"data": 42, "error": "boom"}')
|
|
||||||
|
|
||||||
def test_inspector_error(self, mock_post):
|
|
||||||
failures = discover.AccumulatedFailure()
|
|
||||||
data = collections.OrderedDict(data=42)
|
|
||||||
mock_post.return_value.status_code = 400
|
|
||||||
|
|
||||||
discover.call_inspector(FAKE_ARGS, data, failures)
|
|
||||||
|
|
||||||
mock_post.assert_called_once_with('url',
|
|
||||||
data='{"data": 42, "error": null}')
|
|
||||||
mock_post.return_value.raise_for_status.assert_called_once_with()
|
|
||||||
|
|
||||||
|
|
||||||
@mock.patch.object(discover, 'try_shell')
|
|
||||||
class TestCollectLogs(unittest.TestCase):
|
|
||||||
def _fake_journal_write(self, shell):
|
|
||||||
file_name = shell.rsplit(' ', 1)[1].strip("'")
|
|
||||||
with open(file_name, 'wb') as fp:
|
|
||||||
fp.write(b'journal contents')
|
|
||||||
return ""
|
|
||||||
|
|
||||||
def setUp(self):
|
|
||||||
super(TestCollectLogs, self).setUp()
|
|
||||||
temp_dir = tempfile.mkdtemp()
|
|
||||||
self.addCleanup(lambda: shutil.rmtree(temp_dir))
|
|
||||||
self.files = [os.path.join(temp_dir, fname)
|
|
||||||
for fname in ('main', 'log_1', 'log_2')]
|
|
||||||
for fname in self.files[:2]:
|
|
||||||
with open(fname, 'wb') as fp:
|
|
||||||
fp.write(fname.encode())
|
|
||||||
|
|
||||||
self.fake_args = get_fake_args()
|
|
||||||
self.fake_args.log_file = self.files[0]
|
|
||||||
self.fake_args.system_log_file = self.files[1:]
|
|
||||||
|
|
||||||
def test(self, mock_shell):
|
|
||||||
mock_shell.side_effect = self._fake_journal_write
|
|
||||||
|
|
||||||
res = discover.collect_logs(self.fake_args)
|
|
||||||
res = io.BytesIO(base64.b64decode(res))
|
|
||||||
|
|
||||||
with tarfile.open(fileobj=res) as tar:
|
|
||||||
members = list(sorted((m.name, m.size) for m in tar))
|
|
||||||
self.assertEqual(
|
|
||||||
[('journal', 16)] +
|
|
||||||
list(sorted((name[1:], len(name)) for name in self.files[:2])),
|
|
||||||
members)
|
|
||||||
|
|
||||||
def test_no_journal(self, mock_shell):
|
|
||||||
mock_shell.return_value = None
|
|
||||||
|
|
||||||
res = discover.collect_logs(self.fake_args)
|
|
||||||
res = io.BytesIO(base64.b64decode(res))
|
|
||||||
|
|
||||||
with tarfile.open(fileobj=res) as tar:
|
|
||||||
members = list(sorted((m.name, m.size) for m in tar))
|
|
||||||
self.assertEqual(
|
|
||||||
list(sorted((name[1:], len(name)) for name in self.files[:2])),
|
|
||||||
members)
|
|
||||||
|
|
||||||
|
|
||||||
@mock.patch.object(discover, 'try_call', autospec=True)
|
|
||||||
class TestSetupIpmiCredentials(unittest.TestCase):
|
|
||||||
def setUp(self):
|
|
||||||
super(TestSetupIpmiCredentials, self).setUp()
|
|
||||||
self.resp = {'ipmi_username': 'user',
|
|
||||||
'ipmi_password': 'pwd'}
|
|
||||||
|
|
||||||
def test_ok(self, mock_call):
|
|
||||||
mock_call.return_value = ""
|
|
||||||
|
|
||||||
discover.setup_ipmi_credentials(self.resp)
|
|
||||||
|
|
||||||
mock_call.assert_any_call('ipmitool', 'user', 'set', 'name',
|
|
||||||
'2', 'user')
|
|
||||||
mock_call.assert_any_call('ipmitool', 'user', 'set', 'password',
|
|
||||||
'2', 'pwd')
|
|
||||||
mock_call.assert_any_call('ipmitool', 'user', 'enable', '2')
|
|
||||||
mock_call.assert_any_call('ipmitool', 'channel', 'setaccess', '1', '2',
|
|
||||||
'link=on', 'ipmi=on', 'callin=on',
|
|
||||||
'privilege=4')
|
|
||||||
|
|
||||||
def test_user_failed(self, mock_call):
|
|
||||||
mock_call.return_value = None
|
|
||||||
|
|
||||||
self.assertRaises(RuntimeError, discover.setup_ipmi_credentials,
|
|
||||||
self.resp)
|
|
||||||
|
|
||||||
mock_call.assert_called_once_with('ipmitool', 'user', 'set', 'name',
|
|
||||||
'2', 'user')
|
|
||||||
|
|
||||||
def test_password_failed(self, mock_call):
|
|
||||||
mock_call.side_effect = iter(("", None))
|
|
||||||
|
|
||||||
self.assertRaises(RuntimeError, discover.setup_ipmi_credentials,
|
|
||||||
self.resp)
|
|
||||||
|
|
||||||
mock_call.assert_any_call('ipmitool', 'user', 'set', 'name',
|
|
||||||
'2', 'user')
|
|
||||||
mock_call.assert_any_call('ipmitool', 'user', 'set', 'password',
|
|
||||||
'2', 'pwd')
|
|
@ -1,136 +0,0 @@
|
|||||||
# Licensed under the Apache License, Version 2.0 (the "License");
|
|
||||||
# you may not use this file except in compliance with the License.
|
|
||||||
# You may obtain a copy of the License at
|
|
||||||
#
|
|
||||||
# http://www.apache.org/licenses/LICENSE-2.0
|
|
||||||
#
|
|
||||||
# Unless required by applicable law or agreed to in writing, software
|
|
||||||
# distributed under the License is distributed on an "AS IS" BASIS,
|
|
||||||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
|
|
||||||
# implied.
|
|
||||||
# See the License for the specific language governing permissions and
|
|
||||||
# limitations under the License.
|
|
||||||
|
|
||||||
import unittest
|
|
||||||
|
|
||||||
import mock
|
|
||||||
import requests
|
|
||||||
|
|
||||||
from ironic_inspector_ramdisk import discover
|
|
||||||
from ironic_inspector_ramdisk import main
|
|
||||||
from ironic_inspector_ramdisk.test import test_discover
|
|
||||||
|
|
||||||
|
|
||||||
FAKE_ARGS = test_discover.get_fake_args()
|
|
||||||
|
|
||||||
|
|
||||||
class TestParseArgs(unittest.TestCase):
|
|
||||||
def test(self):
|
|
||||||
args = ['http://url']
|
|
||||||
parsed_args = main.parse_args(args)
|
|
||||||
self.assertEqual('http://url', parsed_args.callback_url)
|
|
||||||
|
|
||||||
def test_log_files(self):
|
|
||||||
args = ['-L', 'log1', '-L', 'log2', 'url']
|
|
||||||
parsed_args = main.parse_args(args)
|
|
||||||
self.assertEqual(['log1', 'log2'],
|
|
||||||
parsed_args.system_log_file)
|
|
||||||
|
|
||||||
|
|
||||||
@mock.patch.object(main, 'setup_logging', lambda args: None)
|
|
||||||
@mock.patch.object(main, 'parse_args', return_value=FAKE_ARGS,
|
|
||||||
autospec=True)
|
|
||||||
@mock.patch.object(discover, 'setup_ipmi_credentials', autospec=True)
|
|
||||||
@mock.patch.object(discover, 'call_inspector', autospec=True,
|
|
||||||
return_value={})
|
|
||||||
@mock.patch.object(discover, 'collect_logs', autospec=True)
|
|
||||||
@mock.patch.object(discover, 'discover_hardware', autospec=True)
|
|
||||||
class TestMain(unittest.TestCase):
|
|
||||||
def test_success(self, mock_discover, mock_logs, mock_callback,
|
|
||||||
mock_setup_ipmi, mock_parse):
|
|
||||||
mock_logs.return_value = 'LOG'
|
|
||||||
|
|
||||||
main.main()
|
|
||||||
|
|
||||||
# FIXME(dtantsur): mock does not copy arguments, so the 2nd argument
|
|
||||||
# actually is not what we expect ({})
|
|
||||||
mock_discover.assert_called_once_with(FAKE_ARGS, mock.ANY, mock.ANY)
|
|
||||||
mock_logs.assert_called_once_with(FAKE_ARGS)
|
|
||||||
mock_callback.assert_called_once_with(FAKE_ARGS, {'logs': 'LOG'},
|
|
||||||
mock.ANY)
|
|
||||||
self.assertFalse(mock_setup_ipmi.called)
|
|
||||||
|
|
||||||
def test_discover_fails(self, mock_discover, mock_logs, mock_callback,
|
|
||||||
mock_setup_ipmi, mock_parse):
|
|
||||||
mock_logs.return_value = 'LOG'
|
|
||||||
mock_discover.side_effect = RuntimeError('boom')
|
|
||||||
|
|
||||||
self.assertRaisesRegexp(SystemExit, '1', main.main)
|
|
||||||
|
|
||||||
mock_discover.assert_called_once_with(FAKE_ARGS, mock.ANY, mock.ANY)
|
|
||||||
mock_logs.assert_called_once_with(FAKE_ARGS)
|
|
||||||
mock_callback.assert_called_once_with(FAKE_ARGS, {'logs': 'LOG'},
|
|
||||||
mock.ANY)
|
|
||||||
failures = mock_callback.call_args[0][2]
|
|
||||||
self.assertIn('boom', failures.get_error())
|
|
||||||
|
|
||||||
def test_collect_logs_fails(self, mock_discover, mock_logs, mock_callback,
|
|
||||||
mock_setup_ipmi, mock_parse):
|
|
||||||
mock_logs.side_effect = RuntimeError('boom')
|
|
||||||
|
|
||||||
main.main()
|
|
||||||
|
|
||||||
mock_discover.assert_called_once_with(FAKE_ARGS, mock.ANY, mock.ANY)
|
|
||||||
mock_logs.assert_called_once_with(FAKE_ARGS)
|
|
||||||
mock_callback.assert_called_once_with(FAKE_ARGS, {}, mock.ANY)
|
|
||||||
|
|
||||||
def test_callback_fails(self, mock_discover, mock_logs, mock_callback,
|
|
||||||
mock_setup_ipmi, mock_parse):
|
|
||||||
mock_logs.return_value = 'LOG'
|
|
||||||
mock_callback.side_effect = requests.HTTPError('boom')
|
|
||||||
|
|
||||||
self.assertRaisesRegexp(SystemExit, '1', main.main)
|
|
||||||
|
|
||||||
mock_discover.assert_called_once_with(FAKE_ARGS, mock.ANY, mock.ANY)
|
|
||||||
mock_logs.assert_called_once_with(FAKE_ARGS)
|
|
||||||
mock_callback.assert_called_once_with(FAKE_ARGS, {'logs': 'LOG'},
|
|
||||||
mock.ANY)
|
|
||||||
|
|
||||||
def test_callback_fails2(self, mock_discover, mock_logs, mock_callback,
|
|
||||||
mock_setup_ipmi, mock_parse):
|
|
||||||
mock_logs.return_value = 'LOG'
|
|
||||||
mock_callback.side_effect = RuntimeError('boom')
|
|
||||||
|
|
||||||
self.assertRaisesRegexp(SystemExit, '1', main.main)
|
|
||||||
|
|
||||||
mock_discover.assert_called_once_with(FAKE_ARGS, mock.ANY, mock.ANY)
|
|
||||||
mock_logs.assert_called_once_with(FAKE_ARGS)
|
|
||||||
mock_callback.assert_called_once_with(FAKE_ARGS, {'logs': 'LOG'},
|
|
||||||
mock.ANY)
|
|
||||||
|
|
||||||
def test_setup_ipmi(self, mock_discover, mock_logs, mock_callback,
|
|
||||||
mock_setup_ipmi, mock_parse):
|
|
||||||
mock_logs.return_value = 'LOG'
|
|
||||||
mock_callback.return_value = {'ipmi_setup_credentials': True}
|
|
||||||
|
|
||||||
main.main()
|
|
||||||
|
|
||||||
mock_discover.assert_called_once_with(FAKE_ARGS, mock.ANY, mock.ANY)
|
|
||||||
mock_logs.assert_called_once_with(FAKE_ARGS)
|
|
||||||
mock_callback.assert_called_once_with(FAKE_ARGS, {'logs': 'LOG'},
|
|
||||||
mock.ANY)
|
|
||||||
mock_setup_ipmi.assert_called_once_with(mock_callback.return_value)
|
|
||||||
|
|
||||||
def test_setup_ipmi_fails(self, mock_discover, mock_logs, mock_callback,
|
|
||||||
mock_setup_ipmi, mock_parse):
|
|
||||||
mock_logs.return_value = 'LOG'
|
|
||||||
mock_callback.return_value = {'ipmi_setup_credentials': True}
|
|
||||||
mock_setup_ipmi.side_effect = RuntimeError('boom')
|
|
||||||
|
|
||||||
self.assertRaisesRegexp(SystemExit, '1', main.main)
|
|
||||||
|
|
||||||
mock_discover.assert_called_once_with(FAKE_ARGS, mock.ANY, mock.ANY)
|
|
||||||
mock_logs.assert_called_once_with(FAKE_ARGS)
|
|
||||||
mock_callback.assert_called_once_with(FAKE_ARGS, {'logs': 'LOG'},
|
|
||||||
mock.ANY)
|
|
||||||
mock_setup_ipmi.assert_called_once_with(mock_callback.return_value)
|
|
@ -6,12 +6,10 @@ cliff>=1.13.0 # Apache-2.0
|
|||||||
eventlet>=0.17.4
|
eventlet>=0.17.4
|
||||||
Flask<1.0,>=0.10
|
Flask<1.0,>=0.10
|
||||||
keystonemiddleware>=1.5.0
|
keystonemiddleware>=1.5.0
|
||||||
netifaces>=0.10.4
|
|
||||||
pbr<2.0,>=0.11
|
pbr<2.0,>=0.11
|
||||||
python-ironicclient>=0.2.1
|
python-ironicclient>=0.2.1
|
||||||
python-keystoneclient>=1.6.0
|
python-keystoneclient>=1.6.0
|
||||||
python-openstackclient>=1.0.3
|
python-openstackclient>=1.0.3
|
||||||
requests>=2.5.2
|
|
||||||
oslo.config>=1.11.0 # Apache-2.0
|
oslo.config>=1.11.0 # Apache-2.0
|
||||||
oslo.i18n>=1.5.0 # Apache-2.0
|
oslo.i18n>=1.5.0 # Apache-2.0
|
||||||
oslo.utils>=1.6.0 # Apache-2.0
|
oslo.utils>=1.6.0 # Apache-2.0
|
||||||
|
@ -18,9 +18,6 @@ classifier =
|
|||||||
[files]
|
[files]
|
||||||
packages =
|
packages =
|
||||||
ironic_inspector
|
ironic_inspector
|
||||||
ironic_inspector_ramdisk
|
|
||||||
scripts =
|
|
||||||
bin/ironic-inspector-ramdisk
|
|
||||||
|
|
||||||
[entry_points]
|
[entry_points]
|
||||||
console_scripts =
|
console_scripts =
|
||||||
|
3
tox.ini
3
tox.ini
@ -9,7 +9,6 @@ deps =
|
|||||||
-r{toxinidir}/plugin-requirements.txt
|
-r{toxinidir}/plugin-requirements.txt
|
||||||
commands =
|
commands =
|
||||||
coverage run --branch --include "ironic_inspector*" -m unittest discover ironic_inspector.test
|
coverage run --branch --include "ironic_inspector*" -m unittest discover ironic_inspector.test
|
||||||
coverage run --branch --include "ironic_inspector_ramdisk*" -a -m unittest discover ironic_inspector_ramdisk.test
|
|
||||||
coverage report -m --fail-under 90
|
coverage report -m --fail-under 90
|
||||||
setenv = PYTHONDONTWRITEBYTECODE=1
|
setenv = PYTHONDONTWRITEBYTECODE=1
|
||||||
passenv = http_proxy HTTP_PROXY https_proxy HTTPS_PROXY no_proxy NO_PROXY
|
passenv = http_proxy HTTP_PROXY https_proxy HTTPS_PROXY no_proxy NO_PROXY
|
||||||
@ -24,7 +23,7 @@ deps =
|
|||||||
-r{toxinidir}/test-requirements.txt
|
-r{toxinidir}/test-requirements.txt
|
||||||
-r{toxinidir}/plugin-requirements.txt
|
-r{toxinidir}/plugin-requirements.txt
|
||||||
commands =
|
commands =
|
||||||
flake8 ironic_inspector ironic_inspector_ramdisk
|
flake8 ironic_inspector
|
||||||
doc8 README.rst CONTRIBUTING.rst HTTP-API.rst
|
doc8 README.rst CONTRIBUTING.rst HTTP-API.rst
|
||||||
|
|
||||||
[flake8]
|
[flake8]
|
||||||
|
Loading…
Reference in New Issue
Block a user