From aad04fa2a7e1c9d71f273d26da25436e5c6b779c Mon Sep 17 00:00:00 2001 From: Michal Jagiello Date: Tue, 20 Jul 2021 10:15:32 +0000 Subject: [PATCH] [TEST] CDS resource-resolution test Issue-ID: TEST-291 Signed-off-by: Michal Jagiello Change-Id: I5d9f55b67942c62f63e11282ef2383fe063d3137 (cherry picked from commit c31b7fb464fbb8bb0c7d8d2b3dc7b20f4a04cff5) --- setup.cfg | 1 + .../cds_resource_resolution_settings.py | 79 +++++++ src/onaptests/scenario/cds_resource_resolution.py | 96 ++++++++ src/onaptests/steps/onboard/cds.py | 38 ++- src/onaptests/steps/simulator/cds_mockserver.py | 62 +++++ .../cds-resource-resolution/cds-mock-server.tar.gz | Bin 0 -> 2376 bytes .../artifacts/cds-resource-resolution/dd.json | 260 +++++++++++++++++++++ .../resource-resolution.zip | Bin 0 -> 6739 bytes 8 files changed, 534 insertions(+), 2 deletions(-) create mode 100644 src/onaptests/configuration/cds_resource_resolution_settings.py create mode 100644 src/onaptests/scenario/cds_resource_resolution.py create mode 100644 src/onaptests/steps/simulator/cds_mockserver.py create mode 100644 src/onaptests/templates/artifacts/cds-resource-resolution/cds-mock-server.tar.gz create mode 100644 src/onaptests/templates/artifacts/cds-resource-resolution/dd.json create mode 100644 src/onaptests/templates/artifacts/cds-resource-resolution/resource-resolution.zip diff --git a/setup.cfg b/setup.cfg index 3b9c670..5b3c6bf 100644 --- a/setup.cfg +++ b/setup.cfg @@ -54,3 +54,4 @@ xtesting.testcase = basic_onboard = onaptests.scenario.basic_onboard:BasicOnboard pnf_macro = onaptests.scenario.pnf_macro:PnfMacro basic_clamp = onaptests.scenario.basic_clamp:BasicClamp + cds_resource_resolution = onaptests.scenario.cds_resource_resolution:CDSResourceResolution diff --git a/src/onaptests/configuration/cds_resource_resolution_settings.py b/src/onaptests/configuration/cds_resource_resolution_settings.py new file mode 100644 index 0000000..bb3b85b --- /dev/null +++ b/src/onaptests/configuration/cds_resource_resolution_settings.py @@ -0,0 +1,79 @@ +from pathlib import Path +from uuid import uuid4 + +from .settings import * # pylint: disable=W0614 + +CLEANUP_FLAG = True +SERVICE_NAME = "CDS resource resolution" +CLOUD_REGION_CLOUD_OWNER = "basicnf-owner" # must not contain _ +CLOUD_REGION_ID = "k8sregion" +CLOUD_REGION_TYPE = "k8s" +CLOUD_REGION_VERSION = "1.0" +CLOUD_OWNER_DEFINED_TYPE = "N/A" +COMPLEX_PHYSICAL_LOCATION_ID = "sdktests" + +PNF_DEFINITION_ATRIFACT_FILE_PATH = Path(Path(__file__).parent.parent, + "templates/artifacts/cds-resource-resolution/cds-mock-server.tar.gz") +PNF_RB_NAME = f"cds-ms-rb-{str(uuid4())}" +PNF_RB_VERSION = "v1" +PNF_PROFILE_ARTIFACT_FILE_PATH = Path(Path(__file__).parent.parent, + "templates/artifacts/profile.tar.gz") +PNF_PROFILE_NAME = f"cds-ms-prof-{str(uuid4())}" +K8S_VERSION = "1.0" +K8S_CONFIG = str(Path(Path(__file__).parent.parent, "templates/artifacts/config")) +CDS_MOCKSERVER_EXPECTATIONS = [ + { + "method": "GET", + "path": "/resource-resolution/get", + "response": '{"value": "A046E51D-44DC-43AE-BBA2-86FCA86C5265"}' + }, + { + "method": "GET", + "path": "/resource-resolution/get/A046E51D-44DC-43AE-BBA2-86FCA86C5265/id", + "response": '{"value": "74FE67C6-50F5-4557-B717-030D79903908"}' + }, + { + "method": "POST", + "path": "/resource-resolution/post", + "response": '{"value": "post:ok"}' + }, + { + "method": "PUT", + "path": "/resource-resolution/put", + "response": '{"value": "put:ok"}' + }, + { + "method": "PATCH", + "path": "/resource-resolution/patch", + "response": '{"value": "patch:ok"}' + }, + { + "method": "DELETE", + "path": "/resource-resolution/delete", + "response": '{"value": "delete:ok"}' + } +] + +CDS_DD_FILE = Path(Path(__file__).parent.parent, "templates/artifacts/cds-resource-resolution/dd.json") +CDS_CBA_UNENRICHED = Path(Path(__file__).parent.parent, "templates/artifacts/cds-resource-resolution/resource-resolution.zip") +CDS_CBA_ENRICHED = "/tmp/resource-resolution-enriched.zip" +CDS_WORKFLOW_NAME = "resource-resolution" +CDS_WORKFLOW_INPUT = { + "template-prefix": [ + "helloworld-velocity", + "helloworld-jinja" + ], + "resolution-key": "regression-test", + "resource-resolution-properties": { + "v_input": "ok", + "j_input": "ok" + } +} +CDS_WORKFLOW_EXPECTED_OUTPUT = { + "resource-resolution-response": { + "meshed-template": { + "helloworld-velocity": "{\n \"default\": \"ok\",\n \"input\": \"ok\",\n \"script\": {\n \"python\": \"ok\",\n \"kotlin\": \"ok\"\n },\n \"db\": \"ok\",\n \"rest\": {\n \"GET\": \"A046E51D-44DC-43AE-BBA2-86FCA86C5265\",\n \"POST\": \"post:ok\",\n \"PUT\": \"put:ok\",\n \"PATCH\": \"patch:ok\",\n \"DELETE\": \"delete:ok\"\n }\n}\n", + "helloworld-jinja": "{\n \"default\": \"ok\",\n \"input\": \"ok\",\n \"script\": {\n \"python\": \"ok\",\n \"kotlin\": {\n \"base\": \"ok\"\n \"from suspend function\": \"ok\"\n }\n },\n \"db\": \"ok\",\n \"rest\": {\n \"GET\": \"A046E51D-44DC-43AE-BBA2-86FCA86C5265\",\n \"GET_ID\": \"74FE67C6-50F5-4557-B717-030D79903908\",\n \"POST\": \"post:ok\",\n \"PUT\": \"put:ok\",\n \"PATCH\": \"patch:ok\",\n \"DELETE\": \"delete:ok\"\n }\n}\n" + } + } +} diff --git a/src/onaptests/scenario/cds_resource_resolution.py b/src/onaptests/scenario/cds_resource_resolution.py new file mode 100644 index 0000000..7e9635f --- /dev/null +++ b/src/onaptests/scenario/cds_resource_resolution.py @@ -0,0 +1,96 @@ +#!/usr/bin/env python +"""CDS resource resolution test scenario.""" + +import logging +import time + +from onapsdk.configuration import settings +from onapsdk.exceptions import SDKException +from xtesting.core import testcase + +from onaptests.steps.base import BaseStep +from onaptests.steps.onboard.cds import CbaProcessStep +from onaptests.steps.simulator.cds_mockserver import CdsMockserverCnfConfigureStep +from onaptests.utils.exceptions import OnapTestException + + +class CDSResourceResolutionStep(BaseStep): + """Step created to run scenario and generate report.""" + + def __init__(self, cleanup=False): + """Initialize step. + + Substeps: + - CdsMockserverCnfConfigureStep, + - CbaProcessStep. + """ + super().__init__(cleanup=cleanup) + self.add_step(CdsMockserverCnfConfigureStep( + cleanup=cleanup + )) + self.add_step(CbaProcessStep( + cleanup=cleanup + )) + + @property + def description(self) -> str: + """Step description. + + Used for reports + + Returns: + str: Step description + + """ + return "CDS resource-resoulution base step" + + @property + def component(self) -> str: + """Component name. + + Name of the component this step relates to. + Usually the name of ONAP component. + + Returns: + str: Component name + + """ + return "PythonSDK-tests" + + +class CDSResourceResolution(testcase.TestCase): + """Enrich simple blueprint using CDS blueprintprocessor.""" + + __logger = logging.getLogger(__name__) + + def __init__(self, **kwargs): + """Init CDS resource resolution use case.""" + if "case_name" not in kwargs: + kwargs["case_name"] = 'basic_cds' + super().__init__(**kwargs) + self.__logger.debug("CDS resource resolution initialization") + self.test = CDSResourceResolutionStep( + cleanup=settings.CLEANUP_FLAG) + self.start_time = None + self.stop_time = None + self.result = 0 + + def run(self): + self.__logger.debug("CDS resource resolution run") + self.start_time = time.time() + try: + self.test.execute() + self.result = 100 + except OnapTestException as exc: + self.result = 0 + self.__logger.error(exc.error_message) + except SDKException: + self.result = 0 + self.__logger.error("SDK Exception") + finally: + self.stop_time = time.time() + + def clean(self): + """Clean Additional resources if needed.""" + self.__logger.info("Generate Test report") + self.test.reports_collection.generate_report() diff --git a/src/onaptests/steps/onboard/cds.py b/src/onaptests/steps/onboard/cds.py index cbd69ce..9239c43 100644 --- a/src/onaptests/steps/onboard/cds.py +++ b/src/onaptests/steps/onboard/cds.py @@ -8,6 +8,7 @@ from typing import Any, Dict from kubernetes import client, config from kubernetes.client.exceptions import ApiException from onapsdk.cds import Blueprint, DataDictionarySet +from onapsdk.cds.blueprint import Workflow from onapsdk.cds.blueprint_processor import Blueprintprocessor from onapsdk.configuration import settings import urllib3 @@ -222,12 +223,45 @@ class CbaPublishStep(CDSBaseStep): @BaseStep.store_state def execute(self) -> None: - """Enrich CBA file. + """Publish CBA file. Use settings values: - - CDS_DD_FILE. + - CDS_CBA_ENRICHED. """ super().execute() blueprint: Blueprint = Blueprint.load_from_file(settings.CDS_CBA_ENRICHED) blueprint.publish() + + +class CbaProcessStep(CDSBaseStep): + """Process CBA step.""" + + def __init__(self, cleanup=False) -> None: + """Initialize CBA process step.""" + super().__init__(cleanup=cleanup) + self.add_step(CbaPublishStep(cleanup=cleanup)) + + @property + def description(self) -> str: + """Step description.""" + return "Process CBA file." + + @BaseStep.store_state + def execute(self) -> None: + """Process CBA file. + + Check if output is equal to expected + + Use settings values: + - CDS_CBA_ENRICHED, + - CDS_WORKFLOW_NAME, + - CDS_WORKFLOW_INPUT + + """ + super().execute() + blueprint: Blueprint = Blueprint.load_from_file(settings.CDS_CBA_ENRICHED) + workflow: Workflow = blueprint.get_workflow_by_name(settings.CDS_WORKFLOW_NAME) + output: Dict[str, Any] = workflow.execute(settings.CDS_WORKFLOW_INPUT) + if not output == settings.CDS_WORKFLOW_EXPECTED_OUTPUT: + raise OnapTestException("Response is not equal to the expected one") diff --git a/src/onaptests/steps/simulator/cds_mockserver.py b/src/onaptests/steps/simulator/cds_mockserver.py new file mode 100644 index 0000000..6933fa0 --- /dev/null +++ b/src/onaptests/steps/simulator/cds_mockserver.py @@ -0,0 +1,62 @@ +# http://www.apache.org/licenses/LICENSE-2.0 +"""CDS mockserver registration module.""" + +import time + +import requests +from onapsdk.configuration import settings + +from onaptests.steps.base import BaseStep +from onaptests.steps.instantiate.msb_k8s import CreateInstanceStep +from onaptests.utils.exceptions import OnapTestException + + +class CdsMockserverCnfConfigureStep(BaseStep): + """Configure cds mockserver expectations.""" + + def __init__(self, cleanup: bool = False) -> None: + """Initialize step. + + Substeps: + - CreateInstanceStep. + """ + super().__init__(cleanup=cleanup) + self.add_step(CreateInstanceStep(cleanup=cleanup)) + + @property + def description(self) -> str: + """Step description.""" + return "Configure cds-mockserver." + + @property + def component(self) -> str: + """Component name.""" + return "Environment" + + @BaseStep.store_state + def execute(self) -> None: + """Create mockserver expectations. + + Use settings values: + - CDS_MOCKSERVER_EXPECTATIONS. + """ + super().execute() + time.sleep(60) # Wait for mockserver + for expectation in settings.CDS_MOCKSERVER_EXPECTATIONS: + try: + response = requests.put( + "http://portal.api.simpledemo.onap.org:30726/mockserver/expectation", + json={ + "httpRequest" : { + "method": expectation["method"], + "path": expectation["path"] + }, + "httpResponse" : { + "body": expectation["response"] + } + } + ) + response.raise_for_status() + except (requests.ConnectionError, requests.HTTPError) as http_error: + self._logger.debug(f"Can't register cds-mockserver expectation: {str(http_error)}") + raise OnapTestException("CDS mockserver not configured") diff --git a/src/onaptests/templates/artifacts/cds-resource-resolution/cds-mock-server.tar.gz b/src/onaptests/templates/artifacts/cds-resource-resolution/cds-mock-server.tar.gz new file mode 100644 index 0000000000000000000000000000000000000000..ad428fbcf85d5211debb12174046195c8f316da5 GIT binary patch literal 2376 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 literal 0 HcmV?d00001 -- 2.16.6