mirror of
https://github.com/ansible/awx.git
synced 2026-03-10 14:09:28 -02:30
Remove system job, replace with scheduled task
This commit is contained in:
@@ -1,83 +0,0 @@
|
|||||||
# Copyright (c) 2015 Ansible, Inc.
|
|
||||||
# All Rights Reserved.
|
|
||||||
|
|
||||||
# Python
|
|
||||||
import subprocess
|
|
||||||
import logging
|
|
||||||
import json
|
|
||||||
|
|
||||||
# Django
|
|
||||||
from django.core.management.base import BaseCommand, CommandError
|
|
||||||
from django.conf import settings
|
|
||||||
|
|
||||||
# AWX
|
|
||||||
from awx.main.models import ExecutionEnvironment
|
|
||||||
|
|
||||||
|
|
||||||
class Command(BaseCommand):
|
|
||||||
"""
|
|
||||||
Management command to cleanup unused execution environment images.
|
|
||||||
"""
|
|
||||||
|
|
||||||
help = 'Remove unused execution environment images'
|
|
||||||
|
|
||||||
def init_logging(self):
|
|
||||||
log_levels = dict(enumerate([logging.ERROR, logging.INFO, logging.DEBUG, 0]))
|
|
||||||
self.logger = logging.getLogger('awx.main.commands.cleanup_images')
|
|
||||||
self.logger.setLevel(log_levels.get(self.verbosity, 0))
|
|
||||||
handler = logging.StreamHandler()
|
|
||||||
handler.setFormatter(logging.Formatter('%(message)s'))
|
|
||||||
self.logger.addHandler(handler)
|
|
||||||
self.logger.propagate = False
|
|
||||||
|
|
||||||
def add_arguments(self, parser):
|
|
||||||
parser.add_argument('--dry-run', dest='dry_run', action='store_true', default=False, help='Dry run mode (show items that would ' 'be removed)')
|
|
||||||
|
|
||||||
def delete_images(self, images_json):
|
|
||||||
if self.dry_run:
|
|
||||||
delete_prefix = "Would delete"
|
|
||||||
else:
|
|
||||||
delete_prefix = "Deleting"
|
|
||||||
for e in images_json:
|
|
||||||
if 'Names' in e:
|
|
||||||
image_names = e['Names']
|
|
||||||
else:
|
|
||||||
image_names = [e["Id"]]
|
|
||||||
image_size = e['Size'] / 1e6
|
|
||||||
for i in image_names:
|
|
||||||
if i not in self.images_in_use and i not in self.deleted:
|
|
||||||
self.deleted.append(i)
|
|
||||||
self.logger.info(f"{delete_prefix} {i}: {image_size:.0f} MB")
|
|
||||||
if not self.dry_run:
|
|
||||||
subprocess.run(['podman', 'rmi', i, '-f'], stdout=subprocess.DEVNULL)
|
|
||||||
|
|
||||||
def cleanup_images(self):
|
|
||||||
self.images_in_use = [ee.image for ee in ExecutionEnvironment.objects.all()]
|
|
||||||
if self.images_in_use:
|
|
||||||
self.logger.info("Execution environment images in use:")
|
|
||||||
for i in self.images_in_use:
|
|
||||||
self.logger.info(f"\t{i}")
|
|
||||||
self.deleted = []
|
|
||||||
# find and remove unused images
|
|
||||||
images_system = subprocess.run("podman images -a --format json".split(" "), capture_output=True)
|
|
||||||
if len(images_system.stdout) > 0:
|
|
||||||
images_system = json.loads(images_system.stdout)
|
|
||||||
|
|
||||||
self.delete_images(images_system)
|
|
||||||
# find and remove dangling images
|
|
||||||
images_system = subprocess.run('podman images -a --filter "dangling=true" --format json'.split(" "), capture_output=True)
|
|
||||||
if len(images_system.stdout) > 0:
|
|
||||||
images_system = json.loads(images_system.stdout)
|
|
||||||
self.delete_images(images_system)
|
|
||||||
if not self.deleted:
|
|
||||||
self.logger.info("Did not find unused images to remove")
|
|
||||||
|
|
||||||
def handle(self, *args, **options):
|
|
||||||
self.verbosity = int(options.get('verbosity', 1))
|
|
||||||
self.init_logging()
|
|
||||||
self.dry_run = bool(options.get('dry_run', False))
|
|
||||||
if self.dry_run:
|
|
||||||
self.logger.info("Dry run enabled, images will not be deleted")
|
|
||||||
if settings.IS_K8S:
|
|
||||||
raise CommandError("Cannot run cleanup tool on k8s installations")
|
|
||||||
self.cleanup_images()
|
|
||||||
@@ -27,6 +27,7 @@ import socket
|
|||||||
import threading
|
import threading
|
||||||
import concurrent.futures
|
import concurrent.futures
|
||||||
from base64 import b64encode
|
from base64 import b64encode
|
||||||
|
import subprocess
|
||||||
|
|
||||||
# Django
|
# Django
|
||||||
from django.conf import settings
|
from django.conf import settings
|
||||||
@@ -59,6 +60,7 @@ from awx.main.constants import PRIVILEGE_ESCALATION_METHODS, STANDARD_INVENTORY_
|
|||||||
from awx.main.access import access_registry
|
from awx.main.access import access_registry
|
||||||
from awx.main.redact import UriCleaner
|
from awx.main.redact import UriCleaner
|
||||||
from awx.main.models import (
|
from awx.main.models import (
|
||||||
|
ExecutionEnvironment,
|
||||||
Schedule,
|
Schedule,
|
||||||
TowerScheduleState,
|
TowerScheduleState,
|
||||||
Instance,
|
Instance,
|
||||||
@@ -396,6 +398,23 @@ def purge_old_stdout_files():
|
|||||||
logger.debug("Removing {}".format(os.path.join(settings.JOBOUTPUT_ROOT, f)))
|
logger.debug("Removing {}".format(os.path.join(settings.JOBOUTPUT_ROOT, f)))
|
||||||
|
|
||||||
|
|
||||||
|
@task(queue=get_local_queuename)
|
||||||
|
def cleanup_execution_environment_images():
|
||||||
|
images_in_use = [ee.image for ee in ExecutionEnvironment.objects.all()]
|
||||||
|
images_system = subprocess.run("podman images -a --format json".split(" "), capture_output=True)
|
||||||
|
if len(images_system.stdout) > 0:
|
||||||
|
images_system = json.loads(images_system.stdout)
|
||||||
|
for e in images_system:
|
||||||
|
if 'Names' in e:
|
||||||
|
image_name = e['Names'][0]
|
||||||
|
else:
|
||||||
|
image_name = e["Id"]
|
||||||
|
image_size = e['Size'] / 1e6
|
||||||
|
if image_name not in images_in_use:
|
||||||
|
logger.debug(f"Cleanup execution environment images: deleting {image_name}, {image_size:.0f} MB")
|
||||||
|
subprocess.run(['podman', 'rmi', image_name, '-f'], stdout=subprocess.DEVNULL)
|
||||||
|
|
||||||
|
|
||||||
@task(queue=get_local_queuename)
|
@task(queue=get_local_queuename)
|
||||||
def cluster_node_heartbeat():
|
def cluster_node_heartbeat():
|
||||||
logger.debug("Cluster node heartbeat task.")
|
logger.debug("Cluster node heartbeat task.")
|
||||||
|
|||||||
@@ -439,6 +439,7 @@ CELERYBEAT_SCHEDULE = {
|
|||||||
'task_manager': {'task': 'awx.main.scheduler.tasks.run_task_manager', 'schedule': timedelta(seconds=20), 'options': {'expires': 20}},
|
'task_manager': {'task': 'awx.main.scheduler.tasks.run_task_manager', 'schedule': timedelta(seconds=20), 'options': {'expires': 20}},
|
||||||
'k8s_reaper': {'task': 'awx.main.tasks.awx_k8s_reaper', 'schedule': timedelta(seconds=60), 'options': {'expires': 50}},
|
'k8s_reaper': {'task': 'awx.main.tasks.awx_k8s_reaper', 'schedule': timedelta(seconds=60), 'options': {'expires': 50}},
|
||||||
'send_subsystem_metrics': {'task': 'awx.main.analytics.analytics_tasks.send_subsystem_metrics', 'schedule': timedelta(seconds=20)},
|
'send_subsystem_metrics': {'task': 'awx.main.analytics.analytics_tasks.send_subsystem_metrics', 'schedule': timedelta(seconds=20)},
|
||||||
|
'cleanup_images': {'task': 'awx.main.tasks.cleanup_execution_environment_images', 'schedule': timedelta(hours=8)},
|
||||||
# 'isolated_heartbeat': set up at the end of production.py and development.py
|
# 'isolated_heartbeat': set up at the end of production.py and development.py
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user