diff options
-rw-r--r-- | lcm/ns/biz/ns_create.py | 2 | ||||
-rw-r--r-- | lcm/ns/biz/ns_get.py | 1 | ||||
-rw-r--r-- | lcm/ns_vnfs/biz/create_vnfs.py | 87 | ||||
-rw-r--r-- | lcm/ns_vnfs/biz/place_vnfs.py | 30 | ||||
-rw-r--r-- | lcm/ns_vnfs/tests/tests.py | 362 | ||||
-rw-r--r-- | lcm/pub/config/config.py | 5 | ||||
-rw-r--r-- | lcm/pub/database/models.py | 1 |
7 files changed, 475 insertions, 13 deletions
diff --git a/lcm/ns/biz/ns_create.py b/lcm/ns/biz/ns_create.py index 1f99f22b..6bcc5a05 100644 --- a/lcm/ns/biz/ns_create.py +++ b/lcm/ns/biz/ns_create.py @@ -54,6 +54,7 @@ class CreateNSService(object): packageInfo = ns_package_info["packageInfo"] self.ns_package_id = ignore_case_get(packageInfo, "nsPackageId") self.nsd_id = ignore_case_get(packageInfo, "nsdId") + self.nsd_invariant_id = ignore_case_get(packageInfo, "nsdInvariantId") logger.debug("CreateNSService::check_nsd_valid::ns_package_id=%s,nsd_id=%s", self.ns_package_id, self.nsd_id) def check_ns_inst_name_exist(self): @@ -69,6 +70,7 @@ class CreateNSService(object): name=self.ns_name, nspackage_id=self.ns_package_id, nsd_id=self.nsd_id, + nsd_invariant_id=self.nsd_invariant_id, description=self.description, status='empty', lastuptime=now_time(), diff --git a/lcm/ns/biz/ns_get.py b/lcm/ns/biz/ns_get.py index 4193bfae..31d26866 100644 --- a/lcm/ns/biz/ns_get.py +++ b/lcm/ns/biz/ns_get.py @@ -40,6 +40,7 @@ class GetNSInfoService(object): 'nsName': ns_inst.name, 'description': ns_inst.description, 'nsdId': ns_inst.nsd_id, + 'nsdInvariantId': ns_inst.nsd_invariant_id, 'vnfInfoId': self.get_vnf_infos(ns_inst.id), 'vlInfo': self.get_vl_infos(ns_inst.id), 'vnffgInfo': self.get_vnffg_infos(ns_inst.id, ns_inst.nsd_model), diff --git a/lcm/ns_vnfs/biz/create_vnfs.py b/lcm/ns_vnfs/biz/create_vnfs.py index ac1847ec..9952fd6e 100644 --- a/lcm/ns_vnfs/biz/create_vnfs.py +++ b/lcm/ns_vnfs/biz/create_vnfs.py @@ -19,7 +19,7 @@ from threading import Thread from lcm.ns.const import OWNER_TYPE from lcm.pub.config.config import REPORT_TO_AAI -from lcm.pub.database.models import NfInstModel, NSInstModel, VmInstModel, VNFFGInstModel, VLInstModel +from lcm.pub.database.models import NfInstModel, NSInstModel, VmInstModel, VNFFGInstModel, VLInstModel, OOFDataModel from lcm.pub.exceptions import NSLCMException from lcm.pub.msapi.aai import create_vnf_aai from lcm.pub.msapi.extsys import get_vnfm_by_id @@ -30,8 +30,10 @@ from lcm.pub.utils.jobutil import JOB_MODEL_STATUS, JobUtil, JOB_TYPE from lcm.pub.utils.share_lock import do_biz_with_share_lock from lcm.pub.utils.timeutil import now_time from lcm.pub.utils.values import ignore_case_get +from lcm.pub.utils import restcall from lcm.ns_vnfs.const import VNF_STATUS, NFVO_VNF_INST_TIMEOUT_SECOND, INST_TYPE, INST_TYPE_NAME from lcm.ns_vnfs.biz.wait_job import wait_job_finish +from lcm.pub.config.config import REG_TO_MSB_REG_PARAM, OOF_BASE_URL, OOF_PASSWD, OOF_USER logger = logging.getLogger(__name__) @@ -75,6 +77,7 @@ class CreateVnfs(Thread): self.create_vnf_in_aai() self.check_nf_package_valid() self.send_nf_init_request_to_vnfm() + self.send_homing_request_to_OOF() self.send_get_vnfm_request_to_extsys() self.send_create_vnf_request_to_resmgr() self.wait_vnfm_job_finish() @@ -208,6 +211,88 @@ class CreateVnfs(Thread): input_params=json.JSONEncoder().encode(self.inputs), lastuptime=now_time()) + def build_homing_request(self): + id = str(uuid.uuid4()) + callback_uri = " {vfcBaseUrl}/api/nslcm/v1/ns/placevnf" + IP = REG_TO_MSB_REG_PARAM["nodes"][0]["ip"] + PORT = REG_TO_MSB_REG_PARAM["nodes"][0]["port"] + vfcBaseUrl = IP + ':' + PORT + callback_uri = callback_uri.format(vfcBaseUrl=vfcBaseUrl) + modelInvariantId = "no-resourceModelInvariantId" + modelVersionId = "no-resourceModelVersionId" + nsInfo = NSInstModel.objects.filter(id=self.ns_inst_id) + placementDemand = { + "resourceModuleName": self.vnf_inst_name, + "serviceResourceId": self.vnfm_nf_inst_id, + "resourceModelInfo": { + "modelInvariantId": modelInvariantId, + "modelVersionId": modelVersionId + } + } + req_body = { + "requestInfo": { + "transactionId": id, + "requestId": id, + "callbackUrl": callback_uri, + "sourceId": "vfc", + "requestType": "create", + "numSolutions": 1, + "optimizers": ["placement"], + "timeout": 600 + }, + "placementInfo": { + "placementDemands": [] + }, + "serviceInfo": { + "serviceInstanceId": self.ns_inst_id, + "serviceName": self.ns_inst_name, + "modelInfo": { + "modelInvariantId": nsInfo[0].nsd_invariant_id, + "modelVersionId": nsInfo[0].nsd_id + } + } + } + req_body["placementInfo"]["placementDemands"].append(placementDemand) + # Stored the init request info inside DB + OOFDataModel.objects.create( + request_id=id, + transaction_id=id, + request_status="init", + request_module_name=self.vnfm_nf_inst_id, + service_resource_id=self.vnf_inst_name, + vim_id="", + cloud_owner="", + cloud_region_id="", + vdu_info="", + ) + return req_body + + def send_homing_request_to_OOF(self): + req_body = self.build_homing_request() + base_url = OOF_BASE_URL + resources = "/api/oof/v1/placement" + resp = restcall.call_req(base_url=base_url, user=OOF_USER, passwd=OOF_PASSWD, + auth_type=restcall.rest_oneway_auth, resource=resources, + method="POST", content=req_body, additional_headers="") + resp_body = resp[-2] + resp_status = resp[-1] + if resp_body: + logger.debug("Got OOF sync response") + else: + logger.warn("Missing OOF sync response") + logger.debug(("OOF sync response code is %s") % resp_status) + if str(resp_status) != '202' or resp[0] != 0: + OOFDataModel.objects.filter(request_id=req_body["requestInfo"]["requestId"], + transaction_id=req_body["requestInfo"]["transactionId"]).update( + request_status="failed", + vim_id="none", + cloud_owner="none", + cloud_region_id="none", + vdu_info="none" + ) + raise Exception("Received a Bad Sync from OOF with response code %s" % resp_status) + logger.info("Completed Homing request to OOF") + def send_get_vnfm_request_to_extsys(self): resp_body = get_vnfm_by_id(self.vnfm_inst_id) self.vnfm_inst_name = ignore_case_get(resp_body, 'name') diff --git a/lcm/ns_vnfs/biz/place_vnfs.py b/lcm/ns_vnfs/biz/place_vnfs.py index 84577b26..18a529e8 100644 --- a/lcm/ns_vnfs/biz/place_vnfs.py +++ b/lcm/ns_vnfs/biz/place_vnfs.py @@ -30,7 +30,7 @@ class PlaceVnfs(object): logger.error("Error occurred in Homing: OOF Async Callback Response is empty") return False if self.data.get('requestStatus') == "completed": - if self.data.get("solutions").get("placementSolutions"): + if self.data.get("solutions").get("placementSolutions") is not None: self.placements = self.data.get("solutions").get("placementSolutions") logger.debug("Got placement solutions in OOF Async Callback response") return True @@ -39,10 +39,14 @@ class PlaceVnfs(object): "does not contain placement solution") return False else: - logger.error( - "Error occurred in Homing: Request has not been completed, the request status is %s, " - "the status message is %s" % self.data.get('requestStatus'), - self.data.get("statusMessage")) + if self.data.get("statusMessage"): + logger.error( + "Error occurred in Homing: Request has not been completed, the request status is %s, " + "the status message is %s" % (self.data.get('requestStatus'), self.data.get("statusMessage"))) + else: + logger.error( + "Error occurred in Homing: Request has not been completed, the request status is %s, " + % self.data.get('requestStatus')) return False def extract(self): @@ -53,9 +57,14 @@ class PlaceVnfs(object): self.update_response_to_db(self.data.get("requestId"), self.data.get("transactionId"), self.data.get("requestStatus"), "none", "none", "none", "none") return + if self.placements == [] or self.placements == [[]]: + logger.debug("No solution found for request %s " % self.data.get("requestId")) + self.update_response_to_db(self.data.get("requestId"), self.data.get("transactionId"), + self.data.get("requestStatus"), "no-solution", "no-solution", + "no-solution", "no-solution") + return for item in self.placements: - if not item: - logger.debug("No solution found for request %s " % self.data.get("requestId")) + if not isinstance(item, list): self.update_response_to_db(self.data.get("requestId"), self.data.get("transactionId"), self.data.get("requestStatus"), "no-solution", "no-solution", "no-solution", "no-solution") @@ -94,8 +103,7 @@ class PlaceVnfs(object): transactionId=self.data.get("transactionId"), requestStatus=self.data.get("requestStatus"), vimId=vim_info['vimId'], - cloudOwner=placement.get("solution").get( - "cloudOwner"), + cloudOwner=placement.get("solution").get("cloudOwner"), cloudRegionId=vim_info['locationId'], vduInfo=vduinfo ) @@ -106,7 +114,7 @@ class PlaceVnfs(object): def get_info_from_directives(self, directives): vduinfo = [] - for directive in directives: + for directive in directives.get("directives"): if directive.get("type") == "tocsa.nodes.nfv.Vdu.Compute": vdu = {"vduName": directive.get("id")} other_directives = [] @@ -137,5 +145,5 @@ class PlaceVnfs(object): vim_id=vimId, cloud_owner=cloudOwner, cloud_region_id=cloudRegionId, - vduinfo=vduInfo + vdu_info=vduInfo ) diff --git a/lcm/ns_vnfs/tests/tests.py b/lcm/ns_vnfs/tests/tests.py index b70d00e3..ba92e194 100644 --- a/lcm/ns_vnfs/tests/tests.py +++ b/lcm/ns_vnfs/tests/tests.py @@ -18,7 +18,7 @@ import mock from django.test import TestCase, Client from rest_framework import status -from lcm.pub.database.models import NfInstModel, JobModel, NSInstModel, VmInstModel +from lcm.pub.database.models import NfInstModel, JobModel, NSInstModel, VmInstModel, OOFDataModel from lcm.pub.exceptions import NSLCMException from lcm.pub.utils import restcall from lcm.pub.utils.jobutil import JOB_MODEL_STATUS @@ -31,6 +31,7 @@ from lcm.ns_vnfs.biz.scale_vnfs import NFManualScaleService from lcm.ns_vnfs.biz.terminate_nfs import TerminateVnfs from lcm.ns_vnfs.const import VNF_STATUS, INST_TYPE from lcm.ns_vnfs.biz import create_vnfs +from lcm.ns_vnfs.biz.place_vnfs import PlaceVnfs class TestGetVnfViews(TestCase): @@ -138,6 +139,59 @@ class TestCreateVnfViews(TestCase): CreateVnfs(data, nf_inst_id, job_id).run() self.assertTrue(NfInstModel.objects.get(nfinstid=nf_inst_id).status, VNF_STATUS.ACTIVE) + @mock.patch.object(restcall, 'call_req') + @mock.patch.object(CreateVnfs, 'build_homing_request') + def test_send_homing_request(self, mock_build_req, mock_call_req): + nf_inst_id, job_id = create_vnfs.prepare_create_params() + OOFDataModel.objects.all().delete() + resp = { + "requestId": "1234", + "transactionId": "1234", + "requestStatus": "accepted" + } + mock_build_req.return_value = { + "requestInfo": { + "transactionId": "1234", + "requestId": "1234", + "callbackUrl": "xx", + "sourceId": "vfc", + "requestType": "create", + "numSolutions": 1, + "optimizers": ["placement"], + "timeout": 600 + }, + "placementInfo": { + "placementDemands": [ + { + "resourceModuleName": "vG", + "serviceResourceId": "1234", + "resourceModelInfo": { + "modelInvariantId": "1234", + "modelVersionId": "1234" + } + } + ] + }, + "serviceInfo": { + "serviceInstanceId": "1234", + "serviceName": "1234", + "modelInfo": { + "modelInvariantId": "5678", + "modelVersionId": "7890" + } + } + } + mock_call_req.return_value = [0, json.JSONEncoder().encode(resp), '202'] + data = { + 'ns_instance_id': ignore_case_get(self.data, 'nsInstanceId'), + 'additional_param_for_ns': ignore_case_get(self.data, 'additionalParamForNs'), + 'additional_param_for_vnf': ignore_case_get(self.data, 'additionalParamForVnf'), + 'vnf_index': ignore_case_get(self.data, 'vnfIndex') + } + CreateVnfs(data, nf_inst_id, job_id).send_homing_request_to_OOF() + ret = OOFDataModel.objects.filter(request_id="1234", transaction_id="1234") + self.assertIsNotNone(ret) + class TestTerminateVnfViews(TestCase): def setUp(self): @@ -630,6 +684,218 @@ class TestGetVimInfoViews(TestCase): self.assertEqual(expect_data["url"], context["url"]) +class TestPlaceVnfViews(TestCase): + def setUp(self): + self.vnf_inst_id = "1234" + self.vnf_inst_name = "vG" + self.client = Client() + OOFDataModel.objects.all().delete() + OOFDataModel.objects.create( + request_id="1234", + transaction_id="1234", + request_status="init", + request_module_name=self.vnf_inst_name, + service_resource_id=self.vnf_inst_id, + vim_id="", + cloud_owner="", + cloud_region_id="", + vdu_info="", + ) + + def tearDown(self): + OOFDataModel.objects.all().delete() + + @mock.patch.object(restcall, 'call_req') + def test_place_vnf(self, mock_call_req): + vdu_info_json = [{ + "vduName": "vG_0", + "flavorName": "HPA.flavor.1", + "directive": [] + }] + PlaceVnfs(vnf_place_request).extract() + db_info = OOFDataModel.objects.filter(request_id=vnf_place_request.get("requestId"), transaction_id=vnf_place_request.get("transactionId")) + self.assertEqual(db_info[0].request_status, "completed") + self.assertEqual(db_info[0].vim_id, "CloudOwner1_DLLSTX1A") + self.assertEqual(db_info[0].cloud_owner, "CloudOwner1") + self.assertEqual(db_info[0].cloud_region_id, "DLLSTX1A") + self.assertEqual(db_info[0].vdu_info, json.dumps(vdu_info_json)) + + def test_place_vnf_with_invalid_response(self): + resp = { + "requestId": "1234", + "transactionId": "1234", + "statusMessage": "xx", + "requestStatus": "pending", + "solutions": { + "placementSolutions": [ + [ + { + "resourceModuleName": self.vnf_inst_name, + "serviceResourceId": self.vnf_inst_id, + "solution": { + "identifierType": "serviceInstanceId", + "identifiers": [ + "xx" + ], + "cloudOwner": "CloudOwner1 " + }, + "assignmentInfo": [] + } + ] + ], + "licenseSolutions": [ + { + "resourceModuleName": "string", + "serviceResourceId": "string", + "entitlementPoolUUID": [ + "string" + ], + "licenseKeyGroupUUID": [ + "string" + ], + "entitlementPoolInvariantUUID": [ + "string" + ], + "licenseKeyGroupInvariantUUID": [ + "string" + ] + } + ] + } + } + PlaceVnfs(resp).extract() + db_info = OOFDataModel.objects.filter(request_id=resp.get("requestId"), transaction_id=resp.get("transactionId")) + self.assertEqual(db_info[0].request_status, "pending") + self.assertEqual(db_info[0].vim_id, "none") + self.assertEqual(db_info[0].cloud_owner, "none") + self.assertEqual(db_info[0].cloud_region_id, "none") + self.assertEqual(db_info[0].vdu_info, "none") + + def test_place_vnf_with_no_assignment_info(self): + resp = { + "requestId": "1234", + "transactionId": "1234", + "statusMessage": "xx", + "requestStatus": "completed", + "solutions": { + "placementSolutions": [ + [ + { + "resourceModuleName": self.vnf_inst_name, + "serviceResourceId": self.vnf_inst_id, + "solution": { + "identifierType": "serviceInstanceId", + "identifiers": [ + "xx" + ], + "cloudOwner": "CloudOwner1 " + } + } + ] + ], + "licenseSolutions": [ + { + "resourceModuleName": "string", + "serviceResourceId": "string", + "entitlementPoolUUID": [ + "string" + ], + "licenseKeyGroupUUID": [ + "string" + ], + "entitlementPoolInvariantUUID": [ + "string" + ], + "licenseKeyGroupInvariantUUID": [ + "string" + ] + } + ] + } + } + PlaceVnfs(resp).extract() + db_info = OOFDataModel.objects.filter(request_id=resp.get("requestId"), transaction_id=resp.get("transactionId")) + self.assertEqual(db_info[0].request_status, "completed") + self.assertEqual(db_info[0].vim_id, "none") + self.assertEqual(db_info[0].cloud_owner, "none") + self.assertEqual(db_info[0].cloud_region_id, "none") + self.assertEqual(db_info[0].vdu_info, "none") + + def test_place_vnf_no_directives(self): + resp = { + "requestId": "1234", + "transactionId": "1234", + "statusMessage": "xx", + "requestStatus": "completed", + "solutions": { + "placementSolutions": [ + [ + { + "resourceModuleName": self.vnf_inst_name, + "serviceResourceId": self.vnf_inst_id, + "solution": { + "identifierType": "serviceInstanceId", + "identifiers": [ + "xx" + ], + "cloudOwner": "CloudOwner1 " + }, + "assignmentInfo": [ + {"key": "locationId", + "value": "DLLSTX1A" + } + ] + } + ] + ], + "licenseSoutions": [ + { + "resourceModuleName": "string", + "serviceResourceId": "string", + "entitlementPoolUUID": [ + "string" + ], + "licenseKeyGroupUUID": [ + "string" + ], + "entitlementPoolInvariantUUID": [ + "string" + ], + "licenseKeyGroupInvariantUUID": [ + "string" + ] + } + ] + } + } + PlaceVnfs(resp).extract() + db_info = OOFDataModel.objects.filter(request_id=resp.get("requestId"), transaction_id=resp.get("transactionId")) + self.assertEqual(db_info[0].request_status, "completed") + self.assertEqual(db_info[0].vim_id, "none") + self.assertEqual(db_info[0].cloud_owner, "none") + self.assertEqual(db_info[0].cloud_region_id, "none") + self.assertEqual(db_info[0].vdu_info, "none") + + def test_place_vnf_with_no_solution(self): + resp = { + "requestId": "1234", + "transactionId": "1234", + "statusMessage": "xx", + "requestStatus": "completed", + "solutions": { + "placementSolutions": [], + "licenseSoutions": [] + } + } + PlaceVnfs(resp).extract() + db_info = OOFDataModel.objects.filter(request_id=resp.get("requestId"), transaction_id=resp.get("transactionId")) + self.assertEqual(db_info[0].request_status, "completed") + self.assertEqual(db_info[0].vim_id, "no-solution") + self.assertEqual(db_info[0].cloud_owner, "no-solution") + self.assertEqual(db_info[0].cloud_region_id, "no-solution") + self.assertEqual(db_info[0].vdu_info, "no-solution") + + vnfd_model_dict = { 'local_storages': [], 'vdus': [ @@ -1377,3 +1643,97 @@ nf_package_info = { }, "imageInfo": [] } + +vnf_place_request = { + "requestId": "1234", + "transactionId": "1234", + "statusMessage": "xx", + "requestStatus": "completed", + "solutions": { + "placementSolutions": [ + [ + { + "resourceModuleName": "vG", + "serviceResourceId": "1234", + "solution": { + "identifierType": "serviceInstanceId", + "identifiers": [ + "xx" + ], + "cloudOwner": "CloudOwner1" + }, + "assignmentInfo": [ + {"key": "isRehome", + "value": "false" + }, + {"key": "locationId", + "value": "DLLSTX1A" + }, + {"key": "locationType", + "value": "openstack-cloud" + }, + {"key": "vimId", + "value": "CloudOwner1_DLLSTX1A" + }, + {"key": "physicalLocationId", + "value": "DLLSTX1223" + }, + {"key": "oofDirectives", + "value": { + "directives": [ + { + "id": "vG_0", + "type": "tocsa.nodes.nfv.Vdu.Compute", + "directives": [ + { + "type": "flavor_directive", + "attributes": [ + { + "attribute_name": "flavor_name", + "attribute_value": "HPA.flavor.1" + } + ] + } + ] + }, + { + "id": "", + "type": "vnf", + "directives": [ + {"type": " ", + "attributes": [ + { + "attribute_name": " ", + "attribute_value": " " + } + ] + } + ] + } + ] + } + } + ] + } + ] + ], + "licenseSolutions": [ + { + "resourceModuleName": "string", + "serviceResourceId": "string", + "entitlementPoolUUID": [ + "string" + ], + "licenseKeyGroupUUID": [ + "string" + ], + "entitlementPoolInvariantUUID": [ + "string" + ], + "licenseKeyGroupInvariantUUID": [ + "string" + ] + } + ] + } +} diff --git a/lcm/pub/config/config.py b/lcm/pub/config/config.py index ebffddca..9d63b254 100644 --- a/lcm/pub/config/config.py +++ b/lcm/pub/config/config.py @@ -68,3 +68,8 @@ MR_PORT = '3904' DEPLOY_WORKFLOW_WHEN_START = False # Support option: activiti/wso2/buildin WORKFLOW_OPTION = "buildin" + +# [OOF config] +OOF_BASE_URL = "http://oof.api.simpledemo.onap.org:8698" +OOF_USER = "vfc_test" +OOF_PASSWD = "vfc_testpwd" diff --git a/lcm/pub/database/models.py b/lcm/pub/database/models.py index a078c6a8..7c6fee73 100644 --- a/lcm/pub/database/models.py +++ b/lcm/pub/database/models.py @@ -36,6 +36,7 @@ class NSInstModel(models.Model): name = models.CharField(db_column='NAME', max_length=200) nspackage_id = models.CharField(db_column='NSPACKAGEID', max_length=200, null=True, blank=True) nsd_id = models.CharField(db_column='NSDID', max_length=200) + nsd_invariant_id = models.CharField(db_column='NSDINVARIANTID', max_length=200) description = models.CharField(db_column='DESCRIPTION', max_length=255, null=True, blank=True) sdncontroller_id = models.CharField(db_column='SDNCONTROLLERID', max_length=200, null=True, blank=True) flavour_id = models.CharField(db_column='FLAVOURID', max_length=200, null=True, blank=True) |