outposts: periodically update state of service connection, show state in UI
This commit is contained in:
parent
7e8e3893eb
commit
5cb7f0794e
|
@ -50,6 +50,7 @@
|
||||||
<th role="columnheader" scope="col">{% trans 'Name' %}</th>
|
<th role="columnheader" scope="col">{% trans 'Name' %}</th>
|
||||||
<th role="columnheader" scope="col">{% trans 'Type' %}</th>
|
<th role="columnheader" scope="col">{% trans 'Type' %}</th>
|
||||||
<th role="columnheader" scope="col">{% trans 'Local?' %}</th>
|
<th role="columnheader" scope="col">{% trans 'Local?' %}</th>
|
||||||
|
<th role="columnheader" scope="col">{% trans 'Status' %}</th>
|
||||||
<th role="cell"></th>
|
<th role="cell"></th>
|
||||||
</tr>
|
</tr>
|
||||||
</thead>
|
</thead>
|
||||||
|
@ -69,6 +70,15 @@
|
||||||
{{ sc.local|yesno:"Yes,No" }}
|
{{ sc.local|yesno:"Yes,No" }}
|
||||||
</span>
|
</span>
|
||||||
</td>
|
</td>
|
||||||
|
<td role="cell">
|
||||||
|
<span>
|
||||||
|
{% if sc.state.healthy %}
|
||||||
|
<i class="fas fa-check pf-m-success"></i> {{ sc.state.version }}
|
||||||
|
{% else %}
|
||||||
|
<i class="fas fa-times pf-m-danger"></i> {% trans 'Unhealthy' %}
|
||||||
|
{% endif %}
|
||||||
|
</span>
|
||||||
|
</td>
|
||||||
<td>
|
<td>
|
||||||
<a class="pf-c-button pf-m-secondary" href="{% url 'passbook_admin:outpost-service-connection-update' pk=sc.pk %}?back={{ request.get_full_path }}">{% trans 'Edit' %}</a>
|
<a class="pf-c-button pf-m-secondary" href="{% url 'passbook_admin:outpost-service-connection-update' pk=sc.pk %}?back={{ request.get_full_path }}">{% trans 'Edit' %}</a>
|
||||||
<a class="pf-c-button pf-m-danger" href="{% url 'passbook_admin:outpost-service-connection-delete' pk=sc.pk %}?back={{ request.get_full_path }}">{% trans 'Delete' %}</a>
|
<a class="pf-c-button pf-m-danger" href="{% url 'passbook_admin:outpost-service-connection-delete' pk=sc.pk %}?back={{ request.get_full_path }}">{% trans 'Delete' %}</a>
|
||||||
|
|
|
@ -48,6 +48,11 @@ class DockerServiceConnectionForm(forms.ModelForm):
|
||||||
fields = ["name", "local", "url", "tls"]
|
fields = ["name", "local", "url", "tls"]
|
||||||
widgets = {
|
widgets = {
|
||||||
"name": forms.TextInput,
|
"name": forms.TextInput,
|
||||||
|
"url": forms.TextInput,
|
||||||
|
}
|
||||||
|
labels = {
|
||||||
|
"url": _("URL"),
|
||||||
|
"tls": _("TLS"),
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -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 kubernetes.config.kube_config import load_kube_config, load_kube_config_from_dict
|
||||||
from model_utils.managers import InheritanceManager
|
from model_utils.managers import InheritanceManager
|
||||||
from packaging.version import LegacyVersion, Version, parse
|
from packaging.version import LegacyVersion, Version, parse
|
||||||
|
from urllib3.exceptions import HTTPError
|
||||||
|
|
||||||
from passbook import __version__
|
from passbook import __version__
|
||||||
from passbook.core.models import Provider, Token, TokenIntents, User
|
from passbook.core.models import Provider, Token, TokenIntents, User
|
||||||
|
@ -115,9 +116,9 @@ class OutpostServiceConnection(models.Model):
|
||||||
"""Get state of service connection"""
|
"""Get state of service connection"""
|
||||||
state_key = f"outpost_service_connection_{self.pk.hex}"
|
state_key = f"outpost_service_connection_{self.pk.hex}"
|
||||||
state = cache.get(state_key, None)
|
state = cache.get(state_key, None)
|
||||||
if state:
|
if not state:
|
||||||
state = self._get_state()
|
state = self._get_state()
|
||||||
cache.set(state_key, state)
|
cache.set(state_key, state, timeout=0)
|
||||||
return state
|
return state
|
||||||
|
|
||||||
def _get_state(self) -> OutpostServiceConnectionState:
|
def _get_state(self) -> OutpostServiceConnectionState:
|
||||||
|
@ -209,7 +210,7 @@ class KubernetesServiceConnection(OutpostServiceConnection):
|
||||||
return OutpostServiceConnectionState(
|
return OutpostServiceConnectionState(
|
||||||
version=version.git_version, healthy=True
|
version=version.git_version, healthy=True
|
||||||
)
|
)
|
||||||
except OpenApiException:
|
except (OpenApiException, HTTPError):
|
||||||
return OutpostServiceConnectionState(version="", healthy=False)
|
return OutpostServiceConnectionState(version="", healthy=False)
|
||||||
|
|
||||||
def client(self) -> ApiClient:
|
def client(self) -> ApiClient:
|
||||||
|
|
|
@ -7,4 +7,9 @@ CELERY_BEAT_SCHEDULE = {
|
||||||
"schedule": crontab(minute="*/5"),
|
"schedule": crontab(minute="*/5"),
|
||||||
"options": {"queue": "passbook_scheduled"},
|
"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"},
|
||||||
|
},
|
||||||
}
|
}
|
||||||
|
|
|
@ -3,6 +3,7 @@ from typing import Any
|
||||||
|
|
||||||
from asgiref.sync import async_to_sync
|
from asgiref.sync import async_to_sync
|
||||||
from channels.layers import get_channel_layer
|
from channels.layers import get_channel_layer
|
||||||
|
from django.core.cache import cache
|
||||||
from django.db.models.base import Model
|
from django.db.models.base import Model
|
||||||
from django.utils.text import slugify
|
from django.utils.text import slugify
|
||||||
from structlog import get_logger
|
from structlog import get_logger
|
||||||
|
@ -15,6 +16,7 @@ from passbook.outposts.models import (
|
||||||
KubernetesServiceConnection,
|
KubernetesServiceConnection,
|
||||||
Outpost,
|
Outpost,
|
||||||
OutpostModel,
|
OutpostModel,
|
||||||
|
OutpostServiceConnection,
|
||||||
OutpostState,
|
OutpostState,
|
||||||
OutpostType,
|
OutpostType,
|
||||||
)
|
)
|
||||||
|
@ -32,6 +34,25 @@ def outpost_controller_all():
|
||||||
outpost_controller.delay(outpost.pk.hex)
|
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)
|
@CELERY_APP.task(bind=True, base=MonitoredTask)
|
||||||
def outpost_controller(self: MonitoredTask, outpost_pk: str):
|
def outpost_controller(self: MonitoredTask, outpost_pk: str):
|
||||||
"""Create/update/monitor the deployment of an Outpost"""
|
"""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)
|
outpost_send_update(instance)
|
||||||
return
|
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():
|
for field in instance._meta.get_fields():
|
||||||
# Each field is checked if it has a `related_model` attribute (when ForeginKeys or M2Ms)
|
# Each field is checked if it has a `related_model` attribute (when ForeginKeys or M2Ms)
|
||||||
# are used, and if it has a value
|
# are used, and if it has a value
|
||||||
|
|
Reference in a new issue