8
8
import re
9
9
import secrets
10
10
import shlex
11
+ import shutil
11
12
import signal as _signal
12
13
import string
13
14
import subprocess
56
57
ProvisionError ,
57
58
ShellScript ,
58
59
configure_constant ,
60
+ create_directory ,
59
61
effective_workdir_root ,
60
62
)
61
63
@@ -975,6 +977,19 @@ def show(
975
977
logger .info (key_to_option (key ).replace ('-' , ' ' ), printable_value , color = 'green' )
976
978
977
979
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
+
978
993
class Guest (tmt .utils .Common ):
979
994
"""
980
995
Guest provisioned for test execution
@@ -1016,6 +1031,8 @@ def get_data_class(cls) -> type[GuestData]:
1016
1031
#: Guest topology hostname or IP address for guest/guest communication.
1017
1032
topology_address : Optional [str ] = None
1018
1033
1034
+ guest_logs : list [GuestLog ] = []
1035
+
1019
1036
become : bool
1020
1037
1021
1038
hardware : Optional [tmt .hardware .Hardware ]
@@ -1113,12 +1130,6 @@ def scripts_path(self) -> Path:
1113
1130
else tmt .steps .execute .DEFAULT_SCRIPTS_DEST_DIR
1114
1131
)
1115
1132
1116
- @property
1117
- def lognames (self ) -> list [str ]:
1118
- """Return name list of logs the guest could provide."""
1119
-
1120
- return []
1121
-
1122
1133
@classmethod
1123
1134
def options (cls , how : Optional [str ] = None ) -> list [tmt .options .ClickOptionDecoratorType ]:
1124
1135
"""
@@ -1737,7 +1748,7 @@ def store_log(self, path: Path, content: str, logname: Optional[str] = None) ->
1737
1748
raise tmt .utils .GeneralError ('Log path is a directory but log name is not defined.' )
1738
1749
1739
1750
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
1741
1752
) -> None :
1742
1753
"""
1743
1754
Get log content and save it to a directory.
@@ -1747,14 +1758,20 @@ def fetch_logs(
1747
1758
:param lognames: name list of logs need to be handled. If not set, all guest logs
1748
1759
would be collected, as reported by :py:attr:`lognames`.
1749
1760
"""
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 ()
1754
1771
if content :
1755
- self .store_log (dirpath , content , logname )
1772
+ self .store_log (dirpath , content , log . name )
1756
1773
else :
1757
- self .store_log (dirpath , '' , logname )
1774
+ self .store_log (dirpath , '' , log . name )
1758
1775
1759
1776
1760
1777
@container
@@ -2792,6 +2809,28 @@ def show(self, keys: Optional[list[str]] = None) -> None:
2792
2809
if hardware :
2793
2810
echo (tmt .utils .format ('hardware' , tmt .utils .dict_to_yaml (hardware .to_spec ())))
2794
2811
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
+
2795
2834
2796
2835
@container
2797
2836
class ProvisionTask (tmt .queue .GuestlessTask [None ]):
0 commit comments