Multihost Roles
Objects that inherits from MultihostRole are directly
accessible in the test. These objects are short-lived, new instance is created
for each test, therefore it is possible to store test related data. The main
purpose of this class is to provide role setup and teardown as well as a place
to implement high-level API for testing your project.
See also
See Multihost Utilities to see how it is possible to share code between multiple role classes (and host classes).
As a first example, we implement a basic code for a client role. This role includes several built-in utilities to automatically get access to functionality we want to use in our tests.
1from pytest_mh import MultihostRole
2from pytest_mh.utils.firewall import Firewalld
3from pytest_mh.utils.fs import LinuxFileSystem
4from pytest_mh.utils.journald import JournaldUtils
5from pytest_mh.utils.services import SystemdServices
6from pytest_mh.utils.tc import LinuxTrafficControl
7
8class ClientRole(MultihostRole[ClientHost]):
9 def __init__(self, *args, **kwargs) -> None:
10 super().__init__(*args, **kwargs)
11
12 self.fs: LinuxFileSystem = LinuxFileSystem(self.host)
13 """
14 File system manipulation.
15 """
16
17 self.svc: SystemdServices = SystemdServices(self.host)
18 """
19 Systemd service management.
20 """
21
22 self.firewall: Firewalld = Firewalld(self.host).postpone_setup()
23 """
24 Configure firewall using firewalld.
25 """
26
27 self.tc: LinuxTrafficControl = LinuxTrafficControl(self.host).postpone_setup()
28 """
29 Traffic control manipulation.
30 """
31
32 self.journald: JournaldUtils = JournaldUtils(self.host)
33 """
34 Journald utilities.
35 """
36
37 def setup(self) -> None:
38 """
39 Called before execution of each test.
40
41 * stop the client
42 * remove client's database and logs
43 """
44 super().setup()
45
46 self.svc.stop("my-project-client")
47 self.fs.rm("/var/lib/my-project-client")
48 self.fs.rm("/var/log/my-project-client")
49
50 def teardown(self) -> None:
51 """
52 Called after execution of each test.
53 """
54 # It is not required to restore removed files or restart
55 # the service. This is done automatically by the utilities.
56 super().teardown()
The following snippet add a high-level API to add a local user. It uses a built-in CLI builder, that can help you to prepare a command line for execution. Notice, that all local users that are created during a test are later removed during teardown.
1from typing import Self
2
3from pytest_mh import MultihostRole
4from pytest_mh.cli import CLIBuilder, CLIBuilderArgs
5from pytest_mh.conn import ProcessLogLevel
6from pytest_mh.utils.firewall import Firewalld
7from pytest_mh.utils.fs import LinuxFileSystem
8from pytest_mh.utils.journald import JournaldUtils
9from pytest_mh.utils.services import SystemdServices
10from pytest_mh.utils.tc import LinuxTrafficControl
11
12
13class ClientRole(MultihostRole[ClientHost]):
14 def __init__(self, *args, **kwargs) -> None:
15 super().__init__(*args, **kwargs)
16
17 self.fs: LinuxFileSystem = LinuxFileSystem(self.host)
18 """
19 File system manipulation.
20 """
21
22 self.svc: SystemdServices = SystemdServices(self.host)
23 """
24 Systemd service management.
25 """
26
27 self.firewall: Firewalld = Firewalld(self.host).postpone_setup()
28 """
29 Configure firewall using firewalld.
30 """
31
32 self.tc: LinuxTrafficControl = LinuxTrafficControl(self.host).postpone_setup()
33 """
34 Traffic control manipulation.
35 """
36
37 self.journald: JournaldUtils = JournaldUtils(self.host)
38 """
39 Journald utilities.
40 """
41
42 self.cli: CLIBuilder = CLIBuilder(self.host.conn)
43 """
44 CLI builder helper.
45 """
46
47 self._added_users: list[str] = []
48 """
49 List of local users that were created during the test.
50 """
51
52 def setup(self) -> None:
53 """
54 Called before execution of each test.
55
56 * stop the client
57 * remove client's database and logs
58 """
59 super().setup()
60
61 self.svc.stop("my-project-client")
62 self.fs.rm("/var/lib/my-project-client")
63 self.fs.rm("/var/log/my-project-client")
64
65 def teardown(self) -> None:
66 """
67 Called after execution of each test.
68 """
69 # It is not required to restore removed files or restart
70 # the service. This is done automatically by the utilities.
71
72 # Delete users that we added
73 if self._users:
74 cmd = "\n".join([f"userdel '{x}' --force --remove" for x in self._users]) + "\n"
75 self.host.conn.run("set -e\n\n" + cmd)
76
77 super().teardown()
78
79 def add_local_user(
80 self,
81 *,
82 name: str,
83 uid: int | None = None,
84 gid: int | None = None,
85 password: str | None = "Secret123",
86 home: str | None = None,
87 gecos: str | None = None,
88 shell: str | None = None,
89 ) -> Self:
90 """
91 Create new local user.
92
93 :param uid: User id, defaults to None
94 :type uid: int | None, optional
95 :param gid: Primary group id, defaults to None
96 :type gid: int | None, optional
97 :param password: Password, defaults to 'Secret123'
98 :type password: str, optional
99 :param home: Home directory, defaults to None
100 :type home: str | None, optional
101 :param gecos: GECOS, defaults to None
102 :type gecos: str | None, optional
103 :param shell: Login shell, defaults to None
104 :type shell: str | None, optional
105 :return: Self.
106 :rtype: Self
107 """
108 if home is not None:
109 self.fs.backup(home)
110
111 args: CLIBuilderArgs = {
112 "name": (self.cli.option.POSITIONAL, name),
113 "uid": (self.cli.option.VALUE, uid),
114 "gid": (self.cli.option.VALUE, gid),
115 "home": (self.cli.option.VALUE, home),
116 "gecos": (self.cli.option.VALUE, gecos),
117 "shell": (self.cli.option.VALUE, shell),
118 }
119
120 passwd = f" && passwd --stdin '{name}'" if password else ""
121 self.logger.info(f'Creating local user "{name}" on {self.host.hostname}')
122 self.host.conn.run(self.cli.command("useradd", args) + passwd, input=password, log_level=ProcessLogLevel.Error)
123
124 self._users.append(name)
125
126 return self
See also
The examples above are very trivial in order to show the idea. To see a feature-rich roles that are actively used to test a real life project, checkout the sssd-test-framework roles. These roles provide extensive, high-level API to manage users, group and other objects in LDAP, IPA, SambaDC and Active Directory as well as tools to manage and test SSSD.