Skip to content

Commit cacaa99

Browse files
mcgovCopilot
andcommitted
Dpdk: roll back broken installs and reuse downloaded assets
A failure part way through a source installation left the node with a half installed dpdk or rdma-core, and every later test on that node failed for an unrelated reason. Wrap the install steps so a failure uninstalls what was applied, removes the extracted source, and marks the node dirty if even the cleanup fails, then re-raises the original error. The asset removal guards that were specific to the rdma-core installer now live on the base Installer as _delete_assets, so every installer gets the same protection against deleting '/' or the working path. Downloads and extraction are also skipped when the asset is already on the node, and dpdk-stable is fetched from the github mirror, which is far more reliable than dpdk.org from Azure. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> Copilot-Session: 5d5f58ad-b9df-4420-ad37-22caee78e925
1 parent c49b9aa commit cacaa99

2 files changed

Lines changed: 61 additions & 28 deletions

File tree

lisa/microsoft/testsuites/dpdk/common.py

Lines changed: 60 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@
1717
from lisa.tools.lscpu import CpuArchitecture
1818
from lisa.util import UnsupportedDistroException
1919

20-
DPDK_STABLE_GIT_REPO = "https://dpdk.org/git/dpdk-stable"
20+
DPDK_STABLE_GIT_REPO = "https://github.com/dpdk/dpdk-stable.git"
2121

2222
# azure routing table magic subnet prefix
2323
# signals 'route all traffic on this subnet'
@@ -150,6 +150,7 @@ def download(self) -> PurePath:
150150
self._tar_url,
151151
overwrite=False,
152152
file_path=str(work_path),
153+
skip_exists=True,
153154
)
154155
remote_path = node.get_pure_path(tarfile)
155156
self.tar_filename = remote_path.name
@@ -167,12 +168,13 @@ def download(self) -> PurePath:
167168
# force name as tarfile name
168169
# add option to skip files which already exist on disk
169170
# in the event we have already extracted this specific tar
170-
node.tools[Tar].extract(
171-
file=str(remote_path),
172-
dest_dir=str(work_path),
173-
gzip=True,
174-
skip_existing_files=True,
175-
)
171+
if not node.shell.exists(self.asset_path):
172+
node.tools[Tar].extract(
173+
file=str(remote_path),
174+
dest_dir=str(work_path),
175+
gzip=True,
176+
skip_existing_files=True,
177+
)
176178
return self.asset_path
177179

178180

@@ -186,7 +188,8 @@ class Installer:
186188
# First we download the assets to ensure asset_path is set
187189
# even if we end up skipping re-installation
188190
def _setup_node(self) -> None:
189-
self._download_assets()
191+
if not hasattr(self, "asset_path"):
192+
self._download_assets()
190193

191194
# check if the package is already installed:
192195
# Is the package installed from source? Or from the package manager?
@@ -209,6 +212,40 @@ def _install(self) -> None:
209212
def _uninstall(self) -> None:
210213
raise NotImplementedError(f"_clean_previous_installation {self._err_msg}")
211214

215+
def _asset_path_exists(self) -> bool:
216+
return hasattr(self, "asset_path") and self._node.shell.exists(self.asset_path)
217+
218+
def _delete_assets(self) -> None:
219+
if not self._asset_path_exists():
220+
return
221+
asset_path = self.asset_path
222+
delattr(self, "asset_path")
223+
working_path = str(self._node.get_working_path())
224+
assert_that(str(asset_path)).described_as(
225+
"Test bug: Installer source path was empty during attempted cleanup!"
226+
).is_not_empty()
227+
assert_that(str(asset_path)).described_as(
228+
"Test bug: Installer source path was set to root dir '/' "
229+
"during attempted cleanup!"
230+
).is_not_equal_to("/")
231+
assert_that(str(asset_path)).described_as(
232+
f"Test bug: Installer source path {asset_path} was set to working path "
233+
f"'{working_path}' during attempted cleanup!"
234+
).is_not_equal_to(working_path)
235+
self._node.execute(f"rm -rf {str(asset_path)}", shell=True)
236+
237+
def _rollback_installation(self) -> None:
238+
try:
239+
if self._check_if_installed():
240+
self._uninstall()
241+
self._delete_assets()
242+
except Exception as err:
243+
self._node.log.debug(
244+
f"Installer cleanup failed; marking node dirty. {str(err)}"
245+
)
246+
self._node.mark_dirty()
247+
raise err
248+
212249
# install the dependencies
213250
def _install_dependencies(self) -> None:
214251
if self._os_dependencies is not None:
@@ -230,9 +267,20 @@ def _should_install(self, required_version: Optional[VersionInfo] = None) -> boo
230267
def do_installation(self, required_version: Optional[VersionInfo] = None) -> None:
231268
self._setup_node()
232269
if self._should_install():
233-
self._uninstall()
234-
self._install_dependencies()
235-
self._install()
270+
# any issues here could result in a broken installation.
271+
# If the node is still usable, we don't want to discard it.
272+
# So attempt to roll back a broken installation and re-raise the problem.
273+
# This avoids re-deployments and ensures a transient issue causing a broken
274+
# installation doesn't propagate failures into future tests on
275+
# that same node.
276+
try:
277+
self._download_assets()
278+
self._uninstall()
279+
self._install_dependencies()
280+
self._install()
281+
except Exception as e:
282+
self._rollback_installation()
283+
raise e
236284

237285
def __init__(
238286
self,
@@ -289,7 +337,7 @@ def force_dpdk_default_source(variables: Dict[str, Any]) -> None:
289337
variables["dpdk_source"] = DPDK_STABLE_GIT_REPO
290338

291339

292-
_UBUNTU_LTS_VERSIONS = ["24.4.0", "22.4.0", "20.4.0", "18.4.0"]
340+
_UBUNTU_LTS_VERSIONS = ["26.4.0", "24.4.0", "22.4.0", "20.4.0", "18.4.0"]
293341

294342

295343
# see https://ubuntu.com/about/release-cycle

lisa/microsoft/testsuites/dpdk/rdmacore.py

Lines changed: 1 addition & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,3 @@
1-
from assertpy import assert_that
21
from microsoft.testsuites.dpdk.common import (
32
DependencyInstaller,
43
Installer,
@@ -14,7 +13,7 @@
1413

1514
RDMA_CORE_MANA_DEFAULT_SOURCE = (
1615
"https://github.com/linux-rdma/rdma-core/"
17-
"releases/download/v50.1/rdma-core-50.1.tar.gz"
16+
"releases/download/v59.0/rdma-core-59.0.tar.gz"
1817
)
1918
RDMA_CORE_SOURCE_DEPENDENCIES = DependencyInstaller(
2019
[
@@ -166,20 +165,6 @@ def _uninstall(self) -> None:
166165
self._node.tools[Make].run(
167166
parameters="uninstall", shell=True, sudo=True, cwd=self.asset_path
168167
)
169-
working_path = str(self._node.get_working_path())
170-
assert_that(str(self.asset_path)).described_as(
171-
"RDMA Installer source path was empty during attempted cleanup!"
172-
).is_not_empty()
173-
assert_that(str(self.asset_path)).described_as(
174-
"RDMA Installer source path was set to root dir "
175-
"'/' during attempted cleanup!"
176-
).is_not_equal_to("/")
177-
assert_that(str(self.asset_path)).described_as(
178-
f"RDMA Installer source path {self.asset_path} was set to "
179-
f"working path '{working_path}' during attempted cleanup!"
180-
).is_not_equal_to(working_path)
181-
# remove source code directory
182-
self._node.execute(f"rm -rf {str(self.asset_path)}", shell=True)
183168

184169
def get_installed_version(self) -> VersionInfo:
185170
version: VersionInfo = self._node.tools[Pkgconfig].get_package_version(

0 commit comments

Comments
 (0)