Skip to content
Open
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions tests/clean/runs/test.sh
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ rlJournalStart
rlRun -s "tmt clean runs --dry -v --workdir-root $tmprun"
rlAssertGrep "Would remove workdir '$run1'" "$rlRun_LOG"
rlAssertGrep "Would remove workdir '$run2'" "$rlRun_LOG"
rlAssertGrep "Summary: Would free.*of disk space" "$rlRun_LOG"
rlRun -s "tmt status --workdir-root $tmprun -vv"
rlAssertGrep "(done\s+){1}(todo\s+){6}$run1\s+/plan1" "$rlRun_LOG" -E
rlAssertGrep "(done\s+){1}(todo\s+){6}$run2\s+/plan1" "$rlRun_LOG" -E
Expand All @@ -30,12 +31,14 @@ rlJournalStart
rlPhaseStartTest "Specify ID"
rlRun -s "tmt clean runs -v -i $run1"
rlAssertGrep "Removing workdir '$run1'" "$rlRun_LOG"
rlAssertGrep "Summary: Freed.*of disk space" "$rlRun_LOG"
rlRun -s "tmt status --workdir-root $tmprun -vv"
rlAssertNotGrep "(done\s+){1}(todo\s+){6}$run1\s+/plan1" "$rlRun_LOG" -E
rlAssertGrep "(done\s+){1}(todo\s+){6}$run2\s+/plan1" "$rlRun_LOG" -E

rlRun -s "tmt clean runs -v -l --workdir-root $tmprun"
rlAssertGrep "Removing workdir '$run2'" "$rlRun_LOG"
rlAssertGrep "Summary: Freed.*of disk space" "$rlRun_LOG"
rlRun -s "tmt status --workdir-root $tmprun -vv"
rlAssertNotGrep "(done\s+){1}(todo\s+){6}$run2\s+/plan1" "$rlRun_LOG" -E

Expand Down
31 changes: 26 additions & 5 deletions tmt/base/core.py
Original file line number Diff line number Diff line change
Expand Up @@ -3613,6 +3613,11 @@ def show(self) -> None:
CleanCallback = Callable[[], bool]


def _dir_size(path: Path) -> int:
"""Return the total size in bytes of all files under path."""
return sum(f.stat().st_size for f in path.rglob('*') if f.is_file())
Comment thread
AthreyVinay marked this conversation as resolved.
Outdated


class Clean(tmt.utils.Common):
"""
A class for cleaning up workdirs, guests or images
Expand Down Expand Up @@ -3751,14 +3756,15 @@ def guests(self, run_ids: tuple[str, ...], keep: Optional[int]) -> bool:
successful = False
return successful

def _clean_workdir(self, path: Path) -> bool:
def _clean_workdir(self, path: Path, size: int) -> bool:
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Computing the size outside of _clean_workdir and just let it print out is very weird. We can change the return value of _clean_workdir, to e.g. a tuple of (success, size). _clean_workdir would collect the size, and just tell its caller how much data it did/would remove.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Absolutely - that was my intuition too. The only reason was I was trying to avoid returning a tuple - and I thought dataclass would be an overkill. Will revert to returning a tuple instead.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done: c4b334a

"""
Remove a workdir (unless in dry mode)
"""
formatted_size = tmt.utils.format_size(size)
if self.is_dry_run:
self.verbose(f"Would remove workdir '{path}'.", shift=1)
self.verbose(f"Would remove workdir '{path}' ({formatted_size}).", shift=1)
else:
self.verbose(f"Removing workdir '{path}'.", shift=1)
self.verbose(f"Removing workdir '{path}' ({formatted_size}).", shift=1)
try:
shutil.rmtree(path)
except OSError as error:
Expand All @@ -3777,18 +3783,33 @@ def runs(self, id_: tuple[str, ...], keep: Optional[int]) -> bool:
# the correct one.
last_run = Run(logger=self._logger, cli_invocation=self.cli_invocation)
last_run.load_workdir(with_logfiles=False)
return self._clean_workdir(last_run.run_workdir)
size = _dir_size(last_run.run_workdir)
success = self._clean_workdir(last_run.run_workdir, size)
self.verbose(
f"Summary: {'Would free' if self.is_dry_run else 'Freed'} "
f"{tmt.utils.format_size(size)} of disk space.",
shift=1,
)
return success
all_workdirs = list(tmt.utils.generate_runs(self.workdir_root, id_, all_=True))
if keep is not None:
# Sort by change time of the workdirs and keep the newest workdirs
all_workdirs.sort(key=lambda workdir: workdir.stat().st_ctime, reverse=True)
all_workdirs = all_workdirs[keep:]

successful = True
total_size = 0
for workdir in all_workdirs:
if not self._clean_workdir(workdir):
size = _dir_size(workdir)
total_size += size
if not self._clean_workdir(workdir, size):
successful = False

self.verbose(
f"Summary: {'Would free' if self.is_dry_run else 'Freed'} "
f"{tmt.utils.format_size(total_size)} of disk space.",
shift=1,
)
return successful


Expand Down
13 changes: 11 additions & 2 deletions tmt/steps/provision/testcloud.py
Original file line number Diff line number Diff line change
Expand Up @@ -1589,16 +1589,25 @@ def clean_images(cls, clean: 'tmt.base.core.Clean', dry: bool, workdir_root: Pat
clean.warn(f"Directory '{testcloud_images}' does not exist.", shift=2)
return True
successful = True
total_size = 0
for image in testcloud_images.iterdir():
size = image.stat().st_size
total_size += size
formatted_size = tmt.utils.format_size(size)
if dry:
clean.verbose(f"Would remove '{image}'.", shift=2)
clean.verbose(f"Would remove '{image}' ({formatted_size}).", shift=2)
else:
clean.verbose(f"Removing '{image}'.", shift=2)
clean.verbose(f"Removing '{image}' ({formatted_size}).", shift=2)
try:
image.unlink()
except OSError:
clean.fail(f"Failed to remove '{image}'.", shift=2)
successful = False
clean.verbose(
f"Summary: {'Would free' if dry else 'Freed'} "
f"{tmt.utils.format_size(total_size)} of disk space.",
shift=2,
)
return successful


Expand Down
9 changes: 9 additions & 0 deletions tmt/utils/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -86,6 +86,15 @@
from tmt.hardware import Size


def format_size(size: int) -> str:
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Use tmt.hardware.UNITS instead: tmt.hardware.UNITS(f'{size} bytes').to_compact()

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done: c4b334a

"""Format a byte count into a human-readable string."""
for unit in ('B', 'KB', 'MB', 'GB', 'TB'):
if size < 1024:
return f'{size:.1f} {unit}'
size = int(size / 1024)
return f'{size:.1f} PB'
Comment thread
AthreyVinay marked this conversation as resolved.
Outdated


def sanitize_string(text: str) -> str:
"""Remove invalid Unicode characters from a string"""
try:
Expand Down
Loading