diff --git a/pglogical/README.rst b/pglogical/README.rst new file mode 100644 index 00000000..2cc0aca9 --- /dev/null +++ b/pglogical/README.rst @@ -0,0 +1,97 @@ +========= +pglogical +========= + +.. !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! + !! This file is generated by oca-gen-addon-readme !! + !! changes will be overwritten. !! + !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! + +.. |badge1| image:: https://img.shields.io/badge/maturity-Alpha-red.png + :target: https://odoo-community.org/page/development-status + :alt: Alpha +.. |badge2| image:: https://img.shields.io/badge/licence-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-OCA%2Fserver--backend-lightgray.png?logo=github + :target: https://github.com/OCA/server-backend/tree/12.0/pglogical + :alt: OCA/server-backend +.. |badge4| image:: https://img.shields.io/badge/weblate-Translate%20me-F47D42.png + :target: https://translation.odoo-community.org/projects/server-backend-12-0/server-backend-12-0-pglogical + :alt: Translate me on Weblate +.. |badge5| image:: https://img.shields.io/badge/runbot-Try%20me-875A7B.png + :target: https://runbot.odoo-community.org/runbot/253/12.0 + :alt: Try me on Runbot + +|badge1| |badge2| |badge3| |badge4| |badge5| + +This module supports replication of an Odoo database with `pglogical `__ by also passing DDL to the replicas. You can configure which replica sets to pass DDL to. + +.. IMPORTANT:: + This is an alpha version, the data model and design can change at any time without warning. + Only for development or testing purpose, do not use in production. + `More details on development status `_ + +**Table of contents** + +.. contents:: + :local: + +Configuration +============= + +To configure this module, you need to: + +#. add section ``[pglogical]`` to your odoo configuration file +#. add value ``replication_sets = set1,set2,set3,etc`` in this section +#. add ``pglogical`` to your list of server wide modules + +Example:: + + [pglogical] + replication_sets = ddl_sql + +Usage +===== + +If configured correctly, this module is completely transparent for users. + +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 smashing it by providing a detailed and welcomed +`feedback `_. + +Do not contact contributors directly about support or help with technical issues. + +Credits +======= + +Authors +~~~~~~~ + +* Hunki Enterprises BV + +Contributors +~~~~~~~~~~~~ + +* Holger Brunn (https://hunki-enterprises.com) + +Maintainers +~~~~~~~~~~~ + +This module is maintained by the OCA. + +.. image:: https://odoo-community.org/logo.png + :alt: Odoo Community Association + :target: https://odoo-community.org + +OCA, or the Odoo Community Association, is a nonprofit organization whose +mission is to support the collaborative development of Odoo features and +promote its widespread use. + +This module is part of the `OCA/server-backend `_ project on GitHub. + +You are welcome to contribute. To learn how please visit https://odoo-community.org/page/Contribute. diff --git a/pglogical/__init__.py b/pglogical/__init__.py new file mode 100644 index 00000000..690e7141 --- /dev/null +++ b/pglogical/__init__.py @@ -0,0 +1,3 @@ +# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl). + +from .hooks import post_load diff --git a/pglogical/__manifest__.py b/pglogical/__manifest__.py new file mode 100644 index 00000000..cd1873d5 --- /dev/null +++ b/pglogical/__manifest__.py @@ -0,0 +1,13 @@ +# Copyright 2022 Hunki Enterprises BV +# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl). +{ + "name": "pglogical", + "summary": "Support for replicating Odoo's database", + "version": "12.0.1.0.0", + "development_status": "Alpha", + "category": "Tools", + "website": "https://github.com/OCA/server-backend", + "author": "Hunki Enterprises BV, Odoo Community Association (OCA)", + "license": "AGPL-3", + "post_load": "post_load", +} diff --git a/pglogical/hooks.py b/pglogical/hooks.py new file mode 100644 index 00000000..4ab00c21 --- /dev/null +++ b/pglogical/hooks.py @@ -0,0 +1,50 @@ +# Copyright 2022 Hunki Enterprises BV +# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl). +import logging +from odoo.sql_db import Cursor +from odoo.tools import config + +SECTION_NAME = "pglogical" + + +def post_load(): + """ + Patch cursor to funnel DDL through pglogical.replicate_ddl_command if configured to + do so + """ + + _logger = logging.getLogger("odoo.addons.pglogical") + + if SECTION_NAME not in config.misc: + _logger.info("%s section missing in config, not doing anything", SECTION_NAME) + return + replication_sets = list( + filter(None, config.misc[SECTION_NAME].get("replication_sets", "").split(",")) + ) + if not replication_sets: + _logger.error("no replication sets defined, not doing anything") + return + if config["test_enable"]: + _logger.info("test mode enabled, not doing anything") + return + + _logger.info("patching cursor to intercept ddl") + execute_org = Cursor.execute + + def execute(self, query, params=None, log_exceptions=None): + """Wrap DDL in pglogical.replicate_ddl_command""" + if (query[:6] == "CREATE" or query[:5] == "ALTER" or query[:4] == "DROP") and ( + # don't replicate constraints, triggers, indexes + "CONSTRAINT" not in query + and "TRIGGER" not in query + and "INDEX" not in query + ): + mogrified = self.mogrify(query, params).decode("utf8") + query = "SELECT pglogical.replicate_ddl_command(%s, %s)" + params = (mogrified, execute.replication_sets) + return execute.origin(self, query, params=params, log_exceptions=log_exceptions) + + execute.origin = execute_org + execute.replication_sets = replication_sets + + Cursor.execute = execute diff --git a/pglogical/readme/CONFIGURE.rst b/pglogical/readme/CONFIGURE.rst new file mode 100644 index 00000000..696abe18 --- /dev/null +++ b/pglogical/readme/CONFIGURE.rst @@ -0,0 +1,10 @@ +To configure this module, you need to: + +#. add section ``[pglogical]`` to your odoo configuration file +#. add value ``replication_sets = set1,set2,set3,etc`` in this section +#. add ``pglogical`` to your list of server wide modules + +Example:: + + [pglogical] + replication_sets = ddl_sql diff --git a/pglogical/readme/CONTRIBUTORS.rst b/pglogical/readme/CONTRIBUTORS.rst new file mode 100644 index 00000000..33b6eb2c --- /dev/null +++ b/pglogical/readme/CONTRIBUTORS.rst @@ -0,0 +1 @@ +* Holger Brunn (https://hunki-enterprises.com) diff --git a/pglogical/readme/DESCRIPTION.rst b/pglogical/readme/DESCRIPTION.rst new file mode 100644 index 00000000..39b573c0 --- /dev/null +++ b/pglogical/readme/DESCRIPTION.rst @@ -0,0 +1 @@ +This module supports replication of an Odoo database with `pglogical `__ by also passing DDL to the replicas. You can configure which replica sets to pass DDL to. diff --git a/pglogical/readme/USAGE.rst b/pglogical/readme/USAGE.rst new file mode 100644 index 00000000..0945b6bd --- /dev/null +++ b/pglogical/readme/USAGE.rst @@ -0,0 +1 @@ +If configured correctly, this module is completely transparent for users. diff --git a/pglogical/static/description/icon.png b/pglogical/static/description/icon.png new file mode 100644 index 00000000..3a0328b5 Binary files /dev/null and b/pglogical/static/description/icon.png differ diff --git a/pglogical/static/description/index.html b/pglogical/static/description/index.html new file mode 100644 index 00000000..f92d18ab --- /dev/null +++ b/pglogical/static/description/index.html @@ -0,0 +1,124 @@ +
+
+
+

Module name

+

This module was written to extend the functionality of ... to support ... and allow you to ...

+
+
+
+ +
+
+
+

Installation

+
+
+

To install this module, you need to: +

    +
  • ...
  • +
+

+
+
+
+ + + +
+
+
+
+ +
+
+
+

Configuration

+
+
+

To configure this module, you need to: +

    +
  • ...
  • +
+

+
+
+
+ + + +
+
+
+
+ +
+
+
+

Usage

+
+
+

To use this module, you need to: +

    +
  • ...
  • +
+

+

For further information, please visit: +

+

+
+
+
+ + + +
+
+
+
+ +
+
+
+

Known issues / Roadmap

+
+
+

+

    +
  • ...
  • +
+

+
+
+
+ + + +
+
+
+
+ +
+
+
+

Credits

+
+
+

Contributors

+ +
+
+

Maintainer

+

+ This module is maintained by the OCA.
+ OCA, or the Odoo Community Association, is a nonprofit organization whose mission is to support the collaborative development of Odoo features and promote its widespread use.
+ To contribute to this module, please visit http://odoo-community.org.
+ +

+
+
+
diff --git a/pglogical/tests/__init__.py b/pglogical/tests/__init__.py new file mode 100644 index 00000000..a38e68f7 --- /dev/null +++ b/pglogical/tests/__init__.py @@ -0,0 +1,3 @@ +# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl). + +from . import test_pglogical diff --git a/pglogical/tests/test_pglogical.py b/pglogical/tests/test_pglogical.py new file mode 100644 index 00000000..4ab9d75a --- /dev/null +++ b/pglogical/tests/test_pglogical.py @@ -0,0 +1,92 @@ +# Copyright 2022 Hunki Enterprises BV +# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl). + +import mock +from contextlib import contextmanager +from odoo.sql_db import Cursor +from odoo.tests.common import TransactionCase +from odoo.tools.config import config +from ..hooks import post_load + + +class TestPglogical(TransactionCase): + @contextmanager + def _config(self, misc, test_enable=False): + """ + Temporarily change the config to test_enable=False and impose a custom misc + section + """ + original_misc = config.misc + config.misc = misc + config["test_enable"] = test_enable + yield + config["test_enable"] = True + config.misc = original_misc + + def test_configuration(self): + """Test we react correctly to misconfigurations""" + with self._config( + dict(pglogical=dict(replication_sets="nothing")), True + ), self.assertLogs("odoo.addons.pglogical") as log: + post_load() + self.assertEqual( + log.output, + ["INFO:odoo.addons.pglogical:test mode enabled, not doing anything"], + ) + + with self._config({}), self.assertLogs("odoo.addons.pglogical") as log: + post_load() + + self.assertEqual( + log.output, + [ + "INFO:odoo.addons.pglogical:pglogical section missing in config, " + "not doing anything" + ], + ) + + with self._config(dict(pglogical={"hello": "world"})), self.assertLogs( + "odoo.addons.pglogical" + ) as log: + post_load() + + self.assertEqual( + log.output, + [ + "ERROR:odoo.addons.pglogical:no replication sets defined, " + "not doing anything" + ], + ) + + def test_patching(self): + """Test patching the cursor succeeds""" + with self._config(dict(pglogical=dict(replication_sets="set1,set2"))): + try: + post_load() + self.assertTrue(getattr(Cursor.execute, "origin", False)) + with mock.patch.object(self.env.cr, "_obj") as mock_cursor: + self.env.cr.execute("ALTER TABLE test ADD COLUMN test varchar") + self.assertIn( + "pglogical.replicate_ddl_command", + mock_cursor.execute.call_args[0][0], + ) + + with mock.patch.object(self.env.cr, "_obj") as mock_cursor: + self.env.cr.execute( + "ALTER TABLE test ADD CONSTRAINT test unique(id)" + ) + + self.assertNotIn( + "pglogical.replicate_ddl_command", + mock_cursor.execute.call_args[0][0], + ) + + with mock.patch.object(self.env.cr, "_obj") as mock_cursor: + self.env.cr.execute("SELECT * from test") + + self.assertNotIn( + "pglogical.replicate_ddl_command", + mock_cursor.execute.call_args[0][0], + ) + finally: + Cursor.execute = getattr(Cursor.execute, "origin", Cursor.execute)