From 4a8c706c82a8d8cefa4fee81eeb39b6da9e32965 Mon Sep 17 00:00:00 2001 From: Nils Hamerlinck Date: Sun, 18 Oct 2020 16:02:42 +0700 Subject: [PATCH 1/3] [ADD] check that queue_job_notify trigger exists --- queue_job/jobrunner/runner.py | 22 ++++++++++++++++++---- 1 file changed, 18 insertions(+), 4 deletions(-) diff --git a/queue_job/jobrunner/runner.py b/queue_job/jobrunner/runner.py index 11d5df15d1..db19485d32 100644 --- a/queue_job/jobrunner/runner.py +++ b/queue_job/jobrunner/runner.py @@ -287,12 +287,28 @@ def _has_queue_job(self): cr.execute("SELECT 1 FROM pg_tables WHERE tablename=%s", ('ir_module_module',)) if not cr.fetchone(): + _logger.debug("%s doesn't seem to be an odoo db", self.db_name) return False cr.execute( "SELECT 1 FROM ir_module_module WHERE name=%s AND state=%s", ('queue_job', 'installed') ) - return cr.fetchone() + if not cr.fetchone(): + _logger.debug("queue_job is not installed for db %s", self.db_name) + return False + cr.execute( + """SELECT COUNT(1) + FROM information_schema.triggers + WHERE event_object_table = %s + AND trigger_name = %s""", + ("queue_job", "queue_job_notify"), + ) + if cr.fetchone()[0] != 3: # INSERT, DELETE, UPDATE + _logger.error( + "queue_job_notify trigger is missing in db %s", self.db_name + ) + return False + return True def _initialize(self): with closing(self.conn.cursor()) as cr: @@ -392,9 +408,7 @@ def close_databases(self, remove_jobs=True): def initialize_databases(self): for db_name in self.get_db_names(): db = Database(db_name) - if not db.has_queue_job: - _logger.debug('queue_job is not installed for db %s', db_name) - else: + if db.has_queue_job: self.db_by_name[db_name] = db with db.select_jobs('state in %s', (NOT_DONE,)) as cr: for job_data in cr: From b6a5869f9845768eb2683e10ead7121774040fa6 Mon Sep 17 00:00:00 2001 From: Nils Hamerlinck Date: Thu, 22 Oct 2020 00:03:40 +0700 Subject: [PATCH 2/3] [ADD] queue_job: innocuous test job for debugging/monitoring purposes --- queue_job/controllers/main.py | 33 +++++++++++++++++++++++++++++++++ queue_job/models/queue_job.py | 6 +++++- 2 files changed, 38 insertions(+), 1 deletion(-) diff --git a/queue_job/controllers/main.py b/queue_job/controllers/main.py index 68aba17ab8..fdea986d5d 100644 --- a/queue_job/controllers/main.py +++ b/queue_job/controllers/main.py @@ -7,6 +7,7 @@ from io import StringIO from psycopg2 import OperationalError +from werkzeug.exceptions import Forbidden import odoo from odoo import _, http, tools @@ -127,3 +128,35 @@ def retry_postpone(job, message, seconds=None): raise return "" + + @http.route("/queue_job/create_test_job", type="http", auth="user") + def create_test_job( + self, priority=None, max_retries=None, channel="root", description="Test job" + ): + if not http.request.env.user.has_group("base.group_erp_manager"): + raise Forbidden(_("Access Denied")) + + if priority is not None: + try: + priority = int(priority) + except ValueError: + priority = None + + if max_retries is not None: + try: + max_retries = int(max_retries) + except ValueError: + max_retries = None + + delayed = ( + http.request.env["queue.job"] + .with_delay( + priority=priority, + max_retries=max_retries, + channel=channel, + description=description, + ) + ._test_job() + ) + + return delayed.db_record().uuid diff --git a/queue_job/models/queue_job.py b/queue_job/models/queue_job.py index 696ed9da56..469ea847bc 100644 --- a/queue_job/models/queue_job.py +++ b/queue_job/models/queue_job.py @@ -12,8 +12,8 @@ # * make everybody happy : from odoo.addons.base_sparse_field.models.fields import Serialized -from ..job import STATES, DONE, PENDING, Job from ..fields import JobSerialized +from ..job import DONE, PENDING, STATES, Job, job _logger = logging.getLogger(__name__) @@ -333,6 +333,10 @@ def related_action_open_record(self): }) return action + @job + def _test_job(self): + _logger.info("Running test job.") + class RequeueJob(models.TransientModel): _name = 'queue.requeue.job' From 72f4436e7e9a6615325e8b8c5616c74244c173fa Mon Sep 17 00:00:00 2001 From: Nils Hamerlinck Date: Sun, 3 Jan 2021 19:31:16 +0700 Subject: [PATCH 3/3] [FIX] run doctests as part of standard tests --- queue_job/tests/common.py | 38 ++++++++++++++++++++++++- queue_job/tests/test_runner_channels.py | 6 ++-- queue_job/tests/test_runner_runner.py | 6 ++-- 3 files changed, 41 insertions(+), 9 deletions(-) diff --git a/queue_job/tests/common.py b/queue_job/tests/common.py index 867f647fae..e91a6e3f15 100644 --- a/queue_job/tests/common.py +++ b/queue_job/tests/common.py @@ -1,8 +1,12 @@ # Copyright 2019 Camptocamp # License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl). -import mock +import doctest from contextlib import contextmanager +import mock +from odoo.tests import BaseCase, tagged + +# pylint: disable=odoo-addons-relative-import from odoo.addons.queue_job.job import Job @@ -97,3 +101,35 @@ def test_export(self): delayable = mock.MagicMock(name='DelayableBinding') delayable_cls.return_value = delayable yield delayable_cls, delayable + + +@tagged("doctest") +class OdooDocTestCase(BaseCase): + """ + We need a custom DocTestCase class in order to: + - define test_tags to run as part of standard tests + - output a more meaningful test name than default "DocTestCase.runTest" + """ + + def __init__(self, test): + self.__test = test + self.__name = test._dt_test.name + super().__init__(self.__name) + + def __getattr__(self, item): + if item == self.__name: + return self.__test + + +def load_doctests(module): + """ + Generates a tests loading method for the doctests of the given module + https://docs.python.org/3/library/unittest.html#load-tests-protocol + """ + + def load_tests(loader, tests, ignore): + for test in doctest.DocTestSuite(module): + tests.addTest(OdooDocTestCase(test)) + return tests + + return load_tests diff --git a/queue_job/tests/test_runner_channels.py b/queue_job/tests/test_runner_channels.py index 54e7223c92..d323d00683 100644 --- a/queue_job/tests/test_runner_channels.py +++ b/queue_job/tests/test_runner_channels.py @@ -1,12 +1,10 @@ # Copyright 2015-2016 Camptocamp SA # License LGPL-3.0 or later (http://www.gnu.org/licenses/lgpl.html) -import doctest # pylint: disable=odoo-addons-relative-import # we are testing, we want to test as we were an external consumer of the API from odoo.addons.queue_job.jobrunner import channels +from .common import load_doctests -def load_tests(loader, tests, ignore): - tests.addTests(doctest.DocTestSuite(channels)) - return tests +load_tests = load_doctests(channels) diff --git a/queue_job/tests/test_runner_runner.py b/queue_job/tests/test_runner_runner.py index 5f5ef3c56d..c6486e27ef 100644 --- a/queue_job/tests/test_runner_runner.py +++ b/queue_job/tests/test_runner_runner.py @@ -1,12 +1,10 @@ # Copyright 2015-2016 Camptocamp SA # License LGPL-3.0 or later (http://www.gnu.org/licenses/lgpl.html) -import doctest # pylint: disable=odoo-addons-relative-import # we are testing, we want to test as we were an external consumer of the API from odoo.addons.queue_job.jobrunner import runner +from .common import load_doctests -def load_tests(loader, tests, ignore): - tests.addTests(doctest.DocTestSuite(runner)) - return tests +load_tests = load_doctests(runner)