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
@@ -952,6 +954,19 @@ def show(
952
954
logger .info (key_to_option (key ).replace ('-' , ' ' ), printable_value , color = 'green' )
953
955
954
956
957
+ @container
958
+ class GuestLog :
959
+ name : str
960
+
961
+ def fetch (self ) -> Optional [str ]:
962
+ """
963
+ Fetch and return content of a log.
964
+
965
+ :returns: content of the log, or ``None`` if the log cannot be retrieved.
966
+ """
967
+ raise NotImplementedError
968
+
969
+
955
970
class Guest (tmt .utils .Common ):
956
971
"""
957
972
Guest provisioned for test execution
@@ -993,6 +1008,8 @@ def get_data_class(cls) -> type[GuestData]:
993
1008
#: Guest topology hostname or IP address for guest/guest communication.
994
1009
topology_address : Optional [str ] = None
995
1010
1011
+ guest_logs : list [GuestLog ] = []
1012
+
996
1013
become : bool
997
1014
998
1015
hardware : Optional [tmt .hardware .Hardware ]
@@ -1090,12 +1107,6 @@ def scripts_path(self) -> Path:
1090
1107
else tmt .steps .execute .DEFAULT_SCRIPTS_DEST_DIR
1091
1108
)
1092
1109
1093
- @property
1094
- def lognames (self ) -> list [str ]:
1095
- """Return name list of logs the guest could provide."""
1096
-
1097
- return []
1098
-
1099
1110
@classmethod
1100
1111
def options (cls , how : Optional [str ] = None ) -> list [tmt .options .ClickOptionDecoratorType ]:
1101
1112
"""
@@ -1681,7 +1692,7 @@ def store_log(self, path: Path, content: str, logname: Optional[str] = None) ->
1681
1692
raise tmt .utils .GeneralError ('Log path is a directory but log name is not defined.' )
1682
1693
1683
1694
def fetch_logs (
1684
- self , dirpath : Optional [Path ] = None , lognames : Optional [list [str ]] = None
1695
+ self , dirpath : Optional [Path ] = None , guest_logs : Optional [list [GuestLog ]] = None
1685
1696
) -> None :
1686
1697
"""
1687
1698
Get log content and save it to a directory.
@@ -1691,14 +1702,20 @@ def fetch_logs(
1691
1702
:param lognames: name list of logs need to be handled. If not set, all guest logs
1692
1703
would be collected, as reported by :py:attr:`lognames`.
1693
1704
"""
1694
- lognames = lognames or self .lognames
1695
- dirpath = dirpath or self .workdir or Path .cwd ()
1696
- for logname in lognames :
1697
- content = self .acquire_log (logname )
1705
+
1706
+ guest_logs = guest_logs or self .guest_logs or []
1707
+
1708
+ dirpath = dirpath or (self .workdir / 'logs' if self .workdir else None ) or Path .cwd ()
1709
+ if self .workdir and dirpath == self .workdir / 'logs' :
1710
+ create_directory (
1711
+ path = self .workdir / 'logs' , name = 'logs workdir' , quiet = True , logger = self ._logger
1712
+ )
1713
+ for log in guest_logs :
1714
+ content = log .fetch ()
1698
1715
if content :
1699
- self .store_log (dirpath , content , logname )
1716
+ self .store_log (dirpath , content , log . name )
1700
1717
else :
1701
- self .store_log (dirpath , '' , logname )
1718
+ self .store_log (dirpath , '' , log . name )
1702
1719
1703
1720
1704
1721
@container
@@ -2696,6 +2713,28 @@ def show(self, keys: Optional[list[str]] = None) -> None:
2696
2713
if hardware :
2697
2714
echo (tmt .utils .format ('hardware' , tmt .utils .dict_to_yaml (hardware .to_spec ())))
2698
2715
2716
+ def prune (self , logger : tmt .log .Logger ) -> None :
2717
+ """Do not prune logs"""
2718
+ if self .workdir is None :
2719
+ return
2720
+
2721
+ logs_dir = self .workdir / 'logs'
2722
+ if logs_dir .exists ():
2723
+ for member in self .workdir .iterdir ():
2724
+ if member .name == "logs" :
2725
+ logger .debug (f"Preserve '{ member .relative_to (self .workdir )} '." , level = 3 )
2726
+ continue
2727
+ logger .debug (f"Remove '{ member } '." , level = 3 )
2728
+ try :
2729
+ if member .is_file () or member .is_symlink ():
2730
+ member .unlink ()
2731
+ else :
2732
+ shutil .rmtree (member )
2733
+ except OSError as error :
2734
+ logger .warning (f"Unable to remove '{ member } ': { error } " )
2735
+ else :
2736
+ super ().prune (logger )
2737
+
2699
2738
2700
2739
@container
2701
2740
class ProvisionTask (tmt .queue .GuestlessTask [None ]):
0 commit comments