From d3c0435dfe2851711ac5cd8b68ef52fd7180af38 Mon Sep 17 00:00:00 2001 From: Kartik Ganesh Date: Thu, 30 Nov 2023 14:43:40 -0800 Subject: [PATCH] [Fetch Migration] Added constructs and logic for component and index template migration This includes class representations of component and index template information (and their unit tests). Index_operations now also has new functions to fetch and create component/index templates - unit test coverage for this is TBD. Finally, the template migration logic has been added to metadata_migration Signed-off-by: Kartik Ganesh --- .../python/component_template_info.py | 34 +++++ FetchMigration/python/index_operations.py | 57 +++++++ FetchMigration/python/index_template_info.py | 23 +++ FetchMigration/python/metadata_migration.py | 35 ++++- .../python/tests/test_index_operations.py | 141 ++++++++++++++++++ .../python/tests/test_metadata_migration.py | 74 ++++++++- .../python/tests/test_template_info.py | 52 +++++++ 7 files changed, 412 insertions(+), 4 deletions(-) create mode 100644 FetchMigration/python/component_template_info.py create mode 100644 FetchMigration/python/index_template_info.py create mode 100644 FetchMigration/python/tests/test_template_info.py diff --git a/FetchMigration/python/component_template_info.py b/FetchMigration/python/component_template_info.py new file mode 100644 index 0000000000..b9a0f9b15c --- /dev/null +++ b/FetchMigration/python/component_template_info.py @@ -0,0 +1,34 @@ +# +# Copyright OpenSearch Contributors +# SPDX-License-Identifier: Apache-2.0 +# +# The OpenSearch Contributors require contributions made to +# this file be licensed under the Apache-2.0 license or a +# compatible open source license. +# + + +# Constants +from typing import Optional + +NAME_KEY = "name" +DEFAULT_TEMPLATE_KEY = "component_template" + + +# Class that encapsulates component template information +class ComponentTemplateInfo: + # Private member variables + __name: str + __template_def: Optional[dict] + + def __init__(self, template_payload: dict, template_key: str = DEFAULT_TEMPLATE_KEY): + self.__name = template_payload[NAME_KEY] + self.__template_def = None + if template_key in template_payload: + self.__template_def = template_payload[template_key] + + def get_name(self) -> str: + return self.__name + + def get_template_definition(self) -> dict: + return self.__template_def diff --git a/FetchMigration/python/index_operations.py b/FetchMigration/python/index_operations.py index 03f1f11d19..6c788ad920 100644 --- a/FetchMigration/python/index_operations.py +++ b/FetchMigration/python/index_operations.py @@ -12,8 +12,10 @@ import jsonpath_ng import requests +from component_template_info import ComponentTemplateInfo from endpoint_info import EndpointInfo from index_doc_count import IndexDocCount +from index_template_info import IndexTemplateInfo # Constants SETTINGS_KEY = "settings" @@ -21,6 +23,10 @@ ALIASES_KEY = "aliases" COUNT_KEY = "count" __INDEX_KEY = "index" +__COMPONENT_TEMPLATE_LIST_KEY = "component_templates" +__INDEX_TEMPLATE_LIST_KEY = "index_templates" +__INDEX_TEMPLATES_PATH = "/_index_template" +__COMPONENT_TEMPLATES_PATH = "/_component_template" __ALL_INDICES_ENDPOINT = "*" # (ES 7+) size=0 avoids the "hits" payload to reduce the response size since we're only interested in the aggregation, # and track_total_hits forces an accurate doc-count @@ -106,3 +112,54 @@ def doc_count(indices: set, endpoint: EndpointInfo) -> IndexDocCount: return IndexDocCount(total, count_map) except RuntimeError as e: raise RuntimeError(f"Failed to fetch doc_count: {e!s}") + + +def __fetch_templates(endpoint: EndpointInfo, path: str, root_key: str, factory) -> set: + url: str = endpoint.add_path(path) + # raises RuntimeError in case of any request errors + resp = __send_get_request(url, endpoint) + result = set() + if root_key in resp.json(): + for template in resp.json()[root_key]: + result.add(factory(template)) + return result + + +def fetch_all_component_templates(endpoint: EndpointInfo) -> set[ComponentTemplateInfo]: + try: + # raises RuntimeError in case of any request errors + return __fetch_templates(endpoint, __COMPONENT_TEMPLATES_PATH, __COMPONENT_TEMPLATE_LIST_KEY, + lambda t: ComponentTemplateInfo(t)) + except RuntimeError as e: + raise RuntimeError(f"Failed to fetch component template metadata from cluster endpoint: {e!s}") + + +def fetch_all_index_templates(endpoint: EndpointInfo) -> set[IndexTemplateInfo]: + try: + # raises RuntimeError in case of any request errors + return __fetch_templates(endpoint, __INDEX_TEMPLATES_PATH, __INDEX_TEMPLATE_LIST_KEY, + lambda t: IndexTemplateInfo(t)) + except RuntimeError as e: + raise RuntimeError(f"Failed to fetch index template metadata from cluster endpoint: {e!s}") + + +def __create_templates(templates: set[ComponentTemplateInfo], endpoint: EndpointInfo, template_path: str) -> dict: + failures = dict() + for template in templates: + template_endpoint = endpoint.add_path(template_path + "/" + template.get_name()) + try: + resp = requests.put(template_endpoint, auth=endpoint.get_auth(), verify=endpoint.is_verify_ssl(), + json=template.get_template_definition(), timeout=__TIMEOUT_SECONDS) + resp.raise_for_status() + except requests.exceptions.RequestException as e: + failures[template.get_name()] = e + # Loop completed, return failures if any + return failures + + +def create_component_templates(templates: set[ComponentTemplateInfo], endpoint: EndpointInfo) -> dict: + return __create_templates(templates, endpoint, __COMPONENT_TEMPLATES_PATH) + + +def create_index_templates(templates: set[IndexTemplateInfo], endpoint: EndpointInfo) -> dict: + return __create_templates(templates, endpoint, __INDEX_TEMPLATES_PATH) diff --git a/FetchMigration/python/index_template_info.py b/FetchMigration/python/index_template_info.py new file mode 100644 index 0000000000..aeceb95e92 --- /dev/null +++ b/FetchMigration/python/index_template_info.py @@ -0,0 +1,23 @@ +# +# Copyright OpenSearch Contributors +# SPDX-License-Identifier: Apache-2.0 +# +# The OpenSearch Contributors require contributions made to +# this file be licensed under the Apache-2.0 license or a +# compatible open source license. +# + +from component_template_info import ComponentTemplateInfo + +# Constants +INDEX_TEMPLATE_KEY = "index_template" + + +# Class that encapsulates index template information from a cluster. +# Subclass of ComponentTemplateInfo because the structure of an index +# template is identical to a component template, except that it uses +# a different template key. Also, index templates can be "composed" of +# one or more component templates. +class IndexTemplateInfo(ComponentTemplateInfo): + def __init__(self, template_payload: dict): + super().__init__(template_payload, INDEX_TEMPLATE_KEY) diff --git a/FetchMigration/python/metadata_migration.py b/FetchMigration/python/metadata_migration.py index ce9969346d..28a4a6b6a3 100644 --- a/FetchMigration/python/metadata_migration.py +++ b/FetchMigration/python/metadata_migration.py @@ -92,6 +92,35 @@ def index_metadata_migration(source: EndpointInfo, target: EndpointInfo, return result +# Returns true if there were failures, false otherwise +def __log_template_failures(failures: dict, target_count: int) -> bool: + fail_count = len(failures) + if fail_count > 0: + logging.error(f"Failed to create {fail_count} of {target_count} templates") + for failed_template_name, error in failures.items(): + logging.error(f"Template name {failed_template_name} failed: {error!s}") + # Return true to signal failures + return True + else: + # No failures, return false + return False + + +# Raises RuntimeError if component/index template migration fails +def template_migration(source: EndpointInfo, target: EndpointInfo): + # Fetch and migrate component templates first + templates = index_operations.fetch_all_component_templates(source) + failures = index_operations.create_component_templates(templates, target) + if not __log_template_failures(failures, len(templates)): + # Only migrate index templates if component template migration had no failures + templates = index_operations.fetch_all_index_templates(source) + failures = index_operations.create_index_templates(templates, target) + if __log_template_failures(failures, len(templates)): + raise RuntimeError("Failed to create some index templates") + else: + raise RuntimeError("Failed to create some component templates, aborting index template creation") + + def run(args: MetadataMigrationParams) -> MetadataMigrationResult: # Sanity check if not args.report and len(args.output_file) == 0: @@ -112,6 +141,10 @@ def run(args: MetadataMigrationParams) -> MetadataMigrationResult: if result.migration_indices and len(args.output_file) > 0: write_output(dp_config, result.migration_indices, args.output_file) logging.debug("Wrote output YAML pipeline to: " + args.output_file) + if not args.dryrun: + # Create component and index templates, may raise RuntimeError + template_migration(source_endpoint_info, target_endpoint_info) + # Finally return result return result @@ -143,6 +176,6 @@ def run(args: MetadataMigrationParams) -> MetadataMigrationResult: arg_parser.add_argument("--report", "-r", action="store_true", help="Print a report of the index differences") arg_parser.add_argument("--dryrun", action="store_true", - help="Skips the actual creation of indices on the target cluster") + help="Skips the actual creation of metadata on the target cluster") namespace = arg_parser.parse_args() run(MetadataMigrationParams(namespace.config_file_path, namespace.output_file, namespace.report, namespace.dryrun)) diff --git a/FetchMigration/python/tests/test_index_operations.py b/FetchMigration/python/tests/test_index_operations.py index 06f9502cb4..44a7221bf8 100644 --- a/FetchMigration/python/tests/test_index_operations.py +++ b/FetchMigration/python/tests/test_index_operations.py @@ -15,10 +15,20 @@ from responses import matchers import index_operations +from component_template_info import ComponentTemplateInfo from endpoint_info import EndpointInfo +from index_template_info import IndexTemplateInfo from tests import test_constants +# Helper method to create a template API response +def create_base_template_response(list_name: str, body: dict) -> dict: + return {list_name: [{"name": "test", list_name[:-1]: {"template": { + test_constants.SETTINGS_KEY: body.get(test_constants.SETTINGS_KEY, {}), + test_constants.MAPPINGS_KEY: body.get(test_constants.MAPPINGS_KEY, {}) + }}}]} + + class TestIndexOperations(unittest.TestCase): @responses.activate def test_fetch_all_indices(self): @@ -126,6 +136,137 @@ def test_get_request_errors(self): self.assertRaises(RuntimeError, index_operations.fetch_all_indices, EndpointInfo(test_constants.SOURCE_ENDPOINT)) + @responses.activate + def test_fetch_all_component_templates_empty(self): + # 1 - Empty response + responses.get(test_constants.SOURCE_ENDPOINT + "_component_template", json={}) + result = index_operations.fetch_all_component_templates(EndpointInfo(test_constants.SOURCE_ENDPOINT)) + # Missing key returns empty result + self.assertEqual(0, len(result)) + # 2 - Valid response structure but no templates + responses.get(test_constants.SOURCE_ENDPOINT + "_component_template", json={"component_templates": []}) + result = index_operations.fetch_all_component_templates(EndpointInfo(test_constants.SOURCE_ENDPOINT)) + self.assertEqual(0, len(result)) + # 2 - Invalid response structure + responses.get(test_constants.SOURCE_ENDPOINT + "_component_template", json={"templates": []}) + result = index_operations.fetch_all_component_templates(EndpointInfo(test_constants.SOURCE_ENDPOINT)) + self.assertEqual(0, len(result)) + + @responses.activate + def test_fetch_all_component_templates(self): + # Set up response + test_index = test_constants.BASE_INDICES_DATA[test_constants.INDEX3_NAME] + test_resp = create_base_template_response("component_templates", test_index) + responses.get(test_constants.SOURCE_ENDPOINT + "_component_template", json=test_resp) + result = index_operations.fetch_all_component_templates(EndpointInfo(test_constants.SOURCE_ENDPOINT)) + # Result should contain one template + self.assertEqual(1, len(result)) + template = result.pop() + self.assertTrue(isinstance(template, ComponentTemplateInfo)) + self.assertEqual("test", template.get_name()) + template_def = template.get_template_definition()["template"] + self.assertEqual(test_index[test_constants.SETTINGS_KEY], template_def[test_constants.SETTINGS_KEY]) + self.assertEqual(test_index[test_constants.MAPPINGS_KEY], template_def[test_constants.MAPPINGS_KEY]) + + @responses.activate + def test_fetch_all_index_templates_empty(self): + # 1 - Empty response + responses.get(test_constants.SOURCE_ENDPOINT + "_index_template", json={}) + result = index_operations.fetch_all_index_templates(EndpointInfo(test_constants.SOURCE_ENDPOINT)) + # Missing key returns empty result + self.assertEqual(0, len(result)) + # 2 - Valid response structure but no templates + responses.get(test_constants.SOURCE_ENDPOINT + "_index_template", json={"index_templates": []}) + result = index_operations.fetch_all_index_templates(EndpointInfo(test_constants.SOURCE_ENDPOINT)) + self.assertEqual(0, len(result)) + # 2 - Invalid response structure + responses.get(test_constants.SOURCE_ENDPOINT + "_index_template", json={"templates": []}) + result = index_operations.fetch_all_index_templates(EndpointInfo(test_constants.SOURCE_ENDPOINT)) + self.assertEqual(0, len(result)) + + @responses.activate + def test_fetch_all_index_templates(self): + # Set up base response + key = "index_templates" + test_index_pattern = "test-*" + test_component_template_name = "test_component_template" + test_index = test_constants.BASE_INDICES_DATA[test_constants.INDEX2_NAME] + test_resp = create_base_template_response(key, test_index) + # Add fields specific to index templates + template_body = test_resp[key][0][key[:-1]] + template_body["index_patterns"] = [test_index_pattern] + template_body["composed_of"] = [test_component_template_name] + responses.get(test_constants.SOURCE_ENDPOINT + "_index_template", json=test_resp) + result = index_operations.fetch_all_index_templates(EndpointInfo(test_constants.SOURCE_ENDPOINT)) + # Result should contain one template + self.assertEqual(1, len(result)) + template = result.pop() + self.assertTrue(isinstance(template, IndexTemplateInfo)) + self.assertEqual("test", template.get_name()) + template_def = template.get_template_definition()["template"] + self.assertEqual(test_index[test_constants.SETTINGS_KEY], template_def[test_constants.SETTINGS_KEY]) + self.assertEqual(test_index[test_constants.MAPPINGS_KEY], template_def[test_constants.MAPPINGS_KEY]) + + @responses.activate + def test_fetch_all_templates_errors(self): + # Set up error responses + responses.get(test_constants.SOURCE_ENDPOINT + "_component_template", body=requests.Timeout()) + responses.get(test_constants.SOURCE_ENDPOINT + "_index_template", body=requests.HTTPError()) + self.assertRaises(RuntimeError, index_operations.fetch_all_component_templates, + EndpointInfo(test_constants.SOURCE_ENDPOINT)) + self.assertRaises(RuntimeError, index_operations.fetch_all_index_templates, + EndpointInfo(test_constants.SOURCE_ENDPOINT)) + + @responses.activate + def test_create_templates(self): + # Set up test input + test1_template_def = copy.deepcopy(test_constants.BASE_INDICES_DATA[test_constants.INDEX2_NAME]) + test2_template_def = copy.deepcopy(test_constants.BASE_INDICES_DATA[test_constants.INDEX3_NAME]) + # Remove "aliases" since that's not a valid component template entry + del test1_template_def[test_constants.ALIASES_KEY] + del test2_template_def[test_constants.ALIASES_KEY] + # Test component templates first + test_templates = set() + test_templates.add(ComponentTemplateInfo({"name": "test1", "component_template": test1_template_def})) + test_templates.add(ComponentTemplateInfo({"name": "test2", "component_template": test2_template_def})) + # Set up expected PUT calls with a mock response status + responses.put(test_constants.TARGET_ENDPOINT + "_component_template/test1", + match=[matchers.json_params_matcher(test1_template_def)]) + responses.put(test_constants.TARGET_ENDPOINT + "_component_template/test2", + match=[matchers.json_params_matcher(test2_template_def)]) + failed = index_operations.create_component_templates(test_templates, + EndpointInfo(test_constants.TARGET_ENDPOINT)) + self.assertEqual(0, len(failed)) + # Also test index templates + test_templates.clear() + test_templates.add(IndexTemplateInfo({"name": "test1", "index_template": test1_template_def})) + test_templates.add(IndexTemplateInfo({"name": "test2", "index_template": test2_template_def})) + # Set up expected PUT calls with a mock response status + responses.put(test_constants.TARGET_ENDPOINT + "_index_template/test1", + match=[matchers.json_params_matcher(test1_template_def)]) + responses.put(test_constants.TARGET_ENDPOINT + "_index_template/test2", + match=[matchers.json_params_matcher(test2_template_def)]) + failed = index_operations.create_index_templates(test_templates, EndpointInfo(test_constants.TARGET_ENDPOINT)) + self.assertEqual(0, len(failed)) + + @responses.activate + def test_create_templates_failure(self): + # Set up failures + responses.put(test_constants.TARGET_ENDPOINT + "_component_template/test1", body=requests.Timeout()) + responses.put(test_constants.TARGET_ENDPOINT + "_index_template/test2", body=requests.HTTPError()) + test_input = ComponentTemplateInfo({"name": "test1", "component_template": {}}) + failed = index_operations.create_component_templates({test_input}, EndpointInfo(test_constants.TARGET_ENDPOINT)) + # Verify that failures return their respective errors + self.assertEqual(1, len(failed)) + self.assertTrue("test1" in failed) + self.assertTrue(isinstance(failed["test1"], requests.Timeout)) + test_input = IndexTemplateInfo({"name": "test2", "index_template": {}}) + failed = index_operations.create_index_templates({test_input}, EndpointInfo(test_constants.TARGET_ENDPOINT)) + # Verify that failures return their respective errors + self.assertEqual(1, len(failed)) + self.assertTrue("test2" in failed) + self.assertTrue(isinstance(failed["test2"], requests.HTTPError)) + if __name__ == '__main__': unittest.main() diff --git a/FetchMigration/python/tests/test_metadata_migration.py b/FetchMigration/python/tests/test_metadata_migration.py index 5b38c1b63f..5d53371814 100644 --- a/FetchMigration/python/tests/test_metadata_migration.py +++ b/FetchMigration/python/tests/test_metadata_migration.py @@ -16,6 +16,7 @@ import requests import metadata_migration +from endpoint_info import EndpointInfo from index_doc_count import IndexDocCount from metadata_migration_params import MetadataMigrationParams from tests import test_constants @@ -31,6 +32,7 @@ def setUp(self) -> None: def tearDown(self) -> None: logging.disable(logging.NOTSET) + @patch('metadata_migration.template_migration') @patch('index_operations.doc_count') @patch('metadata_migration.write_output') @patch('metadata_migration.print_report') @@ -38,7 +40,8 @@ def tearDown(self) -> None: @patch('index_operations.fetch_all_indices') # Note that mock objects are passed bottom-up from the patch order above def test_run_report(self, mock_fetch_indices: MagicMock, mock_create_indices: MagicMock, - mock_print_report: MagicMock, mock_write_output: MagicMock, mock_doc_count: MagicMock): + mock_print_report: MagicMock, mock_write_output: MagicMock, mock_doc_count: MagicMock, + mock_template_migration: MagicMock): mock_doc_count.return_value = IndexDocCount(1, dict()) index_to_create = test_constants.INDEX3_NAME index_with_conflict = test_constants.INDEX2_NAME @@ -58,6 +61,7 @@ def test_run_report(self, mock_fetch_indices: MagicMock, mock_create_indices: Ma mock_doc_count.assert_called() mock_print_report.assert_called_once_with(ANY, 1) mock_write_output.assert_not_called() + mock_template_migration.assert_called_once() @patch('index_operations.doc_count') @patch('metadata_migration.print_report') @@ -142,23 +146,28 @@ def test_missing_output_file_non_report(self): test_input = MetadataMigrationParams(test_constants.PIPELINE_CONFIG_RAW_FILE_PATH) self.assertRaises(ValueError, metadata_migration.run, test_input) + @patch('metadata_migration.template_migration') @patch('index_operations.fetch_all_indices') # Note that mock objects are passed bottom-up from the patch order above - def test_no_indices_in_source(self, mock_fetch_indices: MagicMock): + def test_no_indices_in_source(self, mock_fetch_indices: MagicMock, mock_template_migration: MagicMock): mock_fetch_indices.return_value = {} test_input = MetadataMigrationParams(test_constants.PIPELINE_CONFIG_RAW_FILE_PATH, "dummy") test_result = metadata_migration.run(test_input) mock_fetch_indices.assert_called_once() self.assertEqual(0, test_result.target_doc_count) self.assertEqual(0, len(test_result.migration_indices)) + # Templates are still migrated + mock_template_migration.assert_called_once() @patch('metadata_migration.write_output') + @patch('metadata_migration.template_migration') @patch('index_operations.doc_count') @patch('index_operations.create_indices') @patch('index_operations.fetch_all_indices') # Note that mock objects are passed bottom-up from the patch order above def test_failed_indices(self, mock_fetch_indices: MagicMock, mock_create_indices: MagicMock, - mock_doc_count: MagicMock, mock_write_output: MagicMock): + mock_doc_count: MagicMock, mock_template_migration: MagicMock, + mock_write_output: MagicMock): mock_doc_count.return_value = IndexDocCount(1, dict()) # Setup failed indices test_failed_indices_result = { @@ -172,6 +181,65 @@ def test_failed_indices(self, mock_fetch_indices: MagicMock, mock_create_indices test_input = MetadataMigrationParams(test_constants.PIPELINE_CONFIG_RAW_FILE_PATH, "dummy") self.assertRaises(RuntimeError, metadata_migration.run, test_input) mock_create_indices.assert_called_once_with(test_constants.BASE_INDICES_DATA, ANY) + mock_template_migration.assert_not_called() + + @patch('index_operations.create_index_templates') + @patch('index_operations.fetch_all_index_templates') + @patch('index_operations.create_component_templates') + @patch('index_operations.fetch_all_component_templates') + # Note that mock objects are passed bottom-up from the patch order above + def test_template_migration(self, fetch_component: MagicMock, create_component: MagicMock, fetch_index: MagicMock, + create_index: MagicMock): + source = EndpointInfo(test_constants.SOURCE_ENDPOINT) + target = EndpointInfo(test_constants.TARGET_ENDPOINT) + # Base, successful case, so no failures + create_component.return_value = dict() + create_index.return_value = dict() + # Run test migration + metadata_migration.template_migration(source, target) + # Verify that mocks were invoked as expected + fetch_component.assert_called_once_with(source) + fetch_index.assert_called_once_with(source) + create_component.assert_called_once_with(ANY, target) + create_index.assert_called_once_with(ANY, target) + + @patch('index_operations.create_index_templates') + @patch('index_operations.fetch_all_index_templates') + @patch('index_operations.create_component_templates') + @patch('index_operations.fetch_all_component_templates') + # Note that mock objects are passed bottom-up from the patch order above + def test_component_template_migration_failed(self, fetch_component: MagicMock, create_component: MagicMock, + fetch_index: MagicMock, create_index: MagicMock): + source = EndpointInfo(test_constants.SOURCE_ENDPOINT) + target = EndpointInfo(test_constants.TARGET_ENDPOINT) + # Create component index call returns a failure + create_component.return_value = {"test-template": requests.Timeout()} + # Expect the migration to throw RuntimeError + self.assertRaises(RuntimeError, metadata_migration.template_migration, source, target) + fetch_component.assert_called_once_with(source) + create_component.assert_called_once_with(ANY, target) + # Index migration should never occur + fetch_index.assert_not_called() + create_index.assert_not_called() + + @patch('index_operations.create_index_templates') + @patch('index_operations.fetch_all_index_templates') + @patch('index_operations.create_component_templates') + @patch('index_operations.fetch_all_component_templates') + # Note that mock objects are passed bottom-up from the patch order above + def test_index_template_migration_failed(self, fetch_component: MagicMock, create_component: MagicMock, + fetch_index: MagicMock, create_index: MagicMock): + source = EndpointInfo(test_constants.SOURCE_ENDPOINT) + target = EndpointInfo(test_constants.TARGET_ENDPOINT) + # Create component index call returns a failure + create_index.return_value = {"test-template": requests.Timeout()} + # Expect the migration to throw RuntimeError + self.assertRaises(RuntimeError, metadata_migration.template_migration, source, target) + # All mocks should be invoked + fetch_component.assert_called_once_with(source) + fetch_index.assert_called_once_with(source) + create_component.assert_called_once_with(ANY, target) + create_index.assert_called_once_with(ANY, target) if __name__ == '__main__': diff --git a/FetchMigration/python/tests/test_template_info.py b/FetchMigration/python/tests/test_template_info.py new file mode 100644 index 0000000000..da657ef179 --- /dev/null +++ b/FetchMigration/python/tests/test_template_info.py @@ -0,0 +1,52 @@ +# +# Copyright OpenSearch Contributors +# SPDX-License-Identifier: Apache-2.0 +# +# The OpenSearch Contributors require contributions made to +# this file be licensed under the Apache-2.0 license or a +# compatible open source license. +# +import unittest + +from component_template_info import ComponentTemplateInfo +from index_template_info import IndexTemplateInfo + + +class TestTemplateInfo(unittest.TestCase): + # Template info expects at least a NAME key + def test_bad_template_info(self): + with self.assertRaises(KeyError): + ComponentTemplateInfo({}) + IndexTemplateInfo({}) + + def test_empty_template_info(self): + template_payload: dict = {"name": "test", "template": "dontcare"} + info = ComponentTemplateInfo(template_payload) + self.assertEqual("test", info.get_name()) + self.assertIsNone(info.get_template_definition()) + + def test_component_template_info(self): + test_template = {"test": 1} + template_payload: dict = {"name": "test", "component_template": test_template} + info = ComponentTemplateInfo(template_payload) + self.assertEqual("test", info.get_name()) + self.assertEqual(test_template, info.get_template_definition()) + info = IndexTemplateInfo(template_payload) + self.assertEqual("test", info.get_name()) + # Index template uses a different key, so this should be None + self.assertIsNone(info.get_template_definition()) + + def test_index_template_info(self): + test_template = {"test": 1} + template_payload: dict = {"name": "test", "index_template": test_template} + info = IndexTemplateInfo(template_payload) + self.assertEqual("test", info.get_name()) + self.assertEqual(test_template, info.get_template_definition()) + info = ComponentTemplateInfo(template_payload) + self.assertEqual("test", info.get_name()) + # Component template uses a different key, so this should be None + self.assertIsNone(info.get_template_definition()) + + +if __name__ == '__main__': + unittest.main()