diff --git a/jobs/autoindexer/data_source_handler/git_handler.py b/jobs/autoindexer/data_source_handler/git_handler.py index e22426c..682d2f8 100644 --- a/jobs/autoindexer/data_source_handler/git_handler.py +++ b/jobs/autoindexer/data_source_handler/git_handler.py @@ -75,6 +75,7 @@ def __init__(self, index_name: str, config: dict[str, Any], rag_client: KAITORAG self.exclude_matcher = None self.include_matcher = None self.last_indexed_commit = self.config.get("lastIndexedCommit", "") + self.conditions = self.config.get("conditions", []) factory = get_factory(MatcherImplementation.PURE_PYTHON) if self.exclude_paths: @@ -112,13 +113,22 @@ def update_index(self) -> list[str]: # Clone or fetch repository self._setup_repository() + + # Check for previous error conditions in AutoIndexer status + last_indexing_had_errors = any( + condition.get("type") == "AutoIndexerError" and condition.get("status") == "True" + for condition in self.conditions + ) + + if last_indexing_had_errors: + logger.warning("Previous indexing had errors, performing full indexing to recover") # Determine indexing strategy based on configuration if self.commit: # Specific commit requested - index all files at that commit logger.info(f"Indexing specific commit: {self.commit}") self._index_all_files() - elif self.last_indexed_commit: + elif self.last_indexed_commit and not last_indexing_had_errors: # Incremental indexing - process diff since last indexed commit logger.info(f"Incremental indexing since commit: {self.last_indexed_commit}") self._index_diff_files() diff --git a/jobs/autoindexer/data_source_handler/kusto_handler.py b/jobs/autoindexer/data_source_handler/kusto_handler.py index 082be08..c74a836 100644 --- a/jobs/autoindexer/data_source_handler/kusto_handler.py +++ b/jobs/autoindexer/data_source_handler/kusto_handler.py @@ -62,6 +62,7 @@ def __init__(self, index_name: str, config: dict[str, Any], rag_client: KAITORAG self.language = self.config.get("language") self.initial_query = self.config.get("initialQuery") self.incremental_query = self.config.get("incrementalQuery") + self.conditions = self.config.get("conditions", []) self.errors = [] self.total_time = None @@ -98,8 +99,15 @@ def _build_query(self) -> str: For incremental queries, replaces $LAST_INDEXING_TIMESTAMP with the actual timestamp. """ last_timestamp = self._get_last_checkpoint_time() + + last_indexing_had_errors = any( + condition.get("type") == "AutoIndexerError" and condition.get("status") == "True" + for condition in self.conditions + ) + if last_indexing_had_errors: + logger.warning("Previous indexing had errors, using initial query to recover") - if last_timestamp is None: + if last_timestamp is None or last_indexing_had_errors: # First run: use initial query logger.info("🆕 FIRST RUN: Using initialQuery") query = self.initial_query diff --git a/jobs/autoindexer/main.py b/jobs/autoindexer/main.py index a23b77c..ad6b857 100644 --- a/jobs/autoindexer/main.py +++ b/jobs/autoindexer/main.py @@ -231,6 +231,7 @@ def _apply_crd_config(self, crd_config: dict[str, Any]): "paths": git_config.get("paths", []), "excludePaths": git_config.get("excludePaths", []), "lastIndexedCommit": crd_config.get("status", {}).get("lastIndexedCommit", ""), + "conditions": crd_config.get("status", {}).get("conditions", []) }) logger.info("Updated Git data source configuration from CRD") @@ -238,7 +239,8 @@ def _apply_crd_config(self, crd_config: dict[str, Any]): static_config = ds_config["static"] self.datasource_config.update({ "autoindexer_name": autoindexer_full_name, - "urls": static_config.get("urls", []) + "urls": static_config.get("urls", []), + "conditions": crd_config.get("status", {}).get("conditions", []) }) logger.info("Updated Static data source configuration from CRD") @@ -249,6 +251,7 @@ def _apply_crd_config(self, crd_config: dict[str, Any]): "language": database_config.get("language"), "initialQuery": database_config.get("initialQuery"), "incrementalQuery": database_config.get("incrementalQuery"), + "conditions": crd_config.get("status", {}).get("conditions", []) }) logger.info(f"Updated Database data source configuration from CRD (language: {database_config.get('language')})") diff --git a/jobs/autoindexer/tests/test_conditions.py b/jobs/autoindexer/tests/test_conditions.py new file mode 100644 index 0000000..bc7af0c --- /dev/null +++ b/jobs/autoindexer/tests/test_conditions.py @@ -0,0 +1,630 @@ +# Copyright (c) KAITO authors. +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +import pytest +from unittest.mock import Mock, patch, MagicMock +from datetime import UTC, datetime + +from autoindexer.data_source_handler.git_handler import GitDataSourceHandler +from autoindexer.data_source_handler.static_handler import StaticDataSourceHandler +from autoindexer.data_source_handler.handler import DataSourceError +from autoindexer.k8s.k8s_client import AutoIndexerK8sClient +from autoindexer.rag.rag_client import KAITORAGClient +from autoindexer.main import AutoIndexerJob + + +class TestConditionsInDataSourceConfig: + """Test cases for conditions checks within datasource_config and data_source_handlers.""" + + @pytest.fixture + def mock_rag_client(self): + """Fixture providing a mock RAG client.""" + client = Mock(spec=KAITORAGClient) + client.index_documents.return_value = {"success": True, "indexed": 1} + client.list_documents.return_value = Mock(total_items=5, documents=[]) + return client + + @pytest.fixture + def mock_autoindexer_client(self): + """Fixture providing a mock AutoIndexer K8s client.""" + client = Mock(spec=AutoIndexerK8sClient) + client.get_autoindexer.return_value = { + "status": { + "successfulIndexingCount": 0, + "conditions": [ + { + "type": "AutoIndexerSucceeded", + "status": "True", + "reason": "IndexingCompleted", + "message": "Indexing completed successfully", + "lastTransitionTime": "2024-01-01T00:00:00Z", + "observedGeneration": 1 + } + ] + }, + "metadata": {"generation": 1} + } + client._create_condition.return_value = { + "type": "test", + "status": "True", + "reason": "test", + "message": "test", + "lastTransitionTime": "2024-01-01T00:00:00Z", + "observedGeneration": 1 + } + client.update_autoindexer_status.return_value = True + client.namespace = "test-namespace" + return client + + @pytest.fixture + def conditions_with_error(self): + """Fixture providing conditions that include an error.""" + return [ + { + "type": "AutoIndexerError", + "status": "True", + "reason": "IndexingErrors", + "message": "Previous indexing failed with errors", + "lastTransitionTime": "2024-01-01T00:00:00Z", + "observedGeneration": 1 + }, + { + "type": "AutoIndexerSucceeded", + "status": "False", + "reason": "IndexingFailed", + "message": "Indexing was not successful", + "lastTransitionTime": "2024-01-01T00:00:00Z", + "observedGeneration": 1 + } + ] + + @pytest.fixture + def conditions_success_only(self): + """Fixture providing conditions with successful indexing only.""" + return [ + { + "type": "AutoIndexerSucceeded", + "status": "True", + "reason": "IndexingCompleted", + "message": "Indexing completed successfully", + "lastTransitionTime": "2024-01-01T00:00:00Z", + "observedGeneration": 1 + }, + { + "type": "AutoIndexerError", + "status": "False", + "reason": "IndexingCompleted", + "message": "No errors during indexing", + "lastTransitionTime": "2024-01-01T00:00:00Z", + "observedGeneration": 1 + } + ] + + def test_git_handler_config_includes_conditions(self, mock_rag_client, mock_autoindexer_client): + """Test that GitDataSourceHandler properly stores conditions from config.""" + test_conditions = [ + { + "type": "AutoIndexerError", + "status": "True", + "reason": "TestError", + "message": "Test error message" + } + ] + + config = { + "autoindexer_name": "test-autoindexer", + "repository": "https://github.com/test/repo.git", + "branch": "main", + "conditions": test_conditions + } + + handler = GitDataSourceHandler( + index_name="test-index", + config=config, + rag_client=mock_rag_client, + autoindexer_client=mock_autoindexer_client + ) + + assert handler.conditions == test_conditions + assert len(handler.conditions) == 1 + assert handler.conditions[0]["type"] == "AutoIndexerError" + + def test_static_handler_config_includes_conditions(self, mock_rag_client, mock_autoindexer_client): + """Test that StaticDataSourceHandler properly stores conditions from config.""" + test_conditions = [ + { + "type": "AutoIndexerSucceeded", + "status": "True", + "reason": "IndexingCompleted", + "message": "Previous indexing successful" + } + ] + + config = { + "autoindexer_name": "test-autoindexer", + "urls": ["https://example.com/test.txt"], + "conditions": test_conditions + } + + handler = StaticDataSourceHandler( + index_name="test-index", + config=config, + rag_client=mock_rag_client, + autoindexer_client=mock_autoindexer_client + ) + + # StaticDataSourceHandler doesn't explicitly store conditions yet, + # but they should be available in config + assert "conditions" in handler.config + assert handler.config["conditions"] == test_conditions + + def test_git_handler_error_condition_forces_full_index(self, conditions_with_error, mock_rag_client, mock_autoindexer_client): + """Test that previous error conditions force full indexing instead of incremental.""" + config = { + "autoindexer_name": "test-autoindexer", + "repository": "https://github.com/test/repo.git", + "branch": "main", + "lastIndexedCommit": "abc123", # This would normally trigger incremental indexing + "conditions": conditions_with_error + } + + handler = GitDataSourceHandler( + index_name="test-index", + config=config, + rag_client=mock_rag_client, + autoindexer_client=mock_autoindexer_client + ) + + with patch.object(handler, '_setup_repository') as mock_setup, \ + patch.object(handler, '_index_all_files') as mock_index_all, \ + patch.object(handler, '_index_diff_files') as mock_index_diff, \ + patch('tempfile.mkdtemp', return_value='/tmp/test'), \ + patch('os.path.exists', return_value=True), \ + patch('shutil.rmtree'): + + errors = handler.update_index() + + # Should call full indexing, not incremental + mock_index_all.assert_called_once() + mock_index_diff.assert_not_called() + + assert errors == [] + + def test_git_handler_success_condition_allows_incremental_index(self, conditions_success_only, mock_rag_client, mock_autoindexer_client): + """Test that successful conditions allow incremental indexing.""" + config = { + "autoindexer_name": "test-autoindexer", + "repository": "https://github.com/test/repo.git", + "branch": "main", + "lastIndexedCommit": "abc123", + "conditions": conditions_success_only + } + + handler = GitDataSourceHandler( + index_name="test-index", + config=config, + rag_client=mock_rag_client, + autoindexer_client=mock_autoindexer_client + ) + + with patch.object(handler, '_setup_repository') as mock_setup, \ + patch.object(handler, '_index_all_files') as mock_index_all, \ + patch.object(handler, '_index_diff_files') as mock_index_diff, \ + patch('tempfile.mkdtemp', return_value='/tmp/test'), \ + patch('os.path.exists', return_value=True), \ + patch('shutil.rmtree'): + + errors = handler.update_index() + + # Should call incremental indexing, not full + mock_index_diff.assert_called_once() + mock_index_all.assert_not_called() + + assert errors == [] + + def test_git_handler_no_conditions_defaults_to_incremental_index(self, mock_rag_client, mock_autoindexer_client): + """Test that missing conditions default to incremental indexing when last commit exists.""" + config = { + "autoindexer_name": "test-autoindexer", + "repository": "https://github.com/test/repo.git", + "branch": "main", + "lastIndexedCommit": "abc123" + # No conditions provided + } + + handler = GitDataSourceHandler( + index_name="test-index", + config=config, + rag_client=mock_rag_client, + autoindexer_client=mock_autoindexer_client + ) + + with patch.object(handler, '_setup_repository') as mock_setup, \ + patch.object(handler, '_index_all_files') as mock_index_all, \ + patch.object(handler, '_index_diff_files') as mock_index_diff, \ + patch('tempfile.mkdtemp', return_value='/tmp/test'), \ + patch('os.path.exists', return_value=True), \ + patch('shutil.rmtree'): + + errors = handler.update_index() + + # Should call incremental indexing + mock_index_diff.assert_called_once() + mock_index_all.assert_not_called() + + def test_git_handler_no_last_commit_forces_full_index(self, conditions_success_only, mock_rag_client, mock_autoindexer_client): + """Test that missing lastIndexedCommit forces full indexing regardless of conditions.""" + config = { + "autoindexer_name": "test-autoindexer", + "repository": "https://github.com/test/repo.git", + "branch": "main", + "conditions": conditions_success_only + # No lastIndexedCommit + } + + handler = GitDataSourceHandler( + index_name="test-index", + config=config, + rag_client=mock_rag_client, + autoindexer_client=mock_autoindexer_client + ) + + with patch.object(handler, '_setup_repository') as mock_setup, \ + patch.object(handler, '_index_all_files') as mock_index_all, \ + patch.object(handler, '_index_diff_files') as mock_index_diff, \ + patch('tempfile.mkdtemp', return_value='/tmp/test'), \ + patch('os.path.exists', return_value=True), \ + patch('shutil.rmtree'): + + errors = handler.update_index() + + # Should call full indexing + mock_index_all.assert_called_once() + mock_index_diff.assert_not_called() + + @patch.dict('os.environ', { + 'AUTOINDEXER_NAME': 'test-autoindexer', + 'NAMESPACE': 'test-namespace' + }) + @patch('autoindexer.main.AutoIndexerK8sClient') + @patch('autoindexer.main.KAITORAGClient') + @patch('autoindexer.main.GitDataSourceHandler') + def test_main_applies_conditions_to_git_config(self, mock_git_handler_class, mock_rag_client_class, mock_k8s_client_class, mock_rag_client, mock_autoindexer_client): + """Test that main.py properly applies conditions from CRD to Git datasource_config.""" + crd_config = { + "spec": { + "indexName": "test-index", + "ragEngine": "test-rag", + "dataSource": { + "type": "Git", + "git": { + "repository": "https://github.com/test/repo.git", + "branch": "main" + } + } + }, + "status": { + "lastIndexedCommit": "abc123", + "conditions": [ + { + "type": "AutoIndexerError", + "status": "True", + "reason": "IndexingErrors", + "message": "Previous indexing had errors" + } + ] + } + } + + # Setup the mocks + mock_k8s_client_instance = Mock() + mock_k8s_client_instance.get_autoindexer.return_value = crd_config + mock_k8s_client_instance.namespace = "test-namespace" + mock_k8s_client_class.return_value = mock_k8s_client_instance + + mock_rag_client_instance = Mock() + mock_rag_client_class.return_value = mock_rag_client_instance + + mock_git_handler_instance = Mock() + mock_git_handler_class.return_value = mock_git_handler_instance + + # Create the job - should not raise any exceptions + job = AutoIndexerJob() + + # Verify the Git handler was called with correct config including conditions + mock_git_handler_class.assert_called_once() + call_args = mock_git_handler_class.call_args + config = call_args[1]['config'] + + assert "conditions" in config + assert len(config["conditions"]) == 1 + assert config["conditions"][0]["type"] == "AutoIndexerError" + assert config["conditions"][0]["status"] == "True" + + @patch.dict('os.environ', { + 'AUTOINDEXER_NAME': 'test-autoindexer', + 'NAMESPACE': 'test-namespace' + }) + @patch('autoindexer.main.AutoIndexerK8sClient') + @patch('autoindexer.main.KAITORAGClient') + @patch('autoindexer.main.StaticDataSourceHandler') + def test_main_applies_conditions_to_static_config(self, mock_static_handler_class, mock_rag_client_class, mock_k8s_client_class, mock_rag_client, mock_autoindexer_client): + """Test that main.py properly applies conditions from CRD to Static datasource_config.""" + crd_config = { + "spec": { + "indexName": "test-index", + "ragEngine": "test-rag", + "dataSource": { + "type": "Static", + "static": { + "urls": ["https://example.com/test.txt"] + } + } + }, + "status": { + "conditions": [ + { + "type": "AutoIndexerSucceeded", + "status": "True", + "reason": "IndexingCompleted", + "message": "Previous indexing completed successfully" + } + ] + } + } + + # Setup the mocks + mock_k8s_client_instance = Mock() + mock_k8s_client_instance.get_autoindexer.return_value = crd_config + mock_k8s_client_instance.namespace = "test-namespace" + mock_k8s_client_class.return_value = mock_k8s_client_instance + + mock_rag_client_instance = Mock() + mock_rag_client_class.return_value = mock_rag_client_instance + + mock_static_handler_instance = Mock() + mock_static_handler_class.return_value = mock_static_handler_instance + + # Create the job - should not raise any exceptions + job = AutoIndexerJob() + + # Verify the Static handler was called with correct config including conditions + mock_static_handler_class.assert_called_once() + call_args = mock_static_handler_class.call_args + config = call_args[1]['config'] + + assert "conditions" in config + assert len(config["conditions"]) == 1 + assert config["conditions"][0]["type"] == "AutoIndexerSucceeded" + assert config["conditions"][0]["status"] == "True" + + @patch.dict('os.environ', { + 'AUTOINDEXER_NAME': 'test-autoindexer', + 'NAMESPACE': 'test-namespace' + }) + @patch('autoindexer.main.AutoIndexerK8sClient') + @patch('autoindexer.main.KAITORAGClient') + @patch('autoindexer.data_source_handler.kusto_handler.KustoDataSourceHandler') + def test_main_applies_conditions_to_database_config(self, mock_kusto_handler_class, mock_rag_client_class, mock_k8s_client_class, mock_rag_client, mock_autoindexer_client): + """Test that main.py properly applies conditions from CRD to Database datasource_config.""" + crd_config = { + "spec": { + "indexName": "test-index", + "ragEngine": "test-rag", + "dataSource": { + "type": "Database", + "database": { + "language": "kql", + "initialQuery": "SELECT * FROM table" + } + } + }, + "status": { + "conditions": [ + { + "type": "AutoIndexerError", + "status": "False", + "reason": "IndexingCompleted", + "message": "No errors in previous indexing" + } + ] + } + } + + # Setup the mocks + mock_k8s_client_instance = Mock() + mock_k8s_client_instance.get_autoindexer.return_value = crd_config + mock_k8s_client_instance.namespace = "test-namespace" + mock_k8s_client_class.return_value = mock_k8s_client_instance + + mock_rag_client_instance = Mock() + mock_rag_client_class.return_value = mock_rag_client_instance + + mock_kusto_handler_instance = Mock() + mock_kusto_handler_class.return_value = mock_kusto_handler_instance + + # Create the job - should not raise any exceptions + job = AutoIndexerJob() + + # Verify the Kusto handler was called with correct config including conditions + mock_kusto_handler_class.assert_called_once() + call_args = mock_kusto_handler_class.call_args + config = call_args[1]['config'] + + assert "conditions" in config + assert len(config["conditions"]) == 1 + assert config["conditions"][0]["type"] == "AutoIndexerError" + assert config["conditions"][0]["status"] == "False" + + def test_base_handler_preserves_existing_conditions(self, mock_rag_client, mock_autoindexer_client): + """Test that base handler preserves existing conditions when creating status updates.""" + existing_conditions = [ + { + "type": "AutoIndexerScheduled", + "status": "True", + "reason": "JobScheduled", + "message": "Indexing job was scheduled", + "lastTransitionTime": "2024-01-01T00:00:00Z" + }, + { + "type": "ResourceReady", + "status": "True", + "reason": "ResourcesAvailable", + "message": "All required resources are ready" + } + ] + + mock_autoindexer_client.get_autoindexer.return_value = { + "status": { + "conditions": existing_conditions + }, + "metadata": {"generation": 1} + } + + # Setup proper _create_condition mock that returns different conditions based on type + def create_condition_side_effect(condition_type, status, reason, message, **kwargs): + return { + "type": condition_type, + "status": status, + "reason": reason, + "message": message, + "lastTransitionTime": "2024-01-01T00:00:00Z", + "observedGeneration": 1 + } + mock_autoindexer_client._create_condition.side_effect = create_condition_side_effect + + config = { + "autoindexer_name": "test-autoindexer", + "repository": "https://github.com/test/repo.git" + } + + handler = GitDataSourceHandler( + index_name="test-index", + config=config, + rag_client=mock_rag_client, + autoindexer_client=mock_autoindexer_client + ) + + # Create a status update + status_update = handler._create_base_autoindexer_status_update( + index_name="test-index", + autoindexer_name="test-autoindexer", + rag_client=mock_rag_client, + autoindexer_client=mock_autoindexer_client, + errors=[], + indexing_duration_seconds=30 + ) + + # Check that existing conditions are preserved and new ones are added + condition_types = [condition["type"] for condition in status_update["conditions"]] + assert "AutoIndexerScheduled" in condition_types + assert "ResourceReady" in condition_types + assert "AutoIndexerSucceeded" in condition_types + assert "AutoIndexerError" in condition_types + + def test_base_handler_updates_existing_condition_type(self, mock_rag_client, mock_autoindexer_client): + """Test that base handler updates existing condition when same type already exists.""" + existing_conditions = [ + { + "type": "AutoIndexerError", + "status": "True", + "reason": "PreviousError", + "message": "Previous indexing failed", + "lastTransitionTime": "2024-01-01T00:00:00Z" + } + ] + + mock_autoindexer_client.get_autoindexer.return_value = { + "status": { + "conditions": existing_conditions + }, + "metadata": {"generation": 1} + } + + # Setup proper _create_condition mock that returns different conditions based on type + def create_condition_side_effect(condition_type, status, reason, message, **kwargs): + return { + "type": condition_type, + "status": status, + "reason": reason, + "message": message, + "lastTransitionTime": "2024-01-01T00:00:00Z", + "observedGeneration": 1 + } + mock_autoindexer_client._create_condition.side_effect = create_condition_side_effect + + config = { + "autoindexer_name": "test-autoindexer", + "repository": "https://github.com/test/repo.git" + } + + handler = GitDataSourceHandler( + index_name="test-index", + config=config, + rag_client=mock_rag_client, + autoindexer_client=mock_autoindexer_client + ) + + # Create status update - should update the existing AutoIndexerError condition + status_update = handler._create_base_autoindexer_status_update( + index_name="test-index", + autoindexer_name="test-autoindexer", + rag_client=mock_rag_client, + autoindexer_client=mock_autoindexer_client, + errors=[], # No errors this time + indexing_duration_seconds=30 + ) + + # Find the AutoIndexerError condition - should be updated to False + error_condition = None + for condition in status_update["conditions"]: + if condition["type"] == "AutoIndexerError": + error_condition = condition + break + + assert error_condition is not None + assert error_condition["status"] == "False" + assert error_condition["reason"] == "IndexingCompleted" + assert error_condition["message"] == "No errors during indexing" + + def test_conditions_empty_list_handling(self, mock_rag_client, mock_autoindexer_client): + """Test that handlers properly handle empty conditions list.""" + config = { + "autoindexer_name": "test-autoindexer", + "repository": "https://github.com/test/repo.git", + "lastIndexedCommit": "abc123", + "conditions": [] # Empty conditions list + } + + handler = GitDataSourceHandler( + index_name="test-index", + config=config, + rag_client=mock_rag_client, + autoindexer_client=mock_autoindexer_client + ) + + # Empty conditions should not indicate previous errors + assert handler.conditions == [] + + with patch.object(handler, '_setup_repository') as mock_setup, \ + patch.object(handler, '_index_all_files') as mock_index_all, \ + patch.object(handler, '_index_diff_files') as mock_index_diff, \ + patch('tempfile.mkdtemp', return_value='/tmp/test'), \ + patch('os.path.exists', return_value=True), \ + patch('shutil.rmtree'): + + errors = handler.update_index() + + # Should do incremental indexing since no error conditions exist + mock_index_diff.assert_called_once() + mock_index_all.assert_not_called() \ No newline at end of file diff --git a/jobs/autoindexer/tests/test_git_conditions_logic.py b/jobs/autoindexer/tests/test_git_conditions_logic.py new file mode 100644 index 0000000..e8b9839 --- /dev/null +++ b/jobs/autoindexer/tests/test_git_conditions_logic.py @@ -0,0 +1,400 @@ +# Copyright (c) KAITO authors. +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +import pytest +from unittest.mock import Mock, patch +from datetime import UTC, datetime + +from autoindexer.data_source_handler.git_handler import GitDataSourceHandler +from autoindexer.k8s.k8s_client import AutoIndexerK8sClient +from autoindexer.rag.rag_client import KAITORAGClient + + +class TestGitHandlerConditionsLogic: + """Specific tests for how GitDataSourceHandler processes conditions to make indexing decisions.""" + + @pytest.fixture + def mock_rag_client(self): + """Mock RAG client.""" + client = Mock(spec=KAITORAGClient) + client.list_documents.return_value = Mock(total_items=5, documents=[]) + return client + + @pytest.fixture + def mock_autoindexer_client(self): + """Mock AutoIndexer K8s client.""" + client = Mock(spec=AutoIndexerK8sClient) + client.get_autoindexer.return_value = { + "status": {"conditions": []}, + "metadata": {"generation": 1} + } + client._create_condition.return_value = { + "type": "test", "status": "True", "reason": "test", "message": "test" + } + client.update_autoindexer_status.return_value = True + client.namespace = "test" + return client + + def test_condition_evaluation_logic_with_error_true(self, mock_rag_client, mock_autoindexer_client): + """Test the specific condition evaluation logic for AutoIndexerError=True.""" + config = { + "autoindexer_name": "test-autoindexer", + "repository": "https://github.com/test/repo.git", + "lastIndexedCommit": "abc123", + "conditions": [ + { + "type": "AutoIndexerError", + "status": "True", + "reason": "IndexingErrors", + "message": "Network timeout during indexing" + }, + { + "type": "AutoIndexerSucceeded", + "status": "False", + "reason": "IndexingFailed", + "message": "Indexing was not successful" + } + ] + } + + handler = GitDataSourceHandler( + index_name="test-index", + config=config, + rag_client=mock_rag_client, + autoindexer_client=mock_autoindexer_client + ) + + # Test the actual condition evaluation logic from the code + last_indexing_had_errors = any( + condition.get("type") == "AutoIndexerError" and condition.get("status") == "True" + for condition in handler.conditions + ) + + assert last_indexing_had_errors is True + + def test_condition_evaluation_logic_with_error_false(self, mock_rag_client, mock_autoindexer_client): + """Test the condition evaluation logic for AutoIndexerError=False.""" + config = { + "autoindexer_name": "test-autoindexer", + "repository": "https://github.com/test/repo.git", + "lastIndexedCommit": "abc123", + "conditions": [ + { + "type": "AutoIndexerError", + "status": "False", + "reason": "IndexingCompleted", + "message": "No errors during indexing" + }, + { + "type": "AutoIndexerSucceeded", + "status": "True", + "reason": "IndexingCompleted", + "message": "Indexing completed successfully" + } + ] + } + + handler = GitDataSourceHandler( + index_name="test-index", + config=config, + rag_client=mock_rag_client, + autoindexer_client=mock_autoindexer_client + ) + + # Test the condition evaluation logic + last_indexing_had_errors = any( + condition.get("type") == "AutoIndexerError" and condition.get("status") == "True" + for condition in handler.conditions + ) + + assert last_indexing_had_errors is False + + def test_condition_evaluation_with_multiple_error_conditions(self, mock_rag_client, mock_autoindexer_client): + """Test condition evaluation when multiple AutoIndexerError conditions exist.""" + config = { + "autoindexer_name": "test-autoindexer", + "repository": "https://github.com/test/repo.git", + "lastIndexedCommit": "abc123", + "conditions": [ + { + "type": "AutoIndexerError", + "status": "False", + "reason": "IndexingCompleted", + "message": "Previous error resolved" + }, + { + "type": "AutoIndexerError", + "status": "True", + "reason": "IndexingErrors", + "message": "Recent error occurred" + } + ] + } + + handler = GitDataSourceHandler( + index_name="test-index", + config=config, + rag_client=mock_rag_client, + autoindexer_client=mock_autoindexer_client + ) + + # Should detect errors if ANY AutoIndexerError condition has status=True + last_indexing_had_errors = any( + condition.get("type") == "AutoIndexerError" and condition.get("status") == "True" + for condition in handler.conditions + ) + + assert last_indexing_had_errors is True + + def test_condition_evaluation_with_no_error_conditions(self, mock_rag_client, mock_autoindexer_client): + """Test condition evaluation when no AutoIndexerError conditions exist.""" + config = { + "autoindexer_name": "test-autoindexer", + "repository": "https://github.com/test/repo.git", + "lastIndexedCommit": "abc123", + "conditions": [ + { + "type": "AutoIndexerSucceeded", + "status": "True", + "reason": "IndexingCompleted", + "message": "Indexing completed successfully" + }, + { + "type": "ResourceReady", + "status": "True", + "reason": "ResourcesAvailable", + "message": "All required resources are ready" + } + ] + } + + handler = GitDataSourceHandler( + index_name="test-index", + config=config, + rag_client=mock_rag_client, + autoindexer_client=mock_autoindexer_client + ) + + # Should not detect errors when no AutoIndexerError conditions exist + last_indexing_had_errors = any( + condition.get("type") == "AutoIndexerError" and condition.get("status") == "True" + for condition in handler.conditions + ) + + assert last_indexing_had_errors is False + + def test_condition_evaluation_case_sensitivity(self, mock_rag_client, mock_autoindexer_client): + """Test that condition evaluation is case-sensitive as expected.""" + config = { + "autoindexer_name": "test-autoindexer", + "repository": "https://github.com/test/repo.git", + "lastIndexedCommit": "abc123", + "conditions": [ + { + "type": "autoindexererror", # lowercase + "status": "True", + "reason": "IndexingErrors", + "message": "Error condition" + } + ] + } + + handler = GitDataSourceHandler( + index_name="test-index", + config=config, + rag_client=mock_rag_client, + autoindexer_client=mock_autoindexer_client + ) + + # Should NOT detect errors because the type doesn't match exactly + last_indexing_had_errors = any( + condition.get("type") == "AutoIndexerError" and condition.get("status") == "True" + for condition in handler.conditions + ) + + assert last_indexing_had_errors is False + + def test_condition_status_case_sensitivity(self, mock_rag_client, mock_autoindexer_client): + """Test that condition status evaluation is case-sensitive.""" + config = { + "autoindexer_name": "test-autoindexer", + "repository": "https://github.com/test/repo.git", + "lastIndexedCommit": "abc123", + "conditions": [ + { + "type": "AutoIndexerError", + "status": "true", # lowercase + "reason": "IndexingErrors", + "message": "Error condition" + } + ] + } + + handler = GitDataSourceHandler( + index_name="test-index", + config=config, + rag_client=mock_rag_client, + autoindexer_client=mock_autoindexer_client + ) + + # Should NOT detect errors because status doesn't match "True" exactly + last_indexing_had_errors = any( + condition.get("type") == "AutoIndexerError" and condition.get("status") == "True" + for condition in handler.conditions + ) + + assert last_indexing_had_errors is False + + def test_condition_malformed_structure_handling(self, mock_rag_client, mock_autoindexer_client): + """Test that malformed condition structures are handled gracefully.""" + config = { + "autoindexer_name": "test-autoindexer", + "repository": "https://github.com/test/repo.git", + "lastIndexedCommit": "abc123", + "conditions": [ + { + # Missing 'type' field + "status": "True", + "reason": "IndexingErrors" + }, + { + "type": "AutoIndexerError", + # Missing 'status' field + "reason": "IndexingErrors" + }, + { + "type": "AutoIndexerError", + "status": None, # Null status + "reason": "IndexingErrors" + } + ] + } + + handler = GitDataSourceHandler( + index_name="test-index", + config=config, + rag_client=mock_rag_client, + autoindexer_client=mock_autoindexer_client + ) + + # Should handle malformed conditions gracefully and not detect errors + last_indexing_had_errors = any( + condition.get("type") == "AutoIndexerError" and condition.get("status") == "True" + for condition in handler.conditions + ) + + assert last_indexing_had_errors is False + + @patch('tempfile.mkdtemp', return_value='/tmp/test') + @patch('os.path.exists', return_value=True) + @patch('shutil.rmtree') + def test_full_workflow_error_condition_forces_full_indexing(self, mock_rmtree, mock_exists, mock_mkdtemp, + mock_rag_client, mock_autoindexer_client): + """Integration test: Error condition in realistic scenario forces full indexing.""" + # Simulate previous indexing that failed due to network issues + config = { + "autoindexer_name": "production-docs", + "repository": "https://github.com/company/docs.git", + "branch": "main", + "lastIndexedCommit": "abc123def456", # This commit exists and indexing was attempted + "conditions": [ + { + "type": "AutoIndexerError", + "status": "True", + "reason": "NetworkTimeout", + "message": "Failed to clone repository: connection timeout after 30s", + "lastTransitionTime": "2024-01-01T10:30:00Z", + "observedGeneration": 2 + }, + { + "type": "AutoIndexerSucceeded", + "status": "False", + "reason": "IndexingFailed", + "message": "Indexing was interrupted by error", + "lastTransitionTime": "2024-01-01T10:30:00Z", + "observedGeneration": 2 + } + ] + } + + handler = GitDataSourceHandler( + index_name="docs-index", + config=config, + rag_client=mock_rag_client, + autoindexer_client=mock_autoindexer_client + ) + + with patch.object(handler, '_setup_repository') as mock_setup, \ + patch.object(handler, '_index_all_files') as mock_index_all, \ + patch.object(handler, '_index_diff_files') as mock_index_diff: + + # Execute the update_index method + errors = handler.update_index() + + # Verify that despite having lastIndexedCommit, it chose full indexing due to error condition + mock_index_all.assert_called_once() # Full indexing was triggered + mock_index_diff.assert_not_called() # Incremental indexing was NOT triggered + + assert errors == [] # No errors in this run + + @patch('tempfile.mkdtemp', return_value='/tmp/test') + @patch('os.path.exists', return_value=True) + @patch('shutil.rmtree') + def test_full_workflow_success_condition_allows_incremental_indexing(self, mock_rmtree, mock_exists, mock_mkdtemp, + mock_rag_client, mock_autoindexer_client): + """Integration test: Success condition allows incremental indexing.""" + # Simulate previous indexing that completed successfully + config = { + "autoindexer_name": "production-docs", + "repository": "https://github.com/company/docs.git", + "branch": "main", + "lastIndexedCommit": "abc123def456", + "conditions": [ + { + "type": "AutoIndexerSucceeded", + "status": "True", + "reason": "IndexingCompleted", + "message": "Successfully indexed 150 documents from 45 files", + "lastTransitionTime": "2024-01-01T09:00:00Z", + "observedGeneration": 1 + }, + { + "type": "AutoIndexerError", + "status": "False", + "reason": "IndexingCompleted", + "message": "No errors encountered during indexing", + "lastTransitionTime": "2024-01-01T09:00:00Z", + "observedGeneration": 1 + } + ] + } + + handler = GitDataSourceHandler( + index_name="docs-index", + config=config, + rag_client=mock_rag_client, + autoindexer_client=mock_autoindexer_client + ) + + with patch.object(handler, '_setup_repository') as mock_setup, \ + patch.object(handler, '_index_all_files') as mock_index_all, \ + patch.object(handler, '_index_diff_files') as mock_index_diff: + + # Execute the update_index method + errors = handler.update_index() + + # Verify that incremental indexing was chosen due to success conditions + mock_index_diff.assert_called_once() # Incremental indexing was triggered + mock_index_all.assert_not_called() # Full indexing was NOT triggered + + assert errors == [] \ No newline at end of file diff --git a/jobs/autoindexer/tests/test_main_conditions_integration.py b/jobs/autoindexer/tests/test_main_conditions_integration.py new file mode 100644 index 0000000..cd4ba92 --- /dev/null +++ b/jobs/autoindexer/tests/test_main_conditions_integration.py @@ -0,0 +1,341 @@ +# Copyright (c) KAITO authors. +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +import pytest +from unittest.mock import Mock, patch + +from autoindexer.main import AutoIndexerJob + + +class TestMainConditionsIntegration: + """Integration tests for conditions handling in main.py AutoIndexerJob.""" + + @pytest.fixture + def mock_k8s_client(self): + """Mock Kubernetes client.""" + client = Mock() + client.namespace = "test-namespace" + return client + + @pytest.fixture + def mock_rag_client(self): + """Mock RAG client.""" + client = Mock() + return client + + @patch.dict('os.environ', { + 'AUTOINDEXER_NAME': 'test-autoindexer', + 'NAMESPACE': 'test-namespace' + }) + @patch('autoindexer.main.AutoIndexerK8sClient') + @patch('autoindexer.main.KAITORAGClient') + @patch('autoindexer.main.GitDataSourceHandler') + def test_crd_conditions_applied_to_git_config(self, mock_git_handler, mock_rag_client_class, mock_k8s_client_class): + """Test conditions from CRD are properly applied to Git datasource config.""" + # Setup mock CRD response with conditions + mock_k8s_client = Mock() + mock_k8s_client.namespace = "test-namespace" + mock_k8s_client.get_autoindexer.return_value = { + "spec": { + "indexName": "test-index", + "ragEngine": "test-rag", + "dataSource": { + "type": "Git", + "git": { + "repository": "https://github.com/test/repo.git", + "branch": "main", + "paths": ["/src"] + } + }, + "credentials": { + "type": "SecretRef" + } + }, + "status": { + "lastIndexedCommit": "abc123def", + "conditions": [ + { + "type": "AutoIndexerError", + "status": "True", + "reason": "IndexingErrors", + "message": "Previous indexing failed with network timeout", + "lastTransitionTime": "2024-01-01T10:00:00Z", + "observedGeneration": 2 + }, + { + "type": "AutoIndexerSucceeded", + "status": "False", + "reason": "IndexingFailed", + "message": "Indexing did not complete successfully", + "lastTransitionTime": "2024-01-01T10:00:00Z", + "observedGeneration": 2 + } + ] + } + } + mock_k8s_client_class.return_value = mock_k8s_client + + # Setup RAG client mock + mock_rag_client = Mock() + mock_rag_client_class.return_value = mock_rag_client + + # Setup Git handler mock + mock_handler_instance = Mock() + mock_git_handler.return_value = mock_handler_instance + + # Initialize AutoIndexerJob + job = AutoIndexerJob() + + # Verify the Git handler was called with correct config including conditions + mock_git_handler.assert_called_once() + call_args = mock_git_handler.call_args + + # Check that config contains conditions + config = call_args[1]['config'] # keyword arguments + assert 'conditions' in config + assert len(config['conditions']) == 2 + + # Verify first condition (AutoIndexerError) + error_condition = config['conditions'][0] + assert error_condition['type'] == 'AutoIndexerError' + assert error_condition['status'] == 'True' + assert error_condition['reason'] == 'IndexingErrors' + assert 'network timeout' in error_condition['message'] + + # Verify second condition (AutoIndexerSucceeded) + success_condition = config['conditions'][1] + assert success_condition['type'] == 'AutoIndexerSucceeded' + assert success_condition['status'] == 'False' + assert success_condition['reason'] == 'IndexingFailed' + + # Verify other expected config values were also passed through + assert config['repository'] == 'https://github.com/test/repo.git' + assert config['branch'] == 'main' + assert config['paths'] == ['/src'] + assert config['lastIndexedCommit'] == 'abc123def' + + @patch.dict('os.environ', { + 'AUTOINDEXER_NAME': 'test-autoindexer', + 'NAMESPACE': 'test-namespace' + }) + @patch('autoindexer.main.AutoIndexerK8sClient') + @patch('autoindexer.main.KAITORAGClient') + @patch('autoindexer.main.StaticDataSourceHandler') + def test_crd_conditions_applied_to_static_config(self, mock_static_handler, mock_rag_client_class, mock_k8s_client_class): + """Test conditions from CRD are properly applied to Static datasource config.""" + # Setup mock CRD response with conditions + mock_k8s_client = Mock() + mock_k8s_client.namespace = "test-namespace" + mock_k8s_client.get_autoindexer.return_value = { + "spec": { + "indexName": "test-index", + "ragEngine": "test-rag", + "dataSource": { + "type": "Static", + "static": { + "urls": [ + "https://raw.githubusercontent.com/test/repo/main/README.md", + "https://docs.example.com/api-guide.pdf" + ] + } + } + }, + "status": { + "conditions": [ + { + "type": "AutoIndexerSucceeded", + "status": "True", + "reason": "IndexingCompleted", + "message": "All documents indexed successfully", + "lastTransitionTime": "2024-01-01T12:00:00Z", + "observedGeneration": 1 + }, + { + "type": "AutoIndexerError", + "status": "False", + "reason": "IndexingCompleted", + "message": "No errors encountered during indexing", + "lastTransitionTime": "2024-01-01T12:00:00Z", + "observedGeneration": 1 + } + ] + } + } + mock_k8s_client_class.return_value = mock_k8s_client + + # Setup RAG client mock + mock_rag_client = Mock() + mock_rag_client_class.return_value = mock_rag_client + + # Setup Static handler mock + mock_handler_instance = Mock() + mock_static_handler.return_value = mock_handler_instance + + # Initialize AutoIndexerJob + job = AutoIndexerJob() + + # Verify the Static handler was called with correct config including conditions + mock_static_handler.assert_called_once() + call_args = mock_static_handler.call_args + + # Check that config contains conditions + config = call_args[1]['config'] # keyword arguments + assert 'conditions' in config + assert len(config['conditions']) == 2 + + # Verify conditions are preserved correctly + success_condition = next(c for c in config['conditions'] if c['type'] == 'AutoIndexerSucceeded') + assert success_condition['status'] == 'True' + assert success_condition['reason'] == 'IndexingCompleted' + assert 'successfully' in success_condition['message'] + + error_condition = next(c for c in config['conditions'] if c['type'] == 'AutoIndexerError') + assert error_condition['status'] == 'False' + assert 'No errors' in error_condition['message'] + + # Verify other config values + assert config['urls'] == [ + "https://raw.githubusercontent.com/test/repo/main/README.md", + "https://docs.example.com/api-guide.pdf" + ] + + @patch.dict('os.environ', { + 'AUTOINDEXER_NAME': 'test-autoindexer', + 'NAMESPACE': 'test-namespace' + }) + @patch('autoindexer.main.AutoIndexerK8sClient') + @patch('autoindexer.main.KAITORAGClient') + def test_crd_missing_conditions_handled_gracefully(self, mock_rag_client_class, mock_k8s_client_class): + """Test that missing conditions in CRD are handled gracefully.""" + # Setup mock CRD response with no conditions + mock_k8s_client = Mock() + mock_k8s_client.namespace = "test-namespace" + mock_k8s_client.get_autoindexer.return_value = { + "spec": { + "indexName": "test-index", + "ragEngine": "test-rag", + "dataSource": { + "type": "Git", + "git": { + "repository": "https://github.com/test/repo.git" + } + } + }, + "status": { + # No conditions field + } + } + mock_k8s_client_class.return_value = mock_k8s_client + + # Setup RAG client mock + mock_rag_client = Mock() + mock_rag_client_class.return_value = mock_rag_client + + with patch('autoindexer.main.GitDataSourceHandler') as mock_git_handler: + mock_handler_instance = Mock() + mock_git_handler.return_value = mock_handler_instance + + # Should not raise any exceptions + job = AutoIndexerJob() + + # Verify handler was still called with empty conditions + call_args = mock_git_handler.call_args + config = call_args[1]['config'] + assert 'conditions' in config + assert config['conditions'] == [] # Should default to empty list + + @patch.dict('os.environ', { + 'AUTOINDEXER_NAME': 'test-autoindexer', + 'NAMESPACE': 'test-namespace' + }) + @patch('autoindexer.main.AutoIndexerK8sClient') + @patch('autoindexer.main.KAITORAGClient') + def test_crd_empty_status_handled_gracefully(self, mock_rag_client_class, mock_k8s_client_class): + """Test that CRD with empty status is handled gracefully.""" + # Setup mock CRD response with empty status + mock_k8s_client = Mock() + mock_k8s_client.namespace = "test-namespace" + mock_k8s_client.get_autoindexer.return_value = { + "spec": { + "indexName": "test-index", + "ragEngine": "test-rag", + "dataSource": { + "type": "Static", + "static": { + "urls": ["https://example.com/test.txt"] + } + } + } + # No status field at all + } + mock_k8s_client_class.return_value = mock_k8s_client + + # Setup RAG client mock + mock_rag_client = Mock() + mock_rag_client_class.return_value = mock_rag_client + + with patch('autoindexer.main.StaticDataSourceHandler') as mock_static_handler: + mock_handler_instance = Mock() + mock_static_handler.return_value = mock_handler_instance + + # Should not raise any exceptions + job = AutoIndexerJob() + + # Verify handler was still called with empty conditions + call_args = mock_static_handler.call_args + config = call_args[1]['config'] + assert 'conditions' in config + assert config['conditions'] == [] + + @patch.dict('os.environ', { + 'AUTOINDEXER_NAME': 'test-autoindexer', + 'NAMESPACE': 'production-namespace' + }) + @patch('autoindexer.main.AutoIndexerK8sClient') + @patch('autoindexer.main.KAITORAGClient') + @patch('autoindexer.main.NAMESPACE', 'production-namespace') + @patch('autoindexer.main.AUTOINDEXER_NAME', 'test-autoindexer') + def test_autoindexer_name_includes_namespace_in_config(self, mock_rag_client_class, mock_k8s_client_class): + """Test that autoindexer_name in config includes namespace for uniqueness.""" + mock_k8s_client = Mock() + mock_k8s_client.namespace = "production-namespace" + mock_k8s_client.get_autoindexer.return_value = { + "spec": { + "indexName": "docs-index", + "ragEngine": "production-rag", + "dataSource": { + "type": "Git", + "git": { + "repository": "https://github.com/company/docs.git" + } + } + }, + "status": { + "conditions": [] + } + } + mock_k8s_client_class.return_value = mock_k8s_client + + mock_rag_client = Mock() + mock_rag_client_class.return_value = mock_rag_client + + with patch('autoindexer.main.GitDataSourceHandler') as mock_git_handler: + mock_handler_instance = Mock() + mock_git_handler.return_value = mock_handler_instance + + job = AutoIndexerJob() + + # Verify autoindexer_name includes namespace prefix from environment variable + call_args = mock_git_handler.call_args + config = call_args[1]['config'] + assert config['autoindexer_name'] == 'production-namespace_test-autoindexer' \ No newline at end of file diff --git a/jobs/autoindexer/tests/test_main_integration.py b/jobs/autoindexer/tests/test_main_integration.py index 504bc68..1a58c24 100644 --- a/jobs/autoindexer/tests/test_main_integration.py +++ b/jobs/autoindexer/tests/test_main_integration.py @@ -124,7 +124,8 @@ def test_init_success_with_static_data_source(self, valid_env_vars, mock_k8s_cli # Verify static handler was created with expected config expected_config = { "autoindexer_name": "test-namespace_test-autoindexer", - "urls": ["https://example.com/doc.txt"] + "urls": ["https://example.com/doc.txt"], + "conditions": [] } mock_static_handler.assert_called_once_with( index_name="test-index", diff --git a/jobs/autoindexer/utils/log.py b/jobs/autoindexer/utils/log.py index ed7fd68..bd43770 100644 --- a/jobs/autoindexer/utils/log.py +++ b/jobs/autoindexer/utils/log.py @@ -28,6 +28,9 @@ from opentelemetry.sdk.resources import Resource from opentelemetry.semconv.attributes.service_attributes import SERVICE_NAME, SERVICE_VERSION +# Global flag to track if instrumentation has been initialized +_instrumentation_initialized = False + def configure_otel_logging(log_level: str = "INFO", namespace: str = None, autoindexer_name: str = None): """Configure OpenTelemetry native logging for stdout output. @@ -86,8 +89,11 @@ def configure_otel_logging(log_level: str = "INFO", namespace: str = None, autoi root_logger.addHandler(handler) root_logger.setLevel(getattr(logging, log_level.upper(), logging.INFO)) - # Enable automatic instrumentation for standard logging - LoggingInstrumentor().instrument(set_logging_format=True) + # Enable automatic instrumentation for standard logging (only once) + global _instrumentation_initialized + if not _instrumentation_initialized: + LoggingInstrumentor().instrument(set_logging_format=True) + _instrumentation_initialized = True return logger_provider