Enhance tracing context for pythonsdk-tests 23/143123/6
authorFiete Ostkamp <fiete.ostkamp@telekom.de>
Sat, 7 Feb 2026 09:22:26 +0000 (10:22 +0100)
committerFiete Ostkamp <fiete.ostkamp@telekom.de>
Sat, 7 Feb 2026 10:04:52 +0000 (11:04 +0100)
- define spans in central places to connect the disconnected
  traces that are now mostly being produced by the http requests
  the tests are making
- fix tox issue in the pipeline by replacing coala-bears linter
  package which was last updated in  2017
- fix linter warnings

Issue-ID: INT-2345
Change-Id: I53fd7db0c453cb2e3ce58cf69fbac7a30b2a8631
Signed-off-by: Fiete Ostkamp <fiete.ostkamp@telekom.de>
run_test.py
src/onaptests/__init__.py
src/onaptests/scenario/scenario_base.py
src/onaptests/steps/base.py
src/onaptests/steps/onboard/service.py
tox.ini

index 0cd28c0..a00580e 100644 (file)
@@ -1,3 +1,4 @@
+"""Test runner module for ONAP Python SDK tests."""
 import configparser
 import importlib
 import importlib.util
@@ -6,9 +7,12 @@ import os
 import sys
 import glob
 
+from opentelemetry import trace
 from onapsdk.exceptions import ModuleError
 import onaptests.utils.exceptions as onap_test_exceptions
 
+tracer = trace.get_tracer(__name__)
+
 SETTING_FILE_EXCEPTIONS = {
     "clearwater_ims": "clearwater_ims_nomulticloud_settings",
     "basic_cnf": "basic_cnf_yaml_settings",
@@ -23,7 +27,9 @@ MODULES_TO_RELOAD = [
     "onaptests"
 ]
 
+
 def get_entrypoints():
+    """Get entry points from setup.cfg."""
     config = configparser.ConfigParser()
     config.read('setup.cfg')
     entry_points = config['options.entry_points']['xtesting.testcase']
@@ -39,10 +45,12 @@ def get_entrypoints():
         }
     return entry_points_result
 
+
 def check_scenarios(scenarios, entry_points):
+    """Check that all scenarios are registered in entry points."""
     path = importlib.util.find_spec(scenarios.__name__).submodule_search_locations
     modules = glob.glob(f"{path._path[0]}/*.py")
-    all_mods = [ os.path.basename(f)[:-3] for f in modules if os.path.isfile(f) and not f.endswith('__init__.py')]
+    all_mods = [os.path.basename(f)[:-3] for f in modules if os.path.isfile(f) and not f.endswith('__init__.py')]
     for mod_name in all_mods:
         full_mod_name = f"{scenarios.__name__}.{mod_name}"
         if mod_name != "scenario_base":
@@ -55,12 +63,22 @@ def check_scenarios(scenarios, entry_points):
                 raise onap_test_exceptions.TestConfigurationException(
                     f"Scenario defined in {full_mod_name}.py is not added to setup.cfg file")
 
+
 def run_test(test_name, validation, force_cleanup, entry_point):
-    print(f"Configuring {test_name} test")
-    settings_env = "ONAP_PYTHON_SDK_SETTINGS"
-    if force_cleanup:
-        validation_env = "PYTHON_SDK_TESTS_FORCE_CLEANUP"
-        os.environ[validation_env] = "True"
+    """Run a single test scenario."""
+    with tracer.start_as_current_span(
+        f"test.{test_name}",
+        attributes={
+            "test.name": test_name,
+            "test.validation": validation,
+            "test.force_cleanup": force_cleanup
+        }
+    ) as root_span:
+        print(f"Configuring {test_name} test")
+        settings_env = "ONAP_PYTHON_SDK_SETTINGS"
+        if force_cleanup:
+            validation_env = "PYTHON_SDK_TESTS_FORCE_CLEANUP"
+            os.environ[validation_env] = "True"
 
     setting_file_name = f"{test_name}_settings"
     if test_name in SETTING_FILE_EXCEPTIONS:
@@ -98,9 +116,12 @@ def run_test(test_name, validation, force_cleanup, entry_point):
     if validation:
         logger.info(f"Validating {test_name} test")
         test_instance.validate()
+    root_span.set_attribute("test.result", "success")
     return scenarios
 
+
 def validate_scenario_base_class(test_name, scenario, scenarios):
+    """Validate that scenario class properly inherits from ScenarioBase."""
     has_scenario_base = False
     if test_name != scenario.case_name:
         raise onap_test_exceptions.TestConfigurationException(
@@ -119,6 +140,7 @@ def validate_scenario_base_class(test_name, scenario, scenarios):
         raise onap_test_exceptions.TestConfigurationException(
             f"[{test_name}] {scenario.__class__.__name__} class does not inherit from ScenarioBase")
 
+
 def main(argv):
     """Script is used to run one or all the tests.
 
@@ -166,5 +188,6 @@ def main(argv):
         entry_point = entry_points[test_name]
         run_test(test_name, validation, force_cleanup, entry_point)
 
+
 if __name__ == "__main__":
     main(sys.argv[1:])
index 9b917f6..fb3f6d3 100644 (file)
@@ -1 +1,5 @@
 """onaptests package."""
+
+from opentelemetry import trace
+
+tracer = trace.get_tracer(__name__)
index f1b4455..7158906 100644 (file)
@@ -1,6 +1,8 @@
+"""Base scenario classes for ONAP test scenarios."""
 import logging
 import time
 
+from opentelemetry import trace
 from onapsdk.configuration import settings
 from onapsdk.exceptions import SDKException, SettingsError
 from xtesting.core import testcase
@@ -9,6 +11,8 @@ from onaptests.steps.base import BaseStep, YamlTemplateBaseStep
 from onaptests.utils.exceptions import (OnapTestException,
                                         TestConfigurationException)
 
+tracer = trace.get_tracer(__name__)
+
 
 class ScenarioBase(testcase.TestCase):
     """Scenario base class."""
@@ -33,37 +37,57 @@ class ScenarioBase(testcase.TestCase):
         self.test: BaseStep = None
 
     def run(self, **kwargs):
-        """Run scenario and cleanup resources afterwards"""
-        self.start_time = time.time()
-        try:
-            for test_phase in (self.test.execute, self.test.cleanup):
-                phase_name = test_phase.__name__
-                try:
-                    if (phase_name == "cleanup" and settings.CLEANUP_FLAG and
-                            settings.CLEANUP_ACTIVITY_TIMER > 0):
-                        time.sleep(settings.CLEANUP_ACTIVITY_TIMER)
-                    self.__logger.info("%s %s Phase Started",
-                                       self.scenario_name, phase_name.title())
-                    test_phase()
-                    self.result += 50
-                except OnapTestException as exc:
-                    self.__logger.exception("Test Exception %s on %s", str(exc), phase_name)
-                    self.__logger.info("ROOT CAUSE")
-                    self.__logger.info(exc.root_cause)
-                except SDKException as exc:
-                    self.__logger.exception("SDK Exception %s on %s", str(exc), phase_name)
-                    self.__logger.info("ROOT CAUSE")
-                    self.__logger.info(str(exc))
-                except Exception as exc:
-                    self.__logger.exception("General Exception %s on %s", str(exc), phase_name)
-                    if self.general_exception:
-                        exc = ExceptionGroup("General Exceptions", [self.general_exception, exc])  # noqa
-                    self.general_exception = exc
-        finally:
-            self.stop_time = time.time()
-            self.__logger.info(f"{self.scenario_name} Execution {self.result}% Completed")
-        if self.general_exception:
-            raise self.general_exception
+        """Run scenario and cleanup resources afterwards."""
+        with tracer.start_as_current_span(
+            f"scenario.{self.case_name}",
+            attributes={
+                "scenario.name": self.scenario_name,
+                "scenario.case_name": self.case_name
+            }
+        ) as scenario_span:
+            self.start_time = time.time()
+            try:
+                for test_phase in (self.test.execute, self.test.cleanup):
+                    phase_name = test_phase.__name__
+                    with tracer.start_as_current_span(
+                        f"phase.{phase_name}",
+                        attributes={"phase.name": phase_name}
+                    ) as phase_span:
+                        try:
+                            if (phase_name == "cleanup" and settings.CLEANUP_FLAG and
+                                    settings.CLEANUP_ACTIVITY_TIMER > 0):
+                                time.sleep(settings.CLEANUP_ACTIVITY_TIMER)
+                            self.__logger.info("%s %s Phase Started",
+                                               self.scenario_name, phase_name.title())
+                            test_phase()
+                            self.result += 50
+                            phase_span.set_attribute("phase.result", "success")
+                        except OnapTestException as exc:
+                            phase_span.record_exception(exc)
+                            phase_span.set_attribute("phase.result", "failed")
+                            self.__logger.exception("Test Exception %s on %s", str(exc), phase_name)
+                            self.__logger.info("ROOT CAUSE")
+                            self.__logger.info(exc.root_cause)
+                        except SDKException as exc:
+                            phase_span.record_exception(exc)
+                            phase_span.set_attribute("phase.result", "failed")
+                            self.__logger.exception("SDK Exception %s on %s", str(exc), phase_name)
+                            self.__logger.info("ROOT CAUSE")
+                            self.__logger.info(str(exc))
+                        except Exception as exc:
+                            phase_span.record_exception(exc)
+                            phase_span.set_attribute("phase.result", "failed")
+                            self.__logger.exception("General Exception %s on %s", str(exc), phase_name)
+                            if self.general_exception:
+                                exc = ExceptionGroup("General Exceptions", [self.general_exception, exc])  # noqa
+                            self.general_exception = exc
+            finally:
+                self.stop_time = time.time()
+                scenario_span.set_attribute("scenario.result_percentage", self.result)
+                scenario_span.set_attribute("scenario.duration", self.stop_time - self.start_time)
+                self.__logger.info(f"{self.scenario_name} Execution {self.result}% Completed")
+            if self.general_exception:
+                raise self.general_exception
 
     def clean(self):
         """Clean Additional resources if needed."""
@@ -72,13 +96,13 @@ class ScenarioBase(testcase.TestCase):
 
     def validate(self):
         """Validate implementation of the scenario."""
-
         self._validate_service_details()
         self.test.validate_step_implementation()
         self.test.validate_execution()
         self.test.validate_cleanup()
 
     def _validate_service_details(self):
+        """Validate that service details are properly configured."""
         self._check_setting("SERVICE_NAME")
         self._check_setting("SERVICE_DETAILS")
 
@@ -101,6 +125,7 @@ class BaseScenarioStep(BaseStep):
 
     @BaseStep.store_state
     def execute(self) -> None:
+        """Execute base scenario step."""
         super().execute()
 
 
index 9356f4d..3d8c1f4 100644 (file)
@@ -1,3 +1,4 @@
+"""Base step classes for ONAP test scenarios."""
 import functools
 import itertools
 import logging
@@ -7,6 +8,7 @@ import time
 from abc import ABC, abstractmethod
 from typing import Iterator, List, Optional
 
+from opentelemetry import trace
 from onapsdk.aai.business import Customer, ServiceInstance, ServiceSubscription
 from onapsdk.configuration import settings
 from onapsdk.exceptions import SDKException, SettingsError
@@ -19,6 +21,8 @@ from onaptests.utils.exceptions import (OnapTestException,
                                         SubstepExecutionExceptionGroup,
                                         TestConfigurationException)
 
+tracer = trace.get_tracer(__name__)
+
 # pylint: disable=protected-access
 IF_FORCE_CLEANUP = "PYTHON_SDK_TESTS_FORCE_CLEANUP"
 
@@ -41,85 +45,111 @@ class StoreStateHandler(ABC):
                 self._state_execute = True
             initial_exception = None
             error_reason = []
-            try:
-                execution_status: Optional[ReportStepStatus] = ReportStepStatus.FAIL
-                if cleanup:
-                    self._start_cleanup_time = time.time()
-                    try:
-                        if (self._cleanup and self._state_execute and
-                                (not self.has_substeps or self._substeps_executed) and
-                                (self._is_validation_only or
-                                    self.check_preconditions(cleanup=True))):
+
+            operation = "cleanup" if cleanup else "execute"
+            with tracer.start_as_current_span(
+                f"step.{operation}.{self.name}",
+                attributes={
+                    "step.name": self.name,
+                    "step.component": self.component,
+                    "step.description": self.description,
+                    "step.operation": operation,
+                    "step.nesting_level": self._nesting_level,
+                    "step.is_root": self.is_root
+                }
+            ) as step_span:
+                try:
+                    execution_status: Optional[ReportStepStatus] = ReportStepStatus.FAIL
+                    if cleanup:
+                        self._start_cleanup_time = time.time()
+                        try:
+                            if (self._cleanup and self._state_execute and
+                                    (not self.has_substeps or self._substeps_executed) and
+                                    (self._is_validation_only or
+                                        self.check_preconditions(cleanup=True))):
+                                self._log_execution_state("START", cleanup)
+                                if not self._is_validation_only or self._is_force_cleanup:
+                                    fun(self, *args, **kwargs)
+                                self._cleaned_up = True
+                                execution_status = ReportStepStatus.PASS
+                            else:
+                                execution_status = ReportStepStatus.NOT_EXECUTED
+                        except (OnapTestException, SDKException) as test_exc:
+                            initial_exception = test_exc
+                        finally:
+                            self._log_execution_state(execution_status.name, cleanup)
+                            self._cleanup_substeps()
+                        if initial_exception:
+                            new_exception = initial_exception
+                            initial_exception = None
+                            raise new_exception
+                    else:
+                        if self._is_validation_only or self.check_preconditions():
                             self._log_execution_state("START", cleanup)
-                            if not self._is_validation_only or self._is_force_cleanup:
+                            self._execute_substeps()
+                            if not self._is_validation_only:
                                 fun(self, *args, **kwargs)
-                            self._cleaned_up = True
                             execution_status = ReportStepStatus.PASS
+                            self._executed = True
                         else:
                             execution_status = ReportStepStatus.NOT_EXECUTED
-                    except (OnapTestException, SDKException) as test_exc:
-                        initial_exception = test_exc
-                    finally:
-                        self._log_execution_state(execution_status.name, cleanup)
-                        self._cleanup_substeps()
+                except SubstepExecutionException as substep_exc:
+                    step_span.record_exception(substep_exc)
+                    if not cleanup:
+                        execution_status = ReportStepStatus.NOT_EXECUTED
                     if initial_exception:
-                        new_exception = initial_exception
-                        initial_exception = None
-                        raise new_exception
-                else:
-                    if self._is_validation_only or self.check_preconditions():
-                        self._log_execution_state("START", cleanup)
-                        self._execute_substeps()
-                        if not self._is_validation_only:
-                            fun(self, *args, **kwargs)
-                        execution_status = ReportStepStatus.PASS
-                        self._executed = True
+                        substep_exc = OnapTestExceptionGroup("Cleanup Exceptions",
+                                                             [initial_exception, substep_exc])
+                    error_reason = substep_exc.root_cause
+                    raise substep_exc
+                except (OnapTestException, SDKException) as test_exc:
+                    step_span.record_exception(test_exc)
+                    if initial_exception:
+                        test_exc = OnapTestExceptionGroup("Cleanup Exceptions",
+                                                          [initial_exception, test_exc])
+                    if isinstance(test_exc, OnapTestException):
+                        error_reason = test_exc.root_cause
                     else:
-                        execution_status = ReportStepStatus.NOT_EXECUTED
-            except SubstepExecutionException as substep_exc:
-                if not cleanup:
-                    execution_status = ReportStepStatus.NOT_EXECUTED
-                if initial_exception:
-                    substep_exc = OnapTestExceptionGroup("Cleanup Exceptions",
-                                                         [initial_exception, substep_exc])
-                error_reason = substep_exc.root_cause
-                raise substep_exc
-            except (OnapTestException, SDKException) as test_exc:
-                if initial_exception:
-                    test_exc = OnapTestExceptionGroup("Cleanup Exceptions",
-                                                      [initial_exception, test_exc])
-                if isinstance(test_exc, OnapTestException):
-                    error_reason = test_exc.root_cause
-                else:
-                    error_reason = [str(test_exc)]
-                raise test_exc
-            finally:
-                if not cleanup:
-                    self._log_execution_state(execution_status.name, cleanup)
-                if cleanup:
-                    self._cleanup_report = Report(
-                        step_description=self._step_title(cleanup),
-                        step_execution_status=execution_status,
-                        step_execution_duration=time.time() - self._start_cleanup_time,
-                        step_component=self.component,
-                        step_error_reason=error_reason
-                    )
-                else:
-                    if not self._start_execution_time:
-                        if execution_status != ReportStepStatus.NOT_EXECUTED:
-                            self._logger.error("No execution start time saved for %s step. "
-                                               "Fix it by call `super.execute()` "
-                                               "in step class `execute()` method definition",
-                                               self.name)
-                        self._start_execution_time = time.time()
-                    self._execution_report = Report(
-                        step_description=self._step_title(cleanup),
-                        step_execution_status=(execution_status if execution_status else
-                                               ReportStepStatus.FAIL),
-                        step_execution_duration=time.time() - self._start_execution_time,
-                        step_component=self.component,
-                        step_error_reason=error_reason
-                    )
+                        error_reason = [str(test_exc)]
+                    raise test_exc
+                finally:
+                    if not cleanup:
+                        self._log_execution_state(execution_status.name, cleanup)
+
+                    # Set span attributes for final status
+                    if execution_status:
+                        step_span.set_attribute("step.status", execution_status.name)
+                    if error_reason:
+                        step_span.set_attribute("step.error_reasons", str(error_reason))
+
+                    if cleanup:
+                        duration = time.time() - self._start_cleanup_time
+                        step_span.set_attribute("step.duration", duration)
+                        self._cleanup_report = Report(
+                            step_description=self._step_title(cleanup),
+                            step_execution_status=execution_status,
+                            step_execution_duration=duration,
+                            step_component=self.component,
+                            step_error_reason=error_reason
+                        )
+                    else:
+                        if not self._start_execution_time:
+                            if execution_status != ReportStepStatus.NOT_EXECUTED:
+                                self._logger.error("No execution start time saved for %s step. "
+                                                   "Fix it by call `super.execute()` "
+                                                   "in step class `execute()` method definition",
+                                                   self.name)
+                            self._start_execution_time = time.time()
+                        duration = time.time() - self._start_execution_time
+                        step_span.set_attribute("step.duration", duration)
+                        self._execution_report = Report(
+                            step_description=self._step_title(cleanup),
+                            step_execution_status=(execution_status if execution_status else
+                                                   ReportStepStatus.FAIL),
+                            step_execution_duration=duration,
+                            step_component=self.component,
+                            step_error_reason=error_reason
+                        )
             wrapper._is_wrapped = True
         return wrapper
 
@@ -374,7 +404,7 @@ class BaseStep(StoreStateHandler, ABC):
         return True
 
     def _execute_substeps(self) -> None:
-        """Step's action execution.
+        """Execute step's action.
 
         Run all substeps action before it's own action.
         Override this method and remember to call `super().execute()` before.
@@ -388,7 +418,7 @@ class BaseStep(StoreStateHandler, ABC):
                 if step._is_optional:
                     self._logger.info("Step is optional, error ignored, continue test execution")
                 elif step._break_on_error:
-                    raise SubstepExecutionException("", substep_err) # noqa: W0707
+                    raise SubstepExecutionException("", substep_err)  # noqa: W0707
                 else:
                     substep_exceptions.append(substep_err)
         if self._steps:
@@ -416,7 +446,7 @@ class BaseStep(StoreStateHandler, ABC):
                     step._default_cleanup_handler()
             except (OnapTestException, SDKException) as substep_err:
                 try:
-                    raise SubstepExecutionException("", substep_err) # noqa: W0707
+                    raise SubstepExecutionException("", substep_err)  # noqa: W0707
                 except Exception as e:
                     exceptions_to_raise.append(e)
         if len(exceptions_to_raise) > 0:
@@ -425,14 +455,14 @@ class BaseStep(StoreStateHandler, ABC):
             raise SubstepExecutionExceptionGroup("", exceptions_to_raise)
 
     def execute(self) -> None:
-        """Step's execute.
+        """Execute step.
 
         Must be implemented in the steps with store_state decorator
 
         """
 
     def cleanup(self) -> None:
-        """Step's cleanup.
+        """Clean up step.
 
         Not all steps has to have cleanup method
 
@@ -456,7 +486,6 @@ class BaseStep(StoreStateHandler, ABC):
 
     def validate_step_implementation(self):
         """Validate is step addes store_state decorators."""
-
         if not getattr(self.execute, "_is_wrapped", False):
             raise TestConfigurationException(
                 f"{self._step_title()} - store_state decorator not present in execute() method")
@@ -468,7 +497,6 @@ class BaseStep(StoreStateHandler, ABC):
 
     def validate_execution(self):
         """Validate if each step was executed by decorator."""
-
         if self._is_validation_only:
             self._log_execution_state(f"VALIDATE EXECUTION [{self._state_execute}]")
             if not self._state_execute:
@@ -479,7 +507,6 @@ class BaseStep(StoreStateHandler, ABC):
 
     def validate_cleanup(self):
         """Validate if each step was cleaned by decorator."""
-
         if self._is_validation_only:
             for step in reversed(self._steps):
                 step.validate_cleanup()
@@ -496,7 +523,6 @@ class YamlTemplateBaseStep(BaseStep, ABC):
 
     def __init__(self, cleanup: bool):
         """Initialize step."""
-
         super().__init__(cleanup=cleanup)
         self._service_instance: ServiceInstance = None
         self._service_subscription: ServiceSubscription = None
@@ -588,18 +614,28 @@ class DelayStep(BaseStep):
     """Delay step -- useful if some delay between two steps is needed."""
 
     def __init__(self, delay: int, break_on_error=True):
+        """Initialize delay step.
+
+        Args:
+            delay (int): Delay duration in seconds
+            break_on_error (bool): Whether to break on error
+
+        """
         super().__init__(BaseStep.HAS_NO_CLEANUP, break_on_error)
         self.delay: int = delay
 
     @property
     def description(self) -> str:
+        """Step description."""
         return f"Wait for {self.delay} seconds."
 
     @property
     def component(self) -> str:
+        """Component name."""
         return "Python ONAP SDK"
 
     @BaseStep.store_state
     def execute(self):
+        """Execute delay step."""
         super().execute()
         time.sleep(self.delay)
index a35b4da..c28ff8b 100644 (file)
@@ -1,8 +1,10 @@
+"""Service onboarding step module."""
 import time
 from typing import Any, Dict, Iterator
 from urllib.parse import urlencode
 
 import mysql.connector as mysql
+from opentelemetry import trace
 from onapsdk.aai.service_design_and_creation import Model
 from onapsdk.configuration import settings
 from onapsdk.exceptions import InvalidResponse, ResourceNotFound
@@ -24,6 +26,8 @@ from ..base import BaseStep, YamlTemplateBaseStep
 from .pnf import YamlTemplatePnfOnboardStep
 from .vf import YamlTemplateVfOnboardStep
 
+tracer = trace.get_tracer(__name__)
+
 
 class YamlTemplateServiceOnboardStep(YamlTemplateBaseStep):
     """Service onboard using YAML template step."""
@@ -54,6 +58,15 @@ class YamlTemplateServiceOnboardStep(YamlTemplateBaseStep):
         return "SDC"
 
     def check_preconditions(self, cleanup=False) -> bool:
+        """Check preconditions before step execution.
+
+        Args:
+            cleanup (bool): Whether this is cleanup phase
+
+        Returns:
+            bool: True if preconditions are met, False otherwise
+
+        """
         if not super().check_preconditions(cleanup):
             return False
         if cleanup:
@@ -112,20 +125,37 @@ class YamlTemplateServiceOnboardStep(YamlTemplateBaseStep):
                 self.yaml_template[self.service_name]["instantiation_type"])
         else:
             instantiation_type: ServiceInstantiationType = ServiceInstantiationType.A_LA_CARTE
-        try:
-            service: Service = Service.get_by_name(name=self.service_name)
-            if service.distributed:
-                return
-        except ResourceNotFound:
+
+        with tracer.start_as_current_span(
+            "sdc.service.get_or_create",
+            attributes={"service.name": self.service_name}
+        ) as sdc_span:
+            try:
+                service: Service = Service.get_by_name(name=self.service_name)
+                sdc_span.set_attribute("service.existed", True)
+                sdc_span.set_attribute("service.distributed", service.distributed)
+                if service.distributed:
+                    return
+            except ResourceNotFound:
+                sdc_span.set_attribute("service.existed", False)
             self._logger.info("before service create")
             service = Service.create(name=self.service_name,
                                      instantiation_type=instantiation_type,
                                      category=category)
             self._logger.info("after service create")
+            sdc_span.set_attribute("service.instantiation_type", instantiation_type.value)
             self.declare_resources(service)
             self.assign_properties(service)
+
         if service.lifecycle_state != LifecycleState.CERTIFIED:
-            service.lifecycle_operation(LifecycleOperation.CERTIFY)
+            with tracer.start_as_current_span(
+                "sdc.service.certify",
+                attributes={
+                    "service.name": self.service_name,
+                    "service.lifecycle_state": service.lifecycle_state.value
+                }
+            ):
+                service.lifecycle_operation(LifecycleOperation.CERTIFY)
 
     def declare_resources(self, service: Service) -> None:
         """Declare resources.
@@ -193,13 +223,21 @@ class YamlTemplateServiceOnboardStep(YamlTemplateBaseStep):
     @YamlTemplateBaseStep.store_state(cleanup=True)
     def cleanup(self) -> None:
         """Cleanup service onboard step."""
-        try:
-            service: Service = Service.get_by_name(name=self.service_name)
-            if service.lifecycle_state == LifecycleState.CERTIFIED:
-                service.archive()
-            service.delete()
-        except ResourceNotFound:
-            self._logger.info(f"Service {self.service_name} not found")
+        with tracer.start_as_current_span(
+            "sdc.service.delete",
+            attributes={"service.name": self.service_name}
+        ) as cleanup_span:
+            try:
+                service: Service = Service.get_by_name(name=self.service_name)
+                cleanup_span.set_attribute("service.found", True)
+                cleanup_span.set_attribute("service.lifecycle_state", service.lifecycle_state.value)
+                if service.lifecycle_state == LifecycleState.CERTIFIED:
+                    service.archive()
+                service.delete()
+                cleanup_span.set_attribute("service.deleted", True)
+            except ResourceNotFound:
+                cleanup_span.set_attribute("service.found", False)
+                self._logger.info(f"Service {self.service_name} not found")
         super().cleanup()
 
 
@@ -264,16 +302,24 @@ class YamlTemplateServiceDistributionStep(YamlTemplateBaseStep):
     def execute(self):
         """Distribute service."""
         super().execute()
-        service: Service = Service.get_by_name(name=self.service_name)
-        if service:
-            if not service.distributed:
-                service.distribute()
-                self._logger.info(f"Service {self.service_name} distributed successfully.")
+        with tracer.start_as_current_span(
+            "sdc.service.distribute",
+            attributes={"service.name": self.service_name}
+        ) as dist_span:
+            service: Service = Service.get_by_name(name=self.service_name)
+            if service:
+                dist_span.set_attribute("service.found", True)
+                if not service.distributed:
+                    dist_span.set_attribute("service.already_distributed", False)
+                    service.distribute()
+                    self._logger.info(f"Service {self.service_name} distributed successfully.")
+                else:
+                    dist_span.set_attribute("service.already_distributed", True)
+                    self._logger.info(f"Service {self.service_name} is already distributed.")
             else:
-                self._logger.info(f"Service {self.service_name} is already distributed.")
-        else:
-            raise onap_test_exceptions.OnapTestException(f"Service {self.service_name} "
-                                                         f"not found for distribution.")
+                dist_span.set_attribute("service.found", False)
+                raise onap_test_exceptions.OnapTestException(f"Service {self.service_name} "
+                                                             f"not found for distribution.")
 
 
 class BaseServiceDistributionComponentCheckStep(BaseStep):
@@ -361,7 +407,7 @@ class VerifyServiceDistributionInAaiStep(BaseServiceDistributionComponentCheckSt
     """Check service distribution in AAI step."""
 
     class ModelWithGet(Model):
-        """"Workaround to fix """
+        """Workaround to fix."""
 
         @classmethod
         def get_all(cls,
@@ -561,7 +607,6 @@ class VerifyServiceDistributionStatusStep(BaseServiceDistributionComponentCheckS
             notified_module (str): Name of notified module
             component_name (str): Name of the module's component
         """
-
         super().__init__(
             component_name=component_name, break_on_error=False, load_model=False)
         self.component_id = notified_module
diff --git a/tox.ini b/tox.ini
index 2292c42..08075a5 100644 (file)
--- a/tox.ini
+++ b/tox.ini
@@ -5,51 +5,55 @@ skipsdist = true
 requires = pip >= 8
 
 [testenv]
-basepython = python3.9
+basepython = python3.10
 allowlist_externals =
   git
   /bin/sh
   sh
   /bin/bash
   bash
-deps =
-  pyyaml == 3.13
-  bandit == 1.3
-  coala-bears
-  nodeenv
+  xargs
 
 [testenv:json]
+deps =
+skip_install = True
 commands_pre =
     /bin/sh -c "git --no-pager diff HEAD HEAD^ --name-only '*.json' > /tmp/.coalist_json"
 commands =
-# '\ ' at the end of command is needed for a case where above command returns empty list (it adds empty file
-# parameter to '--files' opt
-    /bin/bash -c "coala --non-interactive --disable-caching --no-autoapply-warn json --files $(</tmp/.coalist_json) \ "
+    /bin/bash -c "cat /tmp/.coalist_json | xargs -r python -m json.tool > /dev/null"
 
 [testenv:yaml]
+deps =
+    yamllint
+skip_install = True
 commands_pre =
     /bin/sh -c "git --no-pager diff HEAD HEAD^ --name-only '*.yaml' '*.yml' > /tmp/.coalist_yaml"
 commands =
-# '\ ' at the end of command is needed for a case where above command returns empty list (it adds empty file
-# parameter to '--files' opt
-    /bin/bash -c "coala --non-interactive --disable-caching --no-autoapply-warn yaml --files $(</tmp/.coalist_yaml) \ "
+    /bin/bash -c "cat /tmp/.coalist_yaml | xargs -r yamllint -d relaxed"
 
 [testenv:py]
+deps =
+    flake8
+    flake8-docstrings
+skip_install = True
 commands_pre =
     /bin/sh -c "git --no-pager diff HEAD HEAD^ --name-only '*.py' > /tmp/.coalist_py"
 commands =
-    /bin/bash -c "coala --non-interactive --disable-caching --no-autoapply-warn py --files $(</tmp/.coalist_py) \ "
+    /bin/bash -c "cat /tmp/.coalist_py | xargs -r flake8 --max-line-length=120"
 
 [testenv:md]
+deps =
+    nodeenv
+skip_install = True
 commands_pre =
     nodeenv -p --verbose
-    npm install --global remark-cli
+    npm install --global markdownlint-cli
     /bin/sh -c "git --no-pager diff HEAD HEAD^ --name-only '*.md' > /tmp/.coalist_md"
 commands =
-    /bin/bash -c "coala --non-interactive --disable-caching --no-autoapply-warn md --files $(</tmp/.coalist_md) \ "
+    /bin/bash -c "cat /tmp/.coalist_md | xargs -r markdownlint"
 
 [testenv:pylama]
-basepython = python3.11
+basepython = python3.10
 deps =
     pylama[all]
     -rrequirements.txt
@@ -59,7 +63,7 @@ setenv =
 commands = pylama -o pylama.ini src/
 
 [testenv:validate]
-basepython = python3.11
+basepython = python3.10
 deps =
     -rrequirements.txt
 skip_install = True
@@ -68,7 +72,7 @@ setenv =
 commands = python run_test.py all true
 
 [testenv:run-test]
-basepython = python3.11
+basepython = python3.10
 deps =
     -rrequirements.txt
 skip_install = True
@@ -77,14 +81,14 @@ setenv =
 commands = python run_test.py {posargs}
 
 [testenv:isort-check]
-basepython = python3.11
+basepython = python3.10
 deps =
     isort
 skip_install = True
 commands = isort src/onaptests --check --thirdparty=onapsdk
 
 [testenv:isort-fix]
-basepython = python3.11
+basepython = python3.10
 deps =
     isort
 skip_install = True