mirror of
https://github.com/jwinterm/modoboa.git
synced 2026-10-17 05:41:25 +02:00
Use RQ instead of cron - mailbox ops
This commit is contained in:
@@ -0,0 +1,105 @@
|
|||||||
|
"""Script to register RQ jobs."""
|
||||||
|
|
||||||
|
|
||||||
|
def rename_mailbox(op_id):
|
||||||
|
"""Rename the mailbox folder through a RQ Job."""
|
||||||
|
import logging
|
||||||
|
import os
|
||||||
|
|
||||||
|
from modoboa.admin.app_settings import load_admin_settings
|
||||||
|
from modoboa.admin.models.mailbox import MailboxOperation
|
||||||
|
from modoboa.lib.sysutils import exec_cmd
|
||||||
|
from modoboa.parameters import tools as param_tools
|
||||||
|
|
||||||
|
logger = logging.getLogger("modoboa.admin")
|
||||||
|
|
||||||
|
load_admin_settings()
|
||||||
|
if not param_tools.get_global_parameter("handle_mailboxes"):
|
||||||
|
return
|
||||||
|
try:
|
||||||
|
operation = MailboxOperation.objects.get(pk=op_id)
|
||||||
|
except MailboxOperation.DoesNotExist:
|
||||||
|
logger.error(f"Failed to retrieve operation id {op_id} for renaming")
|
||||||
|
return
|
||||||
|
if not os.path.exists(operation.argument):
|
||||||
|
logger.error(f"Failed to rename {operation.argument}, folder not found")
|
||||||
|
operation.delete()
|
||||||
|
return
|
||||||
|
new_mail_home = operation.mailbox.mail_home
|
||||||
|
dirname = os.path.dirname(new_mail_home)
|
||||||
|
if not os.path.exists(dirname):
|
||||||
|
try:
|
||||||
|
os.makedirs(dirname)
|
||||||
|
except OSError as e:
|
||||||
|
reason = str(e).decode("utf-8")
|
||||||
|
logger.critical(
|
||||||
|
f"renaming of {operation.argument} to {new_mail_home} failed (reason: {reason})"
|
||||||
|
)
|
||||||
|
return
|
||||||
|
code, output = exec_cmd(f"mv {operation.argument} {new_mail_home}")
|
||||||
|
if code:
|
||||||
|
logger.critical(f"Renaming of {new_mail_home} failed (reason: {output})")
|
||||||
|
return
|
||||||
|
operation.delete()
|
||||||
|
|
||||||
|
|
||||||
|
def delete_mailbox(op_id):
|
||||||
|
"""Delete the mailbox folder through a RQ Job."""
|
||||||
|
import logging
|
||||||
|
import os
|
||||||
|
import shutil
|
||||||
|
|
||||||
|
from modoboa.admin.app_settings import load_admin_settings
|
||||||
|
from modoboa.admin.models.mailbox import MailboxOperation
|
||||||
|
from modoboa.parameters import tools as param_tools
|
||||||
|
|
||||||
|
logger = logging.getLogger("modoboa.admin")
|
||||||
|
|
||||||
|
load_admin_settings()
|
||||||
|
if not param_tools.get_global_parameter("handle_mailboxes"):
|
||||||
|
return
|
||||||
|
|
||||||
|
try:
|
||||||
|
operation = MailboxOperation.objects.get(pk=op_id)
|
||||||
|
except MailboxOperation.DoesNotExist:
|
||||||
|
logger.error(f"Failed to retrieve operation id {op_id} for deletion")
|
||||||
|
return
|
||||||
|
|
||||||
|
if not os.path.exists(operation.argument):
|
||||||
|
logger.error(f"Failed to delete {operation.argument}, folder not found")
|
||||||
|
operation.delete()
|
||||||
|
return
|
||||||
|
|
||||||
|
def onerror(function, path, excinfo):
|
||||||
|
import logging
|
||||||
|
|
||||||
|
"""Handle errors."""
|
||||||
|
logger = logging.getLogger("modoboa.admin")
|
||||||
|
logger.critical(f"delete failed (reason: {excinfo})")
|
||||||
|
operation.delete()
|
||||||
|
|
||||||
|
shutil.rmtree(operation.argument, False, onerror)
|
||||||
|
operation.delete()
|
||||||
|
|
||||||
|
|
||||||
|
def handle_remaining_operation():
|
||||||
|
import logging
|
||||||
|
|
||||||
|
import django_rq
|
||||||
|
|
||||||
|
from modoboa.parameters import tools as param_tools
|
||||||
|
from modoboa.admin.app_settings import load_admin_settings
|
||||||
|
from modoboa.admin.models.mailbox import MailboxOperation
|
||||||
|
|
||||||
|
load_admin_settings()
|
||||||
|
if not param_tools.get_global_parameter("handle_mailboxes"):
|
||||||
|
return
|
||||||
|
queue = django_rq.get_queue("dovecot")
|
||||||
|
if queue.started_job_registry.count > 0 or queue.scheduled_job_registry.count > 0:
|
||||||
|
return
|
||||||
|
logger = logging.getLogger("modoboa.admin")
|
||||||
|
for ope in MailboxOperation.objects.all():
|
||||||
|
if ope.type == "rename":
|
||||||
|
rename_mailbox(ope.pk)
|
||||||
|
elif ope.type == "delete":
|
||||||
|
delete_mailbox(ope.pk)
|
||||||
@@ -1,103 +0,0 @@
|
|||||||
"""A management command to apply mailbox operations."""
|
|
||||||
|
|
||||||
import logging
|
|
||||||
import os
|
|
||||||
import shutil
|
|
||||||
|
|
||||||
from django.core.management.base import BaseCommand
|
|
||||||
|
|
||||||
from modoboa.lib.exceptions import InternalError
|
|
||||||
from modoboa.lib.sysutils import exec_cmd
|
|
||||||
from modoboa.parameters import tools as param_tools
|
|
||||||
from ...app_settings import load_admin_settings
|
|
||||||
from ...models import MailboxOperation
|
|
||||||
|
|
||||||
|
|
||||||
class OperationError(Exception):
|
|
||||||
"""Custom exception."""
|
|
||||||
|
|
||||||
pass
|
|
||||||
|
|
||||||
|
|
||||||
class Command(BaseCommand):
|
|
||||||
"""Command definition"""
|
|
||||||
|
|
||||||
help = "Handles rename and delete operations on mailboxes" # NOQA:A003
|
|
||||||
|
|
||||||
def __init__(self, *args, **kwargs):
|
|
||||||
super().__init__(*args, **kwargs)
|
|
||||||
self.logger = logging.getLogger("modoboa.admin")
|
|
||||||
|
|
||||||
def add_arguments(self, parser):
|
|
||||||
"""Add extra arguments to command line."""
|
|
||||||
parser.add_argument(
|
|
||||||
"--pidfile",
|
|
||||||
default="/tmp/handle_mailbox_operations.pid",
|
|
||||||
help="Path to the file that will contain the PID of this process",
|
|
||||||
)
|
|
||||||
|
|
||||||
def rename_mailbox(self, operation):
|
|
||||||
if not os.path.exists(operation.argument):
|
|
||||||
return
|
|
||||||
new_mail_home = operation.mailbox.mail_home
|
|
||||||
dirname = os.path.dirname(new_mail_home)
|
|
||||||
if not os.path.exists(dirname):
|
|
||||||
try:
|
|
||||||
os.makedirs(dirname)
|
|
||||||
except OSError as e:
|
|
||||||
raise OperationError(str(e)) from None
|
|
||||||
code, output = exec_cmd(f"mv {operation.argument} {new_mail_home}")
|
|
||||||
if code:
|
|
||||||
raise OperationError(output)
|
|
||||||
|
|
||||||
def delete_mailbox(self, operation):
|
|
||||||
"""Try to delete a mailbox tree on filesystem."""
|
|
||||||
if not os.path.exists(operation.argument):
|
|
||||||
return
|
|
||||||
|
|
||||||
def onerror(function, path, excinfo):
|
|
||||||
"""Handle errors."""
|
|
||||||
self.logger.critical(f"delete failed (reason: {excinfo})")
|
|
||||||
|
|
||||||
shutil.rmtree(operation.argument, False, onerror)
|
|
||||||
|
|
||||||
def check_pidfile(self, path):
|
|
||||||
"""Check if this command is already running
|
|
||||||
|
|
||||||
:param str path: path to the file containing the PID
|
|
||||||
:return: a boolean, True means we can go further
|
|
||||||
"""
|
|
||||||
if os.path.exists(path):
|
|
||||||
with open(path) as fp:
|
|
||||||
pid = fp.read().strip()
|
|
||||||
code, output = exec_cmd(
|
|
||||||
f"grep handle_mailbox_operations /proc/{pid}/cmdline"
|
|
||||||
)
|
|
||||||
if not code:
|
|
||||||
return False
|
|
||||||
with open(path, "w") as fp:
|
|
||||||
print(os.getpid(), file=fp)
|
|
||||||
return True
|
|
||||||
|
|
||||||
def handle(self, *args, **options):
|
|
||||||
"""Command entry point."""
|
|
||||||
load_admin_settings()
|
|
||||||
if not param_tools.get_global_parameter("handle_mailboxes"):
|
|
||||||
return
|
|
||||||
if not self.check_pidfile(options["pidfile"]):
|
|
||||||
return
|
|
||||||
for ope in MailboxOperation.objects.all():
|
|
||||||
try:
|
|
||||||
f = getattr(self, f"{ope.type}_mailbox")
|
|
||||||
except AttributeError:
|
|
||||||
continue
|
|
||||||
try:
|
|
||||||
f(ope)
|
|
||||||
except (OperationError, InternalError) as e:
|
|
||||||
self.logger.critical(
|
|
||||||
"%s failed (reason: %s)", ope, str(e).decode("utf-8")
|
|
||||||
)
|
|
||||||
else:
|
|
||||||
self.logger.info("%s succeed", ope)
|
|
||||||
ope.delete()
|
|
||||||
os.unlink(options["pidfile"])
|
|
||||||
@@ -9,12 +9,15 @@ from django.db.models.manager import Manager
|
|||||||
from django.utils.encoding import smart_str, force_str
|
from django.utils.encoding import smart_str, force_str
|
||||||
from django.utils.translation import gettext as _, gettext_lazy
|
from django.utils.translation import gettext as _, gettext_lazy
|
||||||
|
|
||||||
|
from modoboa.admin import jobs
|
||||||
from modoboa.core.models import User
|
from modoboa.core.models import User
|
||||||
from modoboa.lib import exceptions as lib_exceptions
|
from modoboa.lib import exceptions as lib_exceptions
|
||||||
from modoboa.lib.email_utils import split_mailbox
|
from modoboa.lib.email_utils import split_mailbox
|
||||||
from modoboa.lib.sysutils import doveadm_cmd
|
from modoboa.lib.sysutils import doveadm_cmd
|
||||||
from modoboa.parameters import tools as param_tools
|
from modoboa.parameters import tools as param_tools
|
||||||
|
|
||||||
|
import django_rq
|
||||||
|
|
||||||
from .base import AdminObject
|
from .base import AdminObject
|
||||||
from .domain import Domain
|
from .domain import Domain
|
||||||
from . import mixins
|
from . import mixins
|
||||||
@@ -192,9 +195,12 @@ class Mailbox(mixins.MessageLimitMixin, AdminObject):
|
|||||||
hm = param_tools.get_global_parameter("handle_mailboxes", raise_exception=False)
|
hm = param_tools.get_global_parameter("handle_mailboxes", raise_exception=False)
|
||||||
if not hm:
|
if not hm:
|
||||||
return
|
return
|
||||||
MailboxOperation.objects.create(
|
mail_op = MailboxOperation.objects.create(
|
||||||
mailbox=self, type="rename", argument=old_mail_home
|
mailbox=self, type="rename", argument=old_mail_home
|
||||||
)
|
)
|
||||||
|
mail_op.save()
|
||||||
|
dovecot_queue = django_rq.get_queue("dovecot")
|
||||||
|
dovecot_queue.enqueue(jobs.rename_mailbox, mail_op.pk)
|
||||||
|
|
||||||
def rename(self, address, domain):
|
def rename(self, address, domain):
|
||||||
"""Rename the mailbox.
|
"""Rename the mailbox.
|
||||||
@@ -223,7 +229,12 @@ class Mailbox(mixins.MessageLimitMixin, AdminObject):
|
|||||||
hm = param_tools.get_global_parameter("handle_mailboxes", raise_exception=False)
|
hm = param_tools.get_global_parameter("handle_mailboxes", raise_exception=False)
|
||||||
if not hm:
|
if not hm:
|
||||||
return
|
return
|
||||||
MailboxOperation.objects.create(type="delete", argument=self.mail_home)
|
mail_op = MailboxOperation.objects.create(
|
||||||
|
type="delete", argument=self.mail_home
|
||||||
|
)
|
||||||
|
mail_op.save()
|
||||||
|
dovecot_queue = django_rq.get_queue("dovecot")
|
||||||
|
dovecot_queue.enqueue(jobs.delete_mailbox, mail_op.pk)
|
||||||
|
|
||||||
def set_quota(self, value=None, override_rules=False):
|
def set_quota(self, value=None, override_rules=False):
|
||||||
"""Set or update quota value for this mailbox.
|
"""Set or update quota value for this mailbox.
|
||||||
|
|||||||
@@ -303,6 +303,9 @@ RQ_QUEUES = {
|
|||||||
"modoboa": {
|
"modoboa": {
|
||||||
"URL": REDIS_URL,
|
"URL": REDIS_URL,
|
||||||
},
|
},
|
||||||
|
"dovecot": {
|
||||||
|
"URL": REDIS_URL,
|
||||||
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
# CACHE
|
# CACHE
|
||||||
|
|||||||
Reference in New Issue
Block a user