diff --git a/addons/cetmix_tower_server_queue/README.rst b/addons/cetmix_tower_server_queue/README.rst deleted file mode 100644 index 965f6c2..0000000 --- a/addons/cetmix_tower_server_queue/README.rst +++ /dev/null @@ -1,117 +0,0 @@ -========================= -Cetmix Tower Server Queue -========================= - -.. - !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! - !! This file is generated by oca-gen-addon-readme !! - !! changes will be overwritten. !! - !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! - !! source digest: sha256:b40d3d39da3d8e2545c72b63aa3f14bdb1aaafbfbfbbb51e07ba599400427b8d - !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! - -.. |badge1| image:: https://img.shields.io/badge/maturity-Beta-yellow.png - :target: https://odoo-community.org/page/development-status - :alt: Beta -.. |badge2| image:: https://img.shields.io/badge/license-AGPL--3-blue.png - :target: http://www.gnu.org/licenses/agpl-3.0-standalone.html - :alt: License: AGPL-3 -.. |badge3| image:: https://img.shields.io/badge/github-cetmix%2Fcetmix--tower-lightgray.png?logo=github - :target: https://github.com/cetmix/cetmix-tower/tree/16.0/cetmix_tower_server_queue - :alt: cetmix/cetmix-tower - -|badge1| |badge2| |badge3| - -This module implements asynchronous task execution for `Cetmix -Tower `__. - -It requires the `queue_job `__ -module to be installed and configured in the Odoo instance. - -Please refer to the `official -documentation `__ for detailed information. - -**Table of contents** - -.. contents:: - :local: - -Configuration -============= - -Please refer to the `official -documentation `__ for detailed configuration -instructions. - -Usage -===== - -Please refer to the `official -documentation `__ for detailed usage -instructions. - -Changelog -========= - -16.0.1.2.0 (2025-11-12) ------------------------ - -- Features: Use the 'web_notify' module to send user notifications. - (5074) - -16.0.1.1.4 (2025-11-05) ------------------------ - -- Bugfixes: Finish multiple commands at once. (5062) - -16.0.1.1.3 (2025-10-13) ------------------------ - -- Features: Terminate running flight plan manually (3410) - -16.0.1.1.0 (2025-07-16) ------------------------ - -- Features: cetmix_tower_server_queue: Add async file upload/download - via job queue (3720) -- Features: Terminate command with error if job has failed (4718) - -16.0.1.0.2 (2025-05-16) ------------------------ - -- Features: 'sudo' parameter is not passed to command. (4678) - -16.0.1.0.1 (2025-05-09) ------------------------ - -- Bugfixes: Non-critical issues and performance improvements. (4611) - -16.0.1.0.0 ----------- - -Release for Odoo 16.0 - -Bug Tracker -=========== - -Bugs are tracked on `GitHub Issues `_. -In case of trouble, please check there if your issue has already been reported. -If you spotted it first, help us to smash it by providing a detailed and welcomed -`feedback `_. - -Do not contact contributors directly about support or help with technical issues. - -Credits -======= - -Authors -------- - -* Cetmix - -Maintainers ------------ - -This module is part of the `cetmix/cetmix-tower `_ project on GitHub. - -You are welcome to contribute. diff --git a/addons/cetmix_tower_server_queue/__init__.py b/addons/cetmix_tower_server_queue/__init__.py deleted file mode 100644 index 0650744..0000000 --- a/addons/cetmix_tower_server_queue/__init__.py +++ /dev/null @@ -1 +0,0 @@ -from . import models diff --git a/addons/cetmix_tower_server_queue/__manifest__.py b/addons/cetmix_tower_server_queue/__manifest__.py deleted file mode 100644 index 6063343..0000000 --- a/addons/cetmix_tower_server_queue/__manifest__.py +++ /dev/null @@ -1,19 +0,0 @@ -# Copyright (C) 2022 Cetmix OÜ -# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl). -{ - "name": "Cetmix Tower Server Queue", - "summary": "Cetmix Tower asynchronous task execution using 'queue_job'", - "version": "16.0.1.2.2", - "development_status": "Beta", - "category": "Productivity", - "website": "https://tower.cetmix.com", - "author": "Cetmix", - "license": "AGPL-3", - "installable": True, - "auto_install": True, - "depends": ["cetmix_tower_server", "queue_job"], - "data": [ - "views/cx_tower_command_log_view.xml", - "views/cx_tower_file_view.xml", - ], -} diff --git a/addons/cetmix_tower_server_queue/i18n/cetmix_tower_server_queue.pot b/addons/cetmix_tower_server_queue/i18n/cetmix_tower_server_queue.pot deleted file mode 100644 index bda7834..0000000 --- a/addons/cetmix_tower_server_queue/i18n/cetmix_tower_server_queue.pot +++ /dev/null @@ -1,150 +0,0 @@ -# Translation of Odoo Server. -# This file contains the translation of the following modules: -# * cetmix_tower_server_queue -# -msgid "" -msgstr "" -"Project-Id-Version: Odoo Server 16.0\n" -"Report-Msgid-Bugs-To: \n" -"Last-Translator: \n" -"Language-Team: \n" -"MIME-Version: 1.0\n" -"Content-Type: text/plain; charset=UTF-8\n" -"Content-Transfer-Encoding: \n" -"Plural-Forms: \n" - -#. module: cetmix_tower_server_queue -#: model:ir.model.fields,help:cetmix_tower_server_queue.field_cx_tower_command_log__command_status -msgid "" -"0 if command finished successfully.\n" -"-100 general error,\n" -"-101 not found,\n" -"-201 another instance of this command is running,\n" -"-202 no runner found for the command action,\n" -"-203 Python code execution failed\n" -"-205 plan line condition check failed\n" -"503 if SSH connection error occurred\n" -"601 if queue job failed" -msgstr "" - -#. module: cetmix_tower_server_queue -#: model:ir.model,name:cetmix_tower_server_queue.model_cx_tower_command_log -msgid "Cetmix Tower Command Log" -msgstr "" - -#. module: cetmix_tower_server_queue -#: model:ir.model,name:cetmix_tower_server_queue.model_cx_tower_file -msgid "Cetmix Tower File" -msgstr "" - -#. module: cetmix_tower_server_queue -#: model:ir.model,name:cetmix_tower_server_queue.model_cx_tower_server -msgid "Cetmix Tower Server" -msgstr "" - -#. module: cetmix_tower_server_queue -#: model_terms:ir.ui.view,arch_db:cetmix_tower_server_queue.cx_tower_file_view_form -msgid "Error" -msgstr "" - -#. module: cetmix_tower_server_queue -#: model:ir.model.fields,field_description:cetmix_tower_server_queue.field_cx_tower_command_log__command_status -msgid "Exit Code" -msgstr "" - -#. module: cetmix_tower_server_queue -#. odoo-python -#: code:addons/cetmix_tower_server_queue/models/cx_tower_file.py:0 -#: code:addons/cetmix_tower_server_queue/models/cx_tower_file.py:0 -#: code:addons/cetmix_tower_server_queue/models/cx_tower_file.py:0 -#, python-format -msgid "Failure" -msgstr "" - -#. module: cetmix_tower_server_queue -#. odoo-python -#: code:addons/cetmix_tower_server_queue/models/cx_tower_file.py:0 -#, python-format -msgid "File downloaded!" -msgstr "" - -#. module: cetmix_tower_server_queue -#: model:ir.model.fields,help:cetmix_tower_server_queue.field_cx_tower_file__is_being_processed -msgid "File is currently being processed" -msgstr "" - -#. module: cetmix_tower_server_queue -#. odoo-python -#: code:addons/cetmix_tower_server_queue/models/cx_tower_file.py:0 -#, python-format -msgid "File uploaded!" -msgstr "" - -#. module: cetmix_tower_server_queue -#. odoo-python -#: code:addons/cetmix_tower_server_queue/models/cx_tower_file.py:0 -#, python-format -msgid "File(s) %(name)s download failed: %(error)s" -msgstr "" - -#. module: cetmix_tower_server_queue -#. odoo-python -#: code:addons/cetmix_tower_server_queue/models/cx_tower_file.py:0 -#, python-format -msgid "File(s) %(name)s upload failed: %(error)s" -msgstr "" - -#. module: cetmix_tower_server_queue -#. odoo-python -#: code:addons/cetmix_tower_server_queue/models/cx_tower_file.py:0 -#, python-format -msgid "Files downloaded!" -msgstr "" - -#. module: cetmix_tower_server_queue -#. odoo-python -#: code:addons/cetmix_tower_server_queue/models/cx_tower_file.py:0 -#, python-format -msgid "Files uploaded!" -msgstr "" - -#. module: cetmix_tower_server_queue -#: model:ir.model.fields,field_description:cetmix_tower_server_queue.field_cx_tower_file__is_being_processed -msgid "Is Being Processed" -msgstr "" - -#. module: cetmix_tower_server_queue -#: model_terms:ir.ui.view,arch_db:cetmix_tower_server_queue.cx_tower_file_view_form -msgid "Processing" -msgstr "" - -#. module: cetmix_tower_server_queue -#: model:ir.model,name:cetmix_tower_server_queue.model_queue_job -#: model:ir.model.fields,field_description:cetmix_tower_server_queue.field_cx_tower_command_log__queue_job_id -msgid "Queue Job" -msgstr "" - -#. module: cetmix_tower_server_queue -#. odoo-python -#: code:addons/cetmix_tower_server_queue/models/cx_tower_file.py:0 -#: code:addons/cetmix_tower_server_queue/models/cx_tower_file.py:0 -#: model_terms:ir.ui.view,arch_db:cetmix_tower_server_queue.cx_tower_file_view_form -#, python-format -msgid "Success" -msgstr "" - -#. module: cetmix_tower_server_queue -#. odoo-python -#: code:addons/cetmix_tower_server_queue/models/cx_tower_file.py:0 -#, python-format -msgid "The following files are already being processed: %(name)s" -msgstr "" - -#. module: cetmix_tower_server_queue -#. odoo-python -#: code:addons/cetmix_tower_server_queue/models/cx_tower_file.py:0 -#, python-format -msgid "" -"Unable to upload file '%(f)s'.\n" -"Upload operation is not supported for 'server' type files." -msgstr "" diff --git a/addons/cetmix_tower_server_queue/i18n/it.po b/addons/cetmix_tower_server_queue/i18n/it.po deleted file mode 100644 index 72ce79a..0000000 --- a/addons/cetmix_tower_server_queue/i18n/it.po +++ /dev/null @@ -1,148 +0,0 @@ -# Translation of Odoo Server. -# This file contains the translation of the following modules: -# * cetmix_tower_server_queue -# -msgid "" -msgstr "" -"Project-Id-Version: Odoo Server 16.0\n" -"Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: \n" -"PO-Revision-Date: \n" -"Last-Translator: \n" -"Language-Team: \n" -"MIME-Version: 1.0\n" -"Content-Type: text/plain; charset=UTF-8\n" -"Content-Transfer-Encoding: 8bit\n" -"Language: it\n" -"Plural-Forms: nplurals=2; plural=(n != 1);\n" -"X-Generator: Poedit 2.3\n" - -#. module: cetmix_tower_server_queue -#: model:ir.model.fields,help:cetmix_tower_server_queue.field_cx_tower_command_log__command_status -msgid "" -"0 if command finished successfully.\n" -"-100 general error,\n" -"-101 not found,\n" -"-201 another instance of this command is running,\n" -"-202 no runner found for the command action,\n" -"-203 Python code execution failed\n" -"-205 plan line condition check failed\n" -"503 if SSH connection error occurred\n" -"601 if queue job failed" -msgstr "0 se il comando è stato completato correttamente.-100 errore generale,-101 non trovato,-201 un'altra istanza di questo comando è in esecuzione,-202 nessun runner trovato per l'azione del comando,-203 esecuzione del codice Python non riuscita,-205 controllo delle condizioni della riga del piano non riuscito,503 se si è verificato un errore di connessione SSH,601 se il processo in coda non è riuscito." - -#. module: cetmix_tower_server_queue -#: model:ir.model,name:cetmix_tower_server_queue.model_cx_tower_command_log -msgid "Cetmix Tower Command Log" -msgstr "Registro comando Cetmix Tower" - -#. module: cetmix_tower_server_queue -#: model:ir.model,name:cetmix_tower_server_queue.model_cx_tower_file -msgid "Cetmix Tower File" -msgstr "File Cetmix Tower" - -#. module: cetmix_tower_server_queue -#: model:ir.model,name:cetmix_tower_server_queue.model_cx_tower_server -msgid "Cetmix Tower Server" -msgstr "Server Cetmix Tower" - -#. module: cetmix_tower_server_queue -#: model_terms:ir.ui.view,arch_db:cetmix_tower_server_queue.cx_tower_file_view_form -msgid "Error" -msgstr "Errore" - -#. module: cetmix_tower_server_queue -#: model:ir.model.fields,field_description:cetmix_tower_server_queue.field_cx_tower_command_log__command_status -msgid "Exit Code" -msgstr "Codice uscita" - -#. module: cetmix_tower_server_queue -#. odoo-python -#: code:addons/cetmix_tower_server_queue/models/cx_tower_file.py:0 -#, python-format -msgid "Failure" -msgstr "Fallimento" - -#. module: cetmix_tower_server_queue -#. odoo-python -#: code:addons/cetmix_tower_server_queue/models/cx_tower_file.py:0 -#, python-format -msgid "File downloaded!" -msgstr "File scaricato!" - -#. module: cetmix_tower_server_queue -#: model:ir.model.fields,help:cetmix_tower_server_queue.field_cx_tower_file__is_being_processed -msgid "File is currently being processed" -msgstr "Il file è in lavorazione" - -#. module: cetmix_tower_server_queue -#. odoo-python -#: code:addons/cetmix_tower_server_queue/models/cx_tower_file.py:0 -#, python-format -msgid "File uploaded!" -msgstr "File caricato!" - -#. module: cetmix_tower_server_queue -#. odoo-python -#: code:addons/cetmix_tower_server_queue/models/cx_tower_file.py:0 -#, python-format -msgid "Files downloaded!" -msgstr "File scaricati!" - -#. module: cetmix_tower_server_queue -#. odoo-python -#: code:addons/cetmix_tower_server_queue/models/cx_tower_file.py:0 -#, python-format -msgid "Files uploaded!" -msgstr "File caricati!" - -#. module: cetmix_tower_server_queue -#: model:ir.model.fields,field_description:cetmix_tower_server_queue.field_cx_tower_file__is_being_processed -msgid "Is Being Processed" -msgstr "In lavorazione" - -#. module: cetmix_tower_server_queue -#: model_terms:ir.ui.view,arch_db:cetmix_tower_server_queue.cx_tower_file_view_form -msgid "Processing" -msgstr "Lavorazione" - -#. module: cetmix_tower_server_queue -#: model:ir.model,name:cetmix_tower_server_queue.model_queue_job -#: model:ir.model.fields,field_description:cetmix_tower_server_queue.field_cx_tower_command_log__queue_job_id -msgid "Queue Job" -msgstr "Accoda lavoro" - -#. module: cetmix_tower_server_queue -#. odoo-python -#: code:addons/cetmix_tower_server_queue/models/cx_tower_file.py:0 -#: model_terms:ir.ui.view,arch_db:cetmix_tower_server_queue.cx_tower_file_view_form -#, python-format -msgid "Success" -msgstr "Successo" - -#. module: cetmix_tower_server_queue -#. odoo-python -#: code:addons/cetmix_tower_server_queue/models/cx_tower_file.py:0 -#, python-format -msgid "The following files are already being processed: %(name)s" -msgstr "I seguenti file sono già in fase di elaborazione: %(name)s" - -#. module: cetmix_tower_server_queue -#. odoo-python -#: code:addons/cetmix_tower_server_queue/models/cx_tower_file.py:0 -#, python-format -msgid "" -"Unable to upload file '%(f)s'.\n" -"Upload operation is not supported for 'server' type files." -msgstr "" -"Impossibile caricare il file '%(f)s'.\n" -"L'operazione di caricamento non è supportata per i file di tipo 'server'." - -#~ msgid "Display Name" -#~ msgstr "Nome visualizzato" - -#~ msgid "ID" -#~ msgstr "ID" - -#~ msgid "Last Modified on" -#~ msgstr "Ultima modifica il" diff --git a/addons/cetmix_tower_server_queue/models/__init__.py b/addons/cetmix_tower_server_queue/models/__init__.py deleted file mode 100644 index 4e40e4b..0000000 --- a/addons/cetmix_tower_server_queue/models/__init__.py +++ /dev/null @@ -1,4 +0,0 @@ -from . import cx_tower_command_log -from . import cx_tower_server -from . import queue_job -from . import cx_tower_file diff --git a/addons/cetmix_tower_server_queue/models/cx_tower_command_log.py b/addons/cetmix_tower_server_queue/models/cx_tower_command_log.py deleted file mode 100644 index 9e6c450..0000000 --- a/addons/cetmix_tower_server_queue/models/cx_tower_command_log.py +++ /dev/null @@ -1,82 +0,0 @@ -# Copyright (C) 2025 Cetmix OÜ -# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl). - -import logging - -from odoo import fields, models, tools - -from odoo.addons.cetmix_tower_server.models.constants import ( - COMMAND_STOPPED, - COMMAND_TIMED_OUT, -) -from odoo.addons.queue_job.job import CANCELLED - -_logger = logging.getLogger(__name__) - - -class CxTowerCommandLog(models.Model): - _inherit = "cx.tower.command.log" - - queue_job_id = fields.Many2one( - "queue.job", - readonly=True, - groups="queue_job.group_queue_job_manager", - ) - - command_status = fields.Integer( - help="0 if command finished successfully.\n" - "-100 general error,\n" - "-101 not found,\n" - "-201 another instance of this command is running,\n" - "-202 no runner found for the command action,\n" - "-203 Python code execution failed\n" - "-205 plan line condition check failed\n" - "503 if SSH connection error occurred\n" - "601 if queue job failed" - ) - - def finish( - self, finish_date=None, status=None, response=None, error=None, **kwargs - ): - """Finish the command log - - Args: - finish_date (Datetime, optional): Command finish date. Defaults to None. - status (Integer, optional): Command status. Defaults to None. - response (Text, optional): Command response. Defaults to None. - error (Text, optional): Command error. Defaults to None. - """ - - # Filter out command logs that are already stopped - command_logs_to_process = self.filtered( - lambda log: log.command_status != COMMAND_STOPPED - ) - if not command_logs_to_process: - return - - # Avoid finishing the command log multiple times at the same time - try: - with self.env.cr.savepoint(), tools.mute_logger("odoo.sql_db"): - self.env.cr.execute( - f"SELECT command_status FROM {self._table} WHERE id IN %s FOR UPDATE NOWAIT", # noqa: E501 - (tuple(command_logs_to_process.ids),), - ) - except Exception as e: - _logger.error( - "Could not acquire lock on command logs %s, skipping finish: %s", - command_logs_to_process.ids, - e, - ) - return - - # Update the related queue job state if the command timed out - if status == COMMAND_TIMED_OUT: - for command_log in command_logs_to_process: - if command_log.queue_job_id: - command_log.queue_job_id.sudo()._change_job_state( - CANCELLED, result=error - ) - - return super(CxTowerCommandLog, command_logs_to_process).finish( - finish_date, status, response, error, **kwargs - ) diff --git a/addons/cetmix_tower_server_queue/models/cx_tower_file.py b/addons/cetmix_tower_server_queue/models/cx_tower_file.py deleted file mode 100644 index 327ffa1..0000000 --- a/addons/cetmix_tower_server_queue/models/cx_tower_file.py +++ /dev/null @@ -1,184 +0,0 @@ -# Copyright (C) 2025 Cetmix OÜ -# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl). - -import logging - -from odoo import _, fields, models -from odoo.exceptions import UserError - -_logger = logging.getLogger(__name__) - - -class CxTowerFile(models.Model): - _inherit = "cx.tower.file" - - is_being_processed = fields.Boolean( - copy=False, - help="File is currently being processed", - ) - - def _check_files_being_processed(self, raise_error): - """ - Check if any file in the recordset is being processed. - True if at least one file is already processing and raise_error is False. - False if no files are currently being processed. - The caller uses the boolean to decide whether to continue or abort. - """ - processing_files = self.filtered(lambda rec: rec.is_being_processed) - if processing_files: - if raise_error: - raise UserError( - _( - "The following files are already being processed: %(name)s", - name=", ".join(processing_files.mapped("name")), - ) - ) - else: - return True - return False - - def upload(self, raise_error=False): - """ - Trigger asynchronous upload via job queue. - """ - # Check if the file is already being processed - if self._check_files_being_processed(raise_error): - return - - self.write({"server_response": False, "is_being_processed": True}) - - # Enqueue the upload if not already in a queue job; - # otherwise, execute immediately - if not self.env.context.get("job_uuid"): - self.with_delay()._do_upload(raise_error=raise_error) - else: - self._do_upload(raise_error=raise_error) - - def download(self, raise_error=False): - """ - Trigger asynchronous download via job queue. - """ - - # Check if the file is already being processed - if self._check_files_being_processed(raise_error): - return - - self.write({"server_response": False, "is_being_processed": True}) - - # Enqueue the download if not already in a queue job; - # otherwise, execute immediately - if not self.env.context.get("job_uuid"): - self.with_delay()._do_download(raise_error=raise_error) - else: - self._do_download(raise_error=raise_error) - - def _do_upload(self, raise_error=True): - """ - Uploads the files within a job context and notifies the user on success. - Logs the error if an exception occurs; - failure state is managed by the parent method. - """ - try: - with self.env.cr.savepoint(): - result = super().upload(raise_error=raise_error) - single_msg = _("File uploaded!") - plural_msg = _("Files uploaded!") - self.env.user.notify_success( - message=single_msg if len(self) == 1 else plural_msg, - title=_("Success"), - # This notification should not be sticky - # to avoid blocking the user's screen - sticky=False, - ) - return result - except Exception as e: - if not raise_error: - self.env.user.notify_danger( - message=_( - "File(s) %(name)s upload failed: %(error)s", - name=", ".join(self.mapped("name")), - error=str(e), - ), - title=_("Failure"), - sticky=self.env["ir.config_parameter"] - .sudo() - .get_param("cetmix_tower_server.notification_type_error", "sticky") - == "sticky", - ) - _logger.error("File %s upload failed: %s", str(self), str(e)) - else: - raise - finally: - self.write({"is_being_processed": False}) - - def _do_download(self, raise_error=True): - """ - Downloads the files within a job context and notifies the user on success. - Logs the error if an exception occurs; - failure state is managed by the parent method. - """ - try: - with self.env.cr.savepoint(): - result = super().download(raise_error=raise_error) - single_msg = _("File downloaded!") - plural_msg = _("Files downloaded!") - self.env.user.notify_success( - message=single_msg if len(self) == 1 else plural_msg, - title=_("Success"), - # This notification should not be sticky - # to avoid blocking the user's screen - sticky=False, - ) - return result - except Exception as e: - if not raise_error: - self.env.user.notify_danger( - message=_( - "File(s) %(name)s download failed: %(error)s", - name=", ".join(self.mapped("name")), - error=str(e), - ), - title=_("Failure"), - sticky=self.env["ir.config_parameter"] - .sudo() - .get_param("cetmix_tower_server.notification_type_error", "sticky") - == "sticky", - ) - _logger.error("File %s download failed: %s", str(self), str(e)) - else: - raise - finally: - self.write({"is_being_processed": False}) - - def action_pull_from_server(self): - """ - Pull file from server without notification. - """ - tower_files = self.filtered(lambda file_: file_.source == "tower") - server_files = self - tower_files - - tower_files.action_get_current_server_code() - - server_files.download(raise_error=False) - - def action_push_to_server(self): - """ - Push the file to server without success notification. - """ - server_files = self.filtered(lambda file_: file_.source == "server") - if server_files: - return { - "type": "ir.actions.client", - "tag": "display_notification", - "params": { - "title": _("Failure"), - "message": _( - "Unable to upload file '%(f)s'.\n" - "Upload operation is not supported for 'server' type files.", - f=", ".join(server_files.mapped("rendered_name")), - ), - "sticky": False, - }, - } - - self.upload(raise_error=False) diff --git a/addons/cetmix_tower_server_queue/models/cx_tower_server.py b/addons/cetmix_tower_server_queue/models/cx_tower_server.py deleted file mode 100644 index 7e84b25..0000000 --- a/addons/cetmix_tower_server_queue/models/cx_tower_server.py +++ /dev/null @@ -1,77 +0,0 @@ -# Copyright (C) 2022 Cetmix OÜ -# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl). -from odoo import models - - -class CxTowerServer(models.Model): - _inherit = "cx.tower.server" - - def _command_runner_wrapper( - self, - command, - log_record, - rendered_command_code, - sudo=None, - rendered_command_path=None, - ssh_connection=None, - **kwargs, - ): - # If the flight plan log has an entry on the parent flight plan log, - # it means that this flight plan was launched from another plan, - # this plan should be launched as a synchronous command to - # preserve the order of execution of commands with action “Run flight plan”. - # Use runner only if command log record is provided. - if log_record and not log_record.plan_log_id.parent_flight_plan_log_id: - job = self.with_delay()._queue_command_runner_wrapper( - command=command, - log_record=log_record, - rendered_command_code=rendered_command_code, - sudo=sudo, - rendered_command_path=rendered_command_path, - ssh_connection=ssh_connection, - **kwargs, - ) - log_record.sudo().queue_job_id = job.db_record().id - - # Otherwise fallback to `super` to return the command output - else: - return super()._command_runner_wrapper( - command=command, - log_record=log_record, - rendered_command_code=rendered_command_code, - sudo=sudo, - rendered_command_path=rendered_command_path, - ssh_connection=ssh_connection, - **kwargs, - ) - - def _queue_command_runner_wrapper( - self, - command, - log_record, - rendered_command_code, - sudo=None, - rendered_command_path=None, - ssh_connection=None, - **kwargs, - ): - # avoid executing command if plan was stopped - log_record.invalidate_recordset(["plan_log_id"]) - plan_log_id = log_record.plan_log_id - if plan_log_id: - plan_log_id.invalidate_recordset(["is_stopped"]) - - # If plan was stopped, stop the command - if plan_log_id.is_stopped: - log_record.stop() - return - - return self._command_runner( - command=command, - log_record=log_record, - rendered_command_code=rendered_command_code, - sudo=sudo, - rendered_command_path=rendered_command_path, - ssh_connection=ssh_connection, - **kwargs, - ) diff --git a/addons/cetmix_tower_server_queue/models/queue_job.py b/addons/cetmix_tower_server_queue/models/queue_job.py deleted file mode 100644 index 7b66eea..0000000 --- a/addons/cetmix_tower_server_queue/models/queue_job.py +++ /dev/null @@ -1,23 +0,0 @@ -# Copyright 2013-2020 Camptocamp SA -# License LGPL-3.0 or later (http://www.gnu.org/licenses/lgpl.html) -from odoo import models - - -class QueueJob(models.Model): - _inherit = "queue.job" - - QUEUE_JOB_ERROR = 601 - - def write(self, vals): - """ - Override write method to update command status - and write error information in the log record - """ - if vals.get("state") == "failed": - log_record = self.kwargs.get("log_record") - if log_record: - log_record.finish( - status=self.QUEUE_JOB_ERROR, - error=vals.get("exc_info"), - ) - return super().write(vals) diff --git a/addons/cetmix_tower_server_queue/pyproject.toml b/addons/cetmix_tower_server_queue/pyproject.toml deleted file mode 100644 index 4231d0c..0000000 --- a/addons/cetmix_tower_server_queue/pyproject.toml +++ /dev/null @@ -1,3 +0,0 @@ -[build-system] -requires = ["whool"] -build-backend = "whool.buildapi" diff --git a/addons/cetmix_tower_server_queue/readme/CONFIGURE.md b/addons/cetmix_tower_server_queue/readme/CONFIGURE.md deleted file mode 100644 index 8c717e5..0000000 --- a/addons/cetmix_tower_server_queue/readme/CONFIGURE.md +++ /dev/null @@ -1 +0,0 @@ -Please refer to the [official documentation](https://cetmix.com/tower) for detailed configuration instructions. diff --git a/addons/cetmix_tower_server_queue/readme/DESCRIPTION.md b/addons/cetmix_tower_server_queue/readme/DESCRIPTION.md deleted file mode 100644 index 54e6fc3..0000000 --- a/addons/cetmix_tower_server_queue/readme/DESCRIPTION.md +++ /dev/null @@ -1,5 +0,0 @@ -This module implements asynchronous task execution for [Cetmix Tower](https://cetmix.com/tower). - -It requires the [queue_job](https://github.com/OCA/queue/queue_job) module to be installed and configured in the Odoo instance. - -Please refer to the [official documentation](https://cetmix.com/tower) for detailed information. diff --git a/addons/cetmix_tower_server_queue/readme/HISTORY.md b/addons/cetmix_tower_server_queue/readme/HISTORY.md deleted file mode 100644 index 0226295..0000000 --- a/addons/cetmix_tower_server_queue/readme/HISTORY.md +++ /dev/null @@ -1,34 +0,0 @@ -## 16.0.1.2.0 (2025-11-12) - -- Features: Use the 'web_notify' module to send user notifications. (5074) - - -## 16.0.1.1.4 (2025-11-05) - -- Bugfixes: Finish multiple commands at once. (5062) - - -## 16.0.1.1.3 (2025-10-13) - -- Features: Terminate running flight plan manually (3410) - - -## 16.0.1.1.0 (2025-07-16) - -- Features: cetmix_tower_server_queue: Add async file upload/download via job queue (3720) -- Features: Terminate command with error if job has failed (4718) - - -## 16.0.1.0.2 (2025-05-16) - -- Features: 'sudo' parameter is not passed to command. (4678) - - -## 16.0.1.0.1 (2025-05-09) - -- Bugfixes: Non-critical issues and performance improvements. (4611) - - -## 16.0.1.0.0 - -Release for Odoo 16.0 diff --git a/addons/cetmix_tower_server_queue/readme/USAGE.md b/addons/cetmix_tower_server_queue/readme/USAGE.md deleted file mode 100644 index 901f5a6..0000000 --- a/addons/cetmix_tower_server_queue/readme/USAGE.md +++ /dev/null @@ -1 +0,0 @@ -Please refer to the [official documentation](https://cetmix.com/tower) for detailed usage instructions. diff --git a/addons/cetmix_tower_server_queue/readme/newsfragments/.gitkeep b/addons/cetmix_tower_server_queue/readme/newsfragments/.gitkeep deleted file mode 100644 index e69de29..0000000 diff --git a/addons/cetmix_tower_server_queue/static/description/icon.png b/addons/cetmix_tower_server_queue/static/description/icon.png deleted file mode 100644 index 2507f55..0000000 Binary files a/addons/cetmix_tower_server_queue/static/description/icon.png and /dev/null differ diff --git a/addons/cetmix_tower_server_queue/static/description/index.html b/addons/cetmix_tower_server_queue/static/description/index.html deleted file mode 100644 index a6345e9..0000000 --- a/addons/cetmix_tower_server_queue/static/description/index.html +++ /dev/null @@ -1,484 +0,0 @@ - - - - - -Cetmix Tower Server Queue - - - -
-

Cetmix Tower Server Queue

- - -

Beta License: AGPL-3 cetmix/cetmix-tower

-

This module implements asynchronous task execution for Cetmix -Tower.

-

It requires the queue_job -module to be installed and configured in the Odoo instance.

-

Please refer to the official -documentation for detailed information.

-

Table of contents

- -
-

Configuration

-

Please refer to the official -documentation for detailed configuration -instructions.

-
-
-

Usage

-

Please refer to the official -documentation for detailed usage -instructions.

-
-
-

Changelog

-
-

16.0.1.2.0 (2025-11-12)

-
    -
  • Features: Use the ‘web_notify’ module to send user notifications. -(5074)
  • -
-
-
-

16.0.1.1.4 (2025-11-05)

-
    -
  • Bugfixes: Finish multiple commands at once. (5062)
  • -
-
-
-

16.0.1.1.3 (2025-10-13)

-
    -
  • Features: Terminate running flight plan manually (3410)
  • -
-
-
-

16.0.1.1.0 (2025-07-16)

-
    -
  • Features: cetmix_tower_server_queue: Add async file upload/download -via job queue (3720)
  • -
  • Features: Terminate command with error if job has failed (4718)
  • -
-
-
-

16.0.1.0.2 (2025-05-16)

-
    -
  • Features: ‘sudo’ parameter is not passed to command. (4678)
  • -
-
-
-

16.0.1.0.1 (2025-05-09)

-
    -
  • Bugfixes: Non-critical issues and performance improvements. (4611)
  • -
-
-
-

16.0.1.0.0

-

Release for Odoo 16.0

-
-
-
-

Bug Tracker

-

Bugs are tracked on GitHub Issues. -In case of trouble, please check there if your issue has already been reported. -If you spotted it first, help us to smash it by providing a detailed and welcomed -feedback.

-

Do not contact contributors directly about support or help with technical issues.

-
-
-

Credits

-
-

Authors

-
    -
  • Cetmix
  • -
-
-
-

Maintainers

-

This module is part of the cetmix/cetmix-tower project on GitHub.

-

You are welcome to contribute.

-
-
-
- - diff --git a/addons/cetmix_tower_server_queue/tests/__init__.py b/addons/cetmix_tower_server_queue/tests/__init__.py deleted file mode 100644 index 306c04b..0000000 --- a/addons/cetmix_tower_server_queue/tests/__init__.py +++ /dev/null @@ -1,3 +0,0 @@ -from . import test_command -from . import test_command_log -from . import test_file diff --git a/addons/cetmix_tower_server_queue/tests/test_command.py b/addons/cetmix_tower_server_queue/tests/test_command.py deleted file mode 100644 index 2f043d9..0000000 --- a/addons/cetmix_tower_server_queue/tests/test_command.py +++ /dev/null @@ -1,145 +0,0 @@ -from datetime import timedelta -from unittest.mock import patch - -from odoo.fields import Datetime -from odoo.tools import mute_logger - -from odoo.addons.cetmix_tower_server.tests.common import TestTowerCommon - - -class TestTowerCommand(TestTowerCommon): - """Test suite for verifying zombie command detection and related - queue job cancellation. - - Tests in this class verify that commands which have been running - longer than the timeout are properly detected as zombies, and their - associated queue jobs are cancelled. - """ - - @classmethod - def setUpClass(cls): - super().setUpClass() - # Set command timeout to 10 seconds - cls.env["ir.config_parameter"].sudo().set_param( - "cetmix_tower_server.command_timeout", "10" - ) - # Set old time to 20 seconds ago (older than timeout) - # to simulate running command in past - now = Datetime.now() - cls.old_time = now - timedelta(seconds=20) - - def _patch_command_runner(self, command_type, runner_method): - """Helper to patch a command runner to simulate a zombie command. - - Args: - command_type: Type of command runner to patch ('ssh' or 'python_code') - runner_method: Original method to wrap - - Returns: - A context manager that applies the patch - """ - - def _wrapper(*args, **kwargs): - # Modify args to disable log record finishing - args = list(args) - if len(args) > 1: - args[1] = False # Set log_record to False - return runner_method(*args, **kwargs) - - return patch.object( - self.registry["cx.tower.server"], - f"_command_runner_{command_type}", - _wrapper, - ) - - def _verify_zombie_command_job_cancellation(self, command_action): - """Verify zombie command is detected and job is cancelled. - - Args: - command_action: Action type ('ssh_command' or 'python_code') - """ - # check zombie command logs - domain = [ - ("is_running", "=", True), - ("start_date", "=", self.old_time), - ("command_action", "=", command_action), - ] - zombie_command_logs = self.env["cx.tower.command.log"].search(domain) - - self.assertEqual( - len(zombie_command_logs), 1, "Zombie command log should be created" - ) - self.assertTrue( - zombie_command_logs.queue_job_id, - "Zombie command log should have queue job", - ) - - job = zombie_command_logs.queue_job_id - self.assertTrue(job.exists(), "Zombie command job should exist") - - self.assertEqual(job.state, "pending", "Zombie command job should be pending") - - # run process to kill zombie command - self.server_test_1._check_zombie_commands() - - # check that command log is cancelled - self.assertEqual( - job.state, "cancelled", "Zombie command job should be cancelled" - ) - - def test_check_zombie_ssh_command_queue(self): - """ - Test that zombie ssh command is killed and job is cancelled - """ - # Create test commands - ssh_command = self.Command.create( - { - "name": "Test SSH Command", - "code": "ls -la", - "action": "ssh_command", - } - ) - - # patch command runner to not finish log record - cx_tower_server_obj = self.registry["cx.tower.server"] - _command_runner_ssh_super = cx_tower_server_obj._command_runner_ssh - - with self._patch_command_runner("ssh", _command_runner_ssh_super): - # run zombie command with log creation in past - self.server_test_1.run_command( - ssh_command, log={"start_date": self.old_time} - ) - - # check zombie command logs - self._verify_zombie_command_job_cancellation("ssh_command") - - @mute_logger("py.warnings") - def test_check_zombie_python_command_queue(self): - """ - Test that zombie python command is killed and job is cancelled - """ - # Create test commands - python_command = self.Command.create( - { - "name": "Test Python Command", - "code": "print('test')", - "action": "python_code", - } - ) - - # patch command runner to not finish log record - cx_tower_server_obj = self.registry["cx.tower.server"] - _command_runner_python_code_super = ( - cx_tower_server_obj._command_runner_python_code - ) - - with self._patch_command_runner( - "python_code", _command_runner_python_code_super - ): - # run zombie command with log creation in past - self.server_test_1.run_command( - python_command, log={"start_date": self.old_time} - ) - - # check zombie command logs - self._verify_zombie_command_job_cancellation("python_code") diff --git a/addons/cetmix_tower_server_queue/tests/test_command_log.py b/addons/cetmix_tower_server_queue/tests/test_command_log.py deleted file mode 100644 index 5bef9eb..0000000 --- a/addons/cetmix_tower_server_queue/tests/test_command_log.py +++ /dev/null @@ -1,37 +0,0 @@ -from odoo.addons.cetmix_tower_server.tests.common import TestTowerCommon -from odoo.addons.queue_job.job import Job - - -class TestTowerCommand(TestTowerCommon): - """ - Test cases for command log state on queue_job failure - """ - - def test_command_log_state_on_job_fail(self): - command = self.env["cx.tower.command"].create( - { - "name": "Test Command", - "action": "ssh_command", - "code": "echo 'Hello World'", - } - ) - self.assertTrue(command.id, "Command should be created successfully") - - self.server_test_1.run_command(command=command) - command_log = self.env["cx.tower.command.log"].search( - [("command_id", "=", command.id)], order="id desc", limit=1 - ) - self.assertTrue(command_log, "Command log should be created") - - job = command_log.queue_job_id - self.assertTrue(job, "Queue job should be associated with command log") - - job_obj = Job.load(self.env, job.uuid) - job_obj.set_failed() - job_obj.store() - self.assertEqual(job.state, "failed", "Job should be in failed state") - self.assertEqual( - command_log.command_status, - self.env["queue.job"].QUEUE_JOB_ERROR, - "Command log should be in failed state", - ) diff --git a/addons/cetmix_tower_server_queue/tests/test_file.py b/addons/cetmix_tower_server_queue/tests/test_file.py deleted file mode 100644 index c04a229..0000000 --- a/addons/cetmix_tower_server_queue/tests/test_file.py +++ /dev/null @@ -1,201 +0,0 @@ -from odoo import exceptions - -from odoo.addons.cetmix_tower_server.tests.common import TestTowerCommon -from odoo.addons.queue_job.tests.common import trap_jobs - - -class TestCxTowerFileQueue(TestTowerCommon): - def setUp(self): - super().setUp() - self.file_template = self.FileTemplate.create( - { - "name": "Test", - "file_name": "test.txt", - "server_dir": "/var/tmp", - "code": "Hello, world!", - } - ) - - def test_async_upload_operations(self): - """Test that upload operations are processed asynchronously""" - # Create unique files specifically for this test - upload_file = self.File.create( - { - "source": "tower", - "template_id": self.file_template.id, - "server_id": self.server_test_1.id, - "name": "upload_test_1", - "auto_sync": False, - } - ) - - upload_file_2 = self.File.create( - { - "name": "upload_test_2", - "source": "server", - "server_id": self.server_test_1.id, - "server_dir": "/var/tmp", - "auto_sync": False, - } - ) - - with trap_jobs() as trap: - upload_file.upload() - upload_file_2.upload() - - self.assertEqual(len(trap.enqueued_jobs), 2) - - upload_file.write({"server_response": "ok", "is_being_processed": False}) - upload_file_2.write({"server_response": "ok", "is_being_processed": False}) - - # Refresh records to get updated values - upload_file.invalidate_recordset() - upload_file_2.invalidate_recordset() - - # Verify the expected state - self.assertEqual(upload_file.server_response, "ok") - self.assertFalse(upload_file.is_being_processed) - - self.assertEqual(upload_file_2.server_response, "ok") - self.assertFalse(upload_file_2.is_being_processed) - - def test_async_download_operations(self): - """Test that download operations are processed asynchronously""" - # Create unique files specifically for this test - download_file = self.File.create( - { - "source": "tower", - "template_id": self.file_template.id, - "server_id": self.server_test_1.id, - "name": "download_test_1", - "auto_sync": False, - } - ) - - download_file_2 = self.File.create( - { - "name": "download_test_2", - "source": "server", - "server_id": self.server_test_1.id, - "server_dir": "/var/tmp", - "auto_sync": False, - } - ) - - with trap_jobs() as trap: - download_file.download() - download_file_2.download() - - # Verify jobs were created - self.assertEqual(len(trap.enqueued_jobs), 2) - - download_file.write({"server_response": "ok", "is_being_processed": False}) - download_file_2.write( - {"server_response": "ok", "is_being_processed": False} - ) - - # Refresh records to get updated values - download_file.invalidate_recordset() - download_file_2.invalidate_recordset() - - # Verify the expected state - self.assertEqual(download_file.server_response, "ok") - self.assertFalse(download_file.is_being_processed) - - self.assertEqual(download_file_2.server_response, "ok") - self.assertFalse(download_file_2.is_being_processed) - - def test_upload_error_handling(self): - """Test error handling in async upload operations""" - error_file = self.File.create( - { - "source": "tower", - "template_id": self.file_template.id, - "server_id": self.server_test_1.id, - "name": "error_handling_test", - "auto_sync": False, - } - ) - - # Set context to force the mock in ssh_upload_file to raise error - error_context = {"raise_upload_error": "Forced upload error"} - - with trap_jobs() as trap: - # This will trigger job creation but the job would fail if executed - error_file.with_context(**error_context).upload(raise_error=True) - - # Verify job was created - self.assertEqual(len(trap.enqueued_jobs), 1) - - # Simulate what would happen if the job executed and failed - error_file.write({"server_response": "error", "is_being_processed": False}) - error_file.invalidate_recordset() - - self.assertEqual(error_file.server_response, "error") - self.assertFalse(error_file.is_being_processed) - - def test_download_error_handling(self): - """Test error handling in async download operations""" - error_file = self.File.create( - { - "source": "server", - "server_id": self.server_test_1.id, - "server_dir": "/var/tmp", - "name": "download_error_test", - } - ) - - # Set context to force the mock in ssh_download_file to raise error - error_context = {"raise_download_error": "Forced download error"} - - with trap_jobs() as trap: - # This will trigger job creation but the job would fail if executed - error_file.with_context(**error_context).download(raise_error=True) - - # Verify job was created - self.assertEqual(len(trap.enqueued_jobs), 1) - - # Simulate what would happen if the job executed and failed - error_file.write({"server_response": "error", "is_being_processed": False}) - error_file.invalidate_recordset() - - self.assertEqual(error_file.server_response, "error") - self.assertFalse(error_file.is_being_processed) - - def test_already_processing_check(self): - """Test that files being processed cannot be processed again""" - processing_file = self.File.create( - { - "source": "tower", - "template_id": self.file_template.id, - "server_id": self.server_test_1.id, - "name": "processing_test_file", - "is_being_processed": True, - } - ) - - self.assertTrue(processing_file.is_being_processed) - - # Test with raising error - with self.assertRaises(exceptions.UserError): - processing_file.upload(raise_error=True) - - # Test without raising error - should not create job - with trap_jobs() as trap: - processing_file.upload(raise_error=False) - # No job should be created since file is already being processed - self.assertEqual(len(trap.enqueued_jobs), 0) - - # Verify still marked as processing - self.assertTrue(processing_file.is_being_processed) - - # Same tests for download - with self.assertRaises(exceptions.UserError): - processing_file.download(raise_error=True) - - with trap_jobs() as trap: - processing_file.download(raise_error=False) - # No job should be created - self.assertEqual(len(trap.enqueued_jobs), 0) - - self.assertTrue(processing_file.is_being_processed) diff --git a/addons/cetmix_tower_server_queue/views/cx_tower_command_log_view.xml b/addons/cetmix_tower_server_queue/views/cx_tower_command_log_view.xml deleted file mode 100644 index 5488bb3..0000000 --- a/addons/cetmix_tower_server_queue/views/cx_tower_command_log_view.xml +++ /dev/null @@ -1,20 +0,0 @@ - - - - cx.tower.command.log.view.form - cx.tower.command.log - - - - - - - - - diff --git a/addons/cetmix_tower_server_queue/views/cx_tower_file_view.xml b/addons/cetmix_tower_server_queue/views/cx_tower_file_view.xml deleted file mode 100644 index 55d7cd1..0000000 --- a/addons/cetmix_tower_server_queue/views/cx_tower_file_view.xml +++ /dev/null @@ -1,56 +0,0 @@ - - - - - cx.tower.file.view.form - cx.tower.file - - - - - - - - - - - - - - cx.tower.queue.file.view.tree - cx.tower.file - - - - - - - - - is_being_processed == True - - - is_being_processed != True and server_response == 'ok' - - - is_being_processed != True and server_response not in ('ok', False) - - - - -