Added post update removal of residual images

After performing an application update, the app
environment doesn't have an image cleanup process
to remove unused docker images

With this update, after the update is complete
the app itself compare both old and new images list
and remove the unused ones

TEST PLAN:
- PASS: Build two application tarballs with distinct images
- PASS: Apply first tarball
- Pass: Set up an update process with the second tarball
- PASS: Check if second tarball is applied
- PASS: List images
- PASS: Check if older images are still listed

Story: 2011262
Task: 51686

Change-Id: I5194d9e6329dc0a35f5638222f4631debcac6a4c
Signed-off-by: jbarboza <joaopedro.barbozalioneza@windriver.com>
This commit is contained in:
jbarboza
2025-03-18 13:25:43 -03:00
parent 12e927617f
commit 4fbe13c388
2 changed files with 139 additions and 0 deletions

View File

@@ -94,6 +94,9 @@ class OpenstackAppLifecycleOperator(base.AppLifecycleOperator):
if hook_info.operation == constants.APP_APPLY_OP and \
hook_info.relative_timing == constants.APP_LIFECYCLE_TIMING_PRE:
return self._pre_update_actions(app)
elif hook_info.operation == constants.APP_APPLY_OP and \
hook_info.relative_timing == constants.APP_LIFECYCLE_TIMING_POST:
return self._post_update_image_actions(app)
# Default behavior
super(OpenstackAppLifecycleOperator, self).app_lifecycle_actions(context, conductor_obj, app_op, app,
@@ -507,6 +510,20 @@ class OpenstackAppLifecycleOperator(base.AppLifecycleOperator):
resource_name='mariadb'
)
def _post_update_image_actions(self, app):
"""Perform post update actions, deleting residual images.
:param app: AppOperator.Application object
"""
images_base_dir = app.sync_imgfile.split(app.name)[0]
app_version_list = app_utils.get_app_version_list(images_base_dir, app.name)
if len(app_version_list) > 1:
LOG.info("Deleting unused images for app %s", app.name)
residual_images = app_utils.get_residual_images(app.sync_imgfile, app.version, app_version_list)
if len(residual_images) > 0:
app_utils.delete_residual_images(residual_images)
def _recover_backup_snapshot(self, app):
"""Perform pre recover backup actions

View File

@@ -628,6 +628,128 @@ def update_helmrelease(release, patch):
LOG.error(f"Unexpected error while updating helmrelease: {e}")
def get_app_version_list(base_dir: str, app_name: str) -> list:
"""
Retrieve a list of versions for a given application from YAML files.
Args:
base_dir (str): The base directory where application data is stored.
app_name (str): The name of the application.
Returns:
list: A list of versions for the specified application.
"""
return os.listdir(os.path.join(base_dir, app_name))
def get_image_list(image_dir: str) -> list:
"""
Retrieve a list of images for a given application from YAML files.
Args:
image_dir (str): The base directory where application data is stored.
Returns:
list: A list of images for the specified application.
"""
with open(image_dir, 'r', encoding='utf-8') as f:
return yaml.safe_load(f)["download_images"]
def get_residual_images(image_file_dir: str, app_version: str, app_version_list: list) -> list:
"""
Retrieve a list of residual images for a given application.
Args:
image_file_dir (str): The directory containing the image files.
app_version (str): The specific version of the application.
app_version_list (list): A list of all available application versions.
Returns:
list: A list of residual images that are present in older versions
but not in the current version.
"""
new_images_list = get_image_list(image_file_dir)
app_version_list.remove(app_version)
old_images_list = []
last_version = app_version
for version in app_version_list:
file_name = image_file_dir.replace(last_version, version)
old_images_list.extend(get_image_list(file_name))
last_version = version
return list(set(old_images_list) - set(new_images_list))
def delete_residual_images(image_list: list):
"""Remove a list of images from the system registry.
Args:
image (list): A list of image names to be removed.
"""
image_json = list_crictl_images()
for image in image_list:
image_id = get_image_id(image, image_json)
if not image_id:
LOG.error(f"Image {image} not found in the system registry.")
continue
LOG.info(f"Removing residual image: {image}")
cmd = [
"crictl", "rmi", image_id
]
try:
process = subprocess.run(
args=["bash", "-c", f"source /etc/platform/openrc && {' '.join(cmd)}"],
capture_output=True,
text=True,
shell=False)
LOG.info(f"Stdout: {process.stdout}")
LOG.info(f"Stderr: {process.stderr}")
process.check_returncode()
except Exception as e:
LOG.error(f"Unexpected error while removing image {image}: {e}")
def list_crictl_images() -> json:
"""List all images in the system registry.
Returns: A dict of images in the system registry.
"""
cmd = [
"crictl", "images", "--output", "json"
]
try:
process = subprocess.run(
args=["bash", "-c", f"source /etc/platform/openrc && {' '.join(cmd)}"],
capture_output=True,
text=True,
shell=False)
LOG.info(f"Stderr: {process.stderr}")
process.check_returncode()
return json.loads(process.stdout)
except Exception as e:
LOG.error(f"Unexpected error while listing images: {e}")
return None
def get_image_id(image_name: str, image_json: json) -> str:
"""Get the image ID from the system registry.
Args:
image_name (str): The name of the image.
image_json (json): The dict of images in the system registry.
Returns:
str: The image ID.
"""
for image in image_json["images"]:
if image_name in image["repoTags"]:
return image["id"]
return None
def get_number_of_controllers() -> int:
"""Get the number of controllers in the system