From 894344673c5f2fc00bb42fa3a520a4c8c8f4fdff Mon Sep 17 00:00:00 2001 From: maopengzhang Date: Mon, 11 Sep 2017 22:46:24 +0800 Subject: Modify catalog API with SDC Modify catalog API with SDC Change-Id: I6a76ab58b4ce4eb67f2b6ae0aa66cfd11ebc54f5 Issue-ID: VFC-32 Signed-off-by: maopengzhang --- catalog/packages/ns_package.py | 18 +++++++--- catalog/packages/tests.py | 34 ++++++++++++++++++- catalog/pub/msapi/nfvolcm.py | 8 +++-- catalog/pub/msapi/sdc.py | 58 ++++++++++++++++++++++++++++---- catalog/pub/utils/restcall.py | 22 ++++++++++-- catalog/swagger/vfc.catalog.swagger.json | 4 +++ 6 files changed, 126 insertions(+), 18 deletions(-) diff --git a/catalog/packages/ns_package.py b/catalog/packages/ns_package.py index 1d062b47..79ccf381 100644 --- a/catalog/packages/ns_package.py +++ b/catalog/packages/ns_package.py @@ -25,6 +25,7 @@ from catalog.pub.msapi import nfvolcm from catalog.pub.msapi import sdc from catalog.pub.utils import fileutil from catalog.pub.utils import toscaparser +from rest_framework import status logger = logging.getLogger(__name__) @@ -51,14 +52,23 @@ def ns_on_distribute(csar_id): def ns_delete_csar(csar_id, force_delete): ret = None + nsinstances = [] try: - ret = NsPackage().delete_csar(csar_id, force_delete) + if force_delete: + ret = NsPackage().delete_csar(csar_id) + return fmt_ns_pkg_rsp(STATUS_SUCCESS, ret[1], "") + nsinstances = nfvolcm.get_nsInstances(csar_id) + if nsinstances: + if len(nsinstances) > 0: + return fmt_ns_pkg_rsp(STATUS_FAILED, "NS instances using the CSAR exists!",status.HTTP_412_PRECONDITION_FAILED) + ret = NsPackage().delete_csar(csar_id) + return fmt_ns_pkg_rsp(STATUS_SUCCESS, ret[1], "") except CatalogException as e: return fmt_ns_pkg_rsp(STATUS_FAILED, e.message) except: logger.error(traceback.format_exc()) return fmt_ns_pkg_rsp(STATUS_FAILED, str(sys.exc_info())) - return fmt_ns_pkg_rsp(STATUS_SUCCESS, ret[1], "") + def ns_get_csars(): ret = None @@ -146,7 +156,7 @@ class NsPackage(object): return nsd,local_file_name,nsd_json - def delete_csar(self, csar_id, force_delete): + def delete_csar(self, csar_id): ''' if force_delete: NSInstModel.objects.filter(nspackage_id=csar_id).delete() @@ -154,7 +164,7 @@ class NsPackage(object): if NSInstModel.objects.filter(nspackage_id=csar_id): raise CatalogException("CSAR(%s) is in using, cannot be deleted." % csar_id) ''' - nfvolcm.delete_ns_inst_mock() + #nfvolcm.delete_ns_inst_mock() NSDModel.objects.filter(id=csar_id).delete() return [0, "Delete CSAR(%s) successfully." % csar_id] diff --git a/catalog/packages/tests.py b/catalog/packages/tests.py index 04387d59..953fece7 100644 --- a/catalog/packages/tests.py +++ b/catalog/packages/tests.py @@ -24,6 +24,7 @@ from catalog.packages.nf_package import NfPkgDeleteThread from django.test import Client from catalog.pub.database.models import NSDModel, NfPackageModel, JobStatusModel, JobModel from rest_framework import status +from catalog.pub.msapi import nfvolcm class PackageTest(unittest.TestCase): @@ -510,7 +511,8 @@ class PackageTest(unittest.TestCase): @mock.patch.object(NfDistributeThread, 'get_vnfd') @mock.patch.object(NsPackage,'get_nsd') - def test_ns_package_delete(self, mock_get_nsd,mock_get_vnfd): + @mock.patch.object(nfvolcm,'get_nsInstances') + def test_ns_package_delete(self, mock_get_nsInstances,mock_get_nsd,mock_get_vnfd): # First distribute a VNF local_file_name = "/url/local/filename" @@ -530,11 +532,41 @@ class PackageTest(unittest.TestCase): self.assert_nsdmodel_result("VCPE_NS", 1) # Finally delete ns package + mock_get_nsInstances.return_value = [] response = self.client.delete("/api/catalog/v1/nspackages/" + str(self.ns_csarId)) self.assertEqual(status.HTTP_202_ACCEPTED, response.status_code, response.content) self.assertEqual("Delete CSAR(123) successfully.", response.data["statusDescription"], response.content) self.assert_nsdmodel_result("VCPE_NS", 0) + @mock.patch.object(NfDistributeThread, 'get_vnfd') + @mock.patch.object(NsPackage,'get_nsd') + @mock.patch.object(nfvolcm,'get_nsInstances') + def test_ns_package_delete_force(self, mock_get_nsInstances,mock_get_nsd,mock_get_vnfd): + + # First distribute a VNF + local_file_name = "/url/local/filename" + vnfd = json.JSONEncoder().encode(self.vnfd_json) + mock_get_vnfd.return_value = self.vnfd_json,local_file_name,vnfd + NfDistributeThread(str(self.nf_csarId), ["1"], "1", "4").run() + self.assert_nfmodel_result(str(self.nf_csarId), 1) + + # Then distribute a NS associated with the below VNF + local_file_name = "/url/local/filename" + nsd = json.JSONEncoder().encode(self.nsd_json) + mock_get_nsd.return_value = self.nsd_json,local_file_name,nsd + response = self.client.post("/api/catalog/v1/nspackages",self.nsdata) + self.assertEqual(status.HTTP_202_ACCEPTED, response.status_code, response.content) + self.assertEqual("CSAR(123) distributed successfully.", response.data["statusDescription"], response.content) + self.assert_nfmodel_result(str(self.nf_csarId), 1) + self.assert_nsdmodel_result("VCPE_NS", 1) + + # Finally delete ns package + mock_get_nsInstances.return_value = [{"csarid":"1"},{"csarid":"2"}] + response = self.client.delete("/api/catalog/v1/nspackages/%sforce"% str(self.ns_csarId)) + self.assertEqual(status.HTTP_202_ACCEPTED, response.status_code, response.content) + self.assertEqual("Delete CSAR(123) successfully.", response.data["statusDescription"], response.content) + self.assert_nsdmodel_result("VCPE_NS", 0) + def test_nf_package_delete_error(self): # Delete it directly self.assert_nfmodel_result("bb",0) diff --git a/catalog/pub/msapi/nfvolcm.py b/catalog/pub/msapi/nfvolcm.py index 17cf686d..550679a7 100644 --- a/catalog/pub/msapi/nfvolcm.py +++ b/catalog/pub/msapi/nfvolcm.py @@ -34,9 +34,11 @@ def call_lcm(resource, method, content=''): content=content) def get_nsInstances(csarid): - nsInstances=call_lcm("/nlcm/v1/ns ","get") - - + ret=call_lcm("/nlcm/v1/ns?csarId=%s"% csarid,"get") + if ret[0] != 0: + logger.error("Status code is %s, detail is %s.", ret[2], ret[1]) + raise CatalogException("Failed to query NS Instances(%s) from NSLCM." % csarid) + return json.JSONDecoder().decode(ret[1]) # Mock code because the REST API from nfvolcm to delete ns instance is not implemented def delete_ns_inst_mock(): diff --git a/catalog/pub/msapi/sdc.py b/catalog/pub/msapi/sdc.py index a4b157c3..e65af4db 100644 --- a/catalog/pub/msapi/sdc.py +++ b/catalog/pub/msapi/sdc.py @@ -14,6 +14,7 @@ import json import logging +import os from catalog.pub.exceptions import CatalogException from catalog.pub.utils import restcall @@ -25,14 +26,35 @@ ASSETTYPE_RESOURCES = "resources" ASSETTYPE_SERVICES = "services" def call_sdc(resource, method, content=''): + additional_headers = { + 'X-ECOMP-InstanceID': 'VFC', + } return restcall.call_req(base_url=SDC_BASE_URL, user=SDC_USER, passwd=SDC_PASSWD, auth_type=restcall.rest_no_auth, resource=resource, method=method, - content=content) + content=content, + additional_headers=additional_headers) +""" +sample of return value +[ + { + "uuid": "c94490a0-f7ef-48be-b3f8-8d8662a37236", + "invariantUUID": "63eaec39-ffbe-411c-a838-448f2c73f7eb", + "name": "underlayvpn", + "version": "2.0", + "toscaModelURL": "/sdc/v1/catalog/resources/c94490a0-f7ef-48be-b3f8-8d8662a37236/toscaModel", + "category": "Volte", + "subCategory": "VolteVF", + "resourceType": "VF", + "lifecycleState": "CERTIFIED", + "lastUpdaterUserId": "jh0003" + } +] +""" def get_artifacts(asset_type): resource = "/sdc/v1/catalog/{assetType}" resource = resource.format(assetType=asset_type) @@ -58,12 +80,34 @@ def delete_artifact(asset_type, asset_id, artifact_id): raise CatalogException("Failed to delete artifacts(%s) from sdc." % artifact_id) return json.JSONDecoder().decode(ret[1]) -def download_artifacts(download_url, local_path): - ret = restcall.call_req(base_url=download_url, +def download_artifacts(download_url, local_path, file_name): + additional_headers = { + 'X-ECOMP-InstanceID': 'VFC', + 'accept': 'application/octet-stream' + } + ret = restcall.call_req(base_url=SDC_BASE_URL, user=SDC_USER, passwd=SDC_PASSWD, - auth_type=rest_no_auth, - resource="", - method="GET") - # TODO: + auth_type=restcall.rest_no_auth, + resource=download_url, + method="GET", + additional_headers=additional_headers) + if ret[0] != 0: + logger.error("Status code is %s, detail is %s.", ret[2], ret[1]) + raise CatalogException("Failed to download %s from sdc." % download_url) + local_file_name = os.path.join(local_path, file_name) + local_file = open(local_file_name, 'wb') + local_file.write(ret[1]) + local_file.close() + return local_file_name + + + + + + + + + + diff --git a/catalog/pub/utils/restcall.py b/catalog/pub/utils/restcall.py index a027fae4..1753285d 100644 --- a/catalog/pub/utils/restcall.py +++ b/catalog/pub/utils/restcall.py @@ -29,7 +29,8 @@ HTTP_404_NOTFOUND, HTTP_403_FORBIDDEN, HTTP_401_UNAUTHORIZED, HTTP_400_BADREQUES logger = logging.getLogger(__name__) -def call_req(base_url, user, passwd, auth_type, resource, method, content=''): +def call_req(base_url, user, passwd, auth_type, resource, method, + content='', additional_headers={}): callid = str(uuid.uuid1()) logger.debug("[%s]call_req('%s','%s','%s',%s,'%s','%s','%s')" % ( callid, base_url, user, passwd, auth_type, resource, method, content)) @@ -46,8 +47,11 @@ def call_req(base_url, user, passwd, auth_type, resource, method, content=''): http.follow_all_redirects = True try: resp, resp_content = http.request(full_url, method=method.upper(), body=content, headers=headers) - resp_status, resp_body = resp['status'], resp_content.decode('UTF-8') - logger.debug("[%s][%d]status=%s,resp_body=%s)" % (callid, retry_times, resp_status, resp_body)) + resp_status, resp_body = resp['status'], resp_content + logger.debug("[%s][%d]status=%s)" % (callid, retry_times, resp_status)) + if headers['accept'] == 'application/json': + resp_body = resp_content.decode('UTF-8') + logger.debug("resp_body=%s", resp_body) if resp_status in status_ok_list: ret = [0, resp_body, resp_status] else: @@ -81,6 +85,18 @@ def req_by_msb(resource, method, content=''): base_url = "http://%s:%s/" % (MSB_SERVICE_IP, MSB_SERVICE_PORT) return call_req(base_url, "", "", rest_no_auth, resource, method, content) +def upload_by_msb(resource, method, file_data={}): + headers = {'Content-Type': 'application/octet-stream'} + full_url = "http://%s:%s/%s" % (MSB_SERVICE_IP, MSB_SERVICE_PORT, resource) + http = httplib2.Http() + resp, resp_content = http.request(full_url, + method=method.upper(), body=file_data, headers=headers) + resp_status, resp_body = resp['status'], resp_content.decode('UTF-8') + if resp_status not in status_ok_list: + logger.error("Status code is %s, detail is %s.", resp_status, resp_body) + return [1, "Failed to upload file.", resp_status] + logger.debug("resp_body=%s", resp_body) + return [0, resp_body, resp_status] def combine_url(base_url, resource): full_url = None diff --git a/catalog/swagger/vfc.catalog.swagger.json b/catalog/swagger/vfc.catalog.swagger.json index f6ae5b26..2d5de2aa 100644 --- a/catalog/swagger/vfc.catalog.swagger.json +++ b/catalog/swagger/vfc.catalog.swagger.json @@ -541,6 +541,10 @@ "vnfVersion": { "type": "string", "description": "VNF Software version" + }, + "downloadUri":{ + "type": "string", + "description": "The download uri of VNF package" } } }, -- cgit 1.2.3-korg