diff --git a/passbook/admin/templates/administration/outpost_service_connection/list.html b/passbook/admin/templates/administration/outpost_service_connection/list.html index fdaf45c20..8b7d64798 100644 --- a/passbook/admin/templates/administration/outpost_service_connection/list.html +++ b/passbook/admin/templates/administration/outpost_service_connection/list.html @@ -50,6 +50,7 @@ {% trans 'Name' %} {% trans 'Type' %} {% trans 'Local?' %} + {% trans 'Status' %} @@ -69,6 +70,15 @@ {{ sc.local|yesno:"Yes,No" }} + + + {% if sc.state.healthy %} + {{ sc.state.version }} + {% else %} + {% trans 'Unhealthy' %} + {% endif %} + + {% trans 'Edit' %} {% trans 'Delete' %} diff --git a/passbook/outposts/forms.py b/passbook/outposts/forms.py index a0de27461..81dd7f31a 100644 --- a/passbook/outposts/forms.py +++ b/passbook/outposts/forms.py @@ -48,6 +48,11 @@ class DockerServiceConnectionForm(forms.ModelForm): fields = ["name", "local", "url", "tls"] widgets = { "name": forms.TextInput, + "url": forms.TextInput, + } + labels = { + "url": _("URL"), + "tls": _("TLS"), } diff --git a/passbook/outposts/models.py b/passbook/outposts/models.py index 26fe22456..b1675bff8 100644 --- a/passbook/outposts/models.py +++ b/passbook/outposts/models.py @@ -25,6 +25,7 @@ from kubernetes.config.incluster_config import load_incluster_config from kubernetes.config.kube_config import load_kube_config, load_kube_config_from_dict from model_utils.managers import InheritanceManager from packaging.version import LegacyVersion, Version, parse +from urllib3.exceptions import HTTPError from passbook import __version__ from passbook.core.models import Provider, Token, TokenIntents, User @@ -115,9 +116,9 @@ class OutpostServiceConnection(models.Model): """Get state of service connection""" state_key = f"outpost_service_connection_{self.pk.hex}" state = cache.get(state_key, None) - if state: + if not state: state = self._get_state() - cache.set(state_key, state) + cache.set(state_key, state, timeout=0) return state def _get_state(self) -> OutpostServiceConnectionState: @@ -209,7 +210,7 @@ class KubernetesServiceConnection(OutpostServiceConnection): return OutpostServiceConnectionState( version=version.git_version, healthy=True ) - except OpenApiException: + except (OpenApiException, HTTPError): return OutpostServiceConnectionState(version="", healthy=False) def client(self) -> ApiClient: diff --git a/passbook/outposts/settings.py b/passbook/outposts/settings.py index 0a7d803a6..9f4a08d56 100644 --- a/passbook/outposts/settings.py +++ b/passbook/outposts/settings.py @@ -7,4 +7,9 @@ CELERY_BEAT_SCHEDULE = { "schedule": crontab(minute="*/5"), "options": {"queue": "passbook_scheduled"}, }, + "outposts_service_connection_check": { + "task": "passbook.outposts.tasks.outpost_service_connection_monitor", + "schedule": crontab(minute=0, hour="*"), + "options": {"queue": "passbook_scheduled"}, + }, } diff --git a/passbook/outposts/tasks.py b/passbook/outposts/tasks.py index 8a368d626..dca2a748f 100644 --- a/passbook/outposts/tasks.py +++ b/passbook/outposts/tasks.py @@ -3,6 +3,7 @@ from typing import Any from asgiref.sync import async_to_sync from channels.layers import get_channel_layer +from django.core.cache import cache from django.db.models.base import Model from django.utils.text import slugify from structlog import get_logger @@ -15,6 +16,7 @@ from passbook.outposts.models import ( KubernetesServiceConnection, Outpost, OutpostModel, + OutpostServiceConnection, OutpostState, OutpostType, ) @@ -32,6 +34,25 @@ def outpost_controller_all(): outpost_controller.delay(outpost.pk.hex) +@CELERY_APP.task() +def outpost_service_connection_state(state_pk: Any): + """Update cached state of a service connection""" + connection: OutpostServiceConnection = ( + OutpostServiceConnection.objects.filter(pk=state_pk).select_subclasses().first() + ) + cache.delete(f"outpost_service_connection_{connection.pk.hex}") + _ = connection.state + + +@CELERY_APP.task(bind=True, base=MonitoredTask) +def outpost_service_connection_monitor(self: MonitoredTask): + """Regularly check the state of Outpost Service Connections""" + for connection in OutpostServiceConnection.objects.select_subclasses(): + cache.delete(f"outpost_service_connection_{connection.pk.hex}") + _ = connection.state + self.set_status(TaskResult(TaskResultStatus.SUCCESSFUL)) + + @CELERY_APP.task(bind=True, base=MonitoredTask) def outpost_controller(self: MonitoredTask, outpost_pk: str): """Create/update/monitor the deployment of an Outpost""" @@ -92,6 +113,10 @@ def outpost_post_save(model_class: str, model_pk: Any): outpost_send_update(instance) return + if isinstance(instance, OutpostServiceConnection): + LOGGER.debug("triggering ServiceConnection state update", instance=instance) + outpost_service_connection_state.delay(instance.pk) + for field in instance._meta.get_fields(): # Each field is checked if it has a `related_model` attribute (when ForeginKeys or M2Ms) # are used, and if it has a value