diff --git a/devstack/exercise.sh b/devstack/exercise.sh new file mode 100755 index 000000000..beae1c19c --- /dev/null +++ b/devstack/exercise.sh @@ -0,0 +1,89 @@ +#!/bin/bash + +set -eux + +INTROSPECTION_SLEEP=${INTROSPECTION_SLEEP:-30} +EXPECTED_CPU_ARCH=${EXPECTED_CPU_ARCH:-x86_64} +EXPECTED_CPUS=${EXPECTED_CPUS:-1} +EXPECTED_MIN_LOCAL_GB=${EXPECTED_MIN_LOCAL_GB:-1} +EXPECTED_MIN_MEMORY_MB=${EXPECTED_MIN_MEMORY_MB:-512} + +ironic_url=$(keystone endpoint-get --service baremetal | tail -n +4 | head -n -1 | tr '|' ' ' | awk '{ print $2; }') +if [ -z "$ironic_url" ]; then + echo "Cannot find Ironic URL" + exit 1 +fi + +nodes=$(ironic node-list | tail -n +4 | head -n -1 | tr '|' ' ' | awk '{ print $1; }') +if [ -z "$nodes" ]; then + echo "No nodes found in Ironic" + exit 1 +fi + +for uuid in $nodes; do + for p in cpus cpu_arch memory_mb local_gb; do + ironic node-update $uuid remove properties/$p > /dev/null || true + done +done + +for uuid in $nodes; do + # TODO(dtantsur): use Ironic API instead + openstack baremetal introspection start $uuid +done + +current_nodes=$nodes +temp_nodes= +while true; do + sleep $INTROSPECTION_SLEEP + for uuid in $current_nodes; do + finished=$(openstack baremetal introspection status $uuid -f value -c finished) + if [ "$finished" = "True" ]; then + error=$(openstack baremetal introspection status $uuid -f value -c error) + if [ "$error" != "None" ]; then + echo "Introspection for $uuid failed: $error" + exit 1 + fi + else + temp_nodes="$temp_nodes $uuid" + fi + done + if [ "$temp_nodes" = "" ]; then + echo "Introspection done" + break + else + current_nodes=$temp_nodes + temp_nodes= + fi +done + +# NOTE(dtantsur): it's hard to get JSON field from Ironic client output, using +# HTTP API and JQ instead. +token=$(keystone token-get | grep ' id ' | tr '|' ' ' | awk '{ print $2; }') + +function curl_ir { + curl -H "X-Auth-Token: $token" -X $1 "$ironic_url/$2" +} + +for uuid in $nodes; do + node_json=$(curl_ir GET v1/nodes/$uuid) + properties=$(echo $node_json | jq '.properties') + echo Properties for $uuid: $properties + if [ "$(echo $properties | jq -r '.cpu_arch')" != "$EXPECTED_CPU_ARCH" ]; then + echo "Expected $EXPECTED_CPU_ARCH" + exit 1 + fi + if [ "$(echo $properties | jq -r '.cpus')" != "$EXPECTED_CPUS" ]; then + echo "Expected $EXPECTED_CPUS" + exit 1 + fi + if [ "$(echo $properties | jq -r '.local_gb')" -lt "$EXPECTED_MIN_LOCAL_GB" ]; then + echo "Expected at least $EXPECTED_MIN_LOCAL_GB" + exit 1 + fi + if [ "$(echo $properties | jq -r '.memory_mb')" -lt "$EXPECTED_MIN_MEMORY_MB" ]; then + echo "Expected at least $EXPECTED_MIN_MEMORY_MB" + exit 1 + fi +done + +echo "Validation passed" diff --git a/functest/env/cpuinfo.txt b/functest/env/cpuinfo.txt deleted file mode 100644 index a4d9a812c..000000000 --- a/functest/env/cpuinfo.txt +++ /dev/null @@ -1,108 +0,0 @@ -processor : 0 -vendor_id : GenuineIntel -cpu family : 6 -model : 58 -model name : Intel(R) Core(TM) i7-3520M CPU @ 2.90GHz -stepping : 9 -microcode : 0x1b -cpu MHz : 2180.550 -cache size : 4096 KB -physical id : 0 -siblings : 4 -core id : 0 -cpu cores : 2 -apicid : 0 -initial apicid : 0 -fpu : yes -fpu_exception : yes -cpuid level : 13 -wp : yes -flags : fpu vme de pse tsc msr pae mce cx8 apic sep mtrr pge mca cmov pat pse36 clflush dts acpi mmx fxsr sse sse2 ss ht tm pbe syscall nx rdtscp lm constant_tsc arch_perfmon pebs bts rep_good nopl xtopology nonstop_tsc aperfmperf eagerfpu pni pclmulqdq dtes64 monitor ds_cpl vmx smx est tm2 ssse3 cx16 xtpr pdcm pcid sse4_1 sse4_2 x2apic popcnt tsc_deadline_timer aes xsave avx f16c rdrand lahf_lm ida arat epb pln pts dtherm tpr_shadow vnmi flexpriority ept vpid fsgsbase smep erms xsaveopt -bugs : -bogomips : 5786.46 -clflush size : 64 -cache_alignment : 64 -address sizes : 36 bits physical, 48 bits virtual -power management: - -processor : 1 -vendor_id : GenuineIntel -cpu family : 6 -model : 58 -model name : Intel(R) Core(TM) i7-3520M CPU @ 2.90GHz -stepping : 9 -microcode : 0x1b -cpu MHz : 2409.718 -cache size : 4096 KB -physical id : 0 -siblings : 4 -core id : 0 -cpu cores : 2 -apicid : 1 -initial apicid : 1 -fpu : yes -fpu_exception : yes -cpuid level : 13 -wp : yes -flags : fpu vme de pse tsc msr pae mce cx8 apic sep mtrr pge mca cmov pat pse36 clflush dts acpi mmx fxsr sse sse2 ss ht tm pbe syscall nx rdtscp lm constant_tsc arch_perfmon pebs bts rep_good nopl xtopology nonstop_tsc aperfmperf eagerfpu pni pclmulqdq dtes64 monitor ds_cpl vmx smx est tm2 ssse3 cx16 xtpr pdcm pcid sse4_1 sse4_2 x2apic popcnt tsc_deadline_timer aes xsave avx f16c rdrand lahf_lm ida arat epb pln pts dtherm tpr_shadow vnmi flexpriority ept vpid fsgsbase smep erms xsaveopt -bugs : -bogomips : 5786.46 -clflush size : 64 -cache_alignment : 64 -address sizes : 36 bits physical, 48 bits virtual -power management: - -processor : 2 -vendor_id : GenuineIntel -cpu family : 6 -model : 58 -model name : Intel(R) Core(TM) i7-3520M CPU @ 2.90GHz -stepping : 9 -microcode : 0x1b -cpu MHz : 2140.562 -cache size : 4096 KB -physical id : 0 -siblings : 4 -core id : 1 -cpu cores : 2 -apicid : 2 -initial apicid : 2 -fpu : yes -fpu_exception : yes -cpuid level : 13 -wp : yes -flags : fpu vme de pse tsc msr pae mce cx8 apic sep mtrr pge mca cmov pat pse36 clflush dts acpi mmx fxsr sse sse2 ss ht tm pbe syscall nx rdtscp lm constant_tsc arch_perfmon pebs bts rep_good nopl xtopology nonstop_tsc aperfmperf eagerfpu pni pclmulqdq dtes64 monitor ds_cpl vmx smx est tm2 ssse3 cx16 xtpr pdcm pcid sse4_1 sse4_2 x2apic popcnt tsc_deadline_timer aes xsave avx f16c rdrand lahf_lm ida arat epb pln pts dtherm tpr_shadow vnmi flexpriority ept vpid fsgsbase smep erms xsaveopt -bugs : -bogomips : 5786.46 -clflush size : 64 -cache_alignment : 64 -address sizes : 36 bits physical, 48 bits virtual -power management: - -processor : 3 -vendor_id : GenuineIntel -cpu family : 6 -model : 58 -model name : Intel(R) Core(TM) i7-3520M CPU @ 2.90GHz -stepping : 9 -microcode : 0x1b -cpu MHz : 2350.699 -cache size : 4096 KB -physical id : 0 -siblings : 4 -core id : 1 -cpu cores : 2 -apicid : 3 -initial apicid : 3 -fpu : yes -fpu_exception : yes -cpuid level : 13 -wp : yes -flags : fpu vme de pse tsc msr pae mce cx8 apic sep mtrr pge mca cmov pat pse36 clflush dts acpi mmx fxsr sse sse2 ss ht tm pbe syscall nx rdtscp lm constant_tsc arch_perfmon pebs bts rep_good nopl xtopology nonstop_tsc aperfmperf eagerfpu pni pclmulqdq dtes64 monitor ds_cpl vmx smx est tm2 ssse3 cx16 xtpr pdcm pcid sse4_1 sse4_2 x2apic popcnt tsc_deadline_timer aes xsave avx f16c rdrand lahf_lm ida arat epb pln pts dtherm tpr_shadow vnmi flexpriority ept vpid fsgsbase smep erms xsaveopt -bugs : -bogomips : 5786.46 -clflush size : 64 -cache_alignment : 64 -address sizes : 36 bits physical, 48 bits virtual -power management: - diff --git a/functest/env/dmidecode b/functest/env/dmidecode deleted file mode 100755 index 1941b8474..000000000 --- a/functest/env/dmidecode +++ /dev/null @@ -1,50 +0,0 @@ -echo "# dmidecode 2.12 -SMBIOS 2.7 present. - -Handle 0x0007, DMI type 16, 23 bytes -Physical Memory Array - Location: System Board Or Motherboard - Use: System Memory - Error Correction Type: None - Maximum Capacity: 16 GB - Error Information Handle: Not Provided - Number Of Devices: 2 - -Handle 0x0008, DMI type 17, 34 bytes -Memory Device - Array Handle: 0x0007 - Error Information Handle: Not Provided - Total Width: 64 bits - Data Width: 64 bits - Size: 8192 MB - Form Factor: SODIMM - Set: None - Locator: ChannelA-DIMM0 - Bank Locator: BANK 0 - Type: DDR3 - Type Detail: Synchronous - Speed: 1600 MHz - Manufacturer: Samsung - Asset Tag: None - Rank: Unknown - Configured Clock Speed: 1600 MHz - -Handle 0x0009, DMI type 17, 34 bytes -Memory Device - Array Handle: 0x0007 - Error Information Handle: Not Provided - Total Width: 64 bits - Data Width: 64 bits - Size: 4096 MB - Form Factor: SODIMM - Set: None - Locator: ChannelB-DIMM0 - Bank Locator: BANK 2 - Type: DDR3 - Type Detail: Synchronous - Speed: 1600 MHz - Manufacturer: Hynix/Hyundai - Asset Tag: None - Rank: Unknown - Configured Clock Speed: 1600 MHz -" diff --git a/functest/env/fdisk b/functest/env/fdisk deleted file mode 100755 index 3a7a7b28c..000000000 --- a/functest/env/fdisk +++ /dev/null @@ -1,10 +0,0 @@ -echo "Disk /dev/sda: 465.8 GiB, 500107862016 bytes, 976773168 sectors -Units: sectors of 1 * 512 = 512 bytes -Sector size (logical/physical): 512 bytes / 4096 bytes -I/O size (minimum/optimal): 4096 bytes / 4096 bytes -Disklabel type: dos -Disk identifier: 0x000b9da0 - -Device Boot Start End Blocks Id System -/dev/sda1 * 2048 1026047 512000 83 Linux -/dev/sda2 1026048 976773119 487873536 83 Linux" diff --git a/functest/env/get_kernel_parameter b/functest/env/get_kernel_parameter deleted file mode 100755 index 98c2e382f..000000000 --- a/functest/env/get_kernel_parameter +++ /dev/null @@ -1,9 +0,0 @@ -if [[ "$1" = "discoverd_callback_url" ]]; -then - echo http://127.0.0.1:5050/v1/continue -elif [[ "$1" = "BOOTIF" ]]; -then - echo 01-11-22-33-44-55-66 -else - echo -fi diff --git a/functest/env/ip b/functest/env/ip deleted file mode 100755 index c46ed1891..000000000 --- a/functest/env/ip +++ /dev/null @@ -1,21 +0,0 @@ -if [ "x$3" == "xem1" ]; -then - echo "1: em1: mtu 1500 qdisc mq state UP group default qlen 1000 - link/ether 11:22:33:44:55:66 brd ff:ff:ff:ff:ff:ff - inet 192.168.0.15/24 brd 192.168.0.255 scope global dynamic wlp3s0 - valid_lft 78195sec preferred_lft 78195sec" -fi - -if [ "x$3" == "xem2" ]; -then - echo "2: em2: mtu 1500 qdisc pfifo_fast state DOWN group default qlen 1000 - link/ether 66:55:44:33:22:11 brd ff:ff:ff:ff:ff:ff" -fi - -if [ "x$3" == "xem3" ]; -then - echo "3: em3: mtu 1500 qdisc mq state UP group default qlen 1000 - link/ether 66:55:44:33:22:11 brd ff:ff:ff:ff:ff:ff - inet 192.168.0.16/24 brd 192.168.0.255 scope global dynamic wlp3s0 - valid_lft 78195sec preferred_lft 78195sec" -fi diff --git a/functest/env/ipmitool b/functest/env/ipmitool deleted file mode 100755 index 8c3cb782d..000000000 --- a/functest/env/ipmitool +++ /dev/null @@ -1,25 +0,0 @@ -if [[ "$1" == "lan" ]]; then - echo "Set in Progress : Set Complete -Auth Type Support : NONE MD2 MD5 PASSWORD -Auth Type Enable : Callback : - : User : MD2 MD5 PASSWORD - : Operator : MD2 MD5 PASSWORD - : Admin : MD2 MD5 PASSWORD - : OEM : -IP Address Source : Static Address -IP Address : 1.2.3.4 -Subnet Mask : 255.255.254.0 -MAC Address : 11:22:11:22:11:22 -SNMP Community String : public -IP Header : TTL=0x40 Flags=0x40 Precedence=0x00 TOS=0x10 -BMC ARP Control : ARP Responses Enabled, Gratuitous ARP Disabled -Gratituous ARP Intrvl : 2.0 seconds -Default Gateway IP : 1.2.3.1 -Default Gateway MAC : 00:00:00:00:00:00 -Backup Gateway IP : 0.0.0.0 -Backup Gateway MAC : 00:00:00:00:00:00 -TFTP Server IP : 0.0.0.0 -NTP Server IP : 1.1.0.0" -else - echo $@ >> ipmi_calls.txt -fi diff --git a/functest/env/lscpu b/functest/env/lscpu deleted file mode 100755 index 5eed3fb95..000000000 --- a/functest/env/lscpu +++ /dev/null @@ -1,24 +0,0 @@ -echo "Architecture: x86_64 -CPU op-mode(s): 32-bit, 64-bit -Byte Order: Little Endian -CPU(s): 4 -On-line CPU(s) list: 0-3 -Thread(s) per core: 2 -Core(s) per socket: 2 -Socket(s): 1 -NUMA node(s): 1 -Vendor ID: GenuineIntel -CPU family: 6 -Model: 58 -Model name: Intel(R) Core(TM) i7-3520M CPU @ 2.90GHz -Stepping: 9 -CPU MHz: 1486.250 -CPU max MHz: 3600.0000 -CPU min MHz: 1200.0000 -BogoMIPS: 5786.46 -Virtualization: VT-x -L1d cache: 32K -L1i cache: 32K -L2 cache: 256K -L3 cache: 4096K -NUMA node0 CPU(s): 0-3" diff --git a/functest/env/modprobe b/functest/env/modprobe deleted file mode 100755 index 379a4c986..000000000 --- a/functest/env/modprobe +++ /dev/null @@ -1 +0,0 @@ -exit 1 diff --git a/functest/env/poweroff b/functest/env/poweroff deleted file mode 100755 index 27ba77dda..000000000 --- a/functest/env/poweroff +++ /dev/null @@ -1 +0,0 @@ -true diff --git a/functest/env/sleep b/functest/env/sleep deleted file mode 100755 index 27ba77dda..000000000 --- a/functest/env/sleep +++ /dev/null @@ -1 +0,0 @@ -true diff --git a/functest/env/troubleshoot b/functest/env/troubleshoot deleted file mode 100755 index 3d1381b4e..000000000 --- a/functest/env/troubleshoot +++ /dev/null @@ -1,2 +0,0 @@ -echo "TROUBLESHOOTING!" -exit 1 diff --git a/ironic_inspector/main.py b/ironic_inspector/main.py index 4eaa36fab..a64b2fba6 100644 --- a/ironic_inspector/main.py +++ b/ironic_inspector/main.py @@ -181,7 +181,7 @@ def create_ssl_context(): return context -def main(args=sys.argv[1:]): # pragma: no cover +def main(args=sys.argv[1:], in_functional_test=False): # pragma: no cover CONF(args, project='ironic-inspector') debug = CONF.debug @@ -193,7 +193,7 @@ def main(args=sys.argv[1:]): # pragma: no cover logging.getLogger('ironicclient.common.http').setLevel( logging.INFO if debug else logging.ERROR) - app_kwargs = {'debug': debug, + app_kwargs = {'debug': debug and not in_functional_test, 'host': CONF.listen_address, 'port': CONF.listen_port} diff --git a/functest/run.py b/ironic_inspector/test/functional.py similarity index 63% rename from functest/run.py rename to ironic_inspector/test/functional.py index 48b5b6166..fb444a50f 100644 --- a/functest/run.py +++ b/ironic_inspector/test/functional.py @@ -11,16 +11,12 @@ # See the License for the specific language governing permissions and # limitations under the License. -from __future__ import print_function - import eventlet eventlet.monkey_patch() +import json import os -import re import shutil -import stat -import subprocess import sys import tempfile import unittest @@ -46,86 +42,63 @@ manage_firewall = False enable_setting_ipmi_credentials = True [DEFAULT] database = %(db_file)s +debug = True """ -ROOT = './functest/env' -RAMDISK = ("https://raw.githubusercontent.com/openstack/diskimage-builder/" - "master/elements/ironic-discoverd-ramdisk/" - "init.d/80-ironic-discoverd-ramdisk") - -JQ = "https://stedolan.github.io/jq/download/linux64/jq" +DEFAULT_SLEEP = 2 class Test(base.NodeTest): def setUp(self): super(Test, self).setUp() - self.node.properties.clear() self.cli = utils.get_client() self.cli.reset_mock() self.cli.node.get.return_value = self.node self.cli.node.update.return_value = self.node - self.temp = tempfile.mkdtemp() - self.addCleanup(lambda: shutil.rmtree(self.temp)) - self.env = os.path.join(self.temp, 'env') - shutil.copytree(ROOT, self.env) - net_ifaces = os.path.join(self.env, 'net') - os.mkdir(net_ifaces) - for fname in ('lo', 'em1', 'em2', 'em3'): - open(os.path.join(net_ifaces, fname), 'wb').close() - - ramdisk_url = os.environ.get('RAMDISK_SOURCE', RAMDISK) - if re.match(r'^https?://', ramdisk_url): - ramdisk = requests.get(ramdisk_url).content - else: - with open(ramdisk_url, 'rb') as f: - ramdisk = f.read() - ramdisk = ramdisk.replace('/proc/cpuinfo', os.path.join(self.env, - 'cpuinfo.txt')) - ramdisk = ramdisk.replace('/sys/class/net', net_ifaces) - self.ramdisk_sh = os.path.join(self.env, 'ramdisk') - with open(self.ramdisk_sh, 'wb') as f: - f.write(ramdisk) - - # jq is not on gate slaves - jq_path = os.path.join(self.env, 'jq') - with open(jq_path, 'wb') as f: - jq = requests.get(JQ, stream=True).raw - shutil.copyfileobj(jq, f) - os.chmod(jq_path, stat.S_IRWXU) - - old_wd = os.getcwd() - os.chdir(self.temp) - self.addCleanup(lambda: os.chdir(old_wd)) - - # These properties come from fake tools in functest/env + # https://github.com/openstack/ironic-inspector/blob/master/HTTP-API.rst # noqa + self.data = { + 'cpus': 4, + 'cpu_arch': 'x86_64', + 'memory_mb': 12288, + 'local_gb': 464, + 'interfaces': { + 'eth1': {'mac': self.macs[0], 'ip': '1.2.1.2'}, + 'eth2': {'mac': '12:12:21:12:21:12'}, + 'eth3': {'mac': self.macs[1], 'ip': '1.2.1.1'}, + }, + 'boot_interface': '01-' + self.macs[0].replace(':', '-'), + 'ipmi_address': self.bmc_address, + } self.patch = [ {'op': 'add', 'path': '/properties/cpus', 'value': '4'}, {'path': '/properties/cpu_arch', 'value': 'x86_64', 'op': 'add'}, {'op': 'add', 'path': '/properties/memory_mb', 'value': '12288'}, {'path': '/properties/local_gb', 'value': '464', 'op': 'add'} ] + self.node.power_state = 'power off' def call_ramdisk(self): - env = os.environ.copy() - env['PATH'] = self.env + ':' + env.get('PATH', '') - - subprocess.check_call(['/bin/bash', '-eux', self.ramdisk_sh], env=env) + res = requests.post('http://127.0.0.1:5050/v1/continue', + data=json.dumps(self.data)) + res.raise_for_status() + return res def test_bmc(self): client.introspect(self.uuid, auth_token='token') - eventlet.greenthread.sleep(1) + eventlet.greenthread.sleep(DEFAULT_SLEEP) self.cli.node.set_power_state.assert_called_once_with(self.uuid, 'reboot') status = client.get_status(self.uuid, auth_token='token') self.assertEqual({'finished': False, 'error': None}, status) - self.call_ramdisk() - eventlet.greenthread.sleep(1) + res = self.call_ramdisk() + self.assertEqual({'uuid': self.uuid}, res.json()) + eventlet.greenthread.sleep(DEFAULT_SLEEP) self.cli.node.update.assert_any_call(self.uuid, self.patch) self.cli.port.create.assert_called_once_with( @@ -144,14 +117,18 @@ class Test(base.NodeTest): self.node.maintenance = True client.introspect(self.uuid, auth_token='token', new_ipmi_username='admin', new_ipmi_password='pwd') - eventlet.greenthread.sleep(1) + eventlet.greenthread.sleep(DEFAULT_SLEEP) self.assertFalse(self.cli.node.set_power_state.called) status = client.get_status(self.uuid, auth_token='token') self.assertEqual({'finished': False, 'error': None}, status) - self.call_ramdisk() - eventlet.greenthread.sleep(1) + res = self.call_ramdisk() + res_json = res.json() + self.assertEqual('admin', res_json['ipmi_username']) + self.assertEqual('pwd', res_json['ipmi_password']) + self.assertTrue(res_json['ipmi_setup_credentials']) + eventlet.greenthread.sleep(DEFAULT_SLEEP) self.cli.node.update.assert_any_call(self.uuid, self.patch) self.cli.node.update.assert_any_call(self.uuid, patch_credentials) @@ -161,11 +138,6 @@ class Test(base.NodeTest): status = client.get_status(self.uuid, auth_token='token') self.assertEqual({'finished': True, 'error': None}, status) - with open(os.path.join(self.temp, 'ipmi_calls.txt'), 'rb') as f: - lines = f.readlines() - self.assertIn('user set name 2 admin\n', lines) - self.assertIn('user set password 2 pwd\n', lines) - @mock.patch.object(utils, 'check_auth') @mock.patch.object(utils, 'get_client') @@ -178,14 +150,28 @@ def run(client_mock, keystone_mock): fp.write(CONF % {'db_file': db_file}) eventlet.greenthread.spawn_n(main.main, - args=['--config-file', conf_file]) + args=['--config-file', conf_file], + in_functional_test=True) eventlet.greenthread.sleep(1) + # Wait for service to start up to 30 seconds + for i in range(10): + try: + requests.get('http://127.0.0.1:5050/v1') + except requests.ConnectionError: + if i == 9: + raise + print('Service did not start yet') + eventlet.greenthread.sleep(3) + else: + break suite = unittest.TestLoader().loadTestsFromTestCase(Test) res = unittest.TextTestRunner().run(suite) - sys.exit(0 if res.wasSuccessful() else 1) + # Make sure all processes finished executing + eventlet.greenthread.sleep(1) + return 0 if res.wasSuccessful() else 1 finally: shutil.rmtree(d) if __name__ == '__main__': - run() + sys.exit(run()) diff --git a/test-requirements.txt b/test-requirements.txt index 015c572a7..18ad1ef2b 100644 --- a/test-requirements.txt +++ b/test-requirements.txt @@ -5,3 +5,4 @@ coverage>=3.6 doc8 # Apache-2.0 hacking<0.11,>=0.10.0 mock>=1.0 +python-ironic-inspector-client>=1.0.1 diff --git a/tox.ini b/tox.ini index c0c974fb8..ffc4a2c22 100644 --- a/tox.ini +++ b/tox.ini @@ -39,10 +39,8 @@ deps = -r{toxinidir}/requirements.txt -r{toxinidir}/test-requirements.txt -r{toxinidir}/plugin-requirements.txt - # TODO(dtantsur): move to test-reqs once it's in global-requirements - python-ironic-inspector-client commands = - python functest/run.py + python -m ironic_inspector.test.functional [testenv:genconfig] commands =