From abe5a64feff3fa36a55bfed39c412f7e02c279f7 Mon Sep 17 00:00:00 2001 From: Fiete Ostkamp Date: Sun, 8 Feb 2026 10:15:29 +0100 Subject: [PATCH] Distribution fixes - readd the recently removed [0] service.distribute() call in `YamlTemplateServiceOnboardStep.execute` - avoid `AttributeError` that occurs when attempting to iterate over `self.service.latest_distribution.distribution_status_list` and `.distribution_status_list` is `None` - add tests for YamlTemplateServiceOnboardStep to test the distribution - add flake8 + black precommit hook (linter also used in the pipeline) [0] https://gerrit.onap.org/r/c/testsuite/pythonsdk-tests/+/140736 Issue-ID: INT-2346 Change-Id: I6627c0a5c8cd4e7b8f7f2a50e2797b9a8a4728ec Signed-off-by: Fiete Ostkamp --- .flake8 | 12 ++ .pre-commit-config.yaml | 14 ++ requirements.txt | 3 + src/onaptests/steps/onboard/service.py | 209 ++++++++++++++++++---------- tests/test_service_onboard.py | 243 +++++++++++++++++++++++++++++++++ 5 files changed, 406 insertions(+), 75 deletions(-) create mode 100644 .flake8 create mode 100644 .pre-commit-config.yaml create mode 100644 tests/test_service_onboard.py diff --git a/.flake8 b/.flake8 new file mode 100644 index 0000000..f107f9b --- /dev/null +++ b/.flake8 @@ -0,0 +1,12 @@ +[flake8] +max-line-length = 100 +exclude = + .git, + __pycache__, + .tox, + .eggs, + *.egg, + build, + dist, + htmlcov, + .pytest_cache, diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml new file mode 100644 index 0000000..87a8ed3 --- /dev/null +++ b/.pre-commit-config.yaml @@ -0,0 +1,14 @@ +# See https://pre-commit.com for more information +# See https://pre-commit.com/hooks.html for more hooks +repos: + - repo: https://github.com/psf/black + rev: stable + hooks: + - id: black + language_version: python3.11 + - repo: https://github.com/pycqa/flake8 + rev: 7.0.0 + hooks: + - id: flake8 + args: ['--config=.flake8'] + additional_dependencies: [] diff --git a/requirements.txt b/requirements.txt index 71d2295..9d6a5a6 100644 --- a/requirements.txt +++ b/requirements.txt @@ -13,3 +13,6 @@ mysql-connector-python==8.3.0 pandas==2.2.1 matplotlib==3.8.3 grpcio-health-checking==1.71.0 +opentelemetry-distro==0.51b0 +opentelemetry-instrumentation-requests==0.51b0 +opentelemetry-exporter-otlp==1.30.0 diff --git a/src/onaptests/steps/onboard/service.py b/src/onaptests/steps/onboard/service.py index c28ff8b..eefb1fe 100644 --- a/src/onaptests/steps/onboard/service.py +++ b/src/onaptests/steps/onboard/service.py @@ -1,4 +1,5 @@ """Service onboarding step module.""" + import time from typing import Any, Dict, Iterator from urllib.parse import urlencode @@ -8,8 +9,7 @@ from opentelemetry import trace from onapsdk.aai.service_design_and_creation import Model from onapsdk.configuration import settings from onapsdk.exceptions import InvalidResponse, ResourceNotFound -from onapsdk.sdc2.component_instance import (ComponentInstance, - ComponentInstanceInput) +from onapsdk.sdc2.component_instance import ComponentInstance, ComponentInstanceInput from onapsdk.sdc2.pnf import Pnf from onapsdk.sdc2.sdc_category import ServiceCategory from onapsdk.sdc2.sdc_resource import LifecycleOperation, LifecycleState @@ -87,7 +87,9 @@ class YamlTemplateServiceOnboardStep(YamlTemplateBaseStep): return self.model_yaml_template if self.is_root: if not self._yaml_template: - with open(settings.SERVICE_YAML_TEMPLATE, "r", encoding="utf-8") as yaml_template: + with open( + settings.SERVICE_YAML_TEMPLATE, "r", encoding="utf-8" + ) as yaml_template: self._yaml_template: dict = load(yaml_template, SafeLoader) return self._yaml_template return self.parent.yaml_template @@ -104,9 +106,12 @@ class YamlTemplateServiceOnboardStep(YamlTemplateBaseStep): """ if self.is_root: if not self._model_yaml_template: - with open(settings.MODEL_YAML_TEMPLATE, "r", - encoding="utf-8") as model_yaml_template: - self._model_yaml_template: dict = load(model_yaml_template, SafeLoader) + with open( + settings.MODEL_YAML_TEMPLATE, "r", encoding="utf-8" + ) as model_yaml_template: + self._model_yaml_template: dict = load( + model_yaml_template, SafeLoader + ) return self._model_yaml_template return self.parent.model_yaml_template @@ -122,13 +127,15 @@ class YamlTemplateServiceOnboardStep(YamlTemplateBaseStep): super().execute() if "instantiation_type" in self.yaml_template[self.service_name]: instantiation_type: ServiceInstantiationType = ServiceInstantiationType( - self.yaml_template[self.service_name]["instantiation_type"]) + self.yaml_template[self.service_name]["instantiation_type"] + ) else: - instantiation_type: ServiceInstantiationType = ServiceInstantiationType.A_LA_CARTE + instantiation_type: ServiceInstantiationType = ( + ServiceInstantiationType.A_LA_CARTE + ) with tracer.start_as_current_span( - "sdc.service.get_or_create", - attributes={"service.name": self.service_name} + "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) @@ -139,11 +146,15 @@ class YamlTemplateServiceOnboardStep(YamlTemplateBaseStep): 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) + 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) + sdc_span.set_attribute( + "service.instantiation_type", instantiation_type.value + ) self.declare_resources(service) self.assign_properties(service) @@ -152,10 +163,11 @@ class YamlTemplateServiceOnboardStep(YamlTemplateBaseStep): "sdc.service.certify", attributes={ "service.name": self.service_name, - "service.lifecycle_state": service.lifecycle_state.value - } + "service.lifecycle_state": service.lifecycle_state.value, + }, ): service.lifecycle_operation(LifecycleOperation.CERTIFY) + service.distribute() def declare_resources(self, service: Service) -> None: """Declare resources. @@ -168,7 +180,7 @@ class YamlTemplateServiceOnboardStep(YamlTemplateBaseStep): """ if "networks" in self.yaml_template[self.service_name]: for net in self.yaml_template[self.service_name]["networks"]: - vl: Vl = Vl.get_by_name(name=net['vl_name']) + vl: Vl = Vl.get_by_name(name=net["vl_name"]) service.add_resource(vl) if "vnfs" in self.yaml_template[self.service_name]: for vnf in self.yaml_template[self.service_name]["vnfs"]: @@ -192,23 +204,30 @@ class YamlTemplateServiceOnboardStep(YamlTemplateBaseStep): if "networks" in self.yaml_template[self.service_name]: for net in self.yaml_template[self.service_name]["networks"]: if "properties" in net: - vl_component: ComponentInstance = service.get_component_by_name(net['vl_name']) + vl_component: ComponentInstance = service.get_component_by_name( + net["vl_name"] + ) self.assign_properties_to_component(vl_component, net["properties"]) if "vnfs" in self.yaml_template[self.service_name]: for vnf in self.yaml_template[self.service_name]["vnfs"]: if "properties" in vnf: - vf_component: ComponentInstance = service.get_component_by_name(vnf["vnf_name"]) + vf_component: ComponentInstance = service.get_component_by_name( + vnf["vnf_name"] + ) self.assign_properties_to_component(vf_component, vnf["properties"]) if "pnfs" in self.yaml_template[self.service_name]: for pnf in self.yaml_template[self.service_name]["pnfs"]: if "properties" in pnf: - pnf_component: ComponentInstance = \ - service.get_component_by_name(pnf["pnf_name"]) - self.assign_properties_to_component(pnf_component, pnf["properties"]) - - def assign_properties_to_component(self, - component: ComponentInstance, - component_properties: Dict[str, Any]) -> None: + pnf_component: ComponentInstance = service.get_component_by_name( + pnf["pnf_name"] + ) + self.assign_properties_to_component( + pnf_component, pnf["properties"] + ) + + def assign_properties_to_component( + self, component: ComponentInstance, component_properties: Dict[str, Any] + ) -> None: """Assign properties to component. Args: @@ -224,13 +243,14 @@ class YamlTemplateServiceOnboardStep(YamlTemplateBaseStep): def cleanup(self) -> None: """Cleanup service onboard step.""" with tracer.start_as_current_span( - "sdc.service.delete", - attributes={"service.name": self.service_name} + "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) + cleanup_span.set_attribute( + "service.lifecycle_state", service.lifecycle_state.value + ) if service.lifecycle_state == LifecycleState.CERTIFIED: service.archive() service.delete() @@ -265,7 +285,9 @@ class YamlTemplateServiceDistributionStep(YamlTemplateBaseStep): return self.model_yaml_template if self.is_root: if not self._yaml_template: - with open(settings.SERVICE_YAML_TEMPLATE, "r", encoding="utf-8") as yaml_template: + with open( + settings.SERVICE_YAML_TEMPLATE, "r", encoding="utf-8" + ) as yaml_template: self._yaml_template: dict = load(yaml_template, SafeLoader) return self._yaml_template return self.parent.yaml_template @@ -282,9 +304,12 @@ class YamlTemplateServiceDistributionStep(YamlTemplateBaseStep): """ if self.is_root: if not self._model_yaml_template: - with open(settings.MODEL_YAML_TEMPLATE, "r", - encoding="utf-8") as model_yaml_template: - self._model_yaml_template: dict = load(model_yaml_template, SafeLoader) + with open( + settings.MODEL_YAML_TEMPLATE, "r", encoding="utf-8" + ) as model_yaml_template: + self._model_yaml_template: dict = load( + model_yaml_template, SafeLoader + ) return self._model_yaml_template return self.parent.model_yaml_template @@ -303,8 +328,7 @@ class YamlTemplateServiceDistributionStep(YamlTemplateBaseStep): """Distribute service.""" super().execute() with tracer.start_as_current_span( - "sdc.service.distribute", - attributes={"service.name": self.service_name} + "sdc.service.distribute", attributes={"service.name": self.service_name} ) as dist_span: service: Service = Service.get_by_name(name=self.service_name) if service: @@ -312,14 +336,19 @@ class YamlTemplateServiceDistributionStep(YamlTemplateBaseStep): if not service.distributed: dist_span.set_attribute("service.already_distributed", False) service.distribute() - self._logger.info(f"Service {self.service_name} distributed successfully.") + 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.") + self._logger.info( + f"Service {self.service_name} is already distributed." + ) else: dist_span.set_attribute("service.found", False) - raise onap_test_exceptions.OnapTestException(f"Service {self.service_name} " - f"not found for distribution.") + raise onap_test_exceptions.OnapTestException( + f"Service {self.service_name} " f"not found for distribution." + ) class BaseServiceDistributionComponentCheckStep(BaseStep): @@ -327,15 +356,16 @@ class BaseServiceDistributionComponentCheckStep(BaseStep): service_model = None - def __init__(self, component_name: str, break_on_error: bool = True, load_model: bool = True): + def __init__( + self, component_name: str, break_on_error: bool = True, load_model: bool = True + ): """Initialize step. Args: component_name (str): Name of tested component break_on_error (bool): If step breaks execution when failed """ - super().__init__(cleanup=BaseStep.HAS_NO_CLEANUP, - break_on_error=break_on_error) + super().__init__(cleanup=BaseStep.HAS_NO_CLEANUP, break_on_error=break_on_error) self.component_name = component_name self.service: Service = None self.load_model = load_model @@ -362,14 +392,17 @@ class BaseServiceDistributionComponentCheckStep(BaseStep): """ if cleanup: return True - return self.load_model or BaseServiceDistributionComponentCheckStep.service_model + return ( + self.load_model or BaseServiceDistributionComponentCheckStep.service_model + ) def execute(self): """Check service distribution status.""" super().execute() if not BaseServiceDistributionComponentCheckStep.service_model: - BaseServiceDistributionComponentCheckStep.service_model = Service.get_by_name( - name=settings.SERVICE_NAME) + BaseServiceDistributionComponentCheckStep.service_model = ( + Service.get_by_name(name=settings.SERVICE_NAME) + ) self.service = BaseServiceDistributionComponentCheckStep.service_model def _raise_reason(self, reason, exc=None): @@ -410,9 +443,9 @@ class VerifyServiceDistributionInAaiStep(BaseServiceDistributionComponentCheckSt """Workaround to fix.""" @classmethod - def get_all(cls, - invariant_id: str = None, - resource_version: str = None) -> Iterator["Model"]: + def get_all( + cls, invariant_id: str = None, resource_version: str = None + ) -> Iterator["Model"]: """Get all models. Args: @@ -424,22 +457,26 @@ class VerifyServiceDistributionInAaiStep(BaseServiceDistributionComponentCheckSt """ filter_parameters: dict = cls.filter_none_key_values( - {"model-invariant-id": invariant_id, - "resource-version": resource_version} + { + "model-invariant-id": invariant_id, + "resource-version": resource_version, + } ) url: str = f"{cls.get_all_url()}?{urlencode(filter_parameters)}" - for model in cls.send_message_json("GET", "Get A&AI sdc models", - url).get("model", []): + for model in cls.send_message_json("GET", "Get A&AI sdc models", url).get( + "model", [] + ): yield Model( invariant_id=model.get("model-invariant-id"), model_type=model.get("model-type"), - resource_version=model.get("resource-version") + resource_version=model.get("resource-version"), ) def __init__(self): """Initialize step.""" BaseServiceDistributionComponentCheckStep.__init__( - self, component_name="AAI", load_model=False) + self, component_name="AAI", load_model=False + ) @BaseStep.store_state def execute(self): @@ -447,10 +484,10 @@ class VerifyServiceDistributionInAaiStep(BaseServiceDistributionComponentCheckSt super().execute() try: aai_services = self.ModelWithGet.get_all( - invariant_id=self.service.invariant_uuid) + invariant_id=self.service.invariant_uuid + ) for aai_service in aai_services: - self._logger.info( - f"Resolved {aai_service.invariant_id} aai service") + self._logger.info(f"Resolved {aai_service.invariant_id} aai service") except ResourceNotFound as e: msg = "Service model is missing in AAI." self._logger.error(msg) @@ -467,7 +504,8 @@ class VerifyServiceDistributionInSdncStep(BaseServiceDistributionComponentCheckS def __init__(self): """Initialize step.""" BaseServiceDistributionComponentCheckStep.__init__( - self, component_name="SDNC", load_model=False) + self, component_name="SDNC", load_model=False + ) @BaseStep.store_state def execute(self): @@ -475,7 +513,8 @@ class VerifyServiceDistributionInSdncStep(BaseServiceDistributionComponentCheckS super().execute() if settings.IN_CLUSTER: login, password = KubernetesHelper.get_credentials_from_secret( - settings.SDNC_SECRET_NAME, self.SDNC_DB_LOGIN, self.SDNC_DB_PASSWORD) + settings.SDNC_SECRET_NAME, self.SDNC_DB_LOGIN, self.SDNC_DB_PASSWORD + ) conn = None try: conn = mysql.connect( @@ -483,10 +522,12 @@ class VerifyServiceDistributionInSdncStep(BaseServiceDistributionComponentCheckS host=settings.SDNC_DB_PRIMARY_HOST, port=settings.SDNC_DB_PORT, user=login, - password=password) + password=password, + ) cursor = conn.cursor() cursor.execute( - f"SELECT * FROM service_model WHERE service_uuid = '{self.service.uuid}';") + f"SELECT * FROM service_model WHERE service_uuid = '{self.service.uuid}';" + ) cursor.fetchall() if cursor.rowcount <= 0: msg = "Service model is missing in SDNC." @@ -511,21 +552,25 @@ class VerifyServiceDistributionStep(BaseStep): COMPONENTS_DISTRIBUTION_VERIFICATION_MAP = { "AAI": VerifyServiceDistributionInAaiStep, "SDNC": VerifyServiceDistributionInSdncStep, - "SO": VerifyServiceDistributionInSoStep + "SO": VerifyServiceDistributionInSoStep, } def __init__(self): """Initialize step.""" super().__init__(cleanup=BaseStep.HAS_NO_CLEANUP) self.add_step(ServiceDistributionWaitStep()) - modules = sorted(settings.SDC_SERVICE_DISTRIBUTION_COMPONENTS, - reverse=True) + modules = sorted(settings.SDC_SERVICE_DISTRIBUTION_COMPONENTS, reverse=True) for notified_module in modules: component_name = notified_module.split("-")[0].upper() - self.add_step(VerifyServiceDistributionStatusStep( - notified_module=notified_module, component_name=component_name)) + self.add_step( + VerifyServiceDistributionStatusStep( + notified_module=notified_module, component_name=component_name + ) + ) try: - self.add_step(self.COMPONENTS_DISTRIBUTION_VERIFICATION_MAP[component_name]()) + self.add_step( + self.COMPONENTS_DISTRIBUTION_VERIFICATION_MAP[component_name]() + ) except KeyError: pass @@ -551,7 +596,8 @@ class VerifyServiceDistributionStep(BaseStep): # under dedicated step anyway if not step.is_executed: raise onap_test_exceptions.ServiceDistributionException( - "Service distribution has failed") + "Service distribution has failed" + ) class ServiceDistributionWaitStep(BaseServiceDistributionComponentCheckStep): @@ -574,17 +620,22 @@ class ServiceDistributionWaitStep(BaseServiceDistributionComponentCheckStep): self._logger.info("******** Check Service Distribution *******") distribution_completed = False nb_try = 0 - while distribution_completed is False and \ - nb_try < settings.SERVICE_DISTRIBUTION_NUMBER_OF_TRIES: + while ( + distribution_completed is False + and nb_try < settings.SERVICE_DISTRIBUTION_NUMBER_OF_TRIES + ): distribution_completed = self.service.distributed if distribution_completed is True: self._logger.info( "Service Distribution for %s is sucessfully finished", - self.service.name) + self.service.name, + ) break self._logger.info( "Service Distribution for %s ongoing, Wait for %d s", - self.service.name, settings.SERVICE_DISTRIBUTION_SLEEP_TIME) + self.service.name, + settings.SERVICE_DISTRIBUTION_SLEEP_TIME, + ) time.sleep(settings.SERVICE_DISTRIBUTION_SLEEP_TIME) nb_try += 1 @@ -608,7 +659,8 @@ class VerifyServiceDistributionStatusStep(BaseServiceDistributionComponentCheckS component_name (str): Name of the module's component """ super().__init__( - component_name=component_name, break_on_error=False, load_model=False) + component_name=component_name, break_on_error=False, load_model=False + ) self.component_id = notified_module @property @@ -626,7 +678,12 @@ class VerifyServiceDistributionStatusStep(BaseServiceDistributionComponentCheckS present = False msg = "" distributed = False - for status in latest_distribution.distribution_status_list: + distribution_status_list = ( + latest_distribution.distribution_status_list + if latest_distribution + else [] + ) + for status in distribution_status_list: if status.component_id == self.component_id: present = True if status.distributed: @@ -642,7 +699,9 @@ failed: {status.error_reason}" msg = f"Service model distribution to {self.component_id} \ was not completed" else: - msg = f"Service model was not distributed to {self.component_id}" + msg = ( + f"Service model was not distributed to {self.component_id}" + ) self._raise_reason(msg) msg = f"Service {self.service.name} is distributed in {self.component_name} \ and {self.component_id}." diff --git a/tests/test_service_onboard.py b/tests/test_service_onboard.py new file mode 100644 index 0000000..a6cc4c5 --- /dev/null +++ b/tests/test_service_onboard.py @@ -0,0 +1,243 @@ +"""Test module for service onboarding functionality.""" +import sys +from unittest import mock + +from onapsdk.configuration import settings +from onapsdk.exceptions import ResourceNotFound +from onapsdk.sdc2.sdc_resource import LifecycleState +from onaptests.steps.onboard.service import YamlTemplateServiceOnboardStep + +# Mock the kubernetes helper module before any imports to avoid settings dependency +sys.modules['onaptests.utils.kubernetes'] = mock.MagicMock() + +# Create and configure settings mock before importing onapsdk +settings.LOG_CONFIG = { + "version": 1, + "disable_existing_loggers": False, + "formatters": { + "default": { + "class": "logging.Formatter", + "format": "%(message)s" + } + }, + "handlers": { + "console": { + "level": "DEBUG", + "class": "logging.StreamHandler", + "formatter": "default" + } + }, + "root": { + "level": "DEBUG", + "handlers": ["console"] + } +} +settings.K8S_TESTS_NAMESPACE = 'test-namespace' +settings.CLEANUP_FLAG = False +settings.SERVICE_YAML_TEMPLATE = 'test.yaml' +settings.MODEL_YAML_TEMPLATE = None +settings.SDC_CLEANUP = False +settings.IF_VALIDATION = False + + +@mock.patch("onaptests.steps.onboard.service.YamlTemplateVfOnboardStep") +@mock.patch("onaptests.steps.onboard.service.YamlTemplatePnfOnboardStep") +@mock.patch("onaptests.steps.onboard.service.settings") +@mock.patch("onaptests.steps.onboard.service.Service") +@mock.patch("onaptests.steps.onboard.service.tracer") +def test_service_distribute_called_for_new_service( + mock_tracer, mock_service_class, mock_settings, mock_pnf_step, mock_vf_step +): + """Test that service.distribute() is called when creating a new service.""" + # Setup mock settings + mock_settings.SERVICE_YAML_TEMPLATE = "test_service.yaml" + mock_settings.MODEL_YAML_TEMPLATE = None + mock_settings.CLEANUP_FLAG = False + + # Setup mock service instance + mock_service_instance = mock.MagicMock() + mock_service_instance.name = "test_service" + mock_service_instance.lifecycle_state = LifecycleState.CERTIFIED + mock_service_instance.distributed = False + + # Mock Service.get_by_name to raise ResourceNotFound (new service scenario) + mock_service_class.get_by_name.side_effect = ResourceNotFound + + # Mock Service.create to return our mock service + mock_service_class.create.return_value = mock_service_instance + + # Setup tracer context managers + mock_tracer.start_as_current_span.return_value.__enter__ = mock.MagicMock() + mock_tracer.start_as_current_span.return_value.__exit__ = mock.MagicMock() + + # Mock the yaml template loading + mock_yaml_data = { + "test_service": { + "instantiation_type": "Macro" + } + } + + with mock.patch("builtins.open", mock.mock_open(read_data="")): + with mock.patch("onaptests.steps.onboard.service.load", return_value=mock_yaml_data): + # Create and execute the step + step = YamlTemplateServiceOnboardStep() + step.execute() + + # Verify that service.distribute() was called + mock_service_instance.distribute.assert_called_once() + + +@mock.patch("onaptests.steps.onboard.service.YamlTemplateVfOnboardStep") +@mock.patch("onaptests.steps.onboard.service.YamlTemplatePnfOnboardStep") +@mock.patch("onaptests.steps.onboard.service.settings") +@mock.patch("onaptests.steps.onboard.service.Service") +@mock.patch("onaptests.steps.onboard.service.tracer") +def test_service_distribute_called_for_not_certified_service( + mock_tracer, mock_service_class, mock_settings, mock_pnf_step, mock_vf_step +): + """Test that service.distribute() is called after certifying a service.""" + # Setup mock settings + mock_settings.SERVICE_YAML_TEMPLATE = "test_service.yaml" + mock_settings.MODEL_YAML_TEMPLATE = None + mock_settings.CLEANUP_FLAG = False + + # Setup mock service instance in NOT_CERTIFIED_CHECKOUT state (returned by get_by_name) + mock_existing_service = mock.MagicMock() + mock_existing_service.name = "test_service" + mock_existing_service.lifecycle_state = LifecycleState.NOT_CERTIFIED_CHECKOUT + mock_existing_service.distributed = False + + # Setup mock for newly created service (returned by Service.create) + mock_new_service = mock.MagicMock() + mock_new_service.name = "test_service" + mock_new_service.lifecycle_state = LifecycleState.NOT_CERTIFIED_CHECKOUT + mock_new_service.distributed = False + + # Mock Service.get_by_name to return existing service + mock_service_class.get_by_name.return_value = mock_existing_service + # Mock Service.create to return new service instance + mock_service_class.create.return_value = mock_new_service + + # Setup tracer context managers + mock_tracer.start_as_current_span.return_value.__enter__ = mock.MagicMock() + mock_tracer.start_as_current_span.return_value.__exit__ = mock.MagicMock() + + # Mock the yaml template loading + mock_yaml_data = { + "test_service": { + "instantiation_type": "Macro" + } + } + + with mock.patch("builtins.open", mock.mock_open(read_data="")): + with mock.patch("onaptests.steps.onboard.service.load", return_value=mock_yaml_data): + # Create and execute the step + step = YamlTemplateServiceOnboardStep() + step.execute() + + # Verify that lifecycle_operation (CERTIFY) was called on new service + mock_new_service.lifecycle_operation.assert_called_once() + + # Verify that service.distribute() was called on new service + mock_new_service.distribute.assert_called_once() + + +@mock.patch("onaptests.steps.onboard.service.YamlTemplateVfOnboardStep") +@mock.patch("onaptests.steps.onboard.service.YamlTemplatePnfOnboardStep") +@mock.patch("onaptests.steps.onboard.service.settings") +@mock.patch("onaptests.steps.onboard.service.Service") +@mock.patch("onaptests.steps.onboard.service.tracer") +def test_service_distribute_not_called_if_already_distributed( + mock_tracer, mock_service_class, mock_settings, mock_pnf_step, mock_vf_step +): + """Test that execution returns early if service is already distributed.""" + # Setup mock settings + mock_settings.SERVICE_YAML_TEMPLATE = "test_service.yaml" + mock_settings.MODEL_YAML_TEMPLATE = None + mock_settings.CLEANUP_FLAG = False + + # Setup mock service instance that is already distributed + mock_service_instance = mock.MagicMock() + mock_service_instance.name = "test_service" + mock_service_instance.lifecycle_state = LifecycleState.CERTIFIED + mock_service_instance.distributed = True + + # Mock Service.get_by_name to return existing distributed service + mock_service_class.get_by_name.return_value = mock_service_instance + + # Setup tracer context managers + mock_tracer.start_as_current_span.return_value.__enter__ = mock.MagicMock() + mock_tracer.start_as_current_span.return_value.__exit__ = mock.MagicMock() + + # Mock the yaml template loading + mock_yaml_data = { + "test_service": { + "instantiation_type": "Macro" + } + } + + with mock.patch("builtins.open", mock.mock_open(read_data="")): + with mock.patch("onaptests.steps.onboard.service.load", return_value=mock_yaml_data): + # Create and execute the step + step = YamlTemplateServiceOnboardStep() + step.execute() + + # Verify that service.distribute() was NOT called (already distributed) + mock_service_instance.distribute.assert_not_called() + # Verify that Service.create was NOT called + mock_service_class.create.assert_not_called() + + +@mock.patch("onaptests.steps.onboard.service.YamlTemplateVfOnboardStep") +@mock.patch("onaptests.steps.onboard.service.YamlTemplatePnfOnboardStep") +@mock.patch("onaptests.steps.onboard.service.settings") +@mock.patch("onaptests.steps.onboard.service.Service") +@mock.patch("onaptests.steps.onboard.service.tracer") +def test_service_distribute_called_for_certified_not_distributed_service( + mock_tracer, mock_service_class, mock_settings, mock_pnf_step, mock_vf_step +): + """Test that service.distribute() is called for a certified service.""" + # Setup mock settings + mock_settings.SERVICE_YAML_TEMPLATE = "test_service.yaml" + mock_settings.MODEL_YAML_TEMPLATE = None + mock_settings.CLEANUP_FLAG = False + + # Setup mock for existing service (returned by get_by_name) + mock_existing_service = mock.MagicMock() + mock_existing_service.name = "test_service" + mock_existing_service.lifecycle_state = LifecycleState.CERTIFIED + mock_existing_service.distributed = False + + # Setup mock for newly created service (returned by Service.create) + mock_new_service = mock.MagicMock() + mock_new_service.name = "test_service" + mock_new_service.lifecycle_state = LifecycleState.CERTIFIED + mock_new_service.distributed = False + + # Mock Service.get_by_name to return existing certified service + mock_service_class.get_by_name.return_value = mock_existing_service + # Mock Service.create to return new service instance + mock_service_class.create.return_value = mock_new_service + + # Setup tracer context managers + mock_tracer.start_as_current_span.return_value.__enter__ = mock.MagicMock() + mock_tracer.start_as_current_span.return_value.__exit__ = mock.MagicMock() + + # Mock the yaml template loading + mock_yaml_data = { + "test_service": { + "instantiation_type": "Macro" + } + } + + with mock.patch("builtins.open", mock.mock_open(read_data="")): + with mock.patch("onaptests.steps.onboard.service.load", return_value=mock_yaml_data): + # Create and execute the step + step = YamlTemplateServiceOnboardStep() + step.execute() + + # Verify that lifecycle_operation was NOT called (already certified) + mock_new_service.lifecycle_operation.assert_not_called() + + # Verify that service.distribute() WAS called on new service + mock_new_service.distribute.assert_called_once() -- 2.16.6