Skip to content

Commit

Permalink
Merge branch 'devel' into fix_schedules_instance_groups
Browse files Browse the repository at this point in the history
  • Loading branch information
ivarmu authored Jul 28, 2023
2 parents 9c90915 + 7838641 commit 4a74999
Show file tree
Hide file tree
Showing 76 changed files with 1,431 additions and 493 deletions.
4 changes: 2 additions & 2 deletions .github/pr_labeler.yml
Original file line number Diff line number Diff line change
Expand Up @@ -15,5 +15,5 @@

"dependencies":
- any: ["awx/ui/package.json"]
- any: ["awx/requirements/*.txt"]
- any: ["awx/requirements/requirements.in"]
- any: ["requirements/*.txt"]
- any: ["requirements/requirements.in"]
7 changes: 5 additions & 2 deletions .github/workflows/devel_images.yml
Original file line number Diff line number Diff line change
Expand Up @@ -48,8 +48,11 @@ jobs:
DEV_DOCKER_TAG_BASE=ghcr.io/${OWNER_LC} COMPOSE_TAG=${GITHUB_REF##*/} make awx-kube-dev-build
DEV_DOCKER_TAG_BASE=ghcr.io/${OWNER_LC} COMPOSE_TAG=${GITHUB_REF##*/} make awx-kube-build
- name: Push image
- name: Push development images
run: |
docker push ghcr.io/${OWNER_LC}/awx_devel:${GITHUB_REF##*/}
docker push ghcr.io/${OWNER_LC}/awx_kube_devel:${GITHUB_REF##*/}
docker push ghcr.io/${OWNER_LC}/awx:${GITHUB_REF##*/}
- name: Push AWX k8s image, only for upstream and feature branches
run: docker push ghcr.io/${OWNER_LC}/awx:${GITHUB_REF##*/}
if: endsWith(github.repository, '/awx')
1 change: 1 addition & 0 deletions Makefile
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
-include awx/ui_next/Makefile

PYTHON := $(notdir $(shell for i in python3.9 python3; do command -v $$i; done|sed 1q))
SHELL := bash
DOCKER_COMPOSE ?= docker-compose
OFFICIAL ?= no
NODE ?= node
Expand Down
3 changes: 2 additions & 1 deletion awx/api/generics.py
Original file line number Diff line number Diff line change
Expand Up @@ -232,7 +232,8 @@ def finalize_response(self, request, response, *args, **kwargs):

response = super(APIView, self).finalize_response(request, response, *args, **kwargs)
time_started = getattr(self, 'time_started', None)
response['X-API-Product-Version'] = get_awx_version()
if request.user.is_authenticated:
response['X-API-Product-Version'] = get_awx_version()
response['X-API-Product-Name'] = server_product_name()

response['X-API-Node'] = settings.CLUSTER_HOST_ID
Expand Down
5 changes: 2 additions & 3 deletions awx/api/urls/urls.py
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@
OAuth2TokenList,
ApplicationOAuth2TokenList,
OAuth2ApplicationDetail,
# HostMetricSummaryMonthlyList, # It will be enabled in future version of the AWX
HostMetricSummaryMonthlyList,
)

from awx.api.views.bulk import (
Expand Down Expand Up @@ -123,8 +123,7 @@
re_path(r'^constructed_inventories/', include(constructed_inventory_urls)),
re_path(r'^hosts/', include(host_urls)),
re_path(r'^host_metrics/', include(host_metric_urls)),
# It will be enabled in future version of the AWX
# re_path(r'^host_metric_summary_monthly/$', HostMetricSummaryMonthlyList.as_view(), name='host_metric_summary_monthly_list'),
re_path(r'^host_metric_summary_monthly/$', HostMetricSummaryMonthlyList.as_view(), name='host_metric_summary_monthly_list'),
re_path(r'^groups/', include(group_urls)),
re_path(r'^inventory_sources/', include(inventory_source_urls)),
re_path(r'^inventory_updates/', include(inventory_update_urls)),
Expand Down
19 changes: 9 additions & 10 deletions awx/api/views/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -1564,16 +1564,15 @@ def delete(self, request, *args, **kwargs):
return Response(status=status.HTTP_204_NO_CONTENT)


# It will be enabled in future version of the AWX
# class HostMetricSummaryMonthlyList(ListAPIView):
# name = _("Host Metrics Summary Monthly")
# model = models.HostMetricSummaryMonthly
# serializer_class = serializers.HostMetricSummaryMonthlySerializer
# permission_classes = (IsSystemAdminOrAuditor,)
# search_fields = ('date',)
#
# def get_queryset(self):
# return self.model.objects.all()
class HostMetricSummaryMonthlyList(ListAPIView):
name = _("Host Metrics Summary Monthly")
model = models.HostMetricSummaryMonthly
serializer_class = serializers.HostMetricSummaryMonthlySerializer
permission_classes = (IsSystemAdminOrAuditor,)
search_fields = ('date',)

def get_queryset(self):
return self.model.objects.all()


class HostList(HostRelatedSearchMixin, ListCreateAPIView):
Expand Down
3 changes: 1 addition & 2 deletions awx/api/views/root.py
Original file line number Diff line number Diff line change
Expand Up @@ -107,8 +107,7 @@ def get(self, request, format=None):
data['groups'] = reverse('api:group_list', request=request)
data['hosts'] = reverse('api:host_list', request=request)
data['host_metrics'] = reverse('api:host_metric_list', request=request)
# It will be enabled in future version of the AWX
# data['host_metric_summary_monthly'] = reverse('api:host_metric_summary_monthly_list', request=request)
data['host_metric_summary_monthly'] = reverse('api:host_metric_summary_monthly_list', request=request)
data['job_templates'] = reverse('api:job_template_list', request=request)
data['jobs'] = reverse('api:job_list', request=request)
data['ad_hoc_commands'] = reverse('api:ad_hoc_command_list', request=request)
Expand Down
87 changes: 87 additions & 0 deletions awx/main/cache.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,87 @@
import functools

from django.conf import settings
from django.core.cache.backends.base import DEFAULT_TIMEOUT
from django.core.cache.backends.redis import RedisCache

from redis.exceptions import ConnectionError, ResponseError, TimeoutError
import socket

# This list comes from what django-redis ignores and the behavior we are trying
# to retain while dropping the dependency on django-redis.
IGNORED_EXCEPTIONS = (TimeoutError, ResponseError, ConnectionError, socket.timeout)

CONNECTION_INTERRUPTED_SENTINEL = object()


def optionally_ignore_exceptions(func=None, return_value=None):
if func is None:
return functools.partial(optionally_ignore_exceptions, return_value=return_value)

@functools.wraps(func)
def wrapper(*args, **kwargs):
try:
return func(*args, **kwargs)
except IGNORED_EXCEPTIONS as e:
if settings.DJANGO_REDIS_IGNORE_EXCEPTIONS:
return return_value
raise e.__cause__ or e

return wrapper


class AWXRedisCache(RedisCache):
"""
We just want to wrap the upstream RedisCache class so that we can ignore
the exceptions that it raises when the cache is unavailable.
"""

@optionally_ignore_exceptions
def add(self, key, value, timeout=DEFAULT_TIMEOUT, version=None):
return super().add(key, value, timeout, version)

@optionally_ignore_exceptions(return_value=CONNECTION_INTERRUPTED_SENTINEL)
def _get(self, key, default=None, version=None):
return super().get(key, default, version)

def get(self, key, default=None, version=None):
value = self._get(key, default, version)
if value is CONNECTION_INTERRUPTED_SENTINEL:
return default
return value

@optionally_ignore_exceptions
def set(self, key, value, timeout=DEFAULT_TIMEOUT, version=None):
return super().set(key, value, timeout, version)

@optionally_ignore_exceptions
def touch(self, key, timeout=DEFAULT_TIMEOUT, version=None):
return super().touch(key, timeout, version)

@optionally_ignore_exceptions
def delete(self, key, version=None):
return super().delete(key, version)

@optionally_ignore_exceptions
def get_many(self, keys, version=None):
return super().get_many(keys, version)

@optionally_ignore_exceptions
def has_key(self, key, version=None):
return super().has_key(key, version)

@optionally_ignore_exceptions
def incr(self, key, delta=1, version=None):
return super().incr(key, delta, version)

@optionally_ignore_exceptions
def set_many(self, data, timeout=DEFAULT_TIMEOUT, version=None):
return super().set_many(data, timeout, version)

@optionally_ignore_exceptions
def delete_many(self, keys, version=None):
return super().delete_many(keys, version)

@optionally_ignore_exceptions
def clear(self):
return super().clear()
9 changes: 9 additions & 0 deletions awx/main/conf.py
Original file line number Diff line number Diff line change
Expand Up @@ -862,6 +862,15 @@
category_slug='system',
)

register(
'HOST_METRIC_SUMMARY_TASK_LAST_TS',
field_class=fields.DateTimeField,
label=_('Last computing date of HostMetricSummaryMonthly'),
allow_null=True,
category=_('System'),
category_slug='system',
)

register(
'AWX_CLEANUP_PATHS',
field_class=fields.BooleanField,
Expand Down
19 changes: 18 additions & 1 deletion awx/main/dispatch/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,23 @@ def notify(self, channel, payload):
with self.conn.cursor() as cur:
cur.execute('SELECT pg_notify(%s, %s);', (channel, payload))

@staticmethod
def current_notifies(conn):
"""
Altered version of .notifies method from psycopg library
This removes the outer while True loop so that we only process
queued notifications
"""
with conn.lock:
try:
ns = conn.wait(psycopg.generators.notifies(conn.pgconn))
except psycopg.errors._NO_TRACEBACK as ex:
raise ex.with_traceback(None)
enc = psycopg._encodings.pgconn_encoding(conn.pgconn)
for pgn in ns:
n = psycopg.connection.Notify(pgn.relname.decode(enc), pgn.extra.decode(enc), pgn.be_pid)
yield n

def events(self, select_timeout=5, yield_timeouts=False):
if not self.conn.autocommit:
raise RuntimeError('Listening for events can only be done in autocommit mode')
Expand All @@ -64,7 +81,7 @@ def events(self, select_timeout=5, yield_timeouts=False):
if yield_timeouts:
yield None
else:
notification_generator = self.conn.notifies()
notification_generator = self.current_notifies(self.conn)
for notification in notification_generator:
yield notification

Expand Down
8 changes: 4 additions & 4 deletions awx/main/dispatch/pool.py
Original file line number Diff line number Diff line change
Expand Up @@ -417,16 +417,16 @@ def cleanup(self):
# the task manager to never do more work
current_task = w.current_task
if current_task and isinstance(current_task, dict):
endings = ['tasks.task_manager', 'tasks.dependency_manager', 'tasks.workflow_manager']
endings = ('tasks.task_manager', 'tasks.dependency_manager', 'tasks.workflow_manager')
current_task_name = current_task.get('task', '')
if any(current_task_name.endswith(e) for e in endings):
if current_task_name.endswith(endings):
if 'started' not in current_task:
w.managed_tasks[current_task['uuid']]['started'] = time.time()
age = time.time() - current_task['started']
w.managed_tasks[current_task['uuid']]['age'] = age
if age > self.task_manager_timeout:
logger.error(f'{current_task_name} has held the advisory lock for {age}, sending SIGTERM to {w.pid}')
os.kill(w.pid, signal.SIGTERM)
logger.error(f'{current_task_name} has held the advisory lock for {age}, sending SIGUSR1 to {w.pid}')
os.kill(w.pid, signal.SIGUSR1)

for m in orphaned:
# if all the workers are dead, spawn at least one
Expand Down
16 changes: 9 additions & 7 deletions awx/main/dispatch/worker/base.py
Original file line number Diff line number Diff line change
Expand Up @@ -121,10 +121,9 @@ def record_statistics(self):
if time.time() - self.last_stats > 1: # buffer stat recording to once per second
try:
self.redis.set(f'awx_{self.name}_statistics', self.pool.debug())
self.last_stats = time.time()
except Exception:
logger.exception(f"encountered an error communicating with redis to store {self.name} statistics")
self.last_stats = time.time()
self.last_stats = time.time()

def run(self, *args, **kwargs):
signal.signal(signal.SIGINT, self.stop)
Expand Down Expand Up @@ -175,9 +174,12 @@ def run_periodic_tasks(self):

# record subsystem metrics for the dispatcher
if current_time - self.last_metrics_gather > 20:
self.pool.produce_subsystem_metrics(self.subsystem_metrics)
self.subsystem_metrics.set('dispatcher_availability', self.listen_cumulative_time / (current_time - self.last_metrics_gather))
self.subsystem_metrics.pipe_execute()
try:
self.pool.produce_subsystem_metrics(self.subsystem_metrics)
self.subsystem_metrics.set('dispatcher_availability', self.listen_cumulative_time / (current_time - self.last_metrics_gather))
self.subsystem_metrics.pipe_execute()
except Exception:
logger.exception(f"encountered an error trying to store {self.name} metrics")
self.listen_cumulative_time = 0.0
self.last_metrics_gather = current_time

Expand Down Expand Up @@ -250,8 +252,8 @@ def work_loop(self, queue, finished, idx, *args):
break
except QueueEmpty:
continue
except Exception as e:
logger.error("Exception on worker {}, restarting: ".format(idx) + str(e))
except Exception:
logger.exception("Exception on worker {}, reconnecting: ".format(idx))
continue
try:
for conn in db.connections.all():
Expand Down
2 changes: 1 addition & 1 deletion awx/main/management/commands/cleanup_host_metrics.py
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,6 @@ def handle(self, *args, **options):
months_ago = options.get('months-ago') or None

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

HostMetric.cleanup_task(months_ago)
9 changes: 9 additions & 0 deletions awx/main/management/commands/host_metric_summary_monthly.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
from django.core.management.base import BaseCommand
from awx.main.tasks.host_metrics import HostMetricSummaryMonthlyTask


class Command(BaseCommand):
help = 'Computing of HostMetricSummaryMonthly'

def handle(self, *args, **options):
HostMetricSummaryMonthlyTask().execute()
6 changes: 3 additions & 3 deletions awx/main/models/inventory.py
Original file line number Diff line number Diff line change
Expand Up @@ -899,18 +899,18 @@ def cleanup_task(cls, months_ago):

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

logger.info(f'Cleanup [HostMetric]: soft-deleting records last automated before {last_automation_before}')
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 [HostMetric]: months_ago({months_ago}) has to be a positive integer value")
logger.error(f"cleanup_host_metrics: months_ago({months_ago}) has to be a positive integer value")


class HostMetricSummaryMonthly(models.Model):
"""
HostMetric summaries computed by scheduled task <TODO> monthly
HostMetric summaries computed by scheduled task 'awx.main.tasks.system.host_metric_summary_monthly' monthly
"""

date = models.DateField(unique=True)
Expand Down
Loading

0 comments on commit 4a74999

Please sign in to comment.