Skip to content

Commit 1db6037

Browse files
committed
squash:new implementation
1 parent 69452f9 commit 1db6037

File tree

8 files changed

+77
-55
lines changed

8 files changed

+77
-55
lines changed

tmt/steps/finish/__init__.py

+1-1
Original file line numberDiff line numberDiff line change
@@ -226,7 +226,7 @@ def go(self, force: bool = False) -> None:
226226

227227
# Stop and remove provisioned guests
228228
for guest in self.plan.provision.guests():
229-
guest.fetch_logs(lognames=guest.lognames)
229+
guest.fetch_logs(guest_logs=guest.guest_logs)
230230
guest.stop()
231231
guest.remove()
232232

tmt/steps/provision/__init__.py

+52-13
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@
88
import re
99
import secrets
1010
import shlex
11+
import shutil
1112
import signal as _signal
1213
import string
1314
import subprocess
@@ -56,6 +57,7 @@
5657
ProvisionError,
5758
ShellScript,
5859
configure_constant,
60+
create_directory,
5961
effective_workdir_root,
6062
)
6163

@@ -975,6 +977,19 @@ def show(
975977
logger.info(key_to_option(key).replace('-', ' '), printable_value, color='green')
976978

977979

980+
@container
981+
class GuestLog:
982+
name: str
983+
984+
def fetch(self) -> Optional[str]:
985+
"""
986+
Fetch and return content of a log.
987+
988+
:returns: content of the log, or ``None`` if the log cannot be retrieved.
989+
"""
990+
raise NotImplementedError
991+
992+
978993
class Guest(tmt.utils.Common):
979994
"""
980995
Guest provisioned for test execution
@@ -1016,6 +1031,8 @@ def get_data_class(cls) -> type[GuestData]:
10161031
#: Guest topology hostname or IP address for guest/guest communication.
10171032
topology_address: Optional[str] = None
10181033

1034+
guest_logs: list[GuestLog] = []
1035+
10191036
become: bool
10201037

10211038
hardware: Optional[tmt.hardware.Hardware]
@@ -1113,12 +1130,6 @@ def scripts_path(self) -> Path:
11131130
else tmt.steps.execute.DEFAULT_SCRIPTS_DEST_DIR
11141131
)
11151132

1116-
@property
1117-
def lognames(self) -> list[str]:
1118-
"""Return name list of logs the guest could provide."""
1119-
1120-
return []
1121-
11221133
@classmethod
11231134
def options(cls, how: Optional[str] = None) -> list[tmt.options.ClickOptionDecoratorType]:
11241135
"""
@@ -1737,7 +1748,7 @@ def store_log(self, path: Path, content: str, logname: Optional[str] = None) ->
17371748
raise tmt.utils.GeneralError('Log path is a directory but log name is not defined.')
17381749

17391750
def fetch_logs(
1740-
self, dirpath: Optional[Path] = None, lognames: Optional[list[str]] = None
1751+
self, dirpath: Optional[Path] = None, guest_logs: Optional[list[GuestLog]] = None
17411752
) -> None:
17421753
"""
17431754
Get log content and save it to a directory.
@@ -1747,14 +1758,20 @@ def fetch_logs(
17471758
:param lognames: name list of logs need to be handled. If not set, all guest logs
17481759
would be collected, as reported by :py:attr:`lognames`.
17491760
"""
1750-
lognames = lognames or self.lognames
1751-
dirpath = dirpath or self.workdir or Path.cwd()
1752-
for logname in lognames:
1753-
content = self.acquire_log(logname)
1761+
1762+
guest_logs = guest_logs or self.guest_logs or []
1763+
1764+
dirpath = dirpath or (self.workdir / 'logs' if self.workdir else None) or Path.cwd()
1765+
if self.workdir and dirpath == self.workdir / 'logs':
1766+
create_directory(
1767+
path=self.workdir / 'logs', name='logs workdir', quiet=True, logger=self._logger
1768+
)
1769+
for log in guest_logs:
1770+
content = log.fetch()
17541771
if content:
1755-
self.store_log(dirpath, content, logname)
1772+
self.store_log(dirpath, content, log.name)
17561773
else:
1757-
self.store_log(dirpath, '', logname)
1774+
self.store_log(dirpath, '', log.name)
17581775

17591776

17601777
@container
@@ -2792,6 +2809,28 @@ def show(self, keys: Optional[list[str]] = None) -> None:
27922809
if hardware:
27932810
echo(tmt.utils.format('hardware', tmt.utils.dict_to_yaml(hardware.to_spec())))
27942811

2812+
def prune(self, logger: tmt.log.Logger) -> None:
2813+
"""Do not prune logs"""
2814+
if self.workdir is None:
2815+
return
2816+
2817+
logs_dir = self.workdir / 'logs'
2818+
if logs_dir.exists():
2819+
for member in self.workdir.iterdir():
2820+
if member.name == "logs":
2821+
logger.debug(f"Preserve '{member.relative_to(self.workdir)}'.", level=3)
2822+
continue
2823+
logger.debug(f"Remove '{member}'.", level=3)
2824+
try:
2825+
if member.is_file() or member.is_symlink():
2826+
member.unlink()
2827+
else:
2828+
shutil.rmtree(member)
2829+
except OSError as error:
2830+
logger.warning(f"Unable to remove '{member}': {error}")
2831+
else:
2832+
super().prune(logger)
2833+
27952834

27962835
@container
27972836
class ProvisionTask(tmt.queue.GuestlessTask[None]):

tmt/steps/provision/artemis.py

-5
Original file line numberDiff line numberDiff line change
@@ -493,11 +493,6 @@ def is_ready(self) -> bool:
493493
# return True if self.guest is not None
494494
return self.primary_address is not None
495495

496-
@property
497-
def lognames(self) -> list[str]:
498-
"""Return name list of logs the guest could provide."""
499-
return []
500-
501496
def _create(self) -> None:
502497
environment: dict[str, Any] = {
503498
'hw': {'arch': self.arch},

tmt/steps/provision/connect.py

-5
Original file line numberDiff line numberDiff line change
@@ -85,11 +85,6 @@ class GuestConnect(tmt.steps.provision.GuestSsh):
8585
soft_reboot: Optional[ShellScript]
8686
hard_reboot: Optional[ShellScript]
8787

88-
@property
89-
def lognames(self) -> list[str]:
90-
"""Return name list of logs the guest could provide."""
91-
return []
92-
9388
def reboot(
9489
self,
9590
hard: bool = False,

tmt/steps/provision/local.py

-16
Original file line numberDiff line numberDiff line change
@@ -32,11 +32,6 @@ def is_ready(self) -> bool:
3232

3333
return True
3434

35-
@property
36-
def lognames(self) -> list[str]:
37-
"""Return name list of logs the guest could provide."""
38-
return []
39-
4035
def _run_ansible(
4136
self,
4237
playbook: tmt.steps.provision.AnsibleApplicable,
@@ -184,17 +179,6 @@ def pull(
184179
Nothing to be done to pull workdir
185180
"""
186181

187-
def acquire_log(self, logname: str) -> Optional[str]:
188-
"""
189-
Fetch and return content of a log.
190-
191-
:param logname: name of the log.
192-
:returns: content of the log, or ``None`` if the log cannot be retrieved.
193-
"""
194-
if logname == 'dmesg':
195-
return self.execute(Command('dmesg')).stdout
196-
return None
197-
198182

199183
@tmt.steps.provides_method('local')
200184
class ProvisionLocal(tmt.steps.provision.ProvisionPlugin[ProvisionLocalData]):

tmt/steps/provision/mrack.py

+24-5
Original file line numberDiff line numberDiff line change
@@ -1186,11 +1186,6 @@ def is_ready(self) -> bool:
11861186
except mrack.errors.MrackError:
11871187
return False
11881188

1189-
@property
1190-
def lognames(self) -> list[str]:
1191-
"""Return name list of logs the guest could provide."""
1192-
return []
1193-
11941189
def _create(self, tmt_name: str) -> None:
11951190
"""
11961191
Create beaker job xml request and submit it to Beaker hub
@@ -1290,6 +1285,11 @@ def get_new_state() -> GuestInspectType:
12901285
raise ProvisionError('Failed to create, provisioning failed.')
12911286

12921287
if state == 'Reserved':
1288+
for key in response["logs"]:
1289+
self.guest_logs.append(
1290+
GuestLogBeaker(self, key.replace('.log', ''), response["logs"][key])
1291+
)
1292+
self.guest_logs.append(GuestLogBeaker(self, 'dmesg'))
12931293
return current
12941294

12951295
raise tmt.utils.WaitingIncompleteError
@@ -1479,3 +1479,22 @@ def guest(self) -> Optional[GuestBeaker]:
14791479
"""
14801480

14811481
return self._guest
1482+
1483+
1484+
@container
1485+
class GuestLogBeaker(tmt.steps.provision.GuestLog):
1486+
def __init__(self, guest: GuestBeaker, name: str, url: Optional[str] = None) -> None:
1487+
self.name = name
1488+
self.url = url
1489+
self.guest = guest
1490+
1491+
def fetch(self) -> Optional[str]:
1492+
"""
1493+
Fetch and return content of a log.
1494+
1495+
:returns: content of the log, or ``None`` if the log cannot be retrieved.
1496+
"""
1497+
1498+
if self.name == 'dmesg':
1499+
return self.guest.execute(Command('dmesg')).stdout
1500+
return tmt.utils.get_url_content(self.url) if self.url else None

tmt/steps/provision/podman.py

-5
Original file line numberDiff line numberDiff line change
@@ -128,11 +128,6 @@ def is_ready(self) -> bool:
128128
)
129129
return str(cmd_output.stdout).strip() == 'true'
130130

131-
@property
132-
def lognames(self) -> list[str]:
133-
"""Return name list of logs the guest could provide."""
134-
return []
135-
136131
def wake(self) -> None:
137132
"""
138133
Wake up the guest

tmt/steps/provision/testcloud.py

-5
Original file line numberDiff line numberDiff line change
@@ -715,11 +715,6 @@ def is_coreos(self) -> bool:
715715
# Is this a CoreOS?
716716
return bool(re.search('coreos|rhcos', self.image.lower()))
717717

718-
@property
719-
def lognames(self) -> list[str]:
720-
"""Return name list of logs the guest could provide."""
721-
return []
722-
723718
def _get_url(self, url: str, message: str) -> requests.Response:
724719
"""
725720
Get url, retry when fails, return response

0 commit comments

Comments
 (0)