Skip to content

Commit

Permalink
Merge pull request #9 from ctrliq/devel
Browse files Browse the repository at this point in the history
Sync to upstream 23.1.0
  • Loading branch information
cigamit authored Sep 12, 2023
2 parents 577f555 + 034ae11 commit 2082947
Show file tree
Hide file tree
Showing 40 changed files with 1,371 additions and 1,060 deletions.
4 changes: 2 additions & 2 deletions .github/workflows/devel_images.yml
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ jobs:
packages: write
contents: read
steps:
- uses: actions/checkout@v2
- uses: actions/checkout@v3

- name: Get python version from Makefile
run: echo py_version=`make PYTHON_VERSION` >> $GITHUB_ENV
Expand All @@ -28,7 +28,7 @@ jobs:
OWNER: '${{ github.repository_owner }}'

- name: Install python ${{ env.py_version }}
uses: actions/setup-python@v2
uses: actions/setup-python@v4
with:
python-version: ${{ env.py_version }}

Expand Down
8 changes: 4 additions & 4 deletions .github/workflows/stage.yml
Original file line number Diff line number Diff line change
Expand Up @@ -41,26 +41,26 @@ jobs:
exit 0
- name: Checkout ascender
uses: actions/checkout@v2
uses: actions/checkout@v3
with:
path: ascender

- name: Get python version from Makefile
run: echo py_version=`make PYTHON_VERSION` >> $GITHUB_ENV

- name: Install python ${{ env.py_version }}
uses: actions/setup-python@v2
uses: actions/setup-python@v4
with:
python-version: ${{ env.py_version }}

# - name: Checkout awx-logos
# uses: actions/checkout@v2
# uses: actions/checkout@v3
# with:
# repository: ansible/awx-logos
# path: awx-logos

# - name: Checkout awx-operator
# uses: actions/checkout@v2
# uses: actions/checkout@v3
# with:
# repository: ansible/awx-operator
# path: awx-operator
Expand Down
4 changes: 4 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -165,3 +165,7 @@ use_dev_supervisor.txt

awx/ui_next/src
awx/ui_next/build

# Docs build stuff
docs/docsite/build/
_readthedocs/
2 changes: 1 addition & 1 deletion Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -383,7 +383,7 @@ test_collection_sanity:
cd $(COLLECTION_INSTALL) && ansible-test sanity $(COLLECTION_SANITY_ARGS)

test_collection_integration: install_collection
cd $(COLLECTION_INSTALL) && ansible-test integration $(COLLECTION_TEST_TARGET)
cd $(COLLECTION_INSTALL) && ansible-test integration -vvv $(COLLECTION_TEST_TARGET)

test_unit:
@if [ "$(VENV_BASE)" ]; then \
Expand Down
5 changes: 5 additions & 0 deletions NOTICE.txt
Original file line number Diff line number Diff line change
Expand Up @@ -13,3 +13,8 @@ Data Files modified by Ctrl IQ, Inc
/ui/package-lock.json
/ui/package.json
/ui/public/static/media/default.strings.json

Templates modified by Ctrl IQ, Inc
/awx/templates/error.html
/awx/templates/rest_framework/api.html
/awx/ui/public/index.html
2 changes: 1 addition & 1 deletion awx/api/serializers.py
Original file line number Diff line number Diff line change
Expand Up @@ -3233,7 +3233,7 @@ def get_field_from_model_or_attrs(fd):
if get_field_from_model_or_attrs('host_config_key') and not inventory:
raise serializers.ValidationError({'host_config_key': _("Cannot enable provisioning callback without an inventory set.")})

prompting_error_message = _("Must either set a default value or ask to prompt on launch.")
prompting_error_message = _("You must either set a default value or ask to prompt on launch.")
if project is None:
raise serializers.ValidationError({'project': _("Job Templates must have a project assigned.")})
elif inventory is None and not get_field_from_model_or_attrs('ask_inventory_on_launch'):
Expand Down
11 changes: 10 additions & 1 deletion awx/main/credential_plugins/conjur.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,8 @@

from django.utils.translation import gettext_lazy as _
import requests
import base64
import binascii


conjur_inputs = {
Expand Down Expand Up @@ -50,6 +52,13 @@
}


def _is_base64(s: str) -> bool:
try:
return base64.b64encode(base64.b64decode(s.encode("utf-8"))) == s.encode("utf-8")
except binascii.Error:
return False


def conjur_backend(**kwargs):
url = kwargs['url']
api_key = kwargs['api_key']
Expand Down Expand Up @@ -77,7 +86,7 @@ def conjur_backend(**kwargs):
token = resp.content.decode('utf-8')

lookup_kwargs = {
'headers': {'Authorization': 'Token token="{}"'.format(token)},
'headers': {'Authorization': 'Token token="{}"'.format(token if _is_base64(token) else base64.b64encode(token.encode('utf-8')).decode('utf-8'))},
'allow_redirects': False,
}

Expand Down
24 changes: 12 additions & 12 deletions awx/main/management/commands/cleanup_host_metrics.py
Original file line number Diff line number Diff line change
@@ -1,22 +1,22 @@
from awx.main.models import HostMetric
from django.core.management.base import BaseCommand
from django.conf import settings
from awx.main.tasks.host_metrics import HostMetricTask


class Command(BaseCommand):
"""
Run soft-deleting of HostMetrics
This command provides cleanup task for HostMetric model.
There are two modes, which run in following order:
- soft cleanup
- - Perform soft-deletion of all host metrics last automated 12 months ago or before.
This is the same as issuing a DELETE request to /api/v2/host_metrics/N/ for all host metrics that match the criteria.
- - updates columns delete, deleted_counter and last_deleted
- hard cleanup
- - Permanently erase from the database all host metrics last automated 36 months ago or before.
This operation happens after the soft deletion has finished.
"""

help = 'Run soft-deleting of HostMetrics'

def add_arguments(self, parser):
parser.add_argument('--months-ago', type=int, dest='months-ago', action='store', help='Threshold in months for soft-deleting')
help = 'Run soft and hard-deletion of HostMetrics'

def handle(self, *args, **options):
months_ago = options.get('months-ago') or None

if not months_ago:
months_ago = getattr(settings, 'CLEANUP_HOST_METRICS_SOFT_THRESHOLD', 12)

HostMetric.cleanup_task(months_ago)
HostMetricTask().cleanup(soft_threshold=settings.CLEANUP_HOST_METRICS_SOFT_THRESHOLD, hard_threshold=settings.CLEANUP_HOST_METRICS_HARD_THRESHOLD)
18 changes: 13 additions & 5 deletions awx/main/models/ha.py
Original file line number Diff line number Diff line change
Expand Up @@ -289,7 +289,10 @@ def mark_offline(self, update_last_seen=False, perform_save=True, errors=''):
if update_last_seen:
update_fields += ['last_seen']
if perform_save:
self.save(update_fields=update_fields)
from awx.main.signals import disable_activity_stream

with disable_activity_stream():
self.save(update_fields=update_fields)
return update_fields

def set_capacity_value(self):
Expand All @@ -309,8 +312,8 @@ def refresh_capacity_fields(self):
self.cpu_capacity = 0
self.mem_capacity = 0 # formula has a non-zero offset, so we make sure it is 0 for hop nodes
else:
self.cpu_capacity = get_cpu_effective_capacity(self.cpu)
self.mem_capacity = get_mem_effective_capacity(self.memory)
self.cpu_capacity = get_cpu_effective_capacity(self.cpu, is_control_node=bool(self.node_type in (Instance.Types.CONTROL, Instance.Types.HYBRID)))
self.mem_capacity = get_mem_effective_capacity(self.memory, is_control_node=bool(self.node_type in (Instance.Types.CONTROL, Instance.Types.HYBRID)))
self.set_capacity_value()

def save_health_data(self, version=None, cpu=0, memory=0, uuid=None, update_last_seen=False, errors=''):
Expand All @@ -333,12 +336,17 @@ def save_health_data(self, version=None, cpu=0, memory=0, uuid=None, update_last
self.version = version
update_fields.append('version')

new_cpu = get_corrected_cpu(cpu)
if self.node_type == Instance.Types.EXECUTION:
new_cpu = cpu
new_memory = memory
else:
new_cpu = get_corrected_cpu(cpu)
new_memory = get_corrected_memory(memory)

if new_cpu != self.cpu:
self.cpu = new_cpu
update_fields.append('cpu')

new_memory = get_corrected_memory(memory)
if new_memory != self.memory:
self.memory = new_memory
update_fields.append('memory')
Expand Down
18 changes: 0 additions & 18 deletions awx/main/models/inventory.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,6 @@
import os.path
from urllib.parse import urljoin

import dateutil.relativedelta
import yaml

# Django
Expand Down Expand Up @@ -893,23 +892,6 @@ def soft_restore(self):
self.deleted = False
self.save(update_fields=['deleted'])

@classmethod
def cleanup_task(cls, months_ago):
try:
months_ago = int(months_ago)
if months_ago <= 0:
raise ValueError()

last_automation_before = now() - dateutil.relativedelta.relativedelta(months=months_ago)

logger.info(f'cleanup_host_metrics: soft-deleting records last automated before {last_automation_before}')
HostMetric.active_objects.filter(last_automation__lt=last_automation_before).update(
deleted=True, deleted_counter=models.F('deleted_counter') + 1, last_deleted=now()
)
settings.CLEANUP_HOST_METRICS_LAST_TS = now()
except (TypeError, ValueError):
logger.error(f"cleanup_host_metrics: months_ago({months_ago}) has to be a positive integer value")


class HostMetricSummaryMonthly(models.Model):
"""
Expand Down
7 changes: 4 additions & 3 deletions awx/main/tasks/callback.py
Original file line number Diff line number Diff line change
Expand Up @@ -208,9 +208,10 @@ def status_handler(self, status_data, runner_config):
# We opened a connection just for that save, close it here now
connections.close_all()
elif status_data['status'] == 'error':
result_traceback = status_data.get('result_traceback', None)
if result_traceback:
self.delay_update(result_traceback=result_traceback)
for field_name in ('result_traceback', 'job_explanation'):
field_value = status_data.get(field_name, None)
if field_value:
self.delay_update(**{field_name: field_value})

def artifacts_handler(self, artifact_dir):
self.artifacts_processed = True
Expand Down
10 changes: 10 additions & 0 deletions awx/main/tasks/helpers.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
from django.utils.timezone import now
from rest_framework.fields import DateTimeField


def is_run_threshold_reached(setting, threshold_seconds):
last_time = DateTimeField().to_internal_value(setting) if setting else None
if not last_time:
return True
else:
return (now() - last_time).total_seconds() > threshold_seconds
75 changes: 66 additions & 9 deletions awx/main/tasks/host_metrics.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,33 +3,90 @@
import logging

from django.conf import settings
from django.db.models import Count
from django.db.models import Count, F
from django.db.models.functions import TruncMonth
from django.utils.timezone import now
from rest_framework.fields import DateTimeField
from awx.main.dispatch import get_task_queuename
from awx.main.dispatch.publish import task
from awx.main.models.inventory import HostMetric, HostMetricSummaryMonthly
from awx.main.tasks.helpers import is_run_threshold_reached
from awx.conf.license import get_license

logger = logging.getLogger('awx.main.tasks.host_metric_summary_monthly')
logger = logging.getLogger('awx.main.tasks.host_metrics')


@task(queue=get_task_queuename)
def cleanup_host_metrics():
if is_run_threshold_reached(getattr(settings, 'CLEANUP_HOST_METRICS_LAST_TS', None), getattr(settings, 'CLEANUP_HOST_METRICS_INTERVAL', 30) * 86400):
logger.info(f"Executing cleanup_host_metrics, last ran at {getattr(settings, 'CLEANUP_HOST_METRICS_LAST_TS', '---')}")
HostMetricTask().cleanup(
soft_threshold=getattr(settings, 'CLEANUP_HOST_METRICS_SOFT_THRESHOLD', 12),
hard_threshold=getattr(settings, 'CLEANUP_HOST_METRICS_HARD_THRESHOLD', 36),
)
logger.info("Finished cleanup_host_metrics")


@task(queue=get_task_queuename)
def host_metric_summary_monthly():
"""Run cleanup host metrics summary monthly task each week"""
if _is_run_threshold_reached(
getattr(settings, 'HOST_METRIC_SUMMARY_TASK_LAST_TS', None), getattr(settings, 'HOST_METRIC_SUMMARY_TASK_INTERVAL', 7) * 86400
):
if is_run_threshold_reached(getattr(settings, 'HOST_METRIC_SUMMARY_TASK_LAST_TS', None), getattr(settings, 'HOST_METRIC_SUMMARY_TASK_INTERVAL', 7) * 86400):
logger.info(f"Executing host_metric_summary_monthly, last ran at {getattr(settings, 'HOST_METRIC_SUMMARY_TASK_LAST_TS', '---')}")
HostMetricSummaryMonthlyTask().execute()
logger.info("Finished host_metric_summary_monthly")


def _is_run_threshold_reached(setting, threshold_seconds):
last_time = DateTimeField().to_internal_value(setting) if setting else DateTimeField().to_internal_value('1970-01-01')
class HostMetricTask:
"""
This class provides cleanup task for HostMetric model.
There are two modes:
- soft cleanup (updates columns delete, deleted_counter and last_deleted)
- hard cleanup (deletes from the db)
"""

def cleanup(self, soft_threshold=None, hard_threshold=None):
"""
Main entrypoint, runs either soft cleanup, hard cleanup or both
:param soft_threshold: (int)
:param hard_threshold: (int)
"""
if hard_threshold is not None:
self.hard_cleanup(hard_threshold)
if soft_threshold is not None:
self.soft_cleanup(soft_threshold)

settings.CLEANUP_HOST_METRICS_LAST_TS = now()

@staticmethod
def soft_cleanup(threshold=None):
if threshold is None:
threshold = getattr(settings, 'CLEANUP_HOST_METRICS_SOFT_THRESHOLD', 12)

try:
threshold = int(threshold)
except (ValueError, TypeError) as e:
raise type(e)("soft_threshold has to be convertible to number") from e

last_automation_before = now() - relativedelta(months=threshold)
rows = HostMetric.active_objects.filter(last_automation__lt=last_automation_before).update(
deleted=True, deleted_counter=F('deleted_counter') + 1, last_deleted=now()
)
logger.info(f'cleanup_host_metrics: soft-deleted records last automated before {last_automation_before}, affected rows: {rows}')

@staticmethod
def hard_cleanup(threshold=None):
if threshold is None:
threshold = getattr(settings, 'CLEANUP_HOST_METRICS_HARD_THRESHOLD', 36)

try:
threshold = int(threshold)
except (ValueError, TypeError) as e:
raise type(e)("hard_threshold has to be convertible to number") from e

return (now() - last_time).total_seconds() > threshold_seconds
last_deleted_before = now() - relativedelta(months=threshold)
queryset = HostMetric.objects.filter(deleted=True, last_deleted__lt=last_deleted_before)
rows = queryset.delete()
logger.info(f'cleanup_host_metrics: hard-deleted records which were soft deleted before {last_deleted_before}, affected rows: {rows[0]}')


class HostMetricSummaryMonthlyTask:
Expand Down
6 changes: 3 additions & 3 deletions awx/main/tasks/receptor.py
Original file line number Diff line number Diff line change
Expand Up @@ -432,16 +432,16 @@ def _run_internal(self, receptor_ctl):
# massive, only ask for last 1000 bytes
startpos = max(stdout_size - 1000, 0)
resultsock, resultfile = receptor_ctl.get_work_results(self.unit_id, startpos=startpos, return_socket=True, return_sockfile=True)
resultsock.setblocking(False) # this makes resultfile reads non blocking
lines = resultfile.readlines()
receptor_output = b"".join(lines).decode()
if receptor_output:
self.task.runner_callback.delay_update(result_traceback=receptor_output)
self.task.runner_callback.delay_update(result_traceback=f'Worker output:\n{receptor_output}')
elif detail:
self.task.runner_callback.delay_update(result_traceback=detail)
self.task.runner_callback.delay_update(result_traceback=f'Receptor detail:\n{detail}')
else:
logger.warning(f'No result details or output from {self.task.instance.log_format}, status:\n{state_name}')
except Exception:
logger.exception(f'Work results error from job id={self.task.instance.id} work_unit={self.task.instance.work_unit_id}')
raise RuntimeError(detail)

return res
Expand Down
Loading

0 comments on commit 2082947

Please sign in to comment.