diff --git a/modoboa/core/commands/templates/settings.py.tpl b/modoboa/core/commands/templates/settings.py.tpl index 03f4a7ce2..d9455bcbf 100644 --- a/modoboa/core/commands/templates/settings.py.tpl +++ b/modoboa/core/commands/templates/settings.py.tpl @@ -230,6 +230,8 @@ MODOBOA_API_URL = 'https://api.modoboa.org/1/' DISABLE_DASHBOARD_EXTERNAL_QUERIES = False +PID_FILE_STORAGE_PATH = '/var/run' + # REDIS REDIS_HOST = 'localhost' diff --git a/modoboa/maillog/management/commands/logparser.py b/modoboa/maillog/management/commands/logparser.py index db9e9cc70..4a4380ff9 100644 --- a/modoboa/maillog/management/commands/logparser.py +++ b/modoboa/maillog/management/commands/logparser.py @@ -27,6 +27,7 @@ import time import rrdtool +from django.conf import settings from django.core.management.base import BaseCommand from django.db.models import Q from django.utils import timezone @@ -619,7 +620,27 @@ class Command(BaseCommand): "--debug", default=False, action="store_true", help="Set debug mode") + def can_start(self): + """Check if another process is already running or not.""" + pidfile = f"{settings.PID_FILE_STORAGE_PATH}/modoboa_logparser.pid" + if os.path.exists(pidfile): + with open(pidfile) as fp: + pid = fp.read() + pid = int(pid.strip()) + try: + os.kill(pid, 0) + except OSError: + pass + else: + return False + with open(pidfile, "w") as fp: + fp.write(f"{os.getpid()}\n") + return True + def handle(self, *args, **options): + if not self.can_start(): + print("Another process is already running, cannot start") + sys.exit(2) if options["logfile"] is None: options["logfile"] = param_tools.get_global_parameter( "logfile", app="maillog") diff --git a/modoboa/maillog/tests/test_views.py b/modoboa/maillog/tests/test_views.py index 5b636e12d..1610bb724 100644 --- a/modoboa/maillog/tests/test_views.py +++ b/modoboa/maillog/tests/test_views.py @@ -5,6 +5,7 @@ import os import shutil import tempfile +from django.conf import settings from django.core.management import call_command from django.urls import reverse from django.test import override_settings @@ -24,6 +25,9 @@ class RunCommandsMixin(object): def tearDown(self): shutil.rmtree(self.workdir) + pid_file = f"{settings.PID_FILE_STORAGE_PATH}/modoboa_logparser.pid" + if os.path.exists(pid_file): + os.remove(pid_file) def run_logparser(self): """Run logparser command.""" @@ -166,3 +170,10 @@ class ManagementCommandsTestCase(RunCommandsMixin, ModoTestCase): self.assertTrue(os.path.exists(path)) self.run_update_statistics(rebuild=True) self.assertTrue(os.path.exists(path)) + + def test_locking(self): + with open(f"{settings.PID_FILE_STORAGE_PATH}/modoboa_logparser.pid", "w") as fp: + fp.write(f"{os.getpid()}\n") + with self.assertRaises(SystemExit) as inst: + self.run_logparser() + self.assertEqual(inst.exception.code, 2) diff --git a/test_project/test_project/settings.py b/test_project/test_project/settings.py index da414290e..3cbab366e 100644 --- a/test_project/test_project/settings.py +++ b/test_project/test_project/settings.py @@ -212,6 +212,8 @@ SPECTACULAR_SETTINGS = { MODOBOA_API_URL = 'https://api.modoboa.org/1/' +PID_FILE_STORAGE_PATH = '/tmp' + # REDIS REDIS_HOST = 'localhost'