From a022fb89cbf798f22f5ed7437de5f0dc6ba860d3 Mon Sep 17 00:00:00 2001 From: Pedro Guimaraes Date: Mon, 11 Sep 2023 14:08:54 +0200 Subject: [PATCH] Register opensearch-knn as a plugin --- config.yaml | 8 ++ .../opensearch/v0/opensearch_ml_plugins.py | 65 ++++++++++++++ .../v0/opensearch_plugin_manager.py | 9 +- tests/unit/lib/test_ml_plugins.py | 89 +++++++++++++++++++ 4 files changed, 170 insertions(+), 1 deletion(-) create mode 100644 config.yaml create mode 100644 lib/charms/opensearch/v0/opensearch_ml_plugins.py create mode 100644 tests/unit/lib/test_ml_plugins.py diff --git a/config.yaml b/config.yaml new file mode 100644 index 000000000..7a26facb0 --- /dev/null +++ b/config.yaml @@ -0,0 +1,8 @@ +# Copyright 2023 Canonical Ltd. +# See LICENSE file for licensing details. + +options: + plugin_opensearch_knn: + default: false + type: boolean + description: Enable opensearch-knn \ No newline at end of file diff --git a/lib/charms/opensearch/v0/opensearch_ml_plugins.py b/lib/charms/opensearch/v0/opensearch_ml_plugins.py new file mode 100644 index 000000000..992cadd66 --- /dev/null +++ b/lib/charms/opensearch/v0/opensearch_ml_plugins.py @@ -0,0 +1,65 @@ +# Copyright 2023 Canonical Ltd. +# See LICENSE file for licensing details. + +"""Implements the KNN and ML-Commons plugins for OpenSearch.""" + +import logging +from typing import Any, Dict + +from charms.opensearch.v0.opensearch_plugins import OpenSearchPlugin + +logger = logging.getLogger(__name__) + + +# The unique Charmhub library identifier, never change it +LIBID = "71166db20ab244099ae966c8055db2df" + +# Increment this major API version when introducing breaking changes +LIBAPI = 0 + +# Increment this PATCH version before using `charmcraft publish-lib` or reset +# to 0 if you are raising the major API version +LIBPATCH = 1 + + +class OpenSearchKnn(OpenSearchPlugin): + """Implements the opensearch-knn plugin.""" + + def __init__( + self, + plugins_path: str, + ): + super().__init__(plugins_path) + + def config(self) -> Dict[str, Dict[str, str]]: + """Returns a dict containing all the configuration needed to be applied in the form. + + Format: + { + "opensearch.yml": {...}, + "keystore": {...}, + } + """ + return {self.CONFIG_YML: {"knn.plugin.enabled": "true"}, "keystore": {}} + + def _is_enabled(self, opensearch_config: Dict[str, Any]) -> None: + self._enabled = opensearch_config.get("knn.plugin.enabled", "false") == "true" + + def disable(self) -> Dict[str, Any]: + """Returns a dict containing different config changes. + + The dict should return: + (1) all the configs to remove + (2) all the configs to add + (3) all the keystore values to be remmoved. + """ + return { + "to_remove_opensearch": ["knn.plugin.enabled"], + "to_add_opensearch": [], + "to_remove_keystore": [], + } + + @property + def name(self) -> str: + """Returns the name of the plugin.""" + return "opensearch-knn" diff --git a/lib/charms/opensearch/v0/opensearch_plugin_manager.py b/lib/charms/opensearch/v0/opensearch_plugin_manager.py index 70986b80d..5ae62f380 100644 --- a/lib/charms/opensearch/v0/opensearch_plugin_manager.py +++ b/lib/charms/opensearch/v0/opensearch_plugin_manager.py @@ -14,6 +14,7 @@ from typing import List from charms.opensearch.v0.opensearch_exceptions import OpenSearchCmdError +from charms.opensearch.v0.opensearch_ml_plugins import OpenSearchKnn from charms.opensearch.v0.opensearch_plugins import ( OpenSearchPlugin, OpenSearchPluginError, @@ -34,7 +35,13 @@ logger = logging.getLogger(__name__) -ConfigExposedPlugins = {} +ConfigExposedPlugins = { + "opensearch-knn": { + "class": OpenSearchKnn, + "config-name": "plugin_opensearch_knn", + "relation-name": None, + } +} class OpenSearchPluginManager: diff --git a/tests/unit/lib/test_ml_plugins.py b/tests/unit/lib/test_ml_plugins.py new file mode 100644 index 000000000..6a2d1c7d3 --- /dev/null +++ b/tests/unit/lib/test_ml_plugins.py @@ -0,0 +1,89 @@ +# Copyright 2023 Canonical Ltd. +# See LICENSE file for licensing details. + +"""Unit test for the opensearch_plugins library.""" +import unittest +from unittest.mock import MagicMock, PropertyMock, call, patch + +import charms +from charms.opensearch.v0.opensearch_ml_plugins import OpenSearchKnn +from charms.rolling_ops.v0.rollingops import RollingOpsManager +from ops.testing import Harness + +from charm import OpenSearchOperatorCharm + +RETURN_LIST_PLUGINS = """opensearch-alerting +opensearch-anomaly-detection +opensearch-asynchronous-search +opensearch-cross-cluster-replication +opensearch-geospatial +opensearch-index-management +opensearch-job-scheduler +opensearch-knn +opensearch-ml +opensearch-notifications +opensearch-notifications-core +opensearch-observability +opensearch-performance-analyzer +opensearch-reports-scheduler +opensearch-security +opensearch-sql +""" + + +class TestOpenSearchKNN(unittest.TestCase): + def setUp(self) -> None: + self.harness = Harness(OpenSearchOperatorCharm) + self.addCleanup(self.harness.cleanup) + self.harness.begin() + self.charm = self.harness.charm + self.charm.opensearch.paths.plugins = "tests/unit/resources" + self.plugin_manager = self.charm.plugin_manager + self.plugin_manager._plugins_path = self.charm.opensearch.paths.plugins + # Override the ConfigExposedPlugins and ensure one single plugin exists + charms.opensearch.v0.opensearch_plugin_manager.ConfigExposedPlugins = { + "opensearch-knn": { + "class": OpenSearchKnn, + "config-name": "plugin_opensearch_knn", + "relation-name": None, + } + } + + @patch.object(RollingOpsManager, "_on_acquire_lock") + @patch( + "charms.opensearch.v0.opensearch_distro.OpenSearchDistribution.version", + new_callable=PropertyMock, + ) + @patch("charms.opensearch.v0.opensearch_plugin_manager.OpenSearchPluginManager.is_installed") + @patch("charms.opensearch.v0.opensearch_distro.OpenSearchDistribution.is_started") + @patch("charms.opensearch.v0.opensearch_config.OpenSearchConfig.load_node") + @patch("charms.opensearch.v0.helper_conf_setter.YamlConfigSetter.put") + def test_config_changed( + self, _, mock_load, mock_is_started, mock_is_installed, mock_version, mock_acquire_lock + ) -> None: + """Tests config_changed with KNN plugin.""" + mock_is_installed.return_value = True + mock_is_started.return_value = True + mock_version.return_value = "2.9.0" + self.plugin_manager._keystore.add = MagicMock() + self.plugin_manager._add_plugin = MagicMock() + self.charm.opensearch.run_bin = MagicMock(return_value=RETURN_LIST_PLUGINS) + mock_load.side_effect = [ + {}, + {}, + {}, + {}, + {"knn.plugin.enabled": "true"}, + {"knn.plugin.enabled": "true"}, + {"knn.plugin.enabled": "true"}, + {"knn.plugin.enabled": "true"}, + {"knn.plugin.enabled": "true"}, + ] + + self.harness.update_config({"plugin_opensearch_knn": True}) + self.charm.opensearch.config.put.assert_has_calls( + [call(self.charm.opensearch_config.CONFIG_YML, "knn.plugin.enabled", "true")] + ) + self.charm.opensearch.run_bin.assert_called_once_with("opensearch-plugin", "list") + self.plugin_manager._add_plugin.assert_not_called() + mock_acquire_lock.assert_called_once()