Skip to content

Commit e0c356c

Browse files
authored
Merge pull request avocado-framework#6169 from harvey0100/cachefix
VMImage Params Caching Request
2 parents a2552f8 + 5596cd0 commit e0c356c

File tree

2 files changed

+117
-6
lines changed

2 files changed

+117
-6
lines changed

avocado/utils/asset.py

Lines changed: 21 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -443,7 +443,9 @@ def fetch(self, timeout=None):
443443

444444
def find_asset_file(self, create_metadata=False):
445445
"""
446-
Search for the asset file in each one of the cache locations
446+
Search for the asset file in each one of the cache locations.
447+
448+
It handles both normal asset names and direct file paths.
447449
448450
:param bool create_metadata: Should this method create the
449451
metadata in case asset file found
@@ -454,6 +456,11 @@ def find_asset_file(self, create_metadata=False):
454456
:raises: OSError
455457
"""
456458

459+
# Try 1: Check if self.name is already a direct file path
460+
if self.name and os.path.isfile(self.name):
461+
return self.name
462+
463+
# Try 2: Search in cache directories using relative_dir
457464
for cache_dir in self.cache_dirs:
458465
cache_dir = os.path.expanduser(cache_dir)
459466
asset_file = os.path.join(cache_dir, self.relative_dir)
@@ -482,20 +489,30 @@ def get_metadata(self):
482489
"""
483490
Returns metadata of the asset if it exists or None.
484491
492+
It handles both normal asset names and direct file paths.
493+
485494
:return: metadata
486495
:rtype: dict or None
487496
"""
488497
try:
489498
asset_file = self.find_asset_file()
490499
except OSError as exc:
500+
LOG.debug("Asset file not found, metadata not available for %s", self.name)
491501
raise OSError("Metadata not available.") from exc
492502

493503
basename = os.path.splitext(asset_file)[0]
494504
metadata_file = f"{basename}_metadata.json"
495505
if os.path.isfile(metadata_file):
496-
with open(metadata_file, "r", encoding="utf-8") as f:
497-
metadata = json.load(f)
498-
return metadata
506+
try:
507+
with open(metadata_file, "r", encoding="utf-8") as f:
508+
metadata = json.load(f)
509+
return metadata
510+
except json.JSONDecodeError as e:
511+
LOG.warning("Invalid JSON in metadata file %s: %s", metadata_file, e)
512+
raise
513+
except OSError as e:
514+
LOG.warning("Cannot read metadata file %s: %s", metadata_file, e)
515+
raise
499516
return None
500517

501518
@property

avocado/utils/vmimage.py

Lines changed: 96 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -119,7 +119,7 @@ def _feed_html_parser(self, url, parser):
119119
data = u.read()
120120
parser.feed(astring.to_text(data, self.HTML_ENCODING))
121121
except HTTPError as exc:
122-
raise ImageProviderError(f"Cannot open {url}") from exc
122+
raise ImageProviderError(f"Network error: Cannot open {url}") from exc
123123

124124
@staticmethod
125125
def get_best_version(versions):
@@ -213,7 +213,7 @@ def get_image_url(self):
213213
else:
214214
cloud = "CloudImages"
215215

216-
if self.url_old_images and int(self.version) <= 38:
216+
if self.url_old_images and int(self.version) <= 40:
217217
self.url_versions = self.url_old_images
218218

219219
self.url_images = self.url_versions + "{version}/" + cloud + "/{arch}/images/"
@@ -633,6 +633,89 @@ def _take_snapshot(self):
633633
process.run(cmd)
634634
return new_image
635635

636+
@classmethod
637+
def _find_cached_image( # pylint: disable=too-many-locals
638+
cls,
639+
cache_dirs,
640+
name=None,
641+
version=None,
642+
build=None,
643+
arch=None,
644+
checksum=None,
645+
algorithm=None,
646+
snapshot_dir=None,
647+
):
648+
"""
649+
Find a cached image using asset.py enhanced built-in functionality.
650+
651+
This version uses the enhanced Asset.get_metadata() method which supports
652+
both normal asset names and direct file paths, maximizing utilization of
653+
existing asset.py caching features.
654+
"""
655+
656+
# pylint: disable-next=invalid-name
657+
ARCH_COMPATIBILITY = {
658+
"x86_64": ["x86_64", "amd64", "64"],
659+
"amd64": ["x86_64", "amd64", "64"],
660+
"aarch64": ["aarch64", "arm64"],
661+
"arm64": ["aarch64", "arm64"],
662+
}
663+
compatible_arches = ARCH_COMPATIBILITY.get(arch, [arch]) if arch else []
664+
665+
def matches_image_criteria(metadata, name, version, build, compatible_arches):
666+
return (
667+
metadata.get("type") == "vmimage"
668+
and (not name or metadata.get("name", "").lower() == name.lower())
669+
and (not version or str(metadata.get("version", "")) == str(version))
670+
and (not build or str(metadata.get("build", "")) == str(build))
671+
and (not compatible_arches or metadata.get("arch") in compatible_arches)
672+
)
673+
674+
# Use Asset.get_all_assets() to find cached assets
675+
for asset_path in asset.Asset.get_all_assets(cache_dirs, sort=False):
676+
try:
677+
temp_asset = asset.Asset(
678+
name=asset_path,
679+
asset_hash=checksum,
680+
algorithm=algorithm,
681+
cache_dirs=cache_dirs,
682+
)
683+
684+
try:
685+
metadata = temp_asset.get_metadata()
686+
if not metadata:
687+
continue
688+
except OSError:
689+
continue
690+
691+
if matches_image_criteria(
692+
metadata, name, version, build, compatible_arches
693+
):
694+
# pylint: disable-next=W0212
695+
if checksum and not temp_asset._verify_hash(asset_path):
696+
LOG.debug("Hash mismatch for cached image: %s", asset_path)
697+
continue
698+
699+
LOG.info("Found matching cached image: %s", asset_path)
700+
return cls(
701+
name=metadata.get("name", name),
702+
url=asset_path,
703+
version=metadata.get("version", version),
704+
arch=metadata.get("arch", arch),
705+
build=metadata.get("build", build),
706+
checksum=checksum,
707+
algorithm=algorithm,
708+
cache_dir=cache_dirs[0],
709+
snapshot_dir=snapshot_dir,
710+
)
711+
712+
except (OSError, ValueError, KeyError, AttributeError) as e:
713+
# Skip assets that can't be processed (common errors during metadata processing)
714+
LOG.debug("Skipping asset %s due to error: %s", asset_path, e)
715+
continue
716+
717+
return None
718+
636719
@classmethod
637720
# pylint: disable=R0913
638721
def from_parameters(
@@ -739,14 +822,25 @@ def get_best_provider(name=None, version=None, build=None, arch=None):
739822
if name == "fedora" and arch in ("ppc64", "ppc64le", "s390x"):
740823
name = "fedorasecondary"
741824

825+
provider_errors = []
742826
for provider in IMAGE_PROVIDERS:
743827
if name is None or name == provider.name.lower():
744828
try:
745829
return provider(**provider_args)
746830
except ImageProviderError as e:
747831
LOG.debug(e)
832+
provider_errors.append(f"{provider.name}: {e}")
833+
except (HTTPError, OSError) as e:
834+
LOG.debug(
835+
"Network error while creating provider %s: %s", provider.name, e
836+
)
837+
# Network errors should raise immediately
838+
raise ImageProviderError(f"Network error: {e}") from e
748839

749840
LOG.debug("Provider for %s not available", name)
841+
if provider_errors:
842+
error_details = "; ".join(provider_errors)
843+
raise ImageProviderError(f"Providers failed - {error_details}")
750844
raise AttributeError("Provider not available")
751845

752846

0 commit comments

Comments
 (0)