Skip to content

Commit 80158fd

Browse files
Tomáš Ďurovecpaul-szczepanek-arm
Tomáš Ďurovec
authored andcommitted
dts: enable copying directories to and from nodes
Currently there is no support to transfer whole directories between the DTS host and the nodes. This change adds this new feature. Signed-off-by: Tomáš Ďurovec <tomas.durovec@pantheon.tech> Signed-off-by: Luca Vizzarro <luca.vizzarro@arm.com>
1 parent 441c5fb commit 80158fd

File tree

3 files changed

+287
-12
lines changed

3 files changed

+287
-12
lines changed

dts/framework/testbed_model/os_session.py

+116-4
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,7 @@
2525
from abc import ABC, abstractmethod
2626
from collections.abc import Iterable
2727
from ipaddress import IPv4Interface, IPv6Interface
28-
from pathlib import Path, PurePath
28+
from pathlib import Path, PurePath, PurePosixPath
2929
from typing import Union
3030

3131
from framework.config import Architecture, NodeConfiguration, NodeInfo
@@ -38,7 +38,7 @@
3838
)
3939
from framework.remote_session.remote_session import CommandResult
4040
from framework.settings import SETTINGS
41-
from framework.utils import MesonArgs
41+
from framework.utils import MesonArgs, TarCompressionFormat
4242

4343
from .cpu import LogicalCore
4444
from .port import Port
@@ -203,6 +203,95 @@ def copy_to(self, source_file: str | Path, destination_dir: str | PurePath) -> N
203203
will be saved.
204204
"""
205205

206+
@abstractmethod
207+
def copy_dir_from(
208+
self,
209+
source_dir: str | PurePath,
210+
destination_dir: str | Path,
211+
compress_format: TarCompressionFormat = TarCompressionFormat.none,
212+
exclude: str | list[str] | None = None,
213+
) -> None:
214+
"""Copy a directory from the remote node to the local filesystem.
215+
216+
Copy `source_dir` from the remote node associated with this remote session to
217+
`destination_dir` on the local filesystem. The new local directory will be created
218+
at `destination_dir` path.
219+
220+
Example:
221+
source_dir = '/remote/path/to/source'
222+
destination_dir = '/local/path/to/destination'
223+
compress_format = TarCompressionFormat.xz
224+
225+
The method will:
226+
1. Create a tarball from `source_dir`, resulting in:
227+
'/remote/path/to/source.tar.xz',
228+
2. Copy '/remote/path/to/source.tar.xz' to
229+
'/local/path/to/destination/source.tar.xz',
230+
3. Extract the contents of the tarball, resulting in:
231+
'/local/path/to/destination/source/',
232+
4. Remove the tarball after extraction
233+
('/local/path/to/destination/source.tar.xz').
234+
235+
Final Path Structure:
236+
'/local/path/to/destination/source/'
237+
238+
Args:
239+
source_dir: The directory on the remote node.
240+
destination_dir: The directory path on the local filesystem.
241+
compress_format: The compression format to use. Defaults to no compression.
242+
exclude: Patterns for files or directories to exclude from the tarball.
243+
These patterns are used with `tar`'s `--exclude` option.
244+
"""
245+
246+
@abstractmethod
247+
def copy_dir_to(
248+
self,
249+
source_dir: str | Path,
250+
destination_dir: str | PurePath,
251+
compress_format: TarCompressionFormat = TarCompressionFormat.none,
252+
exclude: str | list[str] | None = None,
253+
) -> None:
254+
"""Copy a directory from the local filesystem to the remote node.
255+
256+
Copy `source_dir` from the local filesystem to `destination_dir` on the remote node
257+
associated with this remote session. The new remote directory will be created at
258+
`destination_dir` path.
259+
260+
Example:
261+
source_dir = '/local/path/to/source'
262+
destination_dir = '/remote/path/to/destination'
263+
compress_format = TarCompressionFormat.xz
264+
265+
The method will:
266+
1. Create a tarball from `source_dir`, resulting in:
267+
'/local/path/to/source.tar.xz',
268+
2. Copy '/local/path/to/source.tar.xz' to
269+
'/remote/path/to/destination/source.tar.xz',
270+
3. Extract the contents of the tarball, resulting in:
271+
'/remote/path/to/destination/source/',
272+
4. Remove the tarball after extraction
273+
('/remote/path/to/destination/source.tar.xz').
274+
275+
Final Path Structure:
276+
'/remote/path/to/destination/source/'
277+
278+
Args:
279+
source_dir: The directory on the local filesystem.
280+
destination_dir: The directory path on the remote node.
281+
compress_format: The compression format to use. Defaults to no compression.
282+
exclude: Patterns for files or directories to exclude from the tarball.
283+
These patterns are used with `fnmatch.fnmatch` to filter out files.
284+
"""
285+
286+
@abstractmethod
287+
def remove_remote_file(self, remote_file_path: str | PurePath, force: bool = True) -> None:
288+
"""Remove remote file, by default remove forcefully.
289+
290+
Args:
291+
remote_file_path: The file path to remove.
292+
force: If :data:`True`, ignore all warnings and try to remove at all costs.
293+
"""
294+
206295
@abstractmethod
207296
def remove_remote_dir(
208297
self,
@@ -213,11 +302,34 @@ def remove_remote_dir(
213302
"""Remove remote directory, by default remove recursively and forcefully.
214303
215304
Args:
216-
remote_dir_path: The path of the directory to remove.
305+
remote_dir_path: The directory path to remove.
217306
recursive: If :data:`True`, also remove all contents inside the directory.
218307
force: If :data:`True`, ignore all warnings and try to remove at all costs.
219308
"""
220309

310+
@abstractmethod
311+
def create_remote_tarball(
312+
self,
313+
remote_dir_path: str | PurePath,
314+
compress_format: TarCompressionFormat = TarCompressionFormat.none,
315+
exclude: str | list[str] | None = None,
316+
) -> PurePosixPath:
317+
"""Create a tarball from the contents of the specified remote directory.
318+
319+
This method creates a tarball containing all files and directories
320+
within `remote_dir_path`. The tarball will be saved in the directory of
321+
`remote_dir_path` and will be named based on `remote_dir_path`.
322+
323+
Args:
324+
remote_dir_path: The directory path on the remote node.
325+
compress_format: The compression format to use. Defaults to no compression.
326+
exclude: Patterns for files or directories to exclude from the tarball.
327+
These patterns are used with `tar`'s `--exclude` option.
328+
329+
Returns:
330+
The path to the created tarball on the remote node.
331+
"""
332+
221333
@abstractmethod
222334
def extract_remote_tarball(
223335
self,
@@ -227,7 +339,7 @@ def extract_remote_tarball(
227339
"""Extract remote tarball in its remote directory.
228340
229341
Args:
230-
remote_tarball_path: The path of the tarball on the remote node.
342+
remote_tarball_path: The tarball path on the remote node.
231343
expected_dir: If non-empty, check whether `expected_dir` exists after extracting
232344
the archive.
233345
"""

dts/framework/testbed_model/posix_session.py

+84-4
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,13 @@
1818
from framework.config import Architecture, NodeInfo
1919
from framework.exception import DPDKBuildError, RemoteCommandExecutionError
2020
from framework.settings import SETTINGS
21-
from framework.utils import MesonArgs
21+
from framework.utils import (
22+
MesonArgs,
23+
TarCompressionFormat,
24+
convert_to_list_of_string,
25+
create_tarball,
26+
extract_tarball,
27+
)
2228

2329
from .os_session import OSSession
2430

@@ -93,6 +99,48 @@ def copy_to(self, source_file: str | Path, destination_dir: str | PurePath) -> N
9399
"""Overrides :meth:`~.os_session.OSSession.copy_to`."""
94100
self.remote_session.copy_to(source_file, destination_dir)
95101

102+
def copy_dir_from(
103+
self,
104+
source_dir: str | PurePath,
105+
destination_dir: str | Path,
106+
compress_format: TarCompressionFormat = TarCompressionFormat.none,
107+
exclude: str | list[str] | None = None,
108+
) -> None:
109+
"""Overrides :meth:`~.os_session.OSSession.copy_dir_from`."""
110+
source_dir = PurePath(source_dir)
111+
remote_tarball_path = self.create_remote_tarball(source_dir, compress_format, exclude)
112+
113+
self.copy_from(remote_tarball_path, destination_dir)
114+
self.remove_remote_file(remote_tarball_path)
115+
116+
tarball_path = Path(destination_dir, f"{source_dir.name}.{compress_format.extension}")
117+
extract_tarball(tarball_path)
118+
tarball_path.unlink()
119+
120+
def copy_dir_to(
121+
self,
122+
source_dir: str | Path,
123+
destination_dir: str | PurePath,
124+
compress_format: TarCompressionFormat = TarCompressionFormat.none,
125+
exclude: str | list[str] | None = None,
126+
) -> None:
127+
"""Overrides :meth:`~.os_session.OSSession.copy_dir_to`."""
128+
source_dir = Path(source_dir)
129+
tarball_path = create_tarball(source_dir, compress_format, exclude=exclude)
130+
self.copy_to(tarball_path, destination_dir)
131+
tarball_path.unlink()
132+
133+
remote_tar_path = self.join_remote_path(
134+
destination_dir, f"{source_dir.name}.{compress_format.extension}"
135+
)
136+
self.extract_remote_tarball(remote_tar_path)
137+
self.remove_remote_file(remote_tar_path)
138+
139+
def remove_remote_file(self, remote_file_path: str | PurePath, force: bool = True) -> None:
140+
"""Overrides :meth:`~.os_session.OSSession.remove_remote_dir`."""
141+
opts = PosixSession.combine_short_options(f=force)
142+
self.send_command(f"rm{opts} {remote_file_path}")
143+
96144
def remove_remote_dir(
97145
self,
98146
remote_dir_path: str | PurePath,
@@ -103,10 +151,42 @@ def remove_remote_dir(
103151
opts = PosixSession.combine_short_options(r=recursive, f=force)
104152
self.send_command(f"rm{opts} {remote_dir_path}")
105153

106-
def extract_remote_tarball(
154+
def create_remote_tarball(
107155
self,
108-
remote_tarball_path: str | PurePath,
109-
expected_dir: str | PurePath | None = None,
156+
remote_dir_path: str | PurePath,
157+
compress_format: TarCompressionFormat = TarCompressionFormat.none,
158+
exclude: str | list[str] | None = None,
159+
) -> PurePosixPath:
160+
"""Overrides :meth:`~.os_session.OSSession.create_remote_tarball`."""
161+
162+
def generate_tar_exclude_args(exclude_patterns) -> str:
163+
"""Generate args to exclude patterns when creating a tarball.
164+
165+
Args:
166+
exclude_patterns: Patterns for files or directories to exclude from the tarball.
167+
These patterns are used with `tar`'s `--exclude` option.
168+
169+
Returns:
170+
The generated string args to exclude the specified patterns.
171+
"""
172+
if exclude_patterns:
173+
exclude_patterns = convert_to_list_of_string(exclude_patterns)
174+
return "".join([f" --exclude={pattern}" for pattern in exclude_patterns])
175+
return ""
176+
177+
posix_remote_dir_path = PurePosixPath(remote_dir_path)
178+
target_tarball_path = PurePosixPath(f"{remote_dir_path}.{compress_format.extension}")
179+
180+
self.send_command(
181+
f"tar caf {target_tarball_path}{generate_tar_exclude_args(exclude)} "
182+
f"-C {posix_remote_dir_path.parent} {posix_remote_dir_path.name}",
183+
60,
184+
)
185+
186+
return target_tarball_path
187+
188+
def extract_remote_tarball(
189+
self, remote_tarball_path: str | PurePath, expected_dir: str | PurePath | None = None
110190
) -> None:
111191
"""Overrides :meth:`~.os_session.OSSession.extract_remote_tarball`."""
112192
self.send_command(

0 commit comments

Comments
 (0)