Skip to content

Commit 98f7621

Browse files
committed
Robustify pool setup and teardown in storage tests, first step
* New Pool.exec_on_hosts_on_error_rollback method: attempts to run a function for every host, stops at the first error, tries to run a rollback function to undo what may have been partially done, then raises with the collected exception(s). Mainly meant to be used in fixtures setups that alter all hosts in a pool. * New Pool.exec_on_hosts_on_error_continue method: attempts to run a function for every host. Collects errors but continues with the next hosts anyway. Raises at the end with the collected exception(s). * New host_with_saved_yum_state fixture to handle state saving and rollback in a shared package-scope fixture. * New pool_with_saved_yum_state, to handle the same on multiple hosts. * Updated MooseFS fixtures using the above methods and fixtures. Signed-off-by: Samuel Verschelde <[email protected]>
1 parent 5d2a6d8 commit 98f7621

File tree

4 files changed

+87
-18
lines changed

4 files changed

+87
-18
lines changed

lib/pool.py

+53
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
import logging
2+
import traceback
23

34
import lib.commands as commands
45

@@ -19,6 +20,58 @@ def __init__(self, master_hostname_or_ip):
1920
self.uuid = self.master.xe('pool-list', minimal=True)
2021
self.saved_uefi_certs = None
2122

23+
def exec_on_hosts_on_error_rollback(self, func, rollback_func, host_list=[]):
24+
"""
25+
Execute a function on all hosts of the pool.
26+
27+
If something fails: execute rollback_func on each host on which the command was already executed.
28+
Then stop.
29+
Designed mainly for use in fixture setups that alter the hosts state.
30+
"""
31+
hosts_done = []
32+
hosts = host_list if host_list else self.hosts
33+
for h in hosts:
34+
try:
35+
func(h)
36+
hosts_done.append(h)
37+
except Exception as e:
38+
if rollback_func:
39+
logging.warning(
40+
f"An error occurred in `exec_on_hosts_on_error_rollback` for host {h}\n"
41+
f"Backtrace:\n{traceback.format_exc()}"
42+
)
43+
rollback_hosts = hosts_done + [h]
44+
45+
logging.info("Attempting to run the rollback function on host(s) "
46+
f"{', '.join([str(h) for h in rollback_hosts])}...")
47+
try:
48+
self.exec_on_hosts_on_error_continue(rollback_func, rollback_hosts)
49+
except Exception:
50+
pass
51+
raise e
52+
53+
def exec_on_hosts_on_error_continue(self, func, host_list=[]):
54+
"""
55+
Execute a function on all hosts of the pool.
56+
57+
If something fails: store the exception but still attempt the function on the next hosts.
58+
Designed mainly for use in fixture teardowns.
59+
"""
60+
errors = {}
61+
hosts = host_list if host_list else self.hosts
62+
for h in hosts:
63+
try:
64+
func(h)
65+
except Exception as e:
66+
logging.warning(
67+
f"An error occurred in `exec_on_hosts_on_error_continue` for host {h}\n"
68+
f"Backtrace:\n{traceback.format_exc()}"
69+
)
70+
logging.info("Attempting to run the function on the next hosts of the pool if there are any left...")
71+
errors[h.hostname_or_ip] = e
72+
if errors:
73+
raise Exception(f"One or more exceptions were raised in `exec_on_hosts_on_error_continue`: {errors}")
74+
2275
def hosts_uuids(self):
2376
return safe_split(self.master.xe('host-list', {}, minimal=True))
2477

pkgfixtures.py

+13
Original file line numberDiff line numberDiff line change
@@ -23,3 +23,16 @@ def formatted_and_mounted_ext4_disk(host, sr_disk):
2323
setup_formatted_and_mounted_disk(host, sr_disk, 'ext4', mountpoint)
2424
yield mountpoint
2525
teardown_formatted_and_mounted_disk(host, mountpoint)
26+
27+
@pytest.fixture(scope='package')
28+
def host_with_saved_yum_state(host):
29+
host.yum_save_state()
30+
yield host
31+
host.yum_restore_saved_state()
32+
33+
@pytest.fixture(scope='package')
34+
def pool_with_saved_yum_state(host):
35+
for h in host.pool.hosts:
36+
h.yum_save_state()
37+
yield host.pool
38+
host.pool.exec_on_hosts_on_error_continue(lambda h: h.yum_restore_saved_state())

tests/storage/moosefs/conftest.py

+19-16
Original file line numberDiff line numberDiff line change
@@ -1,35 +1,38 @@
11
import logging
22
import pytest
33

4-
from lib.pool import Pool
4+
# explicit import for package-scope fixtures
5+
from pkgfixtures import pool_with_saved_yum_state
56

6-
def enable_moosefs(host):
7+
def install_moosefs(host):
78
assert not host.file_exists('/usr/sbin/mount.moosefs'), \
89
"MooseFS client should not be installed on the host before all tests"
910
host.ssh(['curl https://ppa.moosefs.com/RPM-GPG-KEY-MooseFS > /etc/pki/rpm-gpg/RPM-GPG-KEY-MooseFS'])
1011
host.ssh(['curl http://ppa.moosefs.com/MooseFS-3-el7.repo > /etc/yum.repos.d/MooseFS.repo'])
11-
host.yum_save_state()
1212
host.yum_install(['fuse', 'moosefs-client'])
1313

14+
def uninstall_moosefs_repo(host):
15+
host.ssh(['rm -f /etc/pki/rpm-gpg/RPM-GPG-KEY-MooseFS/etc/yum.repos.d/MooseFS.repo'])
16+
17+
def enable_moosefs(host):
1418
host.activate_smapi_driver('moosefs')
1519

1620
def disable_moosefs(host):
1721
host.deactivate_smapi_driver('moosefs')
1822

19-
host.yum_restore_saved_state()
20-
host.ssh(['rm -f /etc/pki/rpm-gpg/RPM-GPG-KEY-MooseFS'])
21-
host.ssh(['rm -f /etc/yum.repos.d/MooseFS.repo'])
22-
2323
@pytest.fixture(scope='package')
24-
def pool_with_moosefs(hostA1):
25-
pool = Pool(hostA1.hostname_or_ip)
26-
for host in pool.hosts:
27-
enable_moosefs(host)
28-
24+
def pool_with_moosefs_installed(pool_with_saved_yum_state):
25+
pool = pool_with_saved_yum_state
26+
pool.exec_on_hosts_on_error_rollback(install_moosefs, uninstall_moosefs_repo)
2927
yield pool
28+
pool.exec_on_hosts_on_error_continue(uninstall_moosefs_repo)
3029

31-
for host in pool.hosts:
32-
disable_moosefs(host)
30+
@pytest.fixture(scope='package')
31+
def pool_with_moosefs_enabled(pool_with_moosefs_installed):
32+
pool = pool_with_moosefs_installed
33+
pool.exec_on_hosts_on_error_rollback(enable_moosefs, disable_moosefs)
34+
yield pool
35+
pool.exec_on_hosts_on_error_continue(disable_moosefs)
3336

3437
@pytest.fixture(scope='package')
3538
def moosefs_device_config(sr_device_config):
@@ -49,9 +52,9 @@ def moosefs_device_config(sr_device_config):
4952
return config
5053

5154
@pytest.fixture(scope='package')
52-
def moosefs_sr(moosefs_device_config, pool_with_moosefs):
55+
def moosefs_sr(moosefs_device_config, pool_with_moosefs_enabled):
5356
""" MooseFS SR on a specific host. """
54-
sr = pool_with_moosefs.master.sr_create('moosefs', "MooseFS-SR-test", moosefs_device_config, shared=True)
57+
sr = pool_with_moosefs_enabled.master.sr_create('moosefs', "MooseFS-SR-test", moosefs_device_config, shared=True)
5558
yield sr
5659
# teardown
5760
sr.destroy()

tests/storage/moosefs/test_moosefs_sr.py

+2-2
Original file line numberDiff line numberDiff line change
@@ -30,9 +30,9 @@ def test_create_moosefs_sr_without_mfsmount(self, host, moosefs_device_config):
3030
sr.destroy()
3131
assert False, "MooseFS SR creation should failed!"
3232

33-
def test_create_and_destroy_sr(self, moosefs_device_config, pool_with_moosefs):
33+
def test_create_and_destroy_sr(self, moosefs_device_config, pool_with_moosefs_enabled):
3434
# Create and destroy tested in the same test to leave the host as unchanged as possible
35-
master = pool_with_moosefs.master
35+
master = pool_with_moosefs_enabled.master
3636
sr = master.sr_create('moosefs', "MooseFS-SR-test2", moosefs_device_config, shared=True, verify=True)
3737
# import a VM in order to detect vm import issues here rather than in the vm_on_moosefs_sr used in
3838
# the next tests, because errors in fixtures break teardown

0 commit comments

Comments
 (0)