From cd97933abdb647a19a76d1e4916e18ecda43aa9c Mon Sep 17 00:00:00 2001 From: maopengzhang Date: Sat, 8 Sep 2018 16:51:47 +0800 Subject: Support PNFD Support to parser PNFD Change-Id: Id70e11c3973c77d575cb21427e2a2feaedf99fae Issue-ID: VFC-1102 Signed-off-by: maopengzhang --- catalog/packages/biz/pnf_descriptor.py | 15 ++++++ catalog/packages/tests/test_pnf_descriptor.py | 9 ++++ catalog/packages/urls.py | 7 +-- catalog/packages/views/pnf_descriptor_views.py | 29 ++++++++++ catalog/pub/utils/toscaparser/__init__.py | 6 ++- catalog/pub/utils/toscaparser/basemodel.py | 42 +++++++++++++++ catalog/pub/utils/toscaparser/pnfmodel.py | 60 +++++++++++++++++++++ .../pub/utils/toscaparser/testdata/pnf/ran-du.csar | Bin 0 -> 2680 bytes catalog/pub/utils/toscaparser/tests.py | 9 +++- 9 files changed, 172 insertions(+), 5 deletions(-) create mode 100644 catalog/pub/utils/toscaparser/pnfmodel.py create mode 100644 catalog/pub/utils/toscaparser/testdata/pnf/ran-du.csar diff --git a/catalog/packages/biz/pnf_descriptor.py b/catalog/packages/biz/pnf_descriptor.py index e250ba0c..89db9cb9 100644 --- a/catalog/packages/biz/pnf_descriptor.py +++ b/catalog/packages/biz/pnf_descriptor.py @@ -172,3 +172,18 @@ class PnfDescriptor(object): def handle_upload_failed(self, pnf_pkg_id): pnf_pkg = PnfPackageModel.objects.filter(pnfPackageId=pnf_pkg_id) pnf_pkg.update(onboardingState=PKG_STATUS.CREATED) + + def parse_pnfd(self, csar_id, inputs): + ret = None + try: + pnf_pkg = PnfPackageModel.objects.filter(pnfPackageId=csar_id) + if not pnf_pkg: + raise CatalogException("PNF CSAR(%s) does not exist." % csar_id) + csar_path = pnf_pkg[0].localFilePath + ret = {"model": toscaparser.parse_pnfd(csar_path, inputs)} + except CatalogException as e: + return [1, e.message] + except Exception as e: + logger.error(e.message) + return [1, e.message] + return [0, ret] diff --git a/catalog/packages/tests/test_pnf_descriptor.py b/catalog/packages/tests/test_pnf_descriptor.py index e67e35d9..7afdeae1 100644 --- a/catalog/packages/tests/test_pnf_descriptor.py +++ b/catalog/packages/tests/test_pnf_descriptor.py @@ -271,3 +271,12 @@ class TestPnfDescriptor(TestCase): mock_download.side_effect = TypeError("integer type") response = self.client.get("/api/nsd/v1/pnf_descriptors/22/pnfd_content") self.assertEqual(response.status_code, status.HTTP_500_INTERNAL_SERVER_ERROR) + + @mock.patch.object(toscaparser, 'parse_pnfd') + def test_pnfd_parse_normal(self, mock_parse_pnfd): + PnfPackageModel(pnfPackageId="8", pnfdId="10").save() + mock_parse_pnfd.return_value = json.JSONEncoder().encode({"c": "d"}) + req_data = {"csarId": "8", "inputs": []} + resp = self.client.post("/api/catalog/v1/parserpnfd", req_data, format='json') + self.assertEqual(resp.status_code, status.HTTP_202_ACCEPTED) + self.assertEqual({"model": '{"c": "d"}'}, resp.data) diff --git a/catalog/packages/urls.py b/catalog/packages/urls.py index 883c3749..ca090c74 100644 --- a/catalog/packages/urls.py +++ b/catalog/packages/urls.py @@ -25,20 +25,21 @@ urlpatterns = [ url(r'^api/catalog/v1/vnfpackages/(?P[0-9a-zA-Z\-\_]+)$', catalog_views.nf_rd_csar, name='nfpackage_rd'), url(r'^api/catalog/v1/parsernsd$', catalog_views.ns_model_parser, name='nsmodelparser_rc'), url(r'^api/catalog/v1/parservnfd$', catalog_views.vnf_model_parser, name='vnfmodelparser_rc'), + url(r'^api/catalog/v1/parserpnfd$', pnf_descriptor_views.pnf_model_parser, name='pnfmodelparser_rc'), - # NSD + # NSPakcage& NSD url(r'^api/nsd/v1/ns_descriptors$', ns_descriptor_views.ns_descriptors_rc, name='ns_descriptors_rc'), url(r'^api/nsd/v1/ns_descriptors/(?P[0-9a-zA-Z\-\_]+)$', ns_descriptor_views.ns_info_rd, name='ns_info_rd'), url(r'^api/nsd/v1/ns_descriptors/(?P[0-9a-zA-Z\-\_]+)/nsd_content$', ns_descriptor_views.nsd_content_ru, name='nsd_content_ru'), # url(r'^api/nsd/v1/subscriptions', nsd_subscriptions.as_view(), name='subscriptions_rc'), # url(r'^api/nsd/v1/subscriptions/(?P[0-9a-zA-Z\-\_]+)$', nsd_subscription.as_view(), name='subscription_rd'), - # PNF + # PNF Package and PNFD url(r'^api/nsd/v1/pnf_descriptors$', pnf_descriptor_views.pnf_descriptors_rc, name='pnf_descriptors_rc'), url(r'^api/nsd/v1/pnf_descriptors/(?P[0-9a-zA-Z\-\_]+)$', pnf_descriptor_views.pnfd_info_rd, name='pnfd_info_rd'), url(r'^api/nsd/v1/pnf_descriptors/(?P[0-9a-zA-Z\-\_]+)/pnfd_content$', pnf_descriptor_views.pnfd_content_ru, name='pnfd_content_ru'), - # TODO SOL005 & SOL003 + # VNFD url(r'^api/vnfpkgm/v1/vnf_packages$', vnf_package_views.vnf_packages_rc, name='vnf_packages_rc'), url(r'^api/vnfpkgm/v1/vnf_packages/(?P[0-9a-zA-Z\-\_]+)$', vnf_package_views.vnf_package_rd, name='vnf_package_rd'), url(r'^api/vnfpkgm/v1/vnf_packages/(?P[0-9a-zA-Z\-\_]+)/package_content$', vnf_package_views.package_content_ru, name='package_content_ru'), diff --git a/catalog/packages/views/pnf_descriptor_views.py b/catalog/packages/views/pnf_descriptor_views.py index caa7ac23..618e6e05 100644 --- a/catalog/packages/views/pnf_descriptor_views.py +++ b/catalog/packages/views/pnf_descriptor_views.py @@ -27,6 +27,11 @@ from catalog.packages.serializers.pnfd_info import PnfdInfoSerializer from catalog.packages.serializers.pnfd_infos import PnfdInfosSerializer from catalog.packages.views.common import validate_data from catalog.pub.exceptions import CatalogException, ResourceNotFoundException +from catalog.packages.serializers.catalog_serializers import ParseModelRequestSerializer +from catalog.packages.serializers.catalog_serializers import ParseModelResponseSerializer +from catalog.packages.serializers.catalog_serializers import InternalErrorRequestSerializer +from catalog.pub.utils.syscomm import fun_name +from catalog.pub.utils.values import ignore_case_get logger = logging.getLogger(__name__) @@ -186,3 +191,27 @@ def pnfd_content_ru(request, **kwargs): error_code = status.HTTP_500_INTERNAL_SERVER_ERROR error_data = {'error': 'Downloading PNFD content failed.'} return Response(data=error_data, status=error_code) + + +@swagger_auto_schema( + method='POST', + operation_description="Parse PNF model", + request_body=ParseModelRequestSerializer, + responses={ + status.HTTP_202_ACCEPTED: ParseModelResponseSerializer, + status.HTTP_500_INTERNAL_SERVER_ERROR: InternalErrorRequestSerializer}) +@api_view(http_method_names=['POST']) +def pnf_model_parser(request, *args, **kwargs): + csar_id = ignore_case_get(request.data, "csarId") + inputs = ignore_case_get(request.data, "inputs") + logger.debug( + "Enter %s, csar_id=%s, inputs=%s", + fun_name(), + csar_id, + inputs) + ret = PnfDescriptor().parse_pnfd(csar_id, inputs) + logger.info("Leave %s, Return value is %s", fun_name(), ret) + if ret[0] != 0: + return Response(data={'error': ret[1]}, status=status.HTTP_500_INTERNAL_SERVER_ERROR) + response = validate_data(ret[1], ParseModelResponseSerializer) + return Response(data=response.data, status=status.HTTP_202_ACCEPTED) diff --git a/catalog/pub/utils/toscaparser/__init__.py b/catalog/pub/utils/toscaparser/__init__.py index 604bb23f..b172d678 100644 --- a/catalog/pub/utils/toscaparser/__init__.py +++ b/catalog/pub/utils/toscaparser/__init__.py @@ -16,6 +16,7 @@ import json from catalog.pub.utils.toscaparser.nsdmodel import EtsiNsdInfoModel from catalog.pub.utils.toscaparser.vnfdmodel import EtsiVnfdInfoModel +from catalog.pub.utils.toscaparser.pnfmodel import PnfdInfoModel def parse_nsd(path, input_parameters=[]): @@ -33,4 +34,7 @@ def parse_vnfd(path, input_parameters=[]): def parse_pnfd(path, input_parameters=[]): - pass + tosca_obj = PnfdInfoModel(path, input_parameters) + strResponse = json.dumps(tosca_obj, default=lambda obj: obj.__dict__) + strResponse = strResponse.replace(': null', ': ""') + return strResponse diff --git a/catalog/pub/utils/toscaparser/basemodel.py b/catalog/pub/utils/toscaparser/basemodel.py index 465249d7..192e44b6 100644 --- a/catalog/pub/utils/toscaparser/basemodel.py +++ b/catalog/pub/utils/toscaparser/basemodel.py @@ -37,6 +37,24 @@ SECTIONS = (VDU_TYPE, VL_TYPE, CP_TYPE) = \ class BaseInfoModel(object): + def __init__(self, path, params): + tosca = self.buildToscaTemplate(path, params) + self.parseModel(tosca) + + def parseModel(self, tosca): + pass + + def buildInputs(self, top_inputs): + ret = {} + for tmpinput in top_inputs: + tmp = {} + tmp['type'] = tmpinput.type + tmp['description'] = tmpinput.description + tmp['default'] = tmpinput.default + + ret[tmpinput.name] = tmp + return ret + def buildToscaTemplate(self, path, params): file_name = None try: @@ -169,6 +187,30 @@ class BaseInfoModel(object): if tosca.tpl['metadata'].get('UUID', ''): self.metadata['id'] = tosca.tpl['metadata']['UUID'] + def buildNode(self, nodeTemplate, tosca): + inputs = tosca.inputs + parsed_params = tosca.parsed_params + ret = {} + ret['name'] = nodeTemplate.name + ret['nodeType'] = nodeTemplate.type + if 'description' in nodeTemplate.entity_tpl: + ret['description'] = nodeTemplate.entity_tpl['description'] + else: + ret['description'] = '' + if 'metadata' in nodeTemplate.entity_tpl: + ret['metadata'] = nodeTemplate.entity_tpl['metadata'] + else: + ret['metadata'] = '' + props = self.buildProperties_ex(nodeTemplate, tosca.topology_template) + ret['properties'] = self.verify_properties(props, inputs, parsed_params) + ret['requirements'] = self.build_requirements(nodeTemplate) + self.buildCapabilities(nodeTemplate, inputs, ret) + self.buildArtifacts(nodeTemplate, inputs, ret) + interfaces = self.build_interfaces(nodeTemplate) + if interfaces: + ret['interfaces'] = interfaces + return ret + def buildProperties(self, nodeTemplate, parsed_params): properties = {} isMappingParams = parsed_params and len(parsed_params) > 0 diff --git a/catalog/pub/utils/toscaparser/pnfmodel.py b/catalog/pub/utils/toscaparser/pnfmodel.py new file mode 100644 index 00000000..ce531d6d --- /dev/null +++ b/catalog/pub/utils/toscaparser/pnfmodel.py @@ -0,0 +1,60 @@ +# Copyright 2018 ZTE Corporation. +# +# 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 functools +import logging +import os +from catalog.pub.utils.toscaparser.basemodel import BaseInfoModel +logger = logging.getLogger(__name__) + + +class PnfdInfoModel(BaseInfoModel): + + def __init__(self, path, params): + super(PnfdInfoModel, self).__init__(path, params) + + def parseModel(self, tosca): + self.buidMetadata(tosca) + if hasattr(tosca, 'topology_template') and hasattr(tosca.topology_template, 'inputs'): + self.inputs = self.buildInputs(tosca.topology_template.inputs) + + nodeTemplates = map(functools.partial(self.buildNode, tosca=tosca), + tosca.nodetemplates) + print nodeTemplates + self.basepath = self._get_base_path(tosca) + self.pnf = {} + self.get_all_cp(nodeTemplates) + + def _get_base_path(self, tosca): + fpath, fname = os.path.split(tosca.path) + return fpath + + def get_substitution_mappings(self, tosca): + pnf_substitution_mappings = tosca.tpl['topology_template']['substitution_mappings'] + if pnf_substitution_mappings: + self.pnf['type'] = pnf_substitution_mappings['node_type'] + self.pnf['properties'] = pnf_substitution_mappings['properties'] + + def get_all_cp(self, nodeTemplates): + self.pnf['ExtPorts'] = [] + for node in nodeTemplates: + if self.isPnfExtPort(node): + cp = {} + cp['id'] = node['name'] + cp['type'] = node['nodeType'] + cp['properties'] = node['properties'] + self.pnf['ExtPorts'].append(cp) + + def isPnfExtPort(self, node): + return node['nodeType'].find('tosca.nodes.nfv.PnfExtPort') >= 0 diff --git a/catalog/pub/utils/toscaparser/testdata/pnf/ran-du.csar b/catalog/pub/utils/toscaparser/testdata/pnf/ran-du.csar new file mode 100644 index 00000000..77bdedfc Binary files /dev/null and b/catalog/pub/utils/toscaparser/testdata/pnf/ran-du.csar differ diff --git a/catalog/pub/utils/toscaparser/tests.py b/catalog/pub/utils/toscaparser/tests.py index 390929e8..296cb15f 100644 --- a/catalog/pub/utils/toscaparser/tests.py +++ b/catalog/pub/utils/toscaparser/tests.py @@ -17,7 +17,7 @@ import logging from django.test import TestCase -from catalog.pub.utils.toscaparser import parse_vnfd +from catalog.pub.utils.toscaparser import parse_vnfd, parse_pnfd logger = logging.getLogger(__name__) @@ -50,3 +50,10 @@ class TestToscaparser(TestCase): metadata = json.loads(vnfd_json).get("metadata") logger.debug("metadata:%s", metadata) self.assertEqual(("vCPE_%s" % vcpe_part), metadata.get("template_name", "")) + + def test_pnfd_parse(self): + csar_path = os.path.dirname(os.path.abspath(__file__)) + "/testdata/pnf/ran-du.csar" + pnfd_json = parse_pnfd(csar_path) + print pnfd_json + metadata = json.loads(pnfd_json).get("metadata") + self.assertEqual("RAN_DU", metadata.get("template_name", "")) -- cgit 1.2.3-korg