04f876af7b
* build(deps): bump docker from 4.3.1 to 4.4.0 Bumps [docker](https://github.com/docker/docker-py) from 4.3.1 to 4.4.0. - [Release notes](https://github.com/docker/docker-py/releases) - [Commits](https://github.com/docker/docker-py/compare/4.3.1...4.4.0) Signed-off-by: dependabot[bot] <support@github.com> * outposts: fix both network_mode and ports being set during tests Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> Co-authored-by: Jens L <jens@beryju.org> Co-authored-by: Jens Langhammer <jens.langhammer@beryju.org>
166 lines
6.6 KiB
Python
166 lines
6.6 KiB
Python
"""outpost tasks"""
|
|
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
|
|
|
|
from passbook.lib.tasks import MonitoredTask, TaskResult, TaskResultStatus
|
|
from passbook.lib.utils.reflection import path_to_class
|
|
from passbook.outposts.controllers.base import ControllerException
|
|
from passbook.outposts.models import (
|
|
DockerServiceConnection,
|
|
KubernetesServiceConnection,
|
|
Outpost,
|
|
OutpostModel,
|
|
OutpostServiceConnection,
|
|
OutpostState,
|
|
OutpostType,
|
|
)
|
|
from passbook.providers.proxy.controllers.docker import ProxyDockerController
|
|
from passbook.providers.proxy.controllers.kubernetes import ProxyKubernetesController
|
|
from passbook.root.celery import CELERY_APP
|
|
|
|
LOGGER = get_logger()
|
|
|
|
|
|
@CELERY_APP.task()
|
|
def outpost_controller_all():
|
|
"""Launch Controller for all Outposts which support it"""
|
|
for outpost in Outpost.objects.exclude(service_connection=None):
|
|
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"""
|
|
logs = []
|
|
outpost: Outpost = Outpost.objects.get(pk=outpost_pk)
|
|
self.set_uid(slugify(outpost.name))
|
|
try:
|
|
if outpost.type == OutpostType.PROXY:
|
|
service_connection = outpost.service_connection
|
|
if isinstance(service_connection, DockerServiceConnection):
|
|
logs = ProxyDockerController(outpost, service_connection).up_with_logs()
|
|
if isinstance(service_connection, KubernetesServiceConnection):
|
|
logs = ProxyKubernetesController(
|
|
outpost, service_connection
|
|
).up_with_logs()
|
|
LOGGER.debug("---------------Outpost Controller logs starting----------------")
|
|
for log in logs:
|
|
LOGGER.debug(log)
|
|
LOGGER.debug("-----------------Outpost Controller logs end-------------------")
|
|
except ControllerException as exc:
|
|
self.set_status(TaskResult(TaskResultStatus.ERROR).with_error(exc))
|
|
else:
|
|
self.set_status(TaskResult(TaskResultStatus.SUCCESSFUL, logs))
|
|
|
|
|
|
@CELERY_APP.task()
|
|
def outpost_pre_delete(outpost_pk: str):
|
|
"""Delete outpost objects before deleting the DB Object"""
|
|
outpost = Outpost.objects.get(pk=outpost_pk)
|
|
if outpost.type == OutpostType.PROXY:
|
|
service_connection = outpost.service_connection
|
|
if isinstance(service_connection, DockerServiceConnection):
|
|
ProxyDockerController(outpost, service_connection).down()
|
|
if isinstance(service_connection, KubernetesServiceConnection):
|
|
ProxyKubernetesController(outpost, service_connection).down()
|
|
|
|
|
|
@CELERY_APP.task()
|
|
def outpost_post_save(model_class: str, model_pk: Any):
|
|
"""If an Outpost is saved, Ensure that token is created/updated
|
|
|
|
If an OutpostModel, or a model that is somehow connected to an OutpostModel is saved,
|
|
we send a message down the relevant OutpostModels WS connection to trigger an update"""
|
|
model: Model = path_to_class(model_class)
|
|
try:
|
|
instance = model.objects.get(pk=model_pk)
|
|
except model.DoesNotExist:
|
|
LOGGER.warning("Model does not exist", model=model, pk=model_pk)
|
|
return
|
|
|
|
if isinstance(instance, Outpost):
|
|
LOGGER.debug("Ensuring token for outpost", instance=instance)
|
|
_ = instance.token
|
|
LOGGER.debug("Trigger reconcile for outpost")
|
|
outpost_controller.delay(instance.pk)
|
|
return
|
|
|
|
if isinstance(instance, (OutpostModel, Outpost)):
|
|
LOGGER.debug(
|
|
"triggering outpost update from outpostmodel/outpost", instance=instance
|
|
)
|
|
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
|
|
if not hasattr(field, "related_model"):
|
|
continue
|
|
if not field.related_model:
|
|
continue
|
|
if not issubclass(field.related_model, OutpostModel):
|
|
continue
|
|
|
|
field_name = f"{field.name}_set"
|
|
if not hasattr(instance, field_name):
|
|
continue
|
|
|
|
LOGGER.debug("triggering outpost update from from field", field=field.name)
|
|
# Because the Outpost Model has an M2M to Provider,
|
|
# we have to iterate over the entire QS
|
|
for reverse in getattr(instance, field_name).all():
|
|
outpost_send_update(reverse)
|
|
|
|
|
|
def outpost_send_update(model_instace: Model):
|
|
"""Send outpost update to all registered outposts, irregardless to which passbook
|
|
instance they are connected"""
|
|
channel_layer = get_channel_layer()
|
|
if isinstance(model_instace, OutpostModel):
|
|
for outpost in model_instace.outpost_set.all():
|
|
_outpost_single_update(outpost, channel_layer)
|
|
elif isinstance(model_instace, Outpost):
|
|
_outpost_single_update(model_instace, channel_layer)
|
|
|
|
|
|
def _outpost_single_update(outpost: Outpost, layer=None):
|
|
"""Update outpost instances connected to a single outpost"""
|
|
# Ensure token again, because this function is called when anything related to an
|
|
# OutpostModel is saved, so we can be sure permissions are right
|
|
_ = outpost.token
|
|
if not layer: # pragma: no cover
|
|
layer = get_channel_layer()
|
|
for state in OutpostState.for_outpost(outpost):
|
|
LOGGER.debug("sending update", channel=state.uid, outpost=outpost)
|
|
async_to_sync(layer.send)(state.uid, {"type": "event.update"})
|